C++の応用 - PolKit

2026年6月13日 (土) 15:43時点におけるWiki (トーク | 投稿記録)による版
(差分) ← 古い版 | 最新版 (差分) | 新しい版 → (差分)

概要

Polkit (formerly PolicyKit) は、Linuxシステムで特権的な操作を行う際の認可 (authorization) を決定するフレームワークである。
sudoのようにプロセス全体をrootで実行するのではなく、細かいアクション単位で許可を判定できる。

Polkitは2008年頃にDavid Zeuthenによって開発され、D-Busベースの認可フレームワークとして成熟した。
中核となるAuthorityは org.freedesktop.PolicyKit1 というD-Busサービスとして動作し、アプリケーションからSubjectとAction IDを受け取って判定を行う。

Action IDは /usr/share/polkit-1/actions/ ディレクトリの .policy ファイル (XML形式) で定義され、認可ルールは /etc/polkit-1/rules.d/ 等のJavaScriptファイルで記述される。
これにより、管理者はアプリケーションやユーザ/groupごとに詳細なポリシーを設定できる。

C++からPolkitを利用するには、libpolkit-gobject-1 をラップする方法と、D-Busインターフェースを直接呼び出す方法がある。

C++と外部ライブラリのみで開発する場合は、GLib / GObjectのリソース管理やD-Busメッセージのパースを適切に行う必要がある。

Polkitを適切に使用することで、GUIやCLIアプリケーションから最小限の特権で安全にシステム操作を実行できる。
例えば、システム設定の変更やデバイスのマウント等、root権限が必要な操作をユーザに確認しながら行うことが可能である。


Polkitの基本

主要な構成要素

Polkitは、下表に示す要素で構成される。

Polkitの主要な構成要素
要素 説明
Authority D-Bus上の org.freedesktop.PolicyKit1 サービス
認可判定の最終的な権限を持つ。
Subject 認可を求めるエンティティ
通常は ("unix-process", {"pid": uint32, "start-time": uint64}) 形式のD-Bus構造体
Action .policy XMLファイルで定義された操作
Action ID (例: org.freedesktop.policykit.exec) で識別される。
Authentication Agent ユーザにパスワード等を要求するUIプログラム
GUIやTUIで動作する。
Rule files JavaScriptで記述された .rules ファイル
管理者がポリシーをカスタマイズするために使用する。


Subjectには、プロセス形式の unix-process の他、セッション形式の unix-session も存在する。

C++アプリケーションから自プロセスの認可を確認する場合、unix-process Subjectを使用するのが一般的である。

Authentication Agentの実装については、PolkitAgentListener のサブクラス化が必要であるが、本ページではアプリケーション側の認可チェックを中心に解説する。

アクションとルールファイル

Actionは、/usr/share/polkit-1/actions 内の .policy ファイルで定義される。

各アクションには、以下の属性が含まれる。

  • action id
    一意の識別子
    例: org.example.myapp.configure

  • description / message
    ユーザに表示される説明文

  • defaults
    各認可結果 (allow_any, allow_inactive, allow_active) に対するデフォルトポリシー
    yesnoauth_selfauth_admin 等が指定できる。

  • vendor / vendor_url
    アクションの提供者情報


Ruleファイルは、/etc/polkit-1/rules.d//usr/share/polkit-1/rules.d/ に配置される。
ファイル名は <数字>-name.rules の形式で、数字の小さいものから順に評価される。

JavaScript形式のRuleファイルの例を以下に示す。

 polkit.addRule(function(action, subject) {
    if (action.id == "org.example.myapp.configure" && subject.isInGroup("wheel")) {
       return polkit.Result.YES;
    }
 });


この例では、wheel グループに属するユーザが org.example.myapp.configure アクションを実行できるようにしている。

Ruleファイルを編集した後は、polkit デーモンが自動的に再読み込みを行うが、即座に反映させたい場合は sudo systemctl restart polkit コマンドを実行する。


C++からPolkitを利用する方法の比較

C++からPolkitを利用する主な方法を比較する。

Polkit利用方法の比較
方法 依存関係 実装難易度 用途
libpolkit-gobject-1 GLib / GObject、D-Busシステムバス 一般的なアプリケーションの認可チェック
D-Bus直接呼び出し (libdbus等) D-Busライブラリ、システムバス 軽量実装や特殊なD-Bus制御が必要な場合
sd-bus (systemd) libsystemd systemdベースの環境で直接使用


