こんにちは、SREの川野です。今回は、KubernetesのSecretリソースを暗号化するツールであるSealedSecretsについて、暗号化するときに使用する鍵のローテーションについて書いていこうと思います。
SealedSecretsとは
冒頭の文章と重複してしまいますが一言で言うと、KubernetesのSecretリソースを暗号化して管理することができるツールです。SecretリソースをSealedSecretsを使って暗号化すれば、GitHubなどの公開リポジトリで安全に管理することができるようになります。
https://github.com/bitnami-labs/sealed-secrets
SealedSecretsの鍵のローテーションについて
一般的な鍵のローテーションでは、新しい鍵を作成したら古い鍵を使用しないようにすると思いますが、SealedSecretsのSecretリソースを暗号化するためのSealing Keyのローテーションでは、古い鍵は引き続き復号化に使用することができるという特徴を持っています。
Sealing Keyの更新について
SealedSecretsではデフォルトの設定でSealing Keyが自動的に作成されます。新しい鍵が作成されていきますが、古い鍵については削除されず、古い鍵で暗号化されたSealedSecretリソースは復号化することができる状態です。鍵が定期的に作成されていくので、ある鍵で暗号化された数が一定数に抑えられるので、鍵が漏洩した際の被害の影響範囲を比較的抑えられるのではないかと思います。
よりセキュアにしたい場合は、新しい鍵(自動作成or自分で作成)でSealedSecretリソースを再暗号化します。
詳細についてはREADMEをご参照ください。
本記事では、「Sealing Keyの即時更新」と「SealedSecretリソースの再暗号化」についてのオペレーションのご紹介をいたします。
Secretsのローテーションについて
こちらのセクションは補足になります。
Secretsが漏洩した可能性がある場合は、前セクションで触れている「Sealing Keyの更新」と「SealedSecretリソースの再暗号化」だけでは意味をなしていないということに注意が必要です。
この場合は、Secretリソースに登録してある機密データ自体の更新も行う必要があります。
SealedSecretsのインストール
今回はHelmでインストールします。
- Helmリポジトリを追加
$ helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets
"sealed-secrets" has been added to your repositories
$ helm repo list
NAME URL
sealed-secrets https://bitnami-labs.github.io/sealed-secrets
2. チャートをインストール
$ helm search repo sealed-secrets
NAME CHART VERSION APP VERSION DESCRIPTION
sealed-secrets/sealed-secrets 1.13.2 0.13.1 A Helm chart for Sealed Secrets
$ helm install sealed-secrets-controller sealed-secrets/sealed-secrets
NAME: sealed-secrets-controller
LAST DEPLOYED: Tue Mar 9 19:18:32 2021
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
You should now be able to create sealed secrets.
1. Install client-side tool into /usr/local/bin/
GOOS=$(go env GOOS)
GOARCH=$(go env GOARCH)
wget https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.13.1/kubeseal-$GOOS-$GOARCH
sudo install -m 755 kubeseal-$GOOS-$GOARCH /usr/local/bin/kubeseal
2. Create a sealed secret file
# note the use of `--dry-run` - this does not create a secret in your cluster
kubectl create secret generic secret-name --dry-run --from-literal=foo=bar -o [json|yaml] | \
kubeseal \
--controller-name=sealed-secrets-controller \
--controller-namespace=default \
--format [json|yaml] > mysealedsecret.[json|yaml]
The file mysealedsecret.[json|yaml] is a commitable file.
If you would rather not need access to the cluster to generate the sealed secret you can run
kubeseal \
--controller-name=sealed-secrets-controller \
--controller-namespace=default \
--fetch-cert > mycert.pem
to retrieve the public cert used for encryption and store it locally. You can then run 'kubeseal --cert mycert.pem' instead to use the local cert e.g.
kubectl create secret generic secret-name --dry-run --from-literal=foo=bar -o [json|yaml] | \
kubeseal \
--controller-name=sealed-secrets-controller \
--controller-namespace=default \
--format [json|yaml] --cert mycert.pem > mysealedsecret.[json|yaml]
3. Apply the sealed secret
kubectl create -f mysealedsecret.[json|yaml]
Running 'kubectl get secret secret-name -o [json|yaml]' will show the decrypted secret that was generated from the sealed secret.
Both the SealedSecret and generated Secret must have the same name and namespace.
3. SealedSecretsのコントローラーが正常にデプロイできたことを確認
$ kubectl get deploy,po | grep sealed-secrets-controller
deployment.apps/sealed-secrets-controller 1/1 1 1 44s
pod/sealed-secrets-controller-55d459cc4c-d9lr4 1/1 Running 0 45s
SealedSecretリソースをデプロイ
続いては、証明書(Sealing Keyの公開鍵)を使用してSecretリソースをSealedSecretリソースに暗号化します。
まず、適当なSecretリソースを作成するマニフェストを用意します。
kubectl create secret generic test-secret --from-literal=password=hogehoge --dry-run=client -oyaml > test-secret.yaml
$ cat test-secret.yaml
apiVersion: v1
data:
password: aG9nZWhvZ2U=
kind: Secret
metadata:
creationTimestamp: null
name: test-secret
SecretリソースのマニフェストをSealedSecretリソースを定義するマニフェストへ変換します。
kubeseal --controller-namespace=default --format=yaml < test-secret.yaml > test-sealedsecret.yaml
$ cat test-sealedsecret.yaml
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
creationTimestamp: null
name: test-secret
namespace: default
spec:
encryptedData:
password: AgCEuyJx1tOjbr3DfEg438bO9CvRsmbKrDTIGnuEsschi/sJ7UgL22tuz690oD3ZoLitBAwPMKb1Eo482rlmbZKwxsNWts7A5KUz530Zo3BRzi2uYClH99UL15x/7Eg35VaigjpAHrwVV24L42CeF6A6PE15zz43cQs/no3UNSAdzDIk2pQAquls8cJUgPwPxLH+TFGhPjDDivJ15hyX6+4KQQzbTIZIo6TxQlnCdOKCK3qFiJ2YwokdnjuopIhrwIV/DgJbjpu8/qXBXaoWI/Id2eVkIb31AOqFZ6HvF1XR14CF6FLhKmDC/TTcl4fYsApkqseHoAJSN/2QmZ8nAf2dpthnyQcttD4dSIe4VOBG8nO1EU/LzSdXnCw5EYCHPKno7zu5diaPVVgSGzO5BxhGl7V5HGLOQ6gK9UyjkFn35RpkEOFJAG5Aiq+z3QpZRuQHyrAR0ehUHbKoAR1Xe07xPAnJU+TKl8uYlqA91bOsi6r/0EnL6+0iFu/KVKeIswLClXxac9z6FGbyJMezxGBlGsuJdTi1NYxPCxbr1Y7P2JlYeurqezaKVS7TOIGpSCvZYBJW0pZZUvc6DqEhfk9OVgY1Htvgb26A7APqNoQyLAAPdbeUp/v0BVYoMH43sWYXlLvwsJwkyNh0/Jvlvl9EjWy4CzTUzArdWhQvU/M5fEdBTE4tbK/VRckvzSb6/h9JhT4H8XOuOA==
template:
metadata:
creationTimestamp: null
name: test-secret
namespace: default
マニフェストを適用します。
kubectl apply -f test-sealedsecret.yaml
すると、クラスタにSealedSecretリソースが作成されます。
$ kubectl get sealedsecret
NAME AGE
test-secret 18s
また、コントローラーが自動的に復号化したSecretリソースも作成されていることがわかります。
$ kubectl get secret | grep test-secret
test-secret Opaque 1 44s
Sealing Keyの即時更新
コントローラーに --key-cutoff-time=RFC1123形式のタイムゾーン
というオプションを渡すと、この値よりも既存である最新のSealing KeyのCreationTimestampが古かった場合、コントローラーが新しいSealing Keyを作成します。
Create a new key if latest one is older than this cutoff time. RFC1123 format with numeric timezone expected.
https://github.com/bitnami-labs/sealed-secrets/blob/5a46e374e9e8f3e4f7b51dc03825ac95983e406b/cmd/controller/main.go#L47
まず、RFC1123形式の日時を取得します。
$ date -R
Tue, 09 Mar 2021 19:22:46 +0900
helm upgradeコマンドを実行します。(カンマのエスケープに注意してください)
$ helm upgrade sealed-secrets-controller sealed-secrets/sealed-secrets --set commandArgs[0]=--key-cutoff-time\='Tue\, 09 Mar 2021 19:22:46 +0900'
Release "sealed-secrets-controller" has been upgraded. Happy Helming!
NAME: sealed-secrets-controller
LAST DEPLOYED: Tue Mar 9 19:23:32 2021
NAMESPACE: default
STATUS: deployed
REVISION: 2
TEST SUITE: None
NOTES:
You should now be able to create sealed secrets.
1. Install client-side tool into /usr/local/bin/
GOOS=$(go env GOOS)
GOARCH=$(go env GOARCH)
wget https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.13.1/kubeseal-$GOOS-$GOARCH
sudo install -m 755 kubeseal-$GOOS-$GOARCH /usr/local/bin/kubeseal
2. Create a sealed secret file
# note the use of `--dry-run` - this does not create a secret in your cluster
kubectl create secret generic secret-name --dry-run --from-literal=foo=bar -o [json|yaml] | \
kubeseal \
--controller-name=sealed-secrets-controller \
--controller-namespace=default \
--format [json|yaml] > mysealedsecret.[json|yaml]
The file mysealedsecret.[json|yaml] is a commitable file.
If you would rather not need access to the cluster to generate the sealed secret you can run
kubeseal \
--controller-name=sealed-secrets-controller \
--controller-namespace=default \
--fetch-cert > mycert.pem
to retrieve the public cert used for encryption and store it locally. You can then run 'kubeseal --cert mycert.pem' instead to use the local cert e.g.
kubectl create secret generic secret-name --dry-run --from-literal=foo=bar -o [json|yaml] | \
kubeseal \
--controller-name=sealed-secrets-controller \
--controller-namespace=default \
--format [json|yaml] --cert mycert.pem > mysealedsecret.[json|yaml]
3. Apply the sealed secret
kubectl create -f mysealedsecret.[json|yaml]
Running 'kubectl get secret secret-name -o [json|yaml]' will show the decrypted secret that was generated from the sealed secret.
Both the SealedSecret and generated Secret must have the same name and namespace.
上記の変更を行うと、Sealing Keyが作成されます。
$ kubectl get secret --show-labels --sort-by="{.metadata.creationTimestamp}" -w | grep sealed-secrets-key
warning: --watch or --watch-only requested, --sort-by will be ignored
sealed-secrets-key5j4wr kubernetes.io/tls 2 5m11s sealedsecrets.bitnami.com/sealed-secrets-key=active
sealed-secrets-keykprf8 kubernetes.io/tls 2 25s sealedsecrets.bitnami.com/sealed-secrets-key=active
SealedSecretリソースの再暗号化
Sealing Keyは作成されましたが、以下のように既存のSealedSecretリソースは更新されていません。
$ kubectl diff -f test-sealedsecret.yaml
$
続いて、以下のように kubeseal --re-encrypt
を使用して再暗号化します。Secretsがクラスターからクライアントに残されることなく、最新のSealing Keyで再暗号化された新しいSealedSecretリソースが生成されます。
$ kubeseal --re-encrypt -f test-sealedsecret.yaml --controller-namespace=default
再暗号化する前のtest-sealedsecret.yaml
と比べて値が変わっていることを確認できます。
$ kubectl diff -f test-sealedsecret.yaml
diff -u -N /var/folders/nb/dm16t6sd4x764jtm75kv2dz8ndph4v/T/LIVE-429611820/bitnami.com.v1alpha1.SealedSecret.default.test-secret /var/folders/nb/dm16t6sd4x764jtm75kv2dz8ndph4v/T/MERGED-832741531/bitnami.com.v1alpha1.SealedSecret.default.test-secret
--- /var/folders/nb/dm16t6sd4x764jtm75kv2dz8ndph4v/T/LIVE-429611820/bitnami.com.v1alpha1.SealedSecret.default.test-secret 2021-03-09 19:27:59.000000000 +0900
+++ /var/folders/nb/dm16t6sd4x764jtm75kv2dz8ndph4v/T/MERGED-832741531/bitnami.com.v1alpha1.SealedSecret.default.test-secret 2021-03-09 19:27:59.000000000 +0900
@@ -5,7 +5,7 @@
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"bitnami.com/v1alpha1","kind":"SealedSecret","metadata":{"annotations":{},"creationTimestamp":null,"name":"test-secret","namespace":"default"},"spec":{"encryptedData":{"password":"AgCEuyJx1tOjbr3DfEg438bO9CvRsmbKrDTIGnuEsschi/sJ7UgL22tuz690oD3ZoLitBAwPMKb1Eo482rlmbZKwxsNWts7A5KUz530Zo3BRzi2uYClH99UL15x/7Eg35VaigjpAHrwVV24L42CeF6A6PE15zz43cQs/no3UNSAdzDIk2pQAquls8cJUgPwPxLH+TFGhPjDDivJ15hyX6+4KQQzbTIZIo6TxQlnCdOKCK3qFiJ2YwokdnjuopIhrwIV/DgJbjpu8/qXBXaoWI/Id2eVkIb31AOqFZ6HvF1XR14CF6FLhKmDC/TTcl4fYsApkqseHoAJSN/2QmZ8nAf2dpthnyQcttD4dSIe4VOBG8nO1EU/LzSdXnCw5EYCHPKno7zu5diaPVVgSGzO5BxhGl7V5HGLOQ6gK9UyjkFn35RpkEOFJAG5Aiq+z3QpZRuQHyrAR0ehUHbKoAR1Xe07xPAnJU+TKl8uYlqA91bOsi6r/0EnL6+0iFu/KVKeIswLClXxac9z6FGbyJMezxGBlGsuJdTi1NYxPCxbr1Y7P2JlYeurqezaKVS7TOIGpSCvZYBJW0pZZUvc6DqEhfk9OVgY1Htvgb26A7APqNoQyLAAPdbeUp/v0BVYoMH43sWYXlLvwsJwkyNh0/Jvlvl9EjWy4CzTUzArdWhQvU/M5fEdBTE4tbK/VRckvzSb6/h9JhT4H8XOuOA=="},"template":{"metadata":{"creationTimestamp":null,"name":"test-secret","namespace":"default"}}}}
creationTimestamp: "2021-03-09T10:22:23Z"
- generation: 1
+ generation: 2
managedFields:
- apiVersion: bitnami.com/v1alpha1
fieldsType: FieldsV1
@@ -28,7 +28,7 @@
f:namespace: {}
manager: kubectl-client-side-apply
operation: Update
- time: "2021-03-09T10:22:23Z"
+ time: "2021-03-09T10:27:59Z"
name: test-secret
namespace: default
resourceVersion: "5309"
@@ -36,7 +36,7 @@
uid: 7ab9fb2a-15be-4940-bf0c-a2b896fb231d
spec:
encryptedData:
- password: AgCEuyJx1tOjbr3DfEg438bO9CvRsmbKrDTIGnuEsschi/sJ7UgL22tuz690oD3ZoLitBAwPMKb1Eo482rlmbZKwxsNWts7A5KUz530Zo3BRzi2uYClH99UL15x/7Eg35VaigjpAHrwVV24L42CeF6A6PE15zz43cQs/no3UNSAdzDIk2pQAquls8cJUgPwPxLH+TFGhPjDDivJ15hyX6+4KQQzbTIZIo6TxQlnCdOKCK3qFiJ2YwokdnjuopIhrwIV/DgJbjpu8/qXBXaoWI/Id2eVkIb31AOqFZ6HvF1XR14CF6FLhKmDC/TTcl4fYsApkqseHoAJSN/2QmZ8nAf2dpthnyQcttD4dSIe4VOBG8nO1EU/LzSdXnCw5EYCHPKno7zu5diaPVVgSGzO5BxhGl7V5HGLOQ6gK9UyjkFn35RpkEOFJAG5Aiq+z3QpZRuQHyrAR0ehUHbKoAR1Xe07xPAnJU+TKl8uYlqA91bOsi6r/0EnL6+0iFu/KVKeIswLClXxac9z6FGbyJMezxGBlGsuJdTi1NYxPCxbr1Y7P2JlYeurqezaKVS7TOIGpSCvZYBJW0pZZUvc6DqEhfk9OVgY1Htvgb26A7APqNoQyLAAPdbeUp/v0BVYoMH43sWYXlLvwsJwkyNh0/Jvlvl9EjWy4CzTUzArdWhQvU/M5fEdBTE4tbK/VRckvzSb6/h9JhT4H8XOuOA==
+ password: AgBAWDceaDaC2m8cRq3OfL47IoGlj9je6K7H6ZrMDMZjOisC8RsyJhF7KXwp7eB7lK8imcg1dS4v86l2MSYNRYmvRxrd0VdYWi74Zz4kCKrllblMj3qzBwNs7uoRXgdscHvfroNF+/WFGFgtt3qeAYdAnXbuQoeHT/s5H9MoHvFkqsTlbEfQpwfQsxZjS7gvOwAtf7K2PVv4xv1ltt4u8sZ3lmrFIMMqxt0qSxxotvuB7HEX1HDusT4EIWSNqG0D95we2sKRadN95eJ1A3Z885ZaKXcx2d/rQsrE/jfbp0IWnjEzRyzQHcMLwbyBnuAr+501NCCjQEEwuqKoFD2iqndgI00hrNxRfyR5d27zO+EXhdu/hU+N9a3i+f58GVAv3BkbbFQvswKj2FNXzOoBDn3u3ELXwPNpELlb+NllVfLwNrxusj07kKcEkbMoK1y997yfs/kQQWchIKIleeGINyxg5kR7TSOFeCe84wKCiSPZ5Mdd0k2G3jgnHcszCATP/eS4sgnDFOyc+GCoxw3qpsf0Y8oHogPDSBgYwvs9YHvJ0Qirh5EN7RWfKg8WBQ6dTdJTH+LYlM4MtnRRRUQNOy6moo4mN5Tfyv/2OG0ij1Tk2IQifJZxYm8Eq25C66cq6wXP+xplUSmAv9clzD4YDbhc5w5rtTINsu4jtu+COPNjl5I++oD3s4dK8+2PngTc73l6/NfeVlZEeg==
template:
metadata:
creationTimestamp: null
最後にこちらの新しい鍵で暗号化し直したマニフェストを適用して完了です。
kubectl apply -f test-sealedsecret.yaml
上記のマニフェスト適用時のコントローラーのログが以下になっており、SealedSecretリソースが更新されたことがわかります。
$ stern -n default sealed
sealed-secrets-controller-597c496bdf-767x4 sealed-secrets-controller 2021/03/09 10:28:55 Updating default/test-secret
sealed-secrets-controller-597c496bdf-767x4 sealed-secrets-controller 2021/03/09 10:28:56 Event(v1.ObjectReference{Kind:"SealedSecret", Namespace:"default", Name:"test-secret", UID:"7ab9fb2a-15be-4940-bf0c-a2b896fb231d", APIVersion:"bitnami.com/v1alpha1", ResourceVersion:"7656", FieldPath:""}): type: 'Normal' reason: 'Unsealed' SealedSecret unsealed successfully
おわりに
如何でしたでしょうか。
「Sealing Keyの即時更新」と「SealedSecretリソースの再暗号化」について今回は説明させていただきました。主には鍵の漏洩時に行う対応だと思うので、運用のことを考えると漏洩したかどうかの監視をどうすれば良いか検討する必要がありそうです。
最後までご覧いただきありがとうございました!