こんにちは、毛利です。最近趣味サーバをKubernetesに移行して遊んでいます。最近の気づきとしては、Kubernetesの仕組みレベルの視点と、Kubernetesを使う側の視点でそこそこギャップがあるなぁと思いました。今回はSecretを暗号化できるSealedSecretsの話をします。
背景と概要
KubernetesのSecretをGit管理できたらと思ったことはないでしょうか?Git管理することで、以下のようなメリットがありそうです。
- GitHub等のPull Requestやレビューのフローに乗せられる
- ファイル管理をする場所をGitに統一でき、管理場所がばらけない
- バージョン管理ができ、履歴をたどれる
しかし、SecretをGitにそのまま放り込むのはセキュリティの観点からよくないです。
そこで、SealedSecretsの出番です。SealedSecretsでは、Kubernetes環境が持っている秘密鍵でのみSecretを復号して見れるように、Secretを暗号化します。これによって、秘密鍵を持っていないとSecretの元の文字列がわからない仕組みが実現されます。この仕組みはGitOpsでよく使われているようです。
手順
確認した環境はWindows(PowerShell)とArch Linuxです。また、Kubernetesの操作用にLensを使っています。Lensは使いやすくて見た目がかっこいいのでおすすめです。
1. kubesealコマンドを準備
まず、Secretの暗号化など、SealedSecretsに関する作業を手元で行うための kubeseal
コマンドを準備します。
Windowsの場合は Releases · bitnami-labs/sealed-secrets (github.com) からダウンロードしてパスを通すとOKです。
Arch Linuxの場合はpacmanで入ります。
pacman -S kubeseal
2. Helmを使ってSealedSecretsの準備
次にKubernetes側にSealedSecretsの準備をします。
以下のコマンドを実行してSealedSecretsをinstallできるようにします。 環境によっては helm3
ではなく helm
の場合もあるかと思います。適宜読み替えてください。
helm3 repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets
インストール(デプロイ)のコマンドは次の通りです。このコマンドを実行したときに使い方の説明が出ます。メモしておくことをおすすめします。
helm3 install <リリース名> sealed-secrets/sealed-secrets -n <namespace名>
例えば、次のようなコマンドを実行します。
helm3 install my-release sealed-secrets/sealed-secrets -n default
3. 公開鍵の取得
デプロイしたSealedSecretsの公開鍵を取得します。
公式の手順では、リダイレクトを使って公開鍵のファイルを作成していますが、リダイレクトではなく -w
オプションを使うことをおすすめします。リダイレクトを使った場合、PowerShellで作業した場合に、暗号化したSecretを作成する際に error: data does not contain any valid RSA or ECDSA certificates
とエラーが出ることがあります (おそらく文字コードの関係だと思います)。この問題は手元では -w
オプションを使うことで回避できました。
公開鍵を取得するコマンドは以下の通りです。
kubeseal --controller-name=<SealedSecretsのdeployment名> --controller-namespace=<namespace名> --fetch-cert -w <公開鍵の出力先ファイルのパス>
例えば、次のようなコマンドを実行します。
kubeseal --controller-name=my-release-sealed-secrets --controller-namespace=default --fetch-cert -w mycert.pem
4. 暗号化したSecretのyamlを作成
以下のコマンドで実行できます。--from-literal
は複数指定できます。
kubectl create secret generic <Secret名> --dry-run=client --from-literal=<key>=<value> -n <namespace名> -o json | kubeseal --controller-name=<SealedSecretsのdeployment名> --controller-namespace=<namespace名> --cert <公開鍵ファイルのパス> -w <暗号化されたSecretの出力再ファイルのパス
例えば、次のようなコマンドを実行します。
kubectl create secret generic testsecret-20220524 --dry-run=client --from-literal=TOKEN=token -n default -o json | kubeseal --controller-name=my-release-sealed-secrets --controller-namespace=default --cert mycert.pem -w testsecret-20220524-sealed.yaml
上記のコマンドを実行すると、以下のようなファイルが生成されます。Secretの値がシンプルなbase64ではなく、長い文字列になっていることが確認できます。 Secretの値が暗号化されているので、SecretをそのままGitで管理するよりも安全に管理できます。
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
creationTimestamp: null
name: testsecret-20220524
namespace: default
spec:
encryptedData:
TOKEN: <暗号化された文字列>
template:
data: null
metadata:
creationTimestamp: null
name: testsecret-20220524
namespace: default
もしSecretのyamlファイルから作る場合は -f
オプションを使って暗号化できます。できますが、間違って暗号化前のSecretをコミットしてしまう可能性があるのであんまりおすすめしません。
kubeseal --format=yaml --cert=<公開鍵ファイルのパス> -f <暗号化前のSecretファイルのパス> -w <暗号化されたSecretの出力先ファイルのパス>
5. 暗号化されたSecretの適用
暗号化されたSecretの適用は以下のコマンドでできます。上書きしても良い場合は kubectl apply
でもできますが、いざとなったときに戻すことを考えると、更新する際にはSecretの名前を変えて新規で登録することをおすすめします。
kubectl create -f <暗号化後のSecretファイル>
例えば、次のコマンドを実行します。
kubectl create -f testsecret-20220524-sealed.yaml
適用すると、Kubernetes上でSealedSecretsによって復号され、復号されたSecretが登録されます。
復号されたSecretの確認は、以下のコマンドでできます。
kubectl get secret <secret名> -o yaml
例えば、次のような実行結果になり、復号されてSecretが登録されていることが分かります。
$ kubectl get secret testsecret-20220524 -o yaml
apiVersion: v1
data:
TOKEN: dG9rZW4=
kind: Secret
metadata:
creationTimestamp: "2022-05-24T08:54:28Z"
name: testsecret-20220524
namespace: default
ownerReferences:
- apiVersion: bitnami.com/v1alpha1
controller: true
kind: SealedSecret
name: testsecret-20220524
uid: 649e23ee-aa7d-42b3-b519-7240c59f98fe
resourceVersion: "180816"
uid: c8ae473c-0b4a-4df9-a66c-c491c1fb1434
type: Opaque
あとはDeploymentなどからいつもの手順でSecretを参照できます。めでたしめでたし。
おわりに
GitOps、秘密情報の管理どうするんだよと思っていたけどなるほどなぁとなった。動いているシステムとリポジトリ間の相互に(ゆるく)依存関係があるのちょっと不思議な感じ。リポジトリはシステムの一部なのかもね。