libpolkit-gobject-1は、GObjectのリファレンスカウントとGErrorを適切に扱う必要がある。

D-Bus直接呼び出しは、Subject構造体や戻り値のパースを自前で行う必要がある。


Polkitのインストール

パッケージ管理システムからインストール

多くのLinuxディストリビューションには、初期状態でPolkitがインストールされている。

C++から開発する場合は、追加で開発用パッケージが必要となる。

# RHEL
sudo dnf install polkit-devel glib2-devel pkg-config

# SUSE
sudo zypper install polkit-devel glib2-devel pkg-config


ソースコードからインストール

まず、Polkitのビルドに必要な依存関係のライブラリをインストールする。

# RHEL
sudo dnf install meson dbus-devel expat-devel systemd-devel glib2-devel \
                 gtk3-devel gobject-introspection-devel pam-devel       \
                 duktape-devel gtk-doc mozjs115-devel

# SUSE
sudo zypper install meson dbus-1-devel libexpat-devel systemd-devel    \
                    glib2-devel gtk3-devel gobject-introspection-devel \
                    mozjs78-devel pam-devel duktape-devel gtk-doc


Polkit公式Webサイト または GitLabからソースコードをダウンロードする。
ダウンロードしたファイルを解凍する。

tar xf polkit-<バージョン>.tar.gz
cd polkit-<バージョン>


または、git clone コマンドを実行して、ソースコードをダウンロードする。

git clone https://gitlab.freedesktop.org/polkit/polkit.git
cd polkit


Polkitのビルドおよびインストールを行う。
指定可能なオプションは、meson configure コマンドで確認できる。

# RHEL
meson setup build \
      --prefix=<Polkitのインストールディレクトリ> \
      -Dos_type=redhat \
      -Dexamples=true  \
      -Dman=true       \
      -Dgtk_doc=true

# SUSE
meson setup build \
      --prefix=<Polkitのインストールディレクトリ> \
      -Dos_type=suse  \
      -Dexamples=true \
      -Dman=true      \
      -Dgtk_doc=true

meson compile -C build
meson install -C build


CMakeファイルの設定

Pkg-Configを使用する場合

以下の例は、pkg_check_modules コマンドを使用して依存関係を解決する例である。

 # CMakeLists.txtファイル
 
 find_package(PkgConfig REQUIRED)
 
 pkg_check_modules(GLIB2          REQUIRED glib-2.0)
 pkg_check_modules(POLKITGOBJECT2 REQUIRED polkit-gobject-1)
 pkg_check_modules(GOBJECT2       REQUIRED gobject-2.0)
 pkg_check_modules(GIO2           REQUIRED gio-2.0)
 
 add_executable(<プロジェクト名>
    # ...略
 )
 
 include_directories(
    ${GLIB2_INCLUDE_DIRS}
    ${POLKITGOBJECT2_INCLUDE_DIRS}
    ${GOBJECT2_INCLUDE_DIRS}
    ${GIO2_INCLUDE_DIRS}
 )
 
 link_directories(
    ${GLIB2_LIBRARY_DIRS}
    ${POLKITGOBJECT2_LIBRARY_DIRS}
    ${GOBJECT2_LIBRARY_DIRS}
    ${GIO2_LIBRARY_DIRS}
 )
 
 add_definitions(
    ${GLIB2_CFLAGS_OTHER}
    ${POLKITGOBJECT2_CFLAGS_OTHER}
    ${GOBJECT2_CFLAGS_OTHER}
    ${GIO2_CFLAGS_OTHER}
 )
 
 target_link_libraries(<プロジェクト名>
    ${GLIB2_LIBRARIES}
    ${POLKITGOBJECT2_LIBRARIES}
    ${GOBJECT2_LIBRARIES}
    ${GIO2_LIBRARIES}
 )


Pkg-Configを使用しない場合

