kWatanabe 記事一覧へ

kWatanabe の 技術帖

某企業でOSや仮想化の研究をやっているインフラ系エンジニア。オンプレとクラウドのコラボレーションなど、興味ある技術を綴る。

LXCコンテナの中から、chronydでホストの時刻を補正・配信する

  • LXCの標準設定ではセキュリティ上の理由から chrony が動かせないようになっている
  • コンテナはホストの時刻を使うため、NTPクライアント(chronyc)は必要はないが、NTPサーバ(chronyd)をコンテナで運用したいことはままある*1
  • そこで、セキュリティリスクを承知のうえで、コンテナの中で chronyd を動かして、ホストの時刻を修正したり、時刻を配信したり、できるようにする

検証環境

  • Debian 10.6 (buster) amd64 (ホスト、コンテナ共に)
  • LXC 3.1.0
  • 特権コンテナ

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 ページを参照。

linuxjm.osdn.jp

自分の環境では、全てのコンテナで共通的に読み込まれる 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 の設定は重ねがけだが、空欄を指定するとそれまで指定した全ての設定がクリアされるという仕様になっている。

linuxcontainers.org

そこで、共有の 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 を動かすために、特定のコンテナからタイマを制御する権限を復活させるための手順を整理した
  • タイマの制御は、かなりプリミティブな権限なのでくれぐれも自己責任

*1:少なくとも kWatanabe は

*2:root以外のユーザはuser namespaceによりremapされるので、コンテナ内のユーザとホストのユーザは、同じユーザ名でも異なるユーザとして扱われる。

*3:lxc.cap.keep というパラメータがあるが、これは「指定した権限以外を全て剥奪する」効果なので、今回の用途に適さない。