コンテナセキュリティ TetragonとPodSecurity/seccompの機能比較

Sreake事業部

2023.9.11

自己紹介

高島 陸斗

千葉工業大学修士1年生の高島陸斗です。大学院では、コンピュータによる数値計算の厳密解との誤差がどの程度あるのかを調べる精度保証の精度を上げるための研究をしています。サイバーセキュリティに興味があり、ネットワーク系の知識をつけたいと考えて本インターンに参加しました。

井上 裕介

千葉工業大学 情報科学部 情報工学科 学部3年の井上裕介です。Dockerを使ったUbuntuサーバの開発・運用を個人で行っており、Kubernetesを使った冗長化や分散処理の技術に興味があります。本インターンを通してSREについて少しでも多くのことを学べればと思い参加しました。

はじめに

はじめまして、スリーシェイクのSreake 事業部インターン生の高島陸斗と井上裕介です。Sreake 事業部は SRE 技術に強みを持つエンジニアによるコンサルテーションサービスを提供する事業部であり、私たちも SRE 技術の調査と研究を行う目的で2023年8月21日~9月1日に開催された短期インターンに参加しました。
2週間という期間を使って、eBPFをベースに作られたオブザーバビリティおよびセキュリティの機能をもつTetragonとKubernetesのセキュリティ機能の比較を行いました。以下、その成果を記述します。

Tetragonでできること

機能は大きく2つあり、Process execution observation と Generic tracing です。
簡単に言うと、稼働中(Runtime)のコンテナに対して、プロセスやトレースしたい場所の監視、フックポイントでのアクションの設定といった機能があります。

  • Process execution observation
    プロセスの生成および終了をリアルタイム監視を行い、process_execprocess_exitというイベントを生成します。これらのイベントは、linuxレベルのデータだけでなく、Kubernetesレベルのデータも同時に見ることができます。これにより、ノード、プロセス、Kubernetesまたはコンテナ環境の繋がりを知ることができます。
    • process_exec
      プロセスの開始やバイナリ実行についての情報がわかるイベント
    • process_exit
      process_execの反対で、プロセスの終了情報がわかるイベント
  • Generic tracing
    tracepointやkprobeの機能を利用した任意のカーネル関数呼び出しを監視し、フックします。(tracepointやkprobeについては次のセクションで詳しく説明します。)ただし、この機能を使うためには、Tetragonを拡張してTracingPolicyという名前のオブジェクトを構成する必要があります。process_tracepointprocess_kprobesというイベントを生成します。
    • process_tracepoint
      tracepointというlinuxカーネルに組み込まれた内部で発生する様々なイベントをトレースするためのフックポイントを利用したイベントです。
      linuxカーネルにすでに組み込まれているフックポイントしかトレースできないという欠点があります。
    • process_kprobes
      kprobeという独自で特定のカーネル関数呼び出しをトレースすることができる機能を利用したイベントです。
      便利に見えますが、カーネルのビルドに依存するカーネル空間内のオブジェクトに影響を大きく受けるため、監視したい対象がないとすでに作成されたプログラムなどが移植がしにくいという欠点があり、注意が必要です。

プロセスの監視時に出てくる出力は標準だとjson形式で出てきますが見づらいため、tetragonのCLIを利用して確認するとtetragonのログの視認性が高くなります。

eBPFを利用したセキュリティツールTetragon

Tetragonがどんなシステムなのかを知るため、初めから見ていきましょう。

Linuxカーネル

ハードウェアとソフトウェアの架け橋になるものです。これのおかげで、我々が想定しているようなLinuxとしての挙動をしていると言えます。

kallsymsファイル

カーネルもある種のソフトウェアのように動くからには、いろいろな関数や変数などのオブジェクトがたくさんあります。これらをカーネル空間内のシンボルと呼ぶこともあります。
また、ノイマン型のアーキテクチャに縛られている限りは、シンボルもまたアドレスを持つはずです。これらの組み合わせがすべて明記されているのが、kallsymsというファイルです。
ちなみに、この情報はカーネルを破壊する可能性のある情報のため、管理者権限を利用しなければ、アドレスの情報を閲覧することはできないようになっています。

システムコール

一般的なシステムの開発を行っている際に、毎度Linuxカーネルのシンボルを大量に呼び出すことは手間がかかります。また、セキュリティ的に問題があります。
つまり、カーネルの干渉を想定していない開発者がカーネルを破壊する可能性のある行動ができてしまうのは致命的です。しかし、カーネルの機能を利用しなければ、プログラムが動かせないことがほとんどです。

