自己紹介
高島 陸斗
千葉工業大学修士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_execとprocess_exitというイベントを生成します。これらのイベントは、linuxレベルのデータだけでなく、Kubernetesレベルのデータも同時に見ることができます。これにより、ノード、プロセス、Kubernetesまたはコンテナ環境の繋がりを知ることができます。- process_exec
プロセスの開始やバイナリ実行についての情報がわかるイベント - process_exit
process_execの反対で、プロセスの終了情報がわかるイベント
- process_exec
- Generic tracing
tracepointやkprobeの機能を利用した任意のカーネル関数呼び出しを監視し、フックします。(tracepointやkprobeについては次のセクションで詳しく説明します。)ただし、この機能を使うためには、Tetragonを拡張してTracingPolicyという名前のオブジェクトを構成する必要があります。process_tracepointとprocess_kprobesというイベントを生成します。- process_tracepoint
tracepointというlinuxカーネルに組み込まれた内部で発生する様々なイベントをトレースするためのフックポイントを利用したイベントです。
linuxカーネルにすでに組み込まれているフックポイントしかトレースできないという欠点があります。 - process_kprobes
kprobeという独自で特定のカーネル関数呼び出しをトレースすることができる機能を利用したイベントです。
便利に見えますが、カーネルのビルドに依存するカーネル空間内のオブジェクトに影響を大きく受けるため、監視したい対象がないとすでに作成されたプログラムなどが移植がしにくいという欠点があり、注意が必要です。
- process_tracepoint
プロセスの監視時に出てくる出力は標準だと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は多くのコンテナを作成し管理するコンテナオーケストレーションであるため、システムの運用を考える上で適切にセキュリティ対策をしなければ、最悪の場合システム全体が攻撃者から攻撃の的になってしまいます。
このため、攻撃されても問題がない、攻撃をそもそもさせない、攻撃されても被害を一部で済ませられるなど様々な対策方法が存在します。
以下、今回考えたセキュリティについて述べます。ただし、これらの対策は手始め程度のもので、実運用ではさらに多くのケースに考慮してセキュリティを考えていく必要があります。
特権コンテナの規制
特権コンテナ(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の公式ページに一覧が掲載されています。
今回は、コンテナ内のプロセスに KILL
と SETUID
と SETGID
の特権を追加し、AUDIT_WRITE
と SYS_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
の項目に追加でallowPrivilegeEscalation
をfalse
に設定することを推奨します。
特権コンテナを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
で指定したシステムコールの引数に関する設定。index
とtype
で引数の位置と型を指定しています。selectors
監視対象のプロセスを選択するための条件のセクション。さらに下段でイベントに合う条件を指定する。matchPIDs
特定のPID(プロセスID)を持つプロセスを選択する条件。operator
がNotIn
で、特定の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
PodSecurity | Tetragon | |
利点 | ・コード量が短く、適用が容易 ・開発者により作成されたセキュリティ設定をそのまま使用可能 ・感覚的にわかりやすい | ・非常に細かく適応対象およびフック後アクションを設定できる。 ・セキュリティに関する情報を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の公式ドキュメントには存在しないようです。
ですので、このマニフェストでは正確な動作検証を行うことができませんでした。未実装の機能ないし廃止された機能の可能性も考えられるため、今回検証できなかった機能に関してはまた別の機会に改めて確認したいと思います。
seccomp | Tetragon | |
利点 | ・比較的細かく設定ができる ・デフォルトで使用すれば開発者により作成されたセキュリティ設定をそのまま使用できる ・システムコールの名前を書き込むだけで制限無制限を簡単に管理できる | ・非常に細かくイベント発生条件を設定できる ・TracingPolicyオブジェクトを作成すれば、処理を適応できる |
欠点 | ・システムコールレベルでのみ検査され、詳細な引数の検査がない | ・システムコール名からの起動拒否ができないため、対応するカーネル関数を調べて拒否する必要がある |
まとめ
今回のインターンを通じて、Tetragonの概要や使用方法、似たような処理を行うことができるPodSecurityやseccompについて理解することができました。
また、TetragonとPodSecurity、seccompを比較して、Tetragonはかなり細かい設定ができることがわかりました。このため、大まかな設定をPodSecurityやseccompを利用して設定し、これらだけでは足りない部分をTetragonで補うという形で行うことで手早くセキュリティ設定を行うことができる可能性があると感じました。
おまけ(カーネル関数の探し方)
TracingPolicyのkprobeで設定した fd_install
や tcp_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を導入するぞ!
参考文献
- https://speakerdeck.com/chikuwait/learn-ebpf
- https://kubernetes.io/docs/tutorials/security/seccomp/#create-a-pod-with-a-seccomp-profile-that-only-allows-necessary-syscalls
- https://kubernetes.io/ja/docs/concepts/security/pod-security-admission/
- https://tetragon.cilium.io/docs/concepts/tracing-policy/
- https://future-architect.github.io/articles/20230623a/