以下の例は、Pkg-Configを使用せずに直接パスを指定する例である。

 # CMakeLists.txtファイル
 
 add_executable(<プロジェクト名>
    # ...略
 )
 
 ## インクルードファイル
 include_directories(
    # ...略
    /usr/lib64/glib-2.0/include
    /usr/include/glib-2.0
    /usr/include/polkit-1
    # ...略
 )
 
 ## ライブラリファイル
 target_link_libraries(<プロジェクト名>
    # ...略
    glib-2.0
    polkit-gobject-1
    gobject-2.0
    gio-2.0
    # ...略
 )



libpolkit-gobject-1を使用する方法

libpolkit-gobject-1とは

libpolkit-gobject-1は、Polkitの機能をGLib / GObjectベースのC APIで提供するライブラリである。

主な関数として、Authorityを取得する polkit_authority_get_sync() と、認可を確認する polkit_authority_check_authorization_sync() が存在する。
また、Subjectを作成する polkit_unix_process_new_for_owner() 等の関数も提供する。

C++から使用する場合、g_object_unref() によるリソース解放や g_error_free() によるGErrorの解放を徹底する。

また、RAIIラッパーを用意することで、GObjectのリファレンスカウントを安全に管理できる。

開発パッケージのインストール

# RHEL
sudo dnf install pkg-config polkit-devel glib2-devel

# SUSE
sudo zypper install pkg-config polkit-devel glib2-devel


認可チェックの実装例

以下の例では、libpolkit-gobject-1を使用して、Action ID org.example.myapp.configure の認可を確認している。

 #include <iostream>
 #include <memory>
 #include <unistd.h>
 #include <polkit/polkit.h>
 
 // GErrorをRAIIで管理するヘルパー
 struct GErrorDeleter {
    void operator()(GError* e) const { if (e) g_error_free(e); }
 };
 using GErrorPtr = std::unique_ptr<GError, GErrorDeleter>;
 
 // GObjectをRAIIで管理するヘルパー
 struct GObjectDeleter {
    void operator()(gpointer o) const { if (o) g_object_unref(o); }
 };
 template<typename T>
 using GObjectPtr = std::unique_ptr<T, GObjectDeleter>;
 
 int main()
 {
    GError* raw_error = nullptr;
    PolkitAuthority* authority = polkit_authority_get_sync(nullptr, &raw_error);
    GErrorPtr error(raw_error);
    if (!authority) {
       std::cerr << "Failed to get polkit authority: "
                 << (error ? error->message : "unknown") << "\n";
       return 1;
    }
    GObjectPtr<PolkitAuthority> auth_ptr(authority);
 
    // Subjectを作成 (自プロセス)
    pid_t pid = getpid();
    guint64 start_time = 0; // 実際には /proc/self/stat から取得する
    PolkitSubject* subject = polkit_unix_process_new_for_owner(pid, start_time, getuid());
    if (!subject) {
       std::cerr << "Failed to create subject\n";
       return 1;
    }
    GObjectPtr<PolkitSubject> subject_ptr(subject);
 
    // 認可チェック
    PolkitAuthorizationResult* result = polkit_authority_check_authorization_sync(
          authority,
          subject,
          "org.example.myapp.configure",
          nullptr,              // details
          POLKIT_CHECK_AUTHORIZATION_FLAGS_NONE,
          nullptr,              // cancellable
          &raw_error);
 
    error.reset(raw_error);
 
    if (!result) {
       std::cerr << "Authorization check failed: "
                 << (error ? error->message : "unknown") << "\n";
       return 1;
    }
    GObjectPtr<PolkitAuthorizationResult> result_ptr(result);
 
    if (polkit_authorization_result_get_is_authorized(result)) {
       std::cout << "Authorized\n";
    }
    else if (polkit_authorization_result_get_is_challenge(result)) {
       std::cout << "Authentication required\n";
    }
    else {
       std::cout << "Not authorized\n";
    }
 
    return 0;
 }


コンパイル時は、pkg-config を使用して、PolkitとGLibのフラグを取得する。

g++ -std=c++17 polkit_check.cpp -o polkit_check $(pkg-config --cflags --libs polkit-gobject-1)


※注意
start_time において、実際には /proc/self/stat ファイルから取得する必要がある。
polkit_unix_process_new_for_owner() に0を渡すと、Authority側でstart-timeの検証ができない場合がある。

認証エージェントを起動させる場合は、flagsに POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION を指定する。

非同期認可チェックの例