このため、セキュリティに配慮しつつ、カーネルの機能も部分的に使用できるようにしたのがシステムコールです。現実世界の比喩で言うならば、一般人がカーネルという神様にシステムコールによって対話をして、神の恩寵をいただくというようなことです。

ちなみに、システムコールも別段使いやすいというわけではないので、さらにライブラリやコマンドなどを作って開発者が使いやすいようにしています。

  • straceコマンド
    稼働しているプログラムやプロセスがどのようなシステムコールを利用しているのかを確認できるコマンド。

eBPF

普段利用する限りはこの時点で問題はないと考えられるが、デバッグやセキュリティ対策のための監視、トラブルシューティングなどのシステムの根幹部分に着手しないと見えてこないようなインシデントおよびシステム作成を行うこともあると思います。

この場合、カーネルの挙動そのものを見たり、なんならカーネル自体に自作のコードを入れる必要がでてくる場合があります。これを可能にするのがeBPFという強々なテクノロジーです。

ユーザが作ったコードをカーネル空間で動かせるというのは安全性に疑問を感じると思いますが、心配せずとも、以下の図のようにコードの検証および仮想マシンでの処理実行などしっかりセキュリティには配慮されています。

このように、eBPFはカーネル空間に問題のある影響をあたえないように安全にカーネル空間での処理を可能としている機能になっています。 ちなみに、安全性の検証は無限ループにならないか、変なメモリにアクセスしないかなどを調べています。

kprobes

プログラムの途中に、後から利用者が別のプログラムを入れ込むことをフックと言います。
また、フック可能な場所のことをフックポイントと言います。カーネル内で起こるイベントを確認(トレース)するために、カーネルに対してもフックすることがあると思います。

tracepointでは融通が利かないので、カーネルそのものが持っている関数に対してフックをすることができる機能があり、それをkprobesと言います。

驚くべきことに、kallsymsにある関数に対して、フックできるようになります。ちなみに、戻り値に対するフックもでき、kretprobeといいます。 この機能を利用して、eBPFでコードを作成すれば、カーネルに対して自分の思い通りのトレースができるようになると考えられます。

Tetragon

コンテナに対して、eBPFを利用して細かくかつ利用しやすく監視やコンテナの制限を掛けられるようにしたものがTetragonです。
TracingPolicyと呼ばれるリソースを利用し、監視したいカーネル関数やシステムコールをセットし、イベント発生時の動作を書くと処理が実行されます。

Kubernetesと今回考えるセキュリティ

Kubernetesは多くのコンテナを作成し管理するコンテナオーケストレーションであるため、システムの運用を考える上で適切にセキュリティ対策をしなければ、最悪の場合システム全体が攻撃者から攻撃の的になってしまいます。

このため、攻撃されても問題がない、攻撃をそもそもさせない、攻撃されても被害を一部で済ませられるなど様々な対策方法が存在します。
以下、今回考えたセキュリティについて述べます。ただし、これらの対策は手始め程度のもので、実運用ではさらに多くのケースに考慮してセキュリティを考えていく必要があります。

各セキュリティのマニフェストとKubernetesの対応図
※NIC (Network Interface Card) : ネットワークに接続するための媒体

特権コンテナの規制

特権コンテナ(Privileged Containers)は、特権を持つPodのことを指し、この特権を所持するPodはカーネルの機能にアクセスすることができてしまいます。
これにより、ほとんどのセキュリティを無効化が可能となってしまい、最悪の場合ホストマシン全体に影響を及ぼす可能性があります。

しかし、通常のPodでは不可能な処理(システムコールのフックやデバックなど低レベルの操作)が可能なため、完全に使えなくするというわけにはいかないのです。それゆえ、セキュリティインシデントをなるべく最小に抑えるために、特権コンテナの使用には慎重な姿勢が常に求められます。

今回は、この特権コンテナを作成できないようにするセキュリティ対策について考えました。この制限についてはKubernetesの標準機能としてPodSecurityがあるため、今回はPodSecurityとTetragonでの比較を行いました。

システムコールの規制

システムコールを規制することで、アプリの開発者や使用者の行う処理を制限することができます。
例として、書き込み系統の処理を制限したい場合は、writeシステムコールを制限してしまえば、すべての書き込みができなくなります。

システムコールの動きを制限するためには、そのシステムコールそのものをさせないようにするか、システムコール内で使用するカーネル関数を起動させないようにするという方法があります。

