📚

SELinuxについてまとめてみる3(動作検証編)

2023/01/07に公開

はじめに

過去2回のSELinuxの記事は、なんだかんだ座学的な内容になってしまいましたが、今回は実際のSELinuxの動作(アクセス制御)について検証してみたいと思います。

検証内容

今回はSELinuxを利用して、httpdプロセスがアクセスできるファイルの制御をかけてみたいと思います(実用的ではないですが、SELinuxの動作を理解するにはいいかなと思ってます)。

具体的には、作成したindex.htmlに対してSELinuxでアクセス制限をかけて閲覧できなくし、その原因特定と修正方法を通してSELinuxの制御の仕方を学べればなと思います。

事前準備

  1. httpdサービスをインストールします。
$ sudo yum install httpd -y
  1. 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
  1. httpdサービスを起動します。
$ sudo systemctl start httpd
  1. 作成したindex.htmlにアクセスできることを確認します。
$ curl http://127.0.0.1
test

検証

動作・設定確認

  1. 検証するにあたって、実際にアクセス制御されていることを確認できるようにしたいので、SELinuxの設定はenforcingに変更しておきます。
$ sudo setenforce 1
$ getenforce
Enforcing
  1. enforcingにしてもindex.htmlにアクセスできることを確認します。
$ curl http://127.0.0.1
test
  1. とりあえず前回紹介したコマンドで、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  14 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

今までの話をまとめると以下になります。

  1. user_home_typeには今回設定されたuser_home_tを含んでいるため、上記の許可ルールが有効であれば、アクセスできるようになるはず。
  2. httpd_read_user_contentがTrueである必要があるが、Booleanを確認すると無効になっていた。
  3. 上記から、今回の許可ルールは無効であり、許可されていない操作である。
    つまり、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以外に、openmap権限もアクセス拒否されていることがわかります。
検証の最後に有効化したルールの中に、openmapも許可する設定が入ってたため、結果的に問題無く動作したということです。

おわりに

前回までに紹介したコマンドも利用したかったので、無理矢理感が出た感じですが、結果的にはまとまったかなと思います。
また、今回の検証は、主にファイルに対してのアクセス制御を取り上げましたが、SELinuxでは利用するポート等々、色々なものに対して細かく制限することができます。
ただ、どんな制限の仕方にせよ、考え方は今回紹介した内容で十分だと思います。
※自分も勉強しながら書いてるので間違った理解があるかもしれませんが。。

SELinuxについては引き続き記事を書けたらなと思います。

Discussion