SELinux 基礎知識と実践

#はじめに
「SELinux？ Disabled にしとけばいいでしょ」と思っていた自分への戒めです。
自分用の勉強したことまとめです。

#SELinux

アメリカの諜報機関が作成したLinuxセキュリティ拡張機能。Linux 2.6 以降でサポート。
Linux に強制アクセス制御の仕組みを提供する。外部に侵入されないための仕組みではなく、侵入された後にアクセスできる範囲を最小限に抑えるための仕組み。フェールセーフ的な考え方。
ファイアウォールやアンチウイルスソフトとはレイヤが異なる。

## SELinux が有効に働いた例
CVE-2019-5736 において、docker コンテナを実行する runc のバイナリが上書きされ、root権限で任意のコードが実行される脆弱性が発覚した。RHEL の SELinux においては runc に対し適切なアクセス権限が割り当てられているため、root 権限でもアクセス可能範囲を限定できており、攻撃されても被害を低減できるとのこと。

参考：https://security.sios.com/vulnerability/runc-security-vulnerability-20190211.html

## アクセス制御概観
誰が（何が）、何に対して、どのような権限（rwx）を持つのか、を制御する仕組みをアクセス制御（Access Controle）という。
アクセス制御には大別して、任意アクセス制御と強制アクセス制御がある。
SELinux は強制アクセス制御に分類される。

### 任意アクセス制御
アクセス制御を行う対象の所有者が、自由にアクセス制御の設定を変更できる仕組み。
パーミッションは、所有者が chmod や chown でアクセス制御の設定を自由に変更できるので、任意アクセス制御である。

### 強制アクセス制御
システム管理者以外が変更できない、絶対のアクセス制御設定の仕組み。

### アクセス制御の仕組みの概観
アクセス制御を実現する仕組みは多くあるが、概観を示すと以下のようになる。

```
アクセス制御（Access Controle）
|-任意アクセス制御（Discretionary Access Controle）
| |-パーミッション
| |   従来の「ユーザ、グループ、その他」に対し「読み、書き、実行」を設定する仕組み。
| |-ACL（Access Controle List）
|     ユーザ一人ひとり単位にアクセス制御を設定する仕組み。
|-強制アクセス制御（Mandatory Access Controle）
  |-AppArmor
  |-Smack
  |-SELinux（Security-Enhanced Linux）
    |-TE（Type Enforcement）
    |   プロセスごとにアクセス制御する仕組み。
    |-RBAC（Role Based Access Controle）
        ユーザのロールごとにアクセス制御する仕組み。
```

## SELinux の機能概観
### TE（Type Enforcement）
"プロセス"がアクセスできるリソースを制限する機能。
プロセスにドメインを、リソースにタイプを割り当て、その組み合わせごとにアクセス権を設定する。
このアクセス権の設定の集合をアクセスベクタという。

TE の構成をざっくり ER 図に示すと以下のようになる。

```
+-----------+          +-------------+         +----------+
| プロセス   | 1      * |アクセスベクタ| *     1 | リソース |
+-----------+ -------- +-------------+ ------- +----------+
|・ドメイン  |          |・ドメイン    |         |・タイプ  |
+-----------+          |・タイプ      |         +----------+
|・権限(rwx) |          +-------------+
+-----------+
```

子プロセス生成時には、基本的には親プロセスのドメインが引き継がれる。
子プロセスのドメインを変更することを、ドメイン遷移と呼ぶ。

### RBAC（Role Based Access Controle）
"ユーザ"がアクセスできるリソースを制限する機能。
ユーザごとにアクセス可能なロールが決まっており、ロールごとにアクセス可能なドメインが決まっている。
TE により、ドメインごとにアクセス可能なタイプが決まっているので、最終的には、ユーザがアクセス可能なオブジェクトを制御することができる。

RBAC の構成をざっくり ER 図に示すと以下のようになる。

```
+--------------+ *    1 +---------------+ *    * +--------+ *    * +----------+
| Linux ユーザ | ------- |SELinux ユーザ | ------ | ロール  | ------ | ドメイン |
+--------------+        +---------------+        +--------+        +----------+
```

なお、RHEL では全 Linux ユーザが unconfined_u に対応しており、unconfined_u にはアクセス制限がかけられない。
したがって、RHEL では RBAC は意味をなさない（？）（要出典）

## SELinux の機能詳細

### セキュリティコンテキスト
SELinux では、上記のドメインやタイプといったものを、セキュリティコンテキストという形で表現する。

以下はセキュリティコンテキストの例である。

```
例）system_u     :  object_r    : lib_t                    : s0
説明）ユーザ識別子 : ロール識別子 : タイプ（ドメイン）識別子 : MLS
※MLS：Multi Level Security：情報の機密性
```

### SELinux ユーザ

通常の Linux ユーザとは異なり、Linux ユーザごとに対応する SELinux ユーザが設定されている。
SELinux ユーザの例を示す。

* unconfined_u：アクセス制限のないユーザ（RHEL では全 Linux ユーザが unconfined_u になる）

