- LXCの標準設定ではセキュリティ上の理由から chrony が動かせないようになっている
- コンテナはホストの時刻を使うため、NTPクライアント(chronyc)は必要はないが、NTPサーバ(chronyd)をコンテナで運用したいことはままある*1
- そこで、セキュリティリスクを承知のうえで、コンテナの中で chronyd を動かして、ホストの時刻を修正したり、時刻を配信したり、できるようにする
検証環境
LXCコンテナ内でのchronyの挙動
とりあえず動かして見る
普通にLXCでコンテナを作った場合、たとえそれが特権コンテナでも chronyd は動作しない。
$ sudo apt install chrony $ sudo systemctl restart chronyd $ sudo systemctl status chronyd ● chrony.service - chrony, an NTP client/server Loaded: loaded (/lib/systemd/system/chrony.service; enabled; vendor preset: enabled) Active: inactive (dead) Condition: start condition failed at Wed 2020-11-18 04:18:48 UTC; 2min 14s ago └─ ConditionCapability=CAP_SYS_TIME was not met Docs: man:chronyd(8) man:chronyc(1) man:chrony.conf(5) Nov 18 04:18:48 tim systemd[1]: Condition check resulted in chrony, an NTP client/server being skipped.
どうして?
キモになるのはここ。
ConditionCapability=CAP_SYS_TIME was not met
LXCの特権コンテナでは、init が root 権限で実行されている。そのため、何も制限をしないとコンテナの root はホストの root と同等の権限になる*2。
そこでセキュリティ担保のため、コンテナ内の root からは特にセキュリティリスクの高い権限が剥奪されている。CAP_SYS_TIME
はシステムタイマやタイマデバイスを操作するための権限。他にどのような権限があるかは以下の man ページを参照。
自分の環境では、全てのコンテナで共通的に読み込まれる config ファイルで以下のようにいくつかの権限が剥奪されていた。
$ cat /usr/share/lxc/config/common.conf ... # Drop some harmful capabilities lxc.cap.drop = mac_admin mac_override sys_time sys_module sys_rawio ...
lxc.cap.drop
は、ここに定義した権限を剥奪するパラメータ。ここで sys_time
が設定されているので、CAP_SYS_TIME
が剥奪されている。これが、本事象の原因となる。
対策
上記のパラメータから sys_time
を外してしまいたいけども、それをすると全てのコンテナに権限が付与されてしまう。また、困った事にLXCに権限を与えるパラメータはない*3。
仕方がないので、lxc.cap.drop
を再定義する。基本的に lxc.cap.drop
の設定は重ねがけだが、空欄を指定するとそれまで指定した全ての設定がクリアされるという仕様になっている。
そこで、共有の config は手を付けずに、chronyd を導入したいコンテナの config の一番末尾で空欄指定で一度パラメータをクリアしてから、sys_time
を除いた権限を指定する。
$ sudo vim /var/lib/lxc/【コンテナ名】/config ... # Drop some harmful capabilities lxc.cap.drop = lxc.cap.drop = mac_admin mac_override sys_module sys_rawio ...
動作確認
権限を付与するためにコンテナを再起動する。
$ sudo lxc-stop -n 【コンテナ名】 $ sudo lxc-start -n 【コンテナ名】
確認してみる。
$ sudo systemctl status chronyd ● chrony.service - chrony, an NTP client/server Loaded: loaded (/lib/systemd/system/chrony.service; enabled; vendor preset: enabled) Active: active (running) since Wed 2020-11-18 07:04:41 UTC; 10s ago Docs: man:chronyd(8) man:chronyc(1) man:chrony.conf(5) Process: 112 ExecStart=/usr/sbin/chronyd $DAEMON_OPTS (code=exited, status=0/SUCCESS) Process: 124 ExecStartPost=/usr/lib/chrony/chrony-helper update-daemon (code=exited, status=0/SUCCESS) Main PID: 122 (chronyd) Tasks: 2 (limit: 4915) Memory: 1.3M CGroup: /system.slice/chrony.service ├─122 /usr/sbin/chronyd -F -1 └─123 /usr/sbin/chronyd -F -1 Nov 18 07:04:41 tim systemd[1]: Starting chrony, an NTP client/server... Nov 18 07:04:41 tim chronyd[122]: chronyd version 3.4 starting (+CMDMON +NTP +REFCLOCK +RTC +PRIVDROP +SCFILTER +SIGND +ASY NCDNS +SECHASH +IPV6 -DEBUG) Nov 18 07:04:41 tim chronyd[122]: Initial frequency -10.270 ppm Nov 18 07:04:41 tim chronyd[122]: Loaded seccomp filter Nov 18 07:04:41 tim systemd[1]: Started chrony, an NTP client/server. Nov 18 07:04:47 tim chronyd[122]: Selected source 23.131.160.7
これで、コンテナの中から、ホストのシステムタイマーを制御できるようになる。
余談だが、ホストで既に chronyd や systemd-timesyncd など、タイマを制御するデーモンが動いているなら、止めておかないと大変なことになる。
おわりに
- LXCでは、セキュリティ上の理由から、標準でタイマを制御する権限が剥奪されている
- コンテナ内で chronyd を動かすために、特定のコンテナからタイマを制御する権限を復活させるための手順を整理した
- タイマの制御は、かなりプリミティブな権限なのでくれぐれも自己責任で