Secretの扱いと保存時の暗号化
目標
Secretをタイプ別に作成し、注入の方式によって露出する経路がどう変わるかを確認し、RBACで読み取りの範囲を絞ったうえで、保存時の暗号化の設定ファイルを自分で書きます。
なぜ重要なのか
Secretマニフェストの値がbase64になっているため暗号化されているように見えますが、base64はエンコードであって暗号化ではありません。鍵がなく、元に戻すのにコマンド1つで済みます。デフォルトの設定では、Secretはetcdに平文で保存されるため、etcdのバックアップファイルを手に入れた人は、すべてのSecretを読めます。EncryptionConfigurationを有効にして初めて、保存された値が暗号化され、このときidentityプロバイダーは必ずリストの最後でなければなりません。先頭に来ると、平文での保存に戻ります。
注入の方式も重要です。環境変数は/proc/PID/environで読まれ、子プロセスに継承され、クラッシュレポートやデバッグページにまるごと載って流出します。ボリュームマウントのほうが安全で、defaultModeでファイルの権限を絞るのが慣例です。
最後はRBACです。あるネームスペースでget secrets権限を持つ人は、そのネームスペースのすべてのSecretを読めます。resourceNamesで特定のSecretだけを許可できるという事実は、意外とあまり知られていません。
ステップ
- ネームスペース
cks-secretsを作成し、その中にOpaque Secretdb-credを作成してください。キーはusername(値はapp)とpassword(値はpr0d-Db-Pass)の2つです。 cks-secretsにkubernetes.io/dockerconfigjsonタイプのSecretregistry-credを作成してください。サーバーはregistry.cks.local、ユーザーはci、パスワードはci-tokenです。cks-secretsにkubernetes.io/tlsタイプのSecretshop-tlsを作成してください。tls.crtとtls.keyがどちらも空でないようにします。cks-secretsにPodenv-app(コンテナ名app、イメージnginx:1.27-alpine)を作成してください。コンテナのenvFrom[0].secretRef.nameはdb-credにします。cks-secretsにPodvol-app(コンテナ名app)を作成してください。ボリューム名はdb、secret.secretNameはdb-cred、secret.defaultModeは0400(10進数で256)とし、コンテナはそのボリュームを/etc/dbにreadOnly: trueでマウントします。cks-secretsにOpaque Secretapp-configを作成してください。キーはmode(値はstrict)の1つで、immutableはtrueにします。cks-secretsにServiceAccountappを作成し、Roledb-cred-readerを作成してください。apiGroupsはコアグループ、resourcesはsecrets、resourceNamesはdb-credの1つ、verbsはgetの1つです。RoleBindingapp-db-credでSAappにバインドします。そのあと、2つの質問の答え(yes/no)を順番に/root/cks-secrets/can-i.txtに2行で保存してください。1行目はappのSAがsecret/db-credをgetできるか、2行目はsecret/app-configをgetできるかです。/root/cks-secrets/encryption-config.yamlに、保存時の暗号化の設定を書いてください。apiVersion: apiserver.config.k8s.io/v1、kind: EncryptionConfiguration、resources[0].resourcesはsecretsの1つ、providersは2つで、1つ目がaescbc(キー名はkey1、secretは32バイトをbase64エンコードした値)、最後がidentityです。
参考
kubectl create secret generic db-cred --from-literal=username=app --from-literal=password=pr0d-Db-Pass -n cks-secrets- 32バイトのキーの生成:
head -c 32 /dev/urandom | base64 kubectl auth can-i get secret/db-cred -n cks-secrets --as=system:serviceaccount:cks-secrets:app- よくある間違い1:
defaultMode: 0400を引用符で囲むと文字列になり、拒否されます。数値で書いてください。 - よくある間違い2: EncryptionConfigurationで
identityを最初に置くと、新しく書き込まれるSecretが平文で保存されます。 - よくある間違い3: 設定を有効にしても、既存のSecretは書き直されるまで平文のままです。
kubectl get secrets -A -o json | kubectl replace -f -で全体を更新する必要があります。
Opaque Secretを作成する
ネームスペースcks-secretsを作成し、その中にOpaque Secretdb-credを作成してください。キーはusername(値はapp)とpassword(値はpr0d-Db-Pass)の2つです。
kubectl create secret generic --from-literal=が最も速いです。タイプを指定しなければOpaqueです。
レジストリ認証情報のSecret
cks-secretsにkubernetes.io/dockerconfigjsonタイプのSecretregistry-credを作成してください。サーバーはregistry.cks.local、ユーザーはci、パスワードはci-tokenです。
タイプが決まっているSecretは、データのキー名も決まっています。dockerconfigjsonタイプは、.dockerconfigjsonキー1つだけを使います。
TLS Secret
cks-secretsにkubernetes.io/tlsタイプのSecretshop-tlsを作成してください。tls.crtとtls.keyがどちらも空でないようにします。
tls.crtとtls.keyの2つのキーが両方そろっていて初めて、APIサーバーが受け付けます。
環境変数として注入する
cks-secretsにPodenv-app(コンテナ名app、イメージnginx:1.27-alpine)を作成してください。コンテナのenvFrom[0].secretRef.nameはdb-credにします。
envFromのsecretRefは、Secretのすべてのキーを環境変数として展開します。便利ですが、プロセスの環境にそのまま残ることを覚えておいてください。
ボリュームとしてマウントし、権限を絞る
cks-secretsにPodvol-app(コンテナ名app)を作成してください。ボリューム名はdb、secret.secretNameはdb-cred、secret.defaultModeは0400(10進数で256)とし、コンテナはそのボリュームを/etc/dbにreadOnly: trueでマウントします。
defaultModeは、YAMLで8進数として書くと10進数で保存されます。0400は所有者の読み取り専用です。
変更不可のSecret
cks-secretsにOpaque Secretapp-configを作成してください。キーはmode(値はstrict)の1つで、immutableはtrueにします。
immutable: trueを有効にすると、データを変更できなくなり、削除して作り直す必要があります。kubeletのwatchの負荷も減ります。
特定のSecretだけを読めるように制限する
cks-secretsにServiceAccountappを作成し、Roledb-cred-readerを作成してください。apiGroupsはコアグループ、resourcesはsecrets、resourceNamesはdb-credの1つ、verbsはgetの1つです。RoleBindingapp-db-credでSAappにバインドします。そのあと、2つの質問の答え(yes/no)を順番に/root/cks-secrets/can-i.txtに2行で保存してください。1行目はappのSAがsecret/db-credをgetできるか、2行目はsecret/app-configをgetできるかです。
RoleのresourceNamesで名前を固定すれば、そのSecretだけを読めます。結果は、auth can-iにTYPE/NAME形式で問い合わせて確認してください。
保存時の暗号化の設定ファイル
/root/cks-secrets/encryption-config.yamlに、保存時の暗号化の設定を書いてください。apiVersion: apiserver.config.k8s.io/v1、kind: EncryptionConfiguration、resources[0].resourcesはsecretsの1つ、providersは2つで、1つ目がaescbc(キー名はkey1、secretは32バイトをbase64エンコードした値)、最後がidentityです。
プロバイダーのリストは、順序に意味があります。書き込みには最初のものが使われ、読み取りには順番に試されます。identityの位置を誤ると、平文での保存に戻ります。