以下の例では、GMainLoop と非同期APIを使用して認可チェックを行っている。

タイムアウト処理も含めて、GUIや長時間実行されるアプリケーションで使用する場合に適している。

 #include <unistd.h>
 #include <polkit/polkit.h>
  
 void check_authorization(PolkitAuthority *authority, GAsyncResult *res, gpointer user_data);
 gboolean do_cancel(GCancellable *cancellable);
 
 int main()
 {
    const gchar *action_id = "org.example.policykit.do";
    GMainLoop *loop = g_main_loop_new(nullptr, FALSE);
 
    PolkitAuthority *authority = polkit_authority_get_sync(nullptr, nullptr);
 
    // 親プロセスのPIDを使用する例
    auto parent_pid = getppid();
    if (parent_pid == 1) {
       g_printerr("Parent process was reaped by init(1)\n");
       return -1;
    }
 
    PolkitSubject *subject = polkit_unix_process_new_for_owner(parent_pid, 0, getuid());
 
    GCancellable *cancellable = g_cancellable_new();
    g_timeout_add(1000 * 10, (GSourceFunc)do_cancel, cancellable);
 
    polkit_authority_check_authorization(
       authority,
       subject,
       action_id,
       nullptr,
       POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION,
       cancellable,
       (GAsyncReadyCallback)check_authorization,
       loop);
 
    g_main_loop_run(loop);
 
    g_object_unref(authority);
    g_object_unref(subject);
    g_object_unref(cancellable);
    g_main_loop_unref(loop);
 
    return 0;
 }
 
 void check_authorization(PolkitAuthority *authority, GAsyncResult *res, gpointer user_data)
 {
    GError *error = nullptr;
    PolkitAuthorizationResult *result = polkit_authority_check_authorization_finish(authority, res, &error);
 
    if (error != nullptr) {
       g_print("Error checking authorization: %s\n", error->message);
       g_error_free(error);
    }
    else {
       const gchar *result_str;
       if (polkit_authorization_result_get_is_authorized(result)) {
          result_str = "authorized";
       }
       else if (polkit_authorization_result_get_is_challenge(result)) {
          result_str = "challenge";
       }
       else {
          result_str = "not authorized";
       }
 
       g_print("Authorization result: %s\n", result_str);
    }
 
    if (result) g_object_unref(result);
    auto loop = static_cast<GMainLoop*>(user_data);
    g_main_loop_quit(loop);
 }
 
 gboolean do_cancel(GCancellable *cancellable)
 {
    g_print("Timer has expired; cancelling authorization check\n");
    g_cancellable_cancel(cancellable);
    return FALSE;
 }


非同期APIを使用する場合、GCancellableGMainLoop の組み合わせにより、応答待ち中でも他のイベント処理を継続できる。


D-Busを直接使用する方法

D-Busインターフェースの概要

PolkitのAuthorityは、D-Busシステムバス上で以下に示すサービスとして公開されている。

Polkit AuthorityのD-Busインターフェース
項目 内容
Well-known name org.freedesktop.PolicyKit1
Object path /org/freedesktop/PolicyKit1/Authority
Interface org.freedesktop.PolicyKit1.Authority
Method CheckAuthorization
Bus type システムバス


CheckAuthorization() の引数は以下の通りである。

  • subject
    D-Bus構造体 (sv)
    sに "unix-process"、vに {"pid": uint32, "start-time": uint64} の辞書を渡す。

  • action_id
    確認対象のアクションID (string型)

  • details
    追加情報を表す a{ss} 形式の辞書

  • flags
    uint32型
    0 は問い合わせのみ
    1 (AllowUserInteraction) は認証エージェントの起動を許可する。

  • cancellation_id
    キャンセル用の文字列
    空文字列を指定可能


戻り値は (bba{ss}) 形式の構造体であり、is_authorized (bool)、is_challenge (bool)、details (dict) の順に格納される。

認可チェックの実装例 (sd-busを使用)

以下の例では、libsystemdのsd-busを使用して、Authorityを直接呼び出している。