今回は、Podの中での一部のシステムコールを制限するようにするセキュリティ対策について考えました。この制限に関してはKubernetesの標準機能としてseccompがあるため、今回はseccompとTetragonでの比較を行いました。

TetragonとPodSecurity (特権コンテナ起動の規制)

特権コンテナをPodSecurityを使用して規制する

KubernetesにはPodのセキュリティに関して、ベストプラクティスなセキュリティポリシーをクラスタやNamespaceに対して適用できる機能として、バージョン1.23以降からPod Security Admission(PSA)が標準で備わっています。

Kubernetes公式のドキュメントによると、Pod SecurityはPod Security Standardの規定に基づいて次のベストプラクティスを提供します。

  • Privileged
    既知の特権昇格の制限なし。まったく制限のかかっていないポリシー。
  • Baseline
    既知の特権昇格は制限あり。最小限のポリシー。
  • Restricted
    既知の特権昇格の制限あり。その他様々な権限に制限がかけられている厳しいポリシー。

そして、これらのポリシーの違反が確認された際に次の3つの処理を選択することができ、Namespaceごとに特定のPodのデプロイに対して処理を行わせることが可能です。

  • warn : ユーザへの警告表示
  • audit : 監査ログに記録
  • enforce : Podの作成の拒否

(引用元:https://kubernetes.io/docs/concepts/security/pod-security-admission/)

PodSecurityをマニフェストファイルで設定・適用することにより、Namespace単位での特権コンテナの規制をかけることができます。

では、実際にマニフェストファイルを作成して特権コンテナの起動が制限されるか検証してみましょう。ポリシーの適用にはNamespaceの次のラベルがサポートされています。

apiVersion: v1
kind: Namespace
metadata:
  name: "example-podseculity"
  labels:
		pod-security.kubernetes.io/enforce: <policy level>
		pod-security.kubernetes.io/enforce-version: latest
		pod-security.kubernetes.io/audit: <policy level>
		pod-security.kubernetes.io/audit-version: latest
		pod-security.kubernetes.io/warn: <policy level>
		pod-security.kubernetes.io/warn-version: latest

上記のマニフェストファイルのラベルは、バージョン管理された Kubernetes APIの一部として扱われます。
具体的には以下のラベルが設定されています。

  • pod-security.kubernetes.io/enforce
    特権モードのポッドの強制適用に関する情報が含まれています。
  • pod-security.kubernetes.io/enforce-version
    特権モードのポッドの強制適用に関するバージョン情報が含まれています。
  • pod-security.kubernetes.io/audit
    ポッドの監査ポリシーに関する情報が含まれています。
  • pod-security.kubernetes.io/audit-version
    ポッドの監査ポリシーに関するバージョン情報が含まれています。
  • pod-security.kubernetes.io/warn
    制限されたセキュリティ設定を持つポッドに関する警告情報が含まれています。
  • pod-security.kubernetes.io/warn-version
    制限されたセキュリティ設定を持つポッドに関する警告情報のバージョン情報が含まれています。

これにより、Namespace内のリソースに対してPodSecurityのガイドラインを適用することができます。
policy levelには、privileged・baseline・restrictedのどれかを当てはめることで、起動時にポリシーに沿った権限とそのPodが許可されている権限とを比較・確認してくれます。

この時点で特権コンテナとみなされた場合、起動するプロセスはキルされるということです。今回はすべてbaselineに設定します。
では、このマニフェストを早速Kubernetesに適用してみましょう。次のコマンドを実行してください。

kubectl apply -f /home/username/example-podseculity.yaml

Namespaceが作成されたことを確認しておきましょう。

$ kubectl get namespace example-podseculity
NAME                  STATUS   AGE
example-podseculity   Active   24s

特権コンテナに関する設定は、一つのコンテナに対してSecurityContextで個別に設定するセキュリティ項目です。設定項目は複数あり、代表的なものはprivileged(特権コンテナとして実行する)とケーパビリティ(capabilities)の追加・削除でしょうか。
privilegedを適用する例として、特権コンテナを生成するPodのマニフェストファイルは次の通りです。ここでは、privilegedをtrue / falseに設定することで特権コンテナの権限を設定できます。

# ~ 一部抜粋 ~ 
---
apiVersion: v1
kind: Pod
metadata:
  name: "example-privileged" #Podの名前
  labels: #ラベル
    app: pod-privileged #appというキー(項目)のpod-privilegedという値
spec:
  containers:
  - name: nginx-container-0 #コンテナの名前(Podとは別物)
    image: nginx #コンテナの構成元となるimage(nginx, httpd, ubuntu)
    ports: 
    - containerPort: 80 #使用するポート
    securityContext: #PodSecurityに関する項目
      privileged: true #特権コンテナの許可
# ~ 一部抜粋 ~

一方でケーパビリティでは、Linuxカーネルのroot機能を細かく設定することができます。ケーパビリティで使用可能な権限については、Dockerの公式ページに一覧が掲載されています。

今回は、コンテナ内のプロセスに KILLSETUIDSETGIDの特権を追加し、AUDIT_WRITESYS_TIME の特権を削除する項目を記述します。Podのマニフェストファイルは次の通りです。

# ~ 一部抜粋 ~ 
---
apiVersion: v1
kind: Pod
metadata:
  name: "example-capabilities" #Podの名前
  labels: #ラベル
    app: pod-capabilities #appというキー(項目)のpod-capabilitiesという値
spec:
  containers:
  - name: nginx-container-1 #コンテナの名前(Podとは別物)
    image: nginx #コンテナの構成元となるimage(nginx, httpd, ubuntu)
    ports: 
    - containerPort: 80 #使用するポート
    securityContext: #PodSecurityに関する項目
      capabilities: 
        add: ["AUDIT_WRITE", "SYS_TIME"] #ケーパビリティの追加
        drop: ["KILL", "SETUID", "SETGID"] #ケーパビリティの削除
# ~ 一部抜粋 ~

したがって、プロセスはセットUIDやセットGID、KILLの特権を持ち、一方でネットワークの管理やシステム時間の設定に関連する特権は制限されます。では、実際に特権コンテナをデプロイしてみましょう。

動作の比較のため、今回はこのマニフェストを使用します。

apiVersion: v1
kind: Pod
metadata:
  name: "example-privileged" #Podの名前
  labels: #ラベル
    app: pod-privileged #appというキー(項目)のpod-privilegedという値
spec:
  containers:
  - name: nginx-container-0 #コンテナの名前(Podとは別物)
    image: nginx #コンテナの構成元となるimage(nginx, httpd, ubuntu)
    ports: 
    - containerPort: 80 #使用するポート
    securityContext: #PodSecurityに関する項目
      privileged: true #特権コンテナの許可
---
apiVersion: v1
kind: Pod
metadata:
  name: "example-capabilities" #Podの名前
  labels: #ラベル
    app: pod-capabilities #appというキー(項目)のpod-capabilitiesという値
spec:
  containers:
  - name: nginx-container-1 #コンテナの名前(Podとは別物)
    image: nginx #コンテナの構成元となるimage(nginx, httpd, ubuntu)
    ports: 
    - containerPort: 80 #使用するポート
    securityContext: #PodSecurityに関する項目
      capabilities: 
        add: ["AUDIT_WRITE", "SYS_TIME"] #ケーパビリティの追加
        drop: ["KILL", "SETUID", "SETGID"] #ケーパビリティの削除
---
apiVersion: v1
kind: Pod
metadata:
  name: "example-deny-privileged" #Podの名前
  labels: #ラベル
    app: pod-privileged #appというキー(項目)のpod-privilegedという値
spec:
  containers:
  - name: nginx-container-2 #コンテナの名前(Podとは別物)
    image: nginx #コンテナの構成元となるimage(nginx, httpd, ubuntu)
    ports: 
    - containerPort: 80 #使用するポート
    securityContext: #PodSecurityに関する項目
      privileged: false #特権コンテナの許可

Podをデプロイします。すると次のようなエラーが出力されます。

$ kubectl apply -f /home/username/deployment.yaml -n example-podseculity                                                                                                                 
pod/example-deny-privileged created
Error from server (Forbidden): error when creating "/home/username/deployment.yaml": pods "example-privileged" is forbidden: violates PodSecurity "baseline:latest": privileged (container "nginx-container-0" must not set securityContext.privileged=true)
Error from server (Forbidden): error when creating "/home/username/deployment.yaml": pods "example-capabilities" is forbidden: violates PodSecurity "baseline:latest": non-default capabilities (container "nginx-container-1" must not include "SYS_TIME" in securityContext.capabilities.add)

では、Podの方はどうなっているのか確認してみましょう。

$ kubectl get pods -n example-podseculity
NAME                      READY   STATUS    RESTARTS   AGE
example-deny-privileged   1/1     Running   0          4m48s

通常のコンテナであるexample-deny-privilegedは問題なくデプロイされていますね。Namespaceの設定を工夫したり、ケーパビリティで必要な機能だけを追加・削除することで、コンテナに持たせる機能を幅広く設定できます。

このように特権コンテナは、Namespaceに対してPodSecurityを課すことで起動をあらかじめ阻止することができます。また、通常のコンテナにも注意が必要です。

例えば、起動時は非特権であっても、途中で特権昇格により特権コンテナとなる場合があります。そのため、事前にマニフェストファイルのsecurityContextの項目に追加でallowPrivilegeEscalationfalseに設定することを推奨します。

特権コンテナをTetragonを使用して規制する

Tetragonでは、TracingPolicyを使用してマニフェストを作成します。GitHubの公式ページ には -n kube-system が付いていますが、今回はdefaultのNamespaceにインストールすることにします

# helmのアップデートとciliumリポジトリの追加
helm repo add cilium <https://helm.cilium.io>
helm repo update
# tetragonのインストール
helm install tetragon cilium/tetragon
kubectl rollout status ds/tetragon -w
# または
helm install tetragon cilium/tetragon -n default
kubectl rollout status -n default ds/tetragon -w

helm ls -n defaultで確認します。tetragonはdefaultのNamespace上のアプリケーションとして問題なくデプロイしたことが確認できますね。

$ helm ls -n default
NAME            NAMESPACE       REVISION        UPDATED                                 STATUS          CHART           APP VERSION
tetragon        default         1               2023-08-21 10:18:02.65160515 +0000 UTC  deployed        tetragon-0.10.0 0.10.0

また、Ciliumが提供するtetragonのAPIリソースが存在するか確認しておくといいでしょう。

$ kubectl api-resources | grep cilium
tracingpolicies                                cilium.io/v1alpha1                     false        TracingPolicy
tracingpoliciesnamespaced                      cilium.io/v1alpha1                     true         TracingPolicyNamespaced

今回使用するTracingPolicyは次のものになります。

---
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
  name: "example-tracingpolicy"
spec:
  kprobes:
  - call: "fd_install"
    syscall: false
    args:
    - index: 0
      type: int
    - index: 1
      type: "file"
    selectors:
    - matchPIDs:
      - operator: NotIn
        followForks: false
        isNamespacePID: true
        values:
        - 0
      matchCapabilities:
      - type: Effective
        operator: In
        values:
        - "CAP_SYS_ADMIN"
      matchCapabilityChanges:
      - type: Effective
        operator: In
        values:
        - "CAP_SYS_ADMIN"
      matchBinaries:
        - operator: "In"
          values:
          - "/usr/sbin/nginx"
      matchActions:
        - action: Sigkill

spec 以下のTracingPolicyの項目と、適用した権限について順を追って解説します。
なお、ここで記述するカスタムリソースなどの内容はTetragonの公式ドキュメント及びGitHubで公開されているソースコードを参考にしています。

  • kprobes
    Linuxカーネルのプローブ(kprobes)を使用して特定のシステムコールやカーネル関数にフックするための設定。
    • call
      監視対象のシステムコールやカーネル関数をセット。この場合は fd_install を指定してカーネル関数を監視しています。
    • syscall
      システムコールの有効性を制御するブール値。ここでは false に設定されています。
    • args
      callで指定したシステムコールの引数に関する設定
      indextype で引数の位置と型を指定しています。
    • selectors
      監視対象のプロセスを選択するための条件のセクション。さらに下段でイベントに合う条件を指定する。
      • matchPIDs
        特定のPID(プロセスID)を持つプロセスを選択する条件。operatorNotIn で、特定のPIDを除外しています。
      • matchCapabilities
        特定のカーネル権限(ケーパビリティ)を持つプロセスを選択する条件。ここでは CAP_SYS_ADMIN を持つプロセスを選択しています。
      • matchCapabilityChanges
        カーネル権限の変更を監視する条件。ここでは CAP_SYS_ADMIN を持つプロセスの変更を監視しています。
      • matchBinaries
        特定のバイナリを持つプロセスを選択する条件。ここでは /usr/sbin/nginx を持つプロセスを選択しています。ただし、今回は私たちにとって都合の良いディレクトリを選択しているため、結果はあくまで参考としてください。
      • matchActions
        特定の条件が一致した場合に実行するアクションを指定する。ここではプロセスを Sigkill アクションで終了させています。

それでは、TracingPolicyをKubernetesに適応します。先ほどのマニフェストファイルのポリシーを次のコマンドを実行します。

$ kubectl apply -f <example-tracingpolicyのマニフェスト>
$ kubectl rollout restart -n default ds/tetragon

これで、設定は反映されているはずです。kubectl get tracingpolicies でTracingPolicyが生成されているか確認します。

$ kubectl get tracingpolicies
NAME                    AGE
connect                 16h
example-tracingpolicy   28s

特権コンテナは、先程PodSecurityで使用したマニフェストファイルexample-privilegedを用います。

$ kubectl apply -f <example-privilegedのマニフェスト> --namespace=default

Podの起動が確認出来たら、いよいよ特権コンテナの起動を阻止できているかログの確認を行います。

$ kubectl logs nginx-deploy-7db8dc97f7-5kjgb
2023/08/28 19:08:41 [notice] 1#1: start worker process 12493
2023/08/28 19:08:41 [notice] 1#1: signal 29 (SIGIO) received
2023/08/28 19:08:41 [notice] 1#1: signal 17 (SIGCHLD) received from 12492
2023/08/28 19:08:41 [alert] 1#1: worker process 12492 exited on signal 9

ワーカープロセスによってnginxコンテナの起動まではうまく動作するようですが、[alert]によってワーカープロセスがsignal 9、つまりSIGKILLされたことを警告しています。どうやらうまく起動を阻止できたようです。

tetraCLIを使用することでイベントをリアルタイムで直接CLIに表示することができます。イベントログの出力方法は次の二通り。

1.Podのログを通じて取得したものを表示する方法

kubectl logs -n default -l app.kubernetes.io/name=tetragon -c export-stdout -f | tetra getevents -o compact
  • メリット
    リアルタイムでログをフォローしてイベントを取得・追跡するため、イベントとログを同時に見ることができます。
  • デメリット
    ログが大量になる可能性があり、必要なイベント情報を見つけるのが難しいです。また、ログのフォロー中に発生する他のログも表示される可能性があります。

2.直接コンテナ内でコマンドを実行してイベントを取得したものを表示する方法

kubectl exec -it -n default ds/tetragon -c tetragon -- tetra getevents -o compact
  • メリット
    特定のコンテナに対して直接アプリケーション内でコマンドを実行するため、必要な情報をターゲットに近い位置から取得できます。
  • デメリット
    リアルタイムでログを追跡するわけではないため、イベントが発生するたびに手動でコマンドを実行する必要があります。

今回はリアルタイムでログの出力を観察したいので、1のコマンドを使用します。

$ kubectl logs -n default -l app.kubernetes.io/name=tetragon -c export-stdout -f | tetra getevents -o compact
📬 open    default/nginx-deploy-7db8dc97f7-22t2f /usr/sbin/nginx /proc/sys/kernel/ngroups_max 
💥 exit    default/nginx-deploy-7db8dc97f7-22t2f /usr/sbin/nginx -g "daemon off;" SIGKILL
PodSecurityTetragon
利点・コード量が短く、適用が容易
・開発者により作成されたセキュリティ設定をそのまま使用可能
・感覚的にわかりやすい
・非常に細かく適応対象およびフック後アクションを設定できる。
・セキュリティに関する情報をTracingPolicyの1か所にまとめて記述できる。
欠点・調整できるパターンが少なく、細かい調整ができない
・namespaceに対してしか適応できない
・カーネル関数やその引数、pidについてなど非常にカーネルに近い知識が必要
・1つ1つ全て自分で設定していく必要があり、podSecurityでやりたいような大まかなセキュリティ対策をするというようなことをするのは大変
・kprobesを利用した際の移植性がしにくくなる可能性がある。

Tetragonとseccomp (システムコールの規制)

pod作成後のpod内部の処理に関するセキュリティ。

seccompを利用してシステムコールを制限

seccomp (Secure Computing Mode)はLinuxカーネルの機能で、利用することにより、プロセスの権限をサンドボックス化し、ユーザー空間からカーネルに対して実行できるシステムコールを制限することができます。

今回、デフォルト設定とカスタム設定の両方での確認を試みました。
しかし、カスタム設定について、GKEでは権限の問題なのかseccompの動作確認ができず、WSL2を利用した際もログを確認する際にネイティブ環境ではない関係なのかファイル構造が異なり、ログの確認ができませんでした。

恐らく、ノードの設定をいじる必要がある関係でうまくいかなかったと考えられます。このため、ネイティブのUbuntu環境で動作を確認しました。

まず、今回作成するpodは以下のようなプロファイルです。

apiVersion: v1
kind: Pod
metadata:
  name: fine-pod
  labels:
    app: fine-pod
spec:
  securityContext:
    seccompProfile:
      type: Localhost
      localhostProfile: profiles/fine-grained.json
  containers:
  - name: test-container
    image: hashicorp/http-echo:0.2.3
    args:
    - "-text=just made some syscalls!"
    securityContext:
      allowPrivilegeEscalation: false

fine-grained.jsonにseccompでkillするシステムコールを設定します。コードは以下です。

"defaultAction": "SCMP_ACT_ERRNO",
    "architectures": [
        "SCMP_ARCH_X86_64",
        "SCMP_ARCH_X86",
        "SCMP_ARCH_X32"
    ],
    "syscalls": [
        {
            "names": [
                        "accept4",
                         ... #システムコールの名前
                     ],
            "action": "SCMP_ACT_ALLOW"
        }
    ]
}