### ロール
ロールの例を示す。

* object_r：一般的なファイルのロール。ロールはファイルにとって意味はない。
* system_r：プロセスのロール。（/proc/以下のプロセス関連のファイルなどにも使われる）

### ドメイン
プロセスに割り当てられる、ドメインの例を示す。
ドメインは大きく「制限のないドメイン」と「制限のあるドメイン」に分けられる。

「制限のないドメイン」は SELinux によるアクセス制限を受けない。つまり、乗っ取られたら SELinux による防御は意味をなさないということ。（もちろん通常のパーミッションによるアクセス制御は行われる）

* unconfined_t：ログインユーザが起動するプロセスのドメイン
* initrc_t：init で開始されたシステムプロセスのドメイン
* kernel_t：カーネルプロセスのドメイン

「制限のあるドメイン」は、一般的なアプリケーションに割り当てられる。これらのドメインは SELinux によるアクセス制御がなされる。

* http_t：httpd 関連プロセスのドメイン

### タイプ
ファイルに割り当てられる、タイプの例を示す。

* file_t：タイプが割り当てられていないことを示すタイプ。制限されたドメインからはアクセス不可。
* default_t：割り当てるべきタイプが設定されていない場合に、デフォルトで割り当てられるタイプ。制限されたドメインからはアクセス不可。

### ドメイン遷移
実行ファイルのタイプに、ドメインの "entrypoint" パーミッションが与えられている場合、その実行ファイルのプロセスはそのドメインに遷移する。
"entrypoint" パーミッションのあるタイプはおおむね「*****_exec_t」という名称になっている。たとえば、「passwd_exec_t」の実行ファイルを実行したとき、そのプロセスは「passwd_t」ドメインで実行される、ということである。これをドメイン遷移と呼ぶ。
「ドメイン遷移をすればどんなプロセスでもどんなドメインになれるじゃん！」と思われるかもしれないが、「*****_exec_t」にアクセス可能なドメインがそもそも限られており、どんなプロセスでも任意のドメインにドメイン遷移できるわけではない。

## 実機操作

### SELinux の状態

* Disabled  ：無効
* Enforcing ：有効
* Permissive：有効だがアクセス制御は行わず、禁止操作が行われた場合はログにだけ残す。

現在の状態は以下のコマンドで確認できる。

```
# getenforce
Permissive
```

有効・無効の設定は /etc/selinux/config に記載し、再起動する。


```:/etc/selinux/config
SELINUX=permissive
```

以下は Enforcing <-> Permissive の遷移を行うコマンドである。
このコマンドは Disabled のときには使えない。

```
# setenforce Enforcing
```

### セキュリティコンテキスト

ユーザのセキュリティコンテキスト確認

```
# id -Z
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
```

ファイルのセキュリティコンテキスト確認

```
# ls -Z
-rw-r--r--. root root unconfined_u:object_r:admin_home_t:s0 test
```

プロセスのセキュリティコンテキスト確認

```
# ps -Z
LABEL                             PID TTY          TIME CMD
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 4940 pts/0 00:00:01 bash
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 13364 pts/0 00:00:00 ps
```

### 設定ファイル
#### /etc/selinux/targeted/contexts/files/file_contexts
ファイルのセキュリティコンテキスト設定

```:/etc/selinux/targeted/contexts/files/file_contexts

/.*     system_u:object_r:default_t:s0
/[^/]+  --      system_u:object_r:etc_runtime_t:s0
/a?quota\.(user|group)  --      system_u:object_r:quota_db_t:s0
/nsr(/.*)?      system_u:object_r:var_t:s0
/sys(/.*)?      system_u:object_r:sysfs_t:s0
/xen(/.*)?      system_u:object_r:xen_image_t:s0
/mnt(/[^/]*)?   -d      system_u:object_r:mnt_t:s0
/mnt(/[^/]*)?   -l      system_u:object_r:mnt_t:s0
/bin/.* system_u:object_r:bin_t:s0
/dev/.* system_u:object_r:device_t:s0
  :
```


### ブール値
ポリシールールをひとつひとつ設定するのは大変。
SELinuxには、最初からポリシールールを意味のある集まりとしてまとめたもの（httpd でユーザディレクトリのアクセスを許可するか、など）があり、これをブール値と呼ぶ。
ユーザはブール値単位で簡単にアクセス権限を設定できる。

ブール値は semanage boolean -l や getsebool -a 等で確認できる

```
# semanage boolean -l |head
SELinux boolean                状態  初期値 説明

privoxy_contestt_any            (オン   ,   オン)  Allow privoxy to contestt any
smartmon_3ware                 (オフ   ,   オフ)  Allow smartmon to 3ware
mpd_enable_homedirs            (オフ   ,   オフ)  Allow mpd to enable homedirs
xdm_sysadm_login               (オフ   ,   オフ)  Allow xdm to sysadm login
xen_use_nfs                    (オフ   ,   オフ)  Allow xen to use nfs
mozilla_read_content           (オフ   ,   オフ)  Allow mozilla to read content
ssh_chroot_rw_homedirs         (オフ   ,   オフ)  Allow ssh to chroot rw homedirs
mount_anyfile                  (オン   ,   オン)  Allow mount to anyfile
```