以下の例では、エラーハンドリングは簡潔化しているため、実運用ではより詳細なチェックが必要である。

 #include <iostream>
 #include <cstring>
 #include <systemd/sd-bus.h>
 #include <unistd.h>
 
 int main()
 {
    sd_bus* bus = nullptr;
    int r = sd_bus_default_system(&bus);
    if (r < 0) {
       std::cerr << "Failed to connect to system bus: "
                 << strerror(-r) << "\n";
       return 1;
    }
 
    sd_bus_error error = SD_BUS_ERROR_NULL;
    sd_bus_message* reply = nullptr;
    r = sd_bus_call_method(
       bus,
       "org.freedesktop.PolicyKit1",
       "/org/freedesktop/PolicyKit1/Authority",
       "org.freedesktop.PolicyKit1.Authority",
       "CheckAuthorization",
       &error,
       &reply,
       "(sv)sa{ss}us",
       "unix-process",
       "a{sv}", 2,
          "pid", "u", static_cast<uint32_t>(getpid()),
          "start-time", "t", static_cast<uint64_t>(0),
       "org.example.myapp.configure",
       0,           // details: empty a{ss}
       0u,          // flags
       "");         // cancellation_id
 
    if (r < 0) {
       std::cerr << "CheckAuthorization failed: "
                 << error.message << "\n";
       sd_bus_error_free(&error);
       sd_bus_unref(bus);
       return 1;
    }
 
    int authorized = 0;
    int challenge = 0;
    r = sd_bus_message_read(reply, "(bba{ss})", &authorized, &challenge, nullptr);
    if (r < 0) {
       std::cerr << "Failed to parse reply\n";
    }
    else {
       std::cout << "Authorized: " << authorized
                 << ", Challenge: " << challenge << "\n";
    }
 
    sd_bus_message_unref(reply);
    sd_bus_unref(bus);
 
    return 0;
 }


コンパイル時は、pkg-config コマンドを使用してlibsystemdのフラグを取得する。

g++ -std=c++17 polkit_dbus_check.cpp -o polkit_dbus_check $(pkg-config --cflags --libs libsystemd)


D-Bus直接呼び出しでは、Subject構造体の作成や戻り値のパースを正確に行う必要がある。

start-timeに0を渡す場合、Polkitはプロセスのstart-timeを検証できないため、本番環境では /proc/self/stat ファイルから取得すること。


トラブルシューティング

認可が常に拒否される

  • 原因
    • Action IDが /usr/share/polkit-1/actions ディレクトリ内の .policy ファイルで定義されていない。
    • Ruleファイルで明示的に拒否 (return polkit.Result.NO) されている。
    • Subjectの pidstart-time が正しくない。

  • 対策
    • pkaction --verbose でAction IDが登録されているか確認する。
    • Ruleファイルの優先順位 (ファイル名の数字) を確認する。
    • Subjectのstart-timeを /proc/self/stat から正しく取得する。


認証ダイアログが表示されない

  • 原因
    • CheckAuthorization のflagsに AllowUserInteraction (1) が指定されていない。
    • セッションに認証エージェントが登録されていない。
    • SSHやSystemdサービス等の非対話的環境で実行されている。

  • 対策
    • flagsに POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION または 1u を指定する。
    • グラフィカルセッションやPolkitエージェントが動作している環境でテストする。
    • 非対話的環境では、Ruleファイルで事前に許可するか、別途認証方法を用意する。


コンパイル時にGObjectのリンクエラーが発生する

  • 原因
    • pkg-config --libs polkit-gobject-1 の出力に含まれるGLib/GObjectのライブラリがリンクされていない。
    • ヘッダファイルのincludeパスが通っていない。

  • 対策
    • 必ず $(pkg-config --cflags --libs polkit-gobject-1) をコンパイルコマンドに含める。
    • 必要に応じて glib-2.0 のpkg-configも追加する。
    • CMakeを使用する場合は pkg_check_modules(POLKIT polkit-gobject-1) を使用する。


D-Bus呼び出しで "Permission denied" となる

  • 原因
    • システムバスへの接続権限がない。
    • 必要なD-Busポリシーが設定されていない。

  • 対策
    • プログラムが通常ユーザ権限で実行されていることを確認する。
    • D-Bus設定ファイル (/usr/share/dbus-1/system.d/ ディレクトリ等) でサービスへのアクセス権限を確認する。
    • Polkit自身はroot権限で動作する必要があるが、呼び出し側は通常ユーザでよい。



関連情報