C++の応用 - PolKit
概要
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は、下表に示す要素で構成される。
| 要素 | 説明 |
|---|---|
| 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) に対するデフォルトポリシー
yes、no、auth_self、auth_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を利用する主な方法を比較する。
| 方法 | 依存関係 | 実装難易度 | 用途 |
|---|---|---|---|
| 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を使用する場合、GCancellable と GMainLoop の組み合わせにより、応答待ち中でも他のイベント処理を継続できる。
D-Busを直接使用する方法
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}の辞書を渡す。
- D-Bus構造体
- 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の
pidやstart-timeが正しくない。
- Action IDが /usr/share/polkit-1/actions ディレクトリ内の
- 対策
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ファイルで事前に許可するか、別途認証方法を用意する。
- flagsに
コンパイル時に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権限で動作する必要があるが、呼び出し側は通常ユーザでよい。
関連情報