Apacheで cgi-bin 以外のディレクトリにCGIスクリプトを配置する

実行環境

* CentOS Linux release 7.4.1708 (Core)
* Apache/2.4.6 (CentOS)

ここでは、新しく追加するCGIのディレクトリを `/var/www/platform` としています。作成するディレクトリに合わせて適宜読み替えてください

# ディレクトリの作成

Apacheの実行ユーザーで読み書きができるディレクトリを作成します

```shell-session
# mkdir /var/www/platform
# chmod 775 /var/www/platform
# chown root.apache /var/www/platform
```

/var/www/cgi-bin と合わせて root.apache にすれば良いと思います

```shell-session
# ls -ld /var/www/cgi-bin /var/www/platform
drwxrwxr-x. 2 root apache 28 12月  5 19:12 /var/www/cgi-bin
drwxrwxr-x. 5 root apache 64 12月  6 13:45 /var/www/platform
```

# Apacheの設定

基本的には [Apacheのドキュメント]( http://httpd.apache.org/docs/current/howto/cgi.html) の通りに設定します

`/etc/httpd/conf.d` に新しく追加するディレクトリの設定を追加します。ここでは `/etc/httpd/conf.d/test_platform.conf` という名前で新しい設定ファイルを作りましたが、末尾が ".conf" であればどのような名前でも構いません、自分が新たに何の目的で作ったのか分かるような名前にすれば良いと思います

/etc/httpd/conf.d/test_platform.conf 

```text
Alias /platform/ /var/www/platform/
<Directory "/var/www/platform">
    AllowOverride None
    Options ExecCGI
    Require method GET POST OPTIONS
    <FilesMatch (\.cgi|^[^\.]+)$>
        Sethandler cgi-script
    </FilesMatch>
</Directory>
```

この設定で `http://%サーバーのアドレス%/platform/` 配下が `/var/www/platform/` 内のコンテンツとして配置されます。ここでは platform ディレクトリ内に CGIスクリプトだけでなく、画像やHTMLコンテンツの配置も考えて `ScriptAlias` ではなく `Alias` にして `Options ExecCGI` を設定するようにしています。また、 `.cgi` の拡張子だけでなく、拡張子のない場合は CGI スクリプトとして動作するようにしています

変更後は、設定を適用させます

```shell-session
# systemctl reload httpd.service
```

# SELinuxの設定

※ SELinuxを無効にしている場合は、この手順は必要ありません

Apacheの設定が完了し、いざcgiを実行しようとすると 503 エラー。ログを見るとスクリプト実行のパーミッション拒否でした

```shell-session
 AH01215: (13)Permission denied: exec of '/var/www/platform/test' failed
 ```

もちろんファイルのオーナーや実行権の確認はしています

```shell-session
# ls -l
合計 1
-rwxr-xr-x. 1 apache   apache    71 12月  5 18:42 test
```

ネットで見ると、SELinuxの動作が疑わしかったのですが、Auditログや `ausearch --message AVC` コマンドを確認しても拒否された情報が残っていなかったので、Apacheの設定ミスなのかと思ってかなりハマってしまいました。`setenforce 0` を実行するとCGIスクリプトが動作するようになったので、SELinuxの設定は必要だったようです。

SELinuxにはファイルパスに対して、どんな目的で利用されるかといったコンテキストを持っていて、その目的以外のアクセスが行われるとブロックする機能があります。その設定は `/etc/selinux/targeted/contexts/files/file_contexts` にあり、これを見てみると cgi-bin とついた名前のディレクトリに対して httpd_sys_script_exec_t というタイプが設定されるようになってます

```
$ grep 'cgi-bin' /etc/selinux/targeted/contexts/files/file_contexts
/var/www/[^/]*/cgi-bin(/.*)?    system_u:object_r:httpd_sys_script_exec_t:s0
/var/www/html/[^/]*/cgi-bin(/.*)?       system_u:object_r:httpd_sys_script_exec_t:s0
/usr/lib/cgi-bin(/.*)?  system_u:object_r:httpd_sys_script_exec_t:s0
/var/www/cgi-bin(/.*)?  system_u:object_r:httpd_sys_script_exec_t:s0
/usr/lib/mailman.*/cgi-bin/.*   --      system_u:object_r:mailman_cgi_exec_t:s0
/usr/lib/cgi-bin/(nph-)?cgiwrap(d)?     --      system_u:object_r:httpd_suexec_exec_t:s0
/var/www/cgi-bin/munin.*        system_u:object_r:munin_script_exec_t:s0

         :
```

新しいCGIのディレクトリにも、このコンテキストを設定すればCGIスクリプトが実行できるようになります。コンテキストの設定には `semanage` コマンドを使います。ディレクトリ名の後に `(/.*)?` を付けて、正規表現でディレクトリ自身とその配下に含まれるファイルやサブディレクトリを指すようにします

```shell-session
# semanage fcontext -a -t httpd_sys_script_exec_t '/var/www/platform(/.*)?'
# semanage fcontext -C -l
SELinux fcontext                                   タイプ                コンテキスト

/var/www/platform(/.*)?                            all files          system_u:object_r:httpd_sys_script_exec_t:s0
```

/etc/selinux/targeted/contexts/files/file_contexts.local が出来上がるので、中を見てみると、新規登録した `/var/www/platform` が記述されています

実行例

```shell-session
# cat /etc/selinux/targeted/contexts/files/file_contexts.local
# This file is auto-generated by libsemanage
# Do not edit directly.

/var/www/platform(/.*)?    system_u:object_r:httpd_sys_script_exec_t:s0
```

`restorecon` コマンドを実行して、ディレクトリに変更したタイプを設定します

```shell-session
# restorecon -v /var/www/platform
restorecon reset /var/www/platform context unconfined_u:object_r:httpd_sys_content_t:s0->unconfined_u:object_r:httpd_sys_script_exec_t:s0
```

※ /var/www 以下ではないディレクトリであれば変更前のタイプは `httpd_sys_content_t` になってないかもしれません。

`httpd_sys_content_t` から `httpd_sys_script_exec_t` に変わったことを確認します。コンテキストの確認には `ls` コマンドの `-Z` オプションを使います

```shell-session
ディレクトリ自体のコンテキスト
# ls -dZ /var/www/platform
drwxrwxr-x. root apache unconfined_u:object_r:httpd_sys_script_exec_t:s0 /var/www/platform

ディレクトリ内のファイルのコンテキスト
$ ls -l -Z /var/www/platform/test 
-rwxr-xr-x. apache apache unconfined_u:object_r:httpd_sys_script_exec_t:s0 /var/www/platform/test
```

これでCGIスクリプトが実行できるようになっているはずです

## 参考

redhat CUSTOMER PORTAL - 4.6. SELINUX コンテキスト – ファイルのラベル付け
https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/7/html/selinux_users_and_administrators_guide/sect-security-enhanced_linux-working_with_selinux-selinux_contexts_labeling_files