SealedSecretsの鍵のローテーションについて

AvatarPosted by

こんにちは、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でインストールします。

  1. 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リソースの再暗号化」について今回は説明させていただきました。主には鍵の漏洩時に行う対応だと思うので、運用のことを考えると漏洩したかどうかの監視をどうすれば良いか検討する必要がありそうです。

最後までご覧いただきありがとうございました!