## 実践
### httpd でユーザコンテンツを扱う

SELinux が Enforcing の環境で、httpd がユーザディレクトリをアクセスする設定を行う

```:/etc/httpd/conf/httpd.conf 
<VirtualHost ***.***.***.**:80>
ServerAdmin y-****@test.test.jp.test.com
DocumentRoot /home/learnlist/html
             ^^^^^^^^^^^^^^^^^^^^★ユーザディレクトリ
ServerName learn.www.****.test.net
```

対象のページにアクセスしてみるが、SELinux に弾かれ、permission denied.

```
# curl http://learn.www.****.test.net/
Permission denied.
```

setroubleshoot が動いている環境ならば、jounald に SELinux が弾いた旨のログが記載される。
1行目は SELinux がどのような操作を弾いた（prvent）のか、2行目にはこの操作を許可するにはどうすればよいのかが書かれている。

```:/var/log/messages
Jan 28 11:16:58 **** setroubleshoot: SELinux is preventing /usr/sbin/httpd from read access on the file /home/lear...
Jan 28 11:16:58 **** python: SELinux is preventing /usr/sbin/httpd from read access on the file /home/learnlist/h...
```

今回 httpd でユーザディレクトリにアクセスできるようにしたいので、上記2行目の以下の指示に従う。

*If you want to allow httpd to read user content Then you must tell SELinux about this by enabling the 'httpd_read_user_content' boolean.*
*もしあなたが httpd がユーザコンテンツを読み込むことを許可することを望むなら、あなたはこのことを SELinux に 'httpd_read_user_content' を enable にすることで伝えなければならない。*

現時点で 'httpd_read_user_content' は確かに OFF になっている

```
# getsebool 'httpd_read_user_content'
httpd_read_user_content --> off
```

setsebool で ON にする

```
# setsebool 'httpd_read_user_content' on
# getsebool 'httpd_read_user_content'
httpd_read_user_content --> on
```

再起動後も設定を保持するため、setsebool -P で ON にする。

```
# setsebool -P 'httpd_read_user_content' on
```

httpd がユーザコンテンツにアクセスすることが許可され、curl で Web ページにアクセスできるようになった。

```
# curl http://learn.www.****.test.net/
<a href="learnlist">Learning List</a></br>
<a href="kpt">KPT</a></br>
```

### httpd の cgi で python と posgresql を動かす
cgi ファイルにアクセスしたら SELinux に弾かれエラー

```
# curl http://learn.www.****.test.net/learnlist/
```

/var/log/messages を見る

```
Jan 28 11:34:08 **** setroubleshoot: SELinux is preventing /usr/bin/python2.7 from execute access on the file /home...
Jan 28 11:34:08 **** python: SELinux is preventing /usr/bin/python2.7 from execute access on the file /home/learnl...
```

今度はこれを許可するようなブール値が存在しないらしい。
代わりに、以下のコマンドが紹介されている。
これは、SELinux が操作を弾いたログから、その操作を許可するポリシーを自動生成するコマンド。

```
grep index.cgi /var/log/audit/audit.log | audit2allow -M mypol
semodule -i mypol.pp
```

まずは該当の操作を許可するようなポリシーを作る。

```
# grep index.cgi /var/log/audit/audit.log | audit2allow -M mypol
******************** IMPORTANT ***********************
To make this policy package active, execute:

semodule -i mypol.pp
```

作ったポリシーは以下のファイルとして吐き出される。

* mypol.te：Type Enforcement ポリシーが記載されたファイル
* mypol.pp：mypol.te をコンパイルしたオブジェクトファイル

mypol.te の内容は以下のようになっている。

```
# cat mypol.te

module mypol 1.0;

require {
    type postgresql_port_t;
    type httpd_t;
    type user_home_t;
    class tcp_socket name_contestt;
    class lnk_file read;
    class file { execute execute_no_trans ioctl open read };
}

#============= httpd_t ==============

#!!!! This avc can be allowed using one of the these booleans:
#     httpd_can_network_contestt, httpd_can_network_contestt_db
allow httpd_t postgresql_port_t:tcp_socket name_contestt;
allow httpd_t user_home_t:file { execute execute_no_trans };

#!!!! This avc is allowed in the current policy
allow httpd_t user_home_t:file { ioctl open read };

#!!!! This avc can be allowed using the boolean 'httpd_enable_homedirs'
allow httpd_t user_home_t:lnk_file read;
```

作ったポリシーをアクティブにする（インストールする）。

```
# semodule -i mypol.pp
```

cgi で python が実行できるようになり、該当のページにアクセスできるようになった。

```
#  curl http://learn.www.****.test.net/learnlist/
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>気になることリスト</title>
（後略）
```