ここの設定は基本的な設定はすべてkillし、syscallsのプロファイル内のものだけシステムコールの動作を許可しています。

今回は、curlコマンドで必要になるシステムコールを許可するように作成しました。そのため、必要なシステムコールの情報を消すとコマンドがkillされるようになっています。

writeのシステムコールの許可を消した結果以下のように表示されて、curlの実行ができなくなりました。

permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get "http://%2Fvar%2Frun%2Fdocker.sock/v1.24/containers/4912dce433f9/json": dial unix /var/run/docker.sock: connect: permission denied

Tetragonを利用してシステムコールを制御

では、Tetragonを使用して先程と同様のシステムコールをキルできるかやってみましょう。
なるべく同じ状況で検証を行いたいので、先程のマニフェストからseccompを除いたものを使用します(imageはnginxに変更しました)。

apiVersion: v1
kind: Pod
metadata:
  name: "example-tracingpolicy" #Podの名前
  labels: #ラベル
    app: pod-tracingpolicy #appというキー(項目)のpod-tracingpolicyという値
spec:
  containers:
  - name: nginx-tracingpolicy-0 #コンテナの名前(Podとは別物)
    image: nginx #コンテナの構成元となるimage(nginx, httpd, ubuntu)
    ports: 
    - containerPort: 80 #使用するポート
    securityContext: #PodSecurityに関する項目
      privileged: false #特権コンテナの許可
      allowPrivilegeEscalation: false #特権昇格の許可

今回適用するTracingPolicyは次のマニフェストを使用します。
こちらのサンプルコードが便利なのでぜひ活用してください。 https://github.com/cilium/tetragon/tree/main/examples/tracingpolicy

apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
  name: "example-tracingpolicy"
spec:
  kprobes:
  - call: "tcp_connect"
    syscall: false
    args:
    - index: 0
      type: "sock"
    selectors:
    - matchArgs:
      - index: 0
        operator: "NotDAddr"
        values:
        - "10.0.0.0/8"
        - "172.16.0.0/12"
        - "192.168.0.0/16"
        - "127.0.0.0/8"
      matchActions:
      - action: Sigkill
  • spec.kprobes
    これはTracingPolicyの本体で、Linuxカーネルのプローブを設定します。
    • call
      プローブがフックするカーネル関数の名前を指定します。ここでは「tcp_connect」が指定されており、TCP接続時に呼び出されます。
    • syscall
      プローブがシステムコールをフックするかどうかを示すブール値です。「tcp_connect」はカーネル関数なので false に設定されています。
    • args
      プローブが関連付けられたシステムコールの引数に対して設定するフィールドです。ここではソケット(”sock“)の引数を設定しています。
    • selectors
      プローブをトリガーする条件を指定するためのセレクターのリストです。この場合、ソケットの引数に対するマッチ条件が設定されています。
      • matchArgs
        ソケット引数の条件マッチを定義します。ここでは、目的地IPアドレスが特定の範囲に含まれていない場合にマッチします。
      • matchActions
        条件マッチ時に実行されるアクションを指定します。ここでは、アクションとして Sigkill (シグナルを送ってプロセスを終了させる)が設定されています。
        特定のIPアドレス範囲に接続しようとする試みに対してプロセスを強制的に終了するためのセキュリティ対策です。

こちらのTracingPolicyを適用することで、curlコマンドの動作を大幅に制限することが可能です。
主にローカルサービスやローカルクラスタ内のサービスにのみ接続するよう強制するために利用することが考えられそうです。範囲外からのコネクションが試みられた場合、プロセスは強制終了されます。

本来であればこのまま動作確認を行いたいのですか、matchArgsに含まれるNotDAddrという条件はCiliumの公式ドキュメントには存在しないようです。

ですので、このマニフェストでは正確な動作検証を行うことができませんでした。未実装の機能ないし廃止された機能の可能性も考えられるため、今回検証できなかった機能に関してはまた別の機会に改めて確認したいと思います。

seccompTetragon
利点・比較的細かく設定ができる
・デフォルトで使用すれば開発者により作成されたセキュリティ設定をそのまま使用できる
・システムコールの名前を書き込むだけで制限無制限を簡単に管理できる
・非常に細かくイベント発生条件を設定できる
・TracingPolicyオブジェクトを作成すれば、処理を適応できる
欠点・システムコールレベルでのみ検査され、詳細な引数の検査がない・システムコール名からの起動拒否ができないため、対応するカーネル関数を調べて拒否する必要がある

まとめ

今回のインターンを通じて、Tetragonの概要や使用方法、似たような処理を行うことができるPodSecurityやseccompについて理解することができました。

また、TetragonとPodSecurity、seccompを比較して、Tetragonはかなり細かい設定ができることがわかりました。このため、大まかな設定をPodSecurityやseccompを利用して設定し、これらだけでは足りない部分をTetragonで補うという形で行うことで手早くセキュリティ設定を行うことができる可能性があると感じました。

おまけ(カーネル関数の探し方)

TracingPolicyのkprobeで設定した fd_installtcp_connect はどうやって探せばいいでしょうか。
以下のサイトで関数を検索すると該当するカーネルのソースがヒットするので便利です。

https://elixir.bootlin.com/linux/latest/source

ファイルを開くopenシステムコールが呼ばれたときは1029行目のSYSCALL_DEFINE3 > do_sys_open > fd_install と呼ばれます。
nginxがconfファイルを開こうとしたときに、TracingPolicyで制限されたというわけでした。

https://elixir.bootlin.com/linux/v4.7/source/fs/open.c#L1029

long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)
{
	struct open_flags op;
	int fd = build_open_flags(flags, mode, &op);
	struct filename *tmp;

	if (fd)
		return fd;

	tmp = getname(filename);
	if (IS_ERR(tmp))
		return PTR_ERR(tmp);

	fd = get_unused_fd_flags(flags);
	if (fd >= 0) {
		struct file *f = do_filp_open(dfd, tmp, &op);
		if (IS_ERR(f)) {
			put_unused_fd(fd);
			fd = PTR_ERR(f);
		} else {
			fsnotify_open(f);
			fd_install(fd, f);
		}
	}
	putname(tmp);
	return fd;
}

SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
{
	if (force_o_largefile())
		flags |= O_LARGEFILE;

	return do_sys_open(AT_FDCWD, filename, flags, mode);
}

インターンの感想

高島 陸斗

最近インフラやセキュリティについて興味を持ち始めたため、これらの知識は大学の授業でならった程度でKubenetesやcloudに関する内容は初めは話を聞きながら調べて、ギリギリ理解できるかできないかといった感じで精一杯でした。
メンターの方に相談をした際に貰える色々な知識の種をベースに、今回のテーマに関する知識を広げていくことがとても楽しかったです。
特に、今回のテーマは非常に多角的な目線で調べていく必要があったため、1つ1つの技術の繋がりを見れたのはいい経験であると感じました。まだまだインターンだけでは理解できなかった部分や、コンテナ以外の技術に関しても今後も学んでいきたいと次なるステージを目指していけるインターンでした。

井上 裕介

KubernetesやGKEに初めて触れた際は、どこから手を付ければ良いかさっぱりでした。
とりあえず、ドキュメントに書いてある通りに動作確認を行い、少しずつマニフェストに慣れていきました。セキュリティをテーマに、Kubenetesに限らずLinuxの低レベルの次元から検証と考察を行たことで、コンテナセキュリティに対して広い知見が得れたと思います。
大量のドキュメントを読み漁ってマニフェスト作成と実装を繰り返す経験は、中々ハードではありましたが、一つでも多く知識を得ようと必死に取り組めた最高の時間でした。次は自分の環境にKubernetesを導入するぞ!

参考文献

ブログ一覧へ戻る

お気軽にお問い合わせください

SREの設計・技術支援から、
SRE運用内で使用する
ツールの導入など、
SRE全般についてご支援しています。

資料請求・お問い合わせ