SELinuxについてまとめてみる3(動作検証編)
はじめに
過去2回のSELinuxの記事は、なんだかんだ座学的な内容になってしまいましたが、今回は実際のSELinuxの動作(アクセス制御)について検証してみたいと思います。
検証内容
今回はSELinuxを利用して、httpdプロセスがアクセスできるファイルの制御をかけてみたいと思います(実用的ではないですが、SELinuxの動作を理解するにはいいかなと思ってます)。
具体的には、作成したindex.htmlに対してSELinuxでアクセス制限をかけて閲覧できなくし、その原因特定と修正方法を通してSELinuxの制御の仕方を学べればなと思います。
事前準備
- httpdサービスをインストールします。
$ sudo yum install httpd -y
- httpdのrootディレクトリにindex.htmlファイルを作成します。
$ sudo touch /var/www/html/index.html
$ echo "test" > /var/www/html/index.html
# 以下は、rootで実行
$ echo "test" > /var/www/html/index.html
$ cat /var/www/html/index.html
test
- httpdサービスを起動します。
$ sudo systemctl start httpd
- 作成したindex.htmlにアクセスできることを確認します。
$ curl http://127.0.0.1
test
検証
動作・設定確認
- 検証するにあたって、実際にアクセス制御されていることを確認できるようにしたいので、SELinuxの設定は
enforcing
に変更しておきます。
$ sudo setenforce 1
$ getenforce
Enforcing
-
enforcing
にしてもindex.htmlにアクセスできることを確認します。
$ curl http://127.0.0.1
test
- とりあえず前回紹介したコマンドで、index.htmlのセキュリティコンテキストを確認します。
$ sudo ls -Z /var/www/html/index.html
unconfined_u:object_r:httpd_sys_content_t:s0 /var/www/html/index.html
Typeの値がhttpd_sys_content_t
となっていることを確認します。
※前々回お伝えした通り、SElinuxのポリシーをtargeted
で利用している限りは、User、Role、Levelは特に気にしなくて大丈夫です。
セキュリティーコンテキストの設定変更
以降は、セキュリティコンテキストをコンテキストと略します。
では、先ほど確認したindex.htmlのTypeの値を変えてみます。
今回は、前回も少し出てきたuser_home_t
に変えてみたいと思います(これを選択した意味は特にありません)。
$ sudo chcon -t user_home_t /var/www/html/index.html
$ ls -Z /var/www/html/index.html
unconfined_u:object_r:user_home_t:s0 /var/www/html/index.html
設定を変更したので、再度index.htmlにアクセスしてみます。
$ curl http://127.0.0.1
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>403 Forbidden</title>
</head><body>
<h1>Forbidden</h1>
<p>You don't have permission to access this resource.</p>
</body></html>
今度は、403エラーとなってアクセスできませんでした。
ちなみに、index.htmlのDACの設定を確認しておきます。
$ sudo ls -l /var/www/html/index.html
-rw-r--r--. 1 root root 5 1月 4 19:04 /var/www/html/index.html
DACの設定は特に変えていません。
ログを確認してみる
SELinuxのログは、/var/log/audit/audit.log
に出力されます。
直接ログファイルから確認することもできますが、関係ないログも大量に出力されるため、探すのが大変です。
そんな時は、監査ログを調査できる ausearch
コマンドを使うと便利です。
$ sudo ausearch -c httpd
----
time->Wed Jan 4 20:11:16 2023
type=PROCTITLE msg=audit(1672830676.112:883): proctitle=2F7573722F7362696E2F6874747064002D44464F524547524F554E44
type=PATH msg=audit(1672830676.112:883): item=0 name="/var/www/html/index.html" inode=5089729 dev=fd:00 mode=0100644 ouid=0 ogid=0 rdev=00:00 obj=unconfined_u:object_r:user_home_t:s0 nametype=NORMAL cap_fp=0 cap_fi=0 cap_fe=0 cap_fver=0 cap_frootid=0
type=CWD msg=audit(1672830676.112:883): cwd="/"
type=SYSCALL msg=audit(1672830676.112:883): arch=c00000b7 syscall=56 success=no exit=-13 a0=ffffffffffffff9c a1=ffff8c010520 a2=80000 a3=0 items=1 ppid=121988 pid=121990 auid=4294967295 uid=48 gid=48 euid=48 suid=48 fsuid=48 egid=48 sgid=48 fsgid=48 tty=(none) ses=4294967295 comm="httpd" exe="/usr/sbin/httpd" subj=system_u:system_r:httpd_t:s0 key=(null)
type=AVC msg=audit(1672830676.112:883): avc: denied { read } for pid=121990 comm="httpd" name="index.html" dev="dm-0" ino=5089729 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:user_home_t:s0 tclass=file permissive=0
最後の行に、アクセスが拒否された旨のログが出力されていることがわかります。
もう少し細かくみていくと、
denied { read }
:読み取りが拒否された。
name="index.html"
:index.htmlというファイルに対して
scontext=system_u:system_r:httpd_t:s0
:接続元のコンテキスト
tcontext=unconfined_u:object_r:user_home_t:s0
:接続先のコンテキスト(今回は、index.html)
なぜ拒否されたのかを紐解いてみる
SELinuxはアクセス制御を行うものなので、どのような操作を許可(あるいは拒否)するか の設定があるはずです。
DACで言うところのオーナー・グループ・その他ユーザーに対してのrwx
権限といった設定です。
今回でいうと、ファイルのコンテキスト(Type)を変更したことによってアクセスできなくなったので、これが関係してそうです。
SELinuxのアクセス制御ルールの考え方
まずは、SELinuxのアクセス制御ルールの考え方をまとめておきます。
- SELinuxのアクセス制御ルールとして、デフォルトで大量の許可(allow)設定が存在します。
- 拒否設定はないため、許可設定にないものは全て拒否されることになります。
- 追加で許可設定を入れることは可能ですが、デフォルトの許可設定を削除することはできません。
- 許可設定には、「接続元のType」 と 「接続先のType」、それに対して 「許可する権限」 が記載されています。
今回でいうと、
接続先のType
:user_home_t
許可していなかった権限
:read
というところまでは分かりました。
では、ログで出力された 「接続元のType」 は何を意味しているのでしょうか。
接続元のTypeの確認
今回は、curlコマンドを使ってファイルを表示させているので、httpdプロセスが関係してそうです。
SELinuxはファイルだけでなく、プロセスにもコンテキストが付与 されるので、httpdプロセスのコンテキストを確認してみます。
プロセスのコンテキストを確認する方法は、ps -Z
です。
$ sudo ps -efZ | grep httpd
system_u:system_r:httpd_t:s0 root 121988 1 0 19:06 ? 00:00:01 /usr/sbin/httpd -DFOREGROUND
system_u:system_r:httpd_t:s0 apache 121989 121988 0 19:06 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND
system_u:system_r:httpd_t:s0 apache 121990 121988 0 19:06 ? 00:00:07 /usr/sbin/httpd -DFOREGROUND
system_u:system_r:httpd_t:s0 apache 121991 121988 0 19:06 ? 00:00:07 /usr/sbin/httpd -DFOREGROUND
system_u:system_r:httpd_t:s0 apache 121992 121988 0 19:06 ? 00:00:07 /usr/sbin/httpd -DFOREGROUND
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 User 122544 1236 0 20:50 pts/0 00:00:00 grep --color=auto httpd
httpdプロセスが複数立ち上がっています。
左側に記載のコンテキストを確認すると system_u:system_r:httpd_t:s0
と書かれています。
これがhttpdプロセスのコンテキストであり、今回の接続元のTypeの正体になります。
※上記でログを検索した際の接続元の情報と一致しています。
得られた情報からアクセス制御ルールを確認してみる
これで必要な登場人物は揃ったので、アクセス制御ルールを確認していきます。
SELinuxが持つアクセス制御ルールを確認するときは、sesearch
コマンドを利用します。
おさらいですが、今までで分かった情報は以下です。
接続元のType
:httpd_t
接続先のType
:user_home_t
許可していなかった権限
:read
まずは、元々index.htmlに付いていたTypeであるhttpd_sys_content_t
を確認してみます。
$ sudo sesearch -A -t httpd_sys_content_t -s httpd_t
# たくさん表示されるので、必要な箇所のみ抜粋します。
・・・
allow httpd_t httpd_content_type:file { getattr ioctl lock map open read };
・・・
読み方は、
allow 接続元 接続先:種類 {許可する操作}
です。
接続先がhttpd_sys_content_t
ではなく、httpd_content_type
になっています。
なぜ上記の許可ルールが関係するのか、もう少し紐解いていきます。
アトリビュートについて
Typeのコンテキストは、XXX_t
と記載されると書いていましたが、たまに XXX_type
というものも出てきます。
これは、Typeのコンテキストをグループとして一つにまとめたもので、アトリビュート と呼ばれます。
では、今回出てきたhttpd_content_type
グループにhttpd_sys_content_t
が含まれているかを確認してみます。
確認には、seinfo
コマンドを利用します。
$ sudo seinfo -a httpd_content_type -x
Type Attributes: 1
attribute httpd_content_type;
apcupsd_cgi_content_t
apcupsd_cgi_htaccess_t
apcupsd_cgi_ra_content_t
# 長いので途中省略
git_rw_content_t
git_script_exec_t
httpd_sys_content_t # これが該当
httpd_sys_htaccess_t
httpd_sys_ra_content_t
httpd_sys_rw_content_t
# 長いので省略
-a
:アトリビュートを検索するオプション
-x
:詳細を表示するオプション
httpd_content_type
の中にhttpd_sys_content_t
が含まれていることが確認できました。
今回sesearchコマンドで検索して見つけた許可ルールが、
httpd_t
からhttpd_sys_content_t(httpd_content_type)
のファイルに対して、getattr,ioctl,lock,map,open,read
操作を許可する。
ということがわかります。
では、同じ方法で、chcon
で変更したuser_home_t
を確認してみます。
$ sudo sesearch -A -t user_home_t -s httpd_t
allow daemon user_home_t:file { append getattr };
allow domain file_type:blk_file map; [ domain_can_mmap_files ]:True
allow domain file_type:chr_file map; [ domain_can_mmap_files ]:True
allow domain file_type:file map; [ domain_can_mmap_files ]:True
allow domain file_type:lnk_file map; [ domain_can_mmap_files ]:True
allow httpd_t file_type:dir { getattr open search };
allow httpd_t file_type:filesystem getattr;
allow httpd_t user_home_t:file map; [ httpd_read_user_content ]:True
allow httpd_t user_home_type:dir { ioctl lock read }; [ httpd_read_user_content ]:True
allow httpd_t user_home_type:file { getattr ioctl lock open read }; [ httpd_read_user_content ]:True
allow httpd_t user_home_type:lnk_file { getattr read }; [ httpd_enable_homedirs ]:True
httpd_t
からuser_home_t
に対して許可する設定が見当たりませんが、似たものとしてuser_home_type
がありました。
XXX_type
という書き方なので、アトリビュートになります。
allow httpd_t user_home_type:file { getattr ioctl lock open read }; [ httpd_read_user_content ]:True
user_home_type
のアトリビュートにuser_home_t
が含まれているか確認してみます。
$ sudo seinfo -a user_home_type -x
・・・
user_home_t
・・・
user_home_type
は、user_home_t
を含んでいることがわかりました。
上記許可ルールがあるため、一見許可されているようにも見えますが、許可ルールで気になる記載があります。
[ httpd_read_user_content ]:True
上記は、httpd_read_user_content
という設定が、True(有効)
だったら、この許可ルールが有効になることを意味しています。
これについて理解するには、Boolean について理解する必要があります。
Booleanとは
sesearchで確認しても分かる通り、SELinuxの許可ルールは大量に存在するため、1行1行確認して許可ルールのないものを追加・設定するのは大変です。
そのため、一部のルールはシステム側で最初からグルーピングされており、その一つがhttpd_read_user_content
になります。
グルーピングされたものは、On/Off(Boolean)を切り替えることができ、切り替えることで必要な許可ルールを有効(無効)にすることができます。
グルーピングされた一覧とグルーピングの簡易説明、デフォルトのOn/Off設定、現在のOn/Off設定は、以下で確認ができます。
$ sudo semanage boolean -l
SELinux boolean 状態 初期値 説明
abrt_anon_write (オフ , オフ) Allow abrt to anon write
abrt_handle_event (オフ , オフ) Allow abrt to handle event
・・・・・
httpd_mod_auth_ntlm_winbind (オフ , オフ) Allow httpd to mod auth ntlm winbind
httpd_mod_auth_pam (オフ , オフ) Allow httpd to mod auth pam
httpd_read_user_content (オフ , オフ) Allow httpd to read user content
httpd_run_ipa (オフ , オフ) Allow httpd to run ipa
httpd_run_preupgrade (オフ , オフ) Allow httpd to run preupgrade
・・・・
# 以下のコマンドでも確認ができますが、簡易説明やデフォルト値は分かりません。
$ getsebool httpd_read_user_content
httpd_read_user_content --> off
量が多いので抜粋しましたが、httpd_read_user_content
が存在して、設定がOffであることがわかります。
以上のことを踏まえて、再度許可ルールを確認します。
allow httpd_t user_home_type:file { getattr ioctl lock open read }; [ httpd_read_user_content ]:True
今までの話をまとめると以下になります。
-
user_home_type
には今回設定されたuser_home_t
を含んでいるため、上記の許可ルールが有効であれば、アクセスできるようになるはず。 -
httpd_read_user_content
がTrueである必要があるが、Booleanを確認すると無効になっていた。 - 上記から、今回の許可ルールは無効であり、許可されていない操作である。
つまり、httpdプロセスがindex.htmlに対するread権限を無くしたことによってアクセスできなくなった。
アクセスできるように設定を修正してみる
今までのことより、httpd_read_user_content
をOnにしてあげれば、index.htmlにアクセスできるようになるはずです。
以下コマンドで設定をOnにしてみます。
$ sudo setsebool httpd_read_user_content 1
# 以下のコマンドでも設定変更ができます。
$ sudo semanage boolean -m httpd_read_user_content --on
Onになったか確認してみます。
$ sudo semanage boolean -l | grep httpd_read_user_content
httpd_read_user_content (オン , オフ) Allow httpd to read user content
設定がOnになっていることが確認できたので、再度curlコマンドでファイルアクセスを試みます。
$ curl http://127.0.0.1
test
正常にアクセスできることが確認できました。
補足 エラーの全容はPermissiveでないとわからない
検証の中で、read
権限がなかったという話をしましたが、これは正確ではありません。
なぜなら、SELinuxはルールで許可されていない条件に合致した時点で処理を停止し、後続の処理を行わないからです。
今回の場合だと、read
権限 だけ を修正したとしても、その後の処理で別のルールに引っ掛かってしまう可能性があります。
そのため、一連の処理を通してどのようなエラーが出力されるかの確認を行う場合は、Permissive
にした状態でエラーを確認する必要があります。
以下は、Permissiveにした状態で今回の検証のログを出力した結果です。
$ sudo ausearch -c httpd
----
time->Fri Jan 6 00:10:19 2023
type=PROCTITLE msg=audit(1672931419.413:360): proctitle=2F7573722F7362696E2F6874747064002D44464F524547524F554E44
type=PATH msg=audit(1672931419.413:360): item=0 name="/var/www/html/index.html" inode=5089729 dev=fd:00 mode=0100644 ouid=0 ogid=0 rdev=00:00 obj=unconfined_u:object_r:user_home_t:s0 nametype=NORMAL cap_fp=0 cap_fi=0 cap_fe=0 cap_fver=0 cap_frootid=0
type=CWD msg=audit(1672931419.413:360): cwd="/"
type=SYSCALL msg=audit(1672931419.413:360): arch=c00000b7 syscall=56 success=yes exit=19 a0=ffffffffffffff9c a1=ffff900064d0 a2=80000 a3=0 items=1 ppid=1420 pid=1422 auid=4294967295 uid=48 gid=48 euid=48 suid=48 fsuid=48 egid=48 sgid=48 fsgid=48 tty=(none) ses=4294967295 comm="httpd" exe="/usr/sbin/httpd" subj=system_u:system_r:httpd_t:s0 key=(null)
type=AVC msg=audit(1672931419.413:360): avc: denied { open } for pid=1422 comm="httpd" path="/var/www/html/index.html" dev="dm-0" ino=5089729 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:user_home_t:s0 tclass=file permissive=1
type=AVC msg=audit(1672931419.413:360): avc: denied { read } for pid=1422 comm="httpd" name="index.html" dev="dm-0" ino=5089729 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:user_home_t:s0 tclass=file permissive=1
----
time->Fri Jan 6 00:10:19 2023
type=PROCTITLE msg=audit(1672931419.413:361): proctitle=2F7573722F7362696E2F6874747064002D44464F524547524F554E44
type=MMAP msg=audit(1672931419.413:361): fd=19 flags=0x1
type=SYSCALL msg=audit(1672931419.413:361): arch=c00000b7 syscall=222 success=yes exit=281473710469120 a0=0 a1=5 a2=1 a3=1 items=0 ppid=1420 pid=1422 auid=4294967295 uid=48 gid=48 euid=48 suid=48 fsuid=48 egid=48 sgid=48 fsgid=48 tty=(none) ses=4294967295 comm="httpd" exe="/usr/sbin/httpd" subj=system_u:system_r:httpd_t:s0 key=(null)
type=AVC msg=audit(1672931419.413:361): avc: denied { map } for pid=1422 comm="httpd" path="/var/www/html/index.html" dev="dm-0" ino=5089729 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:user_home_t:s0 tclass=file permissive=1
read
以外に、open
やmap
権限もアクセス拒否されていることがわかります。
検証の最後に有効化したルールの中に、open
とmap
も許可する設定が入ってたため、結果的に問題無く動作したということです。
おわりに
前回までに紹介したコマンドも利用したかったので、無理矢理感が出た感じですが、結果的にはまとまったかなと思います。
また、今回の検証は、主にファイルに対してのアクセス制御を取り上げましたが、SELinuxでは利用するポート等々、色々なものに対して細かく制限することができます。
ただ、どんな制限の仕方にせよ、考え方は今回紹介した内容で十分だと思います。
※自分も勉強しながら書いてるので間違った理解があるかもしれませんが。。
SELinuxについては引き続き記事を書けたらなと思います。
Discussion