web-userにコンテキスト割り当て

SE-PostgreSQLについて講演したりすると、確実に質問されるのは
「じゃあ、webのユーザ毎に見えないデータを設定できたりできるんですか?」

理屈の上では可能だが、現状ではCGIに手を入れたりしなければならず、色々と面倒ではある。そこで、その辺を解決する Apache のモジュールを作れば便利になって良いではないかと言う事で日曜プログラマ

先ず、そもそもApacheのモジュールをどう作れば良いのか?
天才プログラマー・ひげぽん氏のブログの記事が参考になった。

.so ファイルのエントリポイントになるのは、module型の変数で、必要なコールバックを埋めていく

module AP_MODULE_DECLARE_DATA selinux_module ={
    STANDARD20_MODULE_STUFF,
    NULL,
    NULL,
    selinux_create_server_config,
    NULL,
    selinux_cmds,
    selinux_register_hooks,
};

モジュールのロード時に先ず呼び出されるのが selinux_register_hooks で、このモジュールが必要とするフックを登録する事ができる。

static void selinux_register_hooks(apr_pool_t *p)
{
    ap_hook_fixups(selinux_setexeccon, NULL, NULL, APR_HOOK_MIDDLE);
}

fixupというのは、HTTP-BASIC認証やらその他諸々が終わり、コンテンツハンドラが起動される直前に呼び出されるフックとの事。ここでは setexeccon() を呼び出すことで、CGIを実行した際のセキュリティコンテキストを設定する事にする。

ユーザとコンテキストの対応付けは、httpd.confに書いておくことにする。設定項目を保持するのは selinux_cfg 構造体で、ここから selinux_user_context のリストとして保持。

typedef struct selinux_user_context
{
    struct selinux_user_context *next;
    char *username;
    char *context;
} selinux_user_context;

typedef struct selinux_cfg
{
    int enable_labeled_network;
    selinux_user_context *head;
} selinux_cfg;

httpd.conf に以下のような記述を見つける度に、selinux_user_contextオブジェクトがチェインされるという訳だ。

<IfModule mod_selinux.c>
UserContext kaigai unconfined_u:system_r:httpd_sys_script_t:s0:c0
UserContext tak unconfined_u:system_r:httpd_sys_script_t:s0:c1
</IfModule>

selinux_cmds変数は、許容するパラメータの一覧で、以下のように定義されている。AP_INIT_TAKE2というのは、引数を2つ取るパラメータと言う意味

static const command_rec selinux_cmds[] =
{
    AP_INIT_TAKE2("UserContext",
              selinux_config_user_context,
              NULL, OR_OPTIONS,
              "UserContext <user> <security context>"),
    {NULL},
};

httpd.confに"UserContext"を発見するたびに、selinux_config_user_contextがコールバックされるわけだ。
この関数が、selinux_cfgにユーザ/コンテキストのペアをチェインしていく。

ここまでが Apache の起動フェーズで行われる処理。

ユーザからのアクセスが来た際に selinux_setexeccon など fixup のフック群が呼び出される。

static int selinux_setexeccon(request_rec *r)
{
    selinux_user_context *suc;
    selinux_cfg *scfg
        = ap_get_module_config(r->server->module_config,
                               &selinux_module);
    const char *username = r->user;
    security_context_t context = NULL;

    if (!scfg || !username)
        goto skip;

    for (suc = scfg->head; suc; suc = suc->next) {
        /* default context */
        if (!suc->username) {
            context = suc->context;
            continue;
        }

        if (!strcmp(suc->username, username)) {
            context = suc->context;
            break;
        }
    }

skip:
    if (setexeccon(context) < 0)
        return HTTP_INTERNAL_SERVER_ERROR;
    return OK;
}

これによって、HTTP-BASIC認証のかかっているページを参照し、認証をクリアした場合は、ユーザ毎に設定されているセキュリティコンテキストが setexeccon() によって設定され、CGIプログラムは当該コンテキストで実行されるということになる。

getcon() と getprevcon()の結果、環境変数の一覧を表示させる簡単な CGI プログラムを書いてみると・・・

current context = unconfined_u:system_r:httpd_sys_script_t:<b>s0:c0</b>
previous context = unconfined_u:system_r:httpd_t:s0
    :
  (中略)
    :
REMOTE_USER=kaigai
    :

キタ────(゜∀゜)────!!!!

確かに、webユーザ "kaigai" に設定したセキュリティコンテキストが反映されている。

ちなみに、標準のポリシーには Apache に setexeccon を許す構造になっていないため、以下の追加ポリシーが必要。

policy_module(mod_selinux, 1.00)

gen_require(`
        type httpd_t;
')

allow httpd_t self : process { setexec };

ifdef(`enable_mcs',`
        gen_require(`
                attribute mcssetcats;
        ')
        typeattribute httpd_t mcssetcats;
')

次の課題は labeled network を反映するようにしたいのだが、モジュールにソケットのファイルディスクリプタが渡って来ていないような気がする。さて・・・どうしたものか。