k8s: secret の仕様を理解する

*筆者はKubernetesの全体を勉強中の身です、誤解がある場合はコメントにてご指摘下さい*

参考資料
--------------------
[Secret (kubernetes.io)](https://kubernetes.io/docs/concepts/configuration/secret/)
[Volumes secret (kubernetes.io)](https://kubernetes.io/docs/concepts/storage/volumes/#secret)
[Authorization Overview : Privilege escalation via pod creation (kubernetes.io)](https://kubernetes.io/docs/reference/access-authn-authz/authorization/#privilege-escalation-via-pod-creation)


本記事の構成
--------------------
- 前提知識/経験: あったら理解が早いです
- 要点: 短くまとめた説明です
- 主な用途: 存在意義の説明です
- 使い方: 実際の使い方の説明です
- 内部実装: 付帯する情報の説明です


前提知識/経験
--------------------
- 機密情報の取扱い方が分かる、例: 「大事なモノは他の人に見えないように隠しておく」、という事を理解できる
- コマンド実行に慣れている
- SSH接続した事がある、OAuthが分かる


Secret
--------------------

本記事の扱う範囲(図を考え中です)
![]()

### 要点

secretはパスワードや秘密鍵等をk8sクラスタ内で**安全に扱うため**のリソースです。
ContainerやImageの中に、パスワードや秘密鍵を入れておくと、これら機密情報の漏洩リスクが大きくなります。
機密情報をsecretリソースとして扱う事で、k8sクラスタにおける機密情報の漏洩リスクを小さくする事が出来ます。
(k8sクラスタを使用する個別のシステム自体が保持する、個人情報等の機密情報はsecretリソースの範囲外と考えています)

```
[(結構)危険] 機密情報 -> Image
[(多分)安全] 機密情報 -> secretリソース
```

#### 保存場所

k8sクラスタにおいて、secretリソースは**不揮発ストレージへ保存しません**。
また、漏洩のリスクを小さくするため、secretリソースの機密部分の情報表示も簡単には行えません。

```
[storage]      [APIサーバ内RAM(ディスク?)]    [Worker Node内RAMディスク]
 機密情報  ->         secretリソース       ->     secretリソース
```

### 主な用途

```
secret -> Container /path/secret -> Other Servers

secret -> Image Registry
       <- Image
```

- (ユーザ側) Container内のパスにマウントして、マウントした場所にコピーされたsecretファイルを、アプリケーションがAPIアクセスキー等として使用します
- (k8s側) Container作成に必要なimageのpullで使います(kubeletプロセスがPodに定義されたimageを使います)

### 使い方

#### secretリソースの登録方法

```
file    -> secret
literal -> secret
```

- ファイルから、secretリソースとしてAPIサーバへ登録します
- コマンドのオプションに直接書いたリテラルで、secretリソースとしてAPIサーバへ登録します

#### Podでsecretリソースを参照する方法

##### Volumeとしてマウント

*以下は依存関係ではなく、データフローのような意図です(依存関係として考える場合、矢印が逆です)*

```
secret -> secret.secretName -> volumes.name -> volumeMounts.name -> volumeMounts.path
```

[こちら"記述に注意が必要: アクセス可能なコンテナを限定化"を参照下さい](#記述に注意が必要: アクセス可能なコンテナを限定化)

##### 環境変数として参照

```
secret -> env.valueFrom.secretKeyRef.name -> env.name
```

```
...
kind: Pod
...
spec:
  containers:
  - name: mycontainer
    ...
    env:
      - name: SECRET_USERNAME
        valueFrom:
          secretKeyRef:
            name: mysecret
            key: username
      - name: SECRET_PASSWORD
        valueFrom:
          secretKeyRef:
            name: mysecret
            key: password
```

#### secretリソースを実際に使う具体例

```
# /secret ディレクトリにマウントされてコピーされた id_rsa 秘密鍵を、SSH接続に使用します
ssh -i /secret/id_rsa user@10.11.12.13
```

#### secretリソースのアクセス権

##### 記述に注意が必要: アクセス可能なコンテナを限定化

secretリソースにアクセス可能なコンテナを限定出来ます。
その方法としては、
secretリソースを使わなければならないプログラム(image)のコンテナ定義に`volumeMounts.name: secret.name`を書くだけでOKです。

```
...
kind: Pod
...
spec:
  containers:
  - name: other-container
    ...
  - name: authorized-container
    ...
    volumeMounts:
    - name: foo
      mountPath: "/path/secret"
      readOnly: true
  volumes:
  - name: foo
    secret:
      secretName: mysecret
```

##### 機能の優先順位に注意が必要: APIサーバポリシーでの読み込み権限と、Pod実行可能権限の不一致

APIサーバポリシーで、ユーザにあるsecretオブジェクトの読み込み権限が無くても、
そのユーザが当該secretオブジェクトを読み込めるPodを作れるならば、
そのユーザは当該secretオブジェクトを見る事が可能です。
TODO: 要検証

##### (理由不明です...) いずれのNodeであっても、root権限を扱えるユーザは、APIサーバのsecretリソースにアクセス可能

root exploitの影響範囲を限定化するため、と記述ありましたが、
想像力が足りておらず理由がよくわかりません。
(`kubelet`のなりすましをすればAPIサーバは分からないそうですが・・・、
secretリソースを本当に必要としているNodeからAPIサーバへアクセスされた場合、
アクセス元が不正なroot exploitか、正当な`kubelet`かの判別が出来ない気がします)

#### APIサーバ内のsecretリソースからコピーされたsecret

```
[o] Pod1 -> Node.secret
[o] Pod2 -> Node.secret
[x] Pod1 -> Pod2.secret
```

- PodからNodeのsecretは参照可能です
- Podから他のPod内のsecretは参照不可能です

#### ファイル

##### プレーンなファイル

- そのままの文字列で書きます
- `kubectl create secret generic <secret_name> --from-file=<file_name>`の`<file_name>`へファイルパスを指定すれば、
secretリソースとして作成出来ます
- `kustomization`で`secretGenerator`を使えば、`kubectl apply -f ...`でもsecretリソースを作成可能です

##### JSON or YAML

*深堀りしてません。深く知りたい場合は参考資料をご参照下さい
(参考資料を見ている限り、`SecretGenerator`を使うよりも弱い感じがしました)*

- `kind: Secret`の`.data`または`.stringData`で記述可能です 
- `.data`はBase64エンコーディングが必要です(多分、扱いやすくするため)
- `.stringData`はそのままの文字列で書きます(k8s側でBase64エンコーディングするようです)

#### リテラル (＝仕様に合うように書いてある文字列そのまま, という意味。例: `0x0F`, `"string"`)

- エスケープ必要な特殊文字があります(`$ \* !`)
- **コマンドのオプション(`--from-literal`)でsecretリソースを登録する場合、コマンドの履歴に機密情報が残る可能性があります**
  - 例えば、`history -d <number>`のような方法で、secretリソースを登録したコマンドをコマンドの履歴から削除する事をお勧めします

#### secretリソースのライフサイクル

##### 生成

- 人が手動で生成した時: `kubectl create secret <sub_command> <secret_name>`

##### 破棄、又は消失

- 人が手動で破棄した時: `kubectl delete secret <secret_name>`
- (未検証での想像)Nodeに何かが起きた時(`kubelet`プロセスが死んだり、`tmpfs`自体に問題が起きたり、Nodeの電源が落ちた場合など)

##### 破棄されずに残留の可能性

未検証であり、想像です。
- `kubelet`プロセスが`tmpfs`を解放する前に死んでしまった時


#### APIサーバ内のsecretリソースからコピーされたsecretのライフサイクル

##### 生成

- APIサーバからNodeへsecretが送られた時( -> tmpfssが生成され、tmpfsの中にsecretがコピーされる)

##### 破棄、又は消失

- secretを必要とするPodがNode内から消えた時(→kubeletプロセスが、tmpfs内のsecretを消す)
- 人が手動でsecretを削除した時
  - Podにマウントされるsecretは読み込み専用にすれば、人が手動で削除は出来ません

```
kubectl exec -it test-projected-volume -- /bin/sh

/ # cd projected-volume/
/projected-volume # rm *
rm: can't remove 'password.txt': Read-only file system
rm: can't remove 'username.txt': Read-only file system
```

### 内部実装

- tmpfs(RAMディスク)を使って保持するので、不揮発なストレージには保存されない(＝Master Nodeが死んだら, secretも消える？)

### 自動生成されるsecret

#### `default-token-`

- k8s-apiserverのAPIにアクセス可能なsecret, このsecretはPodが使用します
- 自動生成を無効化したり、secretを上書きも出来るが、、TODO: 記述を読んでしっかり理解してから追記する

```
kubectl get secret

NAME                  TYPE                                  DATA   AGE
default-token-zqq2v   kubernetes.io/service-account-token   3      4h51m
```

### まだまとめきれていない部分

- `image`を取得する場合のsecretについて(`imagePullSecret`)
- secretの自動マウント
- secretとnamespaceとの関係
- その他制限