SealedSecretを用いてSecretを暗号化する


背景

Kubernetes環境でGitOpsを導入するにあたり、Secretの扱いをどうするか、という課題に直面しました。
GitOpsでは、KubernetesのマニフェストファイルはGitで管理するので、工夫しないとSecretをそのままコミットすることになってしまいます。
Secretの値はbase64でエンコードされているだけなので、簡単にデコードできてしまうため、そのままコミットすることは好ましくありません。

そこで対応方法を調査した結果、SealedSecretというものを見つけ検証したので、本番環境でも有用な使い方をまとめることにしました。

準備

kubesealのインストール

まず最初に、kubesealをインストールする必要があります。
手順は以下のリンクに載っているので、ぜひご覧ください。
sealed-secrets - releases

ここでは、Macでの手順をご紹介します。

$ # kubesealのインストール
$ brew install kubeseal

$ # バージョンの確認
$ kubeseal --version
kubeseal version: v0.12.5

SealedSecret関連のオブジェクトを生成

SealedSecretのCustomResourceDefinitions(CRD)をインストールします。

$ # sealed-secrets-controllerに関連するオブジェクトを生成
$ kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.12.5/controller.yaml

kubesealのヘルプを見ると、namespaceのデフォルトはkube-systemのようです。

$ kubeseal -h
...
--controller-name string           Name of sealed-secrets controller. (default "sealed-secrets-controller")
--controller-namespace string      Namespace of sealed-secrets controller. (default "kube-system")
...

必要なオブジェクトが生成されているか確認します。

$ # Podの確認
$ kubectl get pods -n kube-system
NAME                                         READY   STATUS    RESTARTS   AGE
...
sealed-secrets-controller-867447b788-7sdhq   1/1     Running   0          8m58s
...

$ # Secretの確認
$ kubectl get secrets -n kube-system
NAME                                             TYPE                                  DATA   AGE
...
sealed-secrets-controller-token-xplmz            kubernetes.io/service-account-token   3      23h
sealed-secrets-key4wlwz                          kubernetes.io/tls                     2      9m56s
...

無事に生成されていることが確認できると思います。
これで準備は完了です。

Secretを暗号化する(SealedSecretを生成する)

ここからは使い方を紹介していきます。

証明書の取得

Secretを暗号化するために必要な証明書を取得します。
証明書はコミットする必要がないので、.gitignoreに追加するなどしてコミットできないようにすることをお勧めします。

$ kubeseal --fetch-cert > cert.pem

暗号化するSecretのyamlを生成

すでにSecretが作成されている場合は、それを元にyamlを生成しても構いません。
このyamlもコミットする必要がないので、.gitignoreに追加するなどしてコミットできないようにすることをお勧めします。

$ # sample-secretという名前のSecretのyamlを生成
$ kubectl -n default create secret generic sample-secret \
--from-literal=MYSQL_HOST=localhost \
--from-literal=MYSQL_USER=root \
--from-literal=MYSQL_PASSWORD=password \
--dry-run=client \
-o yaml > secret.yaml

生成されたSecretのyamlはこちら。

secret.yaml
apiVersion: v1
data:
  MYSQL_HOST: bG9jYWxob3N0
  MYSQL_PASSWORD: cGFzc3dvcmQ=
  MYSQL_USER: cm9vdA==
kind: Secret
metadata:
  creationTimestamp: null
  name: sample-secret
  namespace: default

SealedSecretのyamlを生成

上記で生成されたSecretのyamlを用いて、SealedSecretのyamlを生成します。
このとき、証明書の指定フォーマットの指定を忘れないでください。

$ kubeseal --format=yaml --cert=cert.pem \
< secret.yaml \
> sealed-secret.yaml

生成されたSealedSecretのyamlはこちら。

sealed-secret.yaml
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  creationTimestamp: null
  name: sample-secret
  namespace: default
spec:
  encryptedData:
    MYSQL_HOST: AgBPhntyTvUOYPtjJrdZk0fbVyljZFQExAVKAHYjE0rtTNsqaNXygninGs5CPPALfIf/BgmONK35xepHnH7kqc+WNm2nt4MueAMHAtrHQ/8SCkzXbQGLuiyUUds2Q2xVu/rBAVwGfWaKkwi9Db8Y7u6UOLE2wjgk2hpfqnf0KCgR4+u4q936tszBlqQrzk6+ZCqn7oPkOHI3iHJIi4Myo+Xh51Cx0ItEcq18huAK04tTgPoNDJgjLZZnWt83eOJcdqs6mNuI2khsi+/sXizA/naRRDJjiZat5//v/e73ejPRYLJkLCxmbiqBkZUmzBcTgj9PNvOPd+Be/VlKV0I4oO23e4f4CTp+xsq/hxje1qo38zL5UEalbYvtWVCAQCHyA5zcacpD7Sbl0hDSn4JCmXgktrtmgp4iADw2dGDqj7yGyvgQt18VUyWwqIzdhdih9a9LLvqK+gEf9pHs6p6j+rSlvIhKWM9k+vp44h9GLv5ViXB0jFD6Ig5kuyrne6OPceJgj1kuoGQbGdwCvd6thjNytWwBuPujKo5AK4VepLUUh7q7zZwqBv0yXMwSkmFDpJMTmMuy6W32HZvmJw1m/ajaAqlltDKl2pbyDClRLJ/K1n9QDiOEgIdLX3t9C5tJL1G9b1bMTpTfqqYcQbTQnDBEJJ7du4eYQRoT9EP4vI0eyab8beuvBvwIdPvsPWwY+uf7TzuyX0ve6/8=
    MYSQL_PASSWORD: AgAV4O7rOmyqL2JtPZ/XtUIMNW5GPgSMhc8zU0zU7LyaNfu5PPFfZrqmyvV4u0XjiNL0nH0rxCEnbm8ZlYtDQCDTMAZMgQkDSWVq7RGnL0txkE3mtsgzctNvIk54lvoVi7fzsBUHXLWsXu58NgQsGhc/VG25zMIq2ycEKd0nEFoa3EK1EoNsb3gKNS4qGM7hqkS5y7ufOcUR5mVuvXlihrfn9nGcucFsRsMbxsTYWUauM8x0wqxrnw7o74EkA0Gyhu2FI43n4mleubC8pzOEYvurfyfd1UmS7H6j/Cm4VuPtiX/uynvKyb2iVJX3vkeyKnmvhP6AcegfI1UYSD1nLg6taDOUFDdScCRsdI97lF0ehYauinLLp94rRQIXOFWYaSqq8VNbQzApXTDAgSzjazdx17p1IVrspEc7xCzD1Dcn3RiiiSpGnYEbG4rF4pFu8AcKH5j/MSydKN4iJgQ2utOgJ7pUXNeShgRrNf/BI67uus44fgeIXnkG8+LG7/WGhsjnYTRsECZD4g+q2vB3tYeBSFQ7ojUFVN5ChsZgTS+uziIp3jzxu3sWfSt4puGx+BlFp+dxv0Zp1YrRKuje+dBWlCkrf9Sa16SR7DCqKdX+1G0W6rwYYEyXwRW9Y0s6lUZwfpd0kOVcYZABohfH3RkWmxgKjF76xFlyFfHILVtdGJbe1nz1144wAMQeE3jvk/ypeAGy+I8lbg==
    MYSQL_USER: AgDjq7qBJR1DDOWapYF4AhtQi1sQgeglSRhu6tZ+JHPTPPdNFBV+zQk3xzKKRldZeeIlXQ5ZquEdc3lMWYKqg5xvL2e21YhgPv4oWADDvsWxWdklZVLDl0SGPQT64v6K+lPVR5Gia6zIpocvv4v3GbMcCXFkFK/b5HeSU6FcxrAhrQ+vdxSWKfkezO8vrS1wJ3MYLgLSwtiRDsibjpYwVTg0sLDcpTlrDk95IEnmcY8bE6qGJ3BO8mUBPhsBnk5yLJO2MZeOofA9IbpUDIpUJSFcaaXc+PK3C+vKxxJ2Ia94Y07UshUOrl72dTrp9aJv1mdkVfzZ7EHQpHh+2k7b2U1nMFVFwK2QV193vJVSQDM21IzIttE4QOxllv0ei48FVMVw3pJ0ABJMKiYta6KAZvKQ5EJkEIVVHtVqRH6plpBgU72dAPVuJAmdlAQ3I844lj1AKwfydylFRJP/olkHegB5Lu3ScwblicPZt4Q+f+IV79YefKR+Bf8zwLB5bd5uFOVx5Jj3dkiSfTN9P60FE6uA1axxyECafqwcgEmxafODtRpP+94rbAUboWCCXTAqjL+KyclCkXg0lKKLDHBmIzhsV3+eDwlApHWfvs1/x2dyW17Rjs7Nqb6vi/xX+YXT0d67TzNEUFfYFABRN9ABN/YSCWsVxIJXvMulg4RpsGD4g5E4GG5gwj3Jjjfr0yEtsu9fS5Zn
  template:
    metadata:
      creationTimestamp: null
      name: sample-secret
      namespace: default


ご覧の通り、暗号化されるのは、ファイル全体ではなくSecretの各キーに対する値です。
これをapplyすると、Secretが生成されます。そのSecretを確認すると、前述のSecretと一致するはずです。

Gitにはこのファイルをコミットします。
基本的な手順はこれで終わりです。

鍵の扱い

バックアップ

Kubernetesクラスターにある秘密鍵と証明書は、Secretになっています。
これを削除してしまったり、クラスターそのものを削除してしまった場合に、SealedSecretを復号できなくなってしまいます。
それを防ぐ為に、このSecretは必ずバックアップを取りましょう。

$ # ラベルを指定して、Secretを取得
$ kubectl get secret -n kube-system \
-l sealedsecrets.bitnami.com/sealed-secrets-key=active \
-o yaml > sealed-secrets-key.yaml 

生成されたSecretのyamlはこちら。

sealed-secrets-key.yaml
apiVersion: v1
items:
- apiVersion: v1
  data:
    tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVyakNDQXBhZ0F3SUJBZ0lSQUpsYnRkeUI5bmRqZ1ljZFI1Qkh0dk13RFFZSktvWklodmNOQVFFTEJRQXcKQURBZUZ3MHlNREE1TWpReE5ESXhNRGxhRncwek1EQTVNakl4TkRJeE1EbGFNQUF3Z2dJaU1BMEdDU3FHU0liMwpEUUVCQVFVQUE0SUNEd0F3Z2dJS0FvSUNBUUR5cVJQT3pCSzk4UFZvVlJBYVJJV1gyS3dSdmJIZVBMTnhxY2Q4CmQ3UlM2a3pDZUhINzFFa0E2NzlyQWoxZVUzQ3g0dG8zTlNPWkhqNEFIVk1OS256WTVWYXRFY1F4WEZOWjdRci8Kb2hKTFNuS1VHVG55UCszZU55S29wY3hYeUlibkdlUmh0elVQOTRPdzAyWld5MHEreDdyS29uZk11ZUNMTlNsRwpqeXVKelN3UW9HZUFCbXZYL2NUc29pTHNRdHJoK1Jvc29ZL3VIYU1KNDlYaytiSzRrWFJBeEZPenh3dUZGd0JZCk9NVEZnS3d3WWlaM1l2RytOYjBwWGx6MyttZ1NTVkxRem9CallPT0tMRloxWnBBMXRKYWJIcVhwdUF6b2FJOFUKL0svQjV1cWxLNngrMnhtYUFvcjUxTHZubEdxWER4K2hOZXRDd0NUMVB4Uk5PS0ErNTRiTmVaUjh5alVGMDM5MQpUOXM4a1ZKT2RjN3VTTVdkbytCSUVGM2tJcjdiNThrcThiRTdVcERUOUVybG8va2xhNmZOV2hSaldGR3VHdjgrClJCaUZYZFVxZHZTeVNOTC91VndUVHN2SWpZQ2RqajJsMDZJM2lvQWxsUjQvejNDa21ORW5hQ1oxVmEyclRMc0IKRW5yRHhmRGJsUnhMVHpENWRZVHJ1MTd0MTdadHc3emp1Q3RRWlViM2NzWlJER1EwTzlYcWxZbjZJQzVaRjRxeQpXOFF1b3N6Wlo1cVAwSVFXOUZsK3Nvb3lCbjJhdmNtNnZoMFBvOVZYNGl1U2NLcnBKUkFaNHlIYnRIQlJxTXZOCklFYXBua2dZVk1XMGF0UkN1c2gzTktIaFNCakYyM1ZXMWV4Y01lbFdFWVc4TWhub0d2S1RnZjI5ZW9oMjllVXYKdlZNeVhRSURBUUFCb3lNd0lUQU9CZ05WSFE4QkFmOEVCQU1DQUFFd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBTgpCZ2txaGtpRzl3MEJBUXNGQUFPQ0FnRUFaWkRrblBvWVRrTXltTU01cHdlZ2JLSmRwQlRDRDgvbUR3dUZBS1ZRCkhia0lCMVR1NmVwOVgzbzYxQVF4ZWF3Ukd5NVRvS29pVHNqUTRhcCsrNUJqYmpWZHdBREpZWVd6d2twR0taenAKTDFXV3IyTlQzOXYvR1dPOHl3ZnlUb3BQcjZPN3h5dVRPYk4vb0JjdC9lVVZVV3gzemZyM1hxb3FLZ3IzTDlTLwpuYU5abEc1ZFcvNTNEcEFTSHNEMVgrRWZOQXQ5KzZtMXZpZm5FRmxFQmVWeU02Mm9NQ3NPbFdNVGNEcnBIN0JrCkthSTM4Zno2aTQzd3pJK1ZncDAzVUx0aVVsMnZ4ZVdJbHdFcXVGeUZoTDFkeFBqR0JFNHAraGhCNGR3a3hGVEgKelk1bVFweUhJNWdvK1FvQk1pclZzcFZXeW9HNHo3WUJNdGZSamYweWdWUXlXd2RPbUpZMDF3NVM4ektMMG9tYgpmZU5wbWdFY1NkNWo3SEIydklnVWJGVWxlaERIZkszcXFGR2FsOXYwMXZWckJ5aklnRW9TUGhUOVFlVkh6TXk1CnI4Ump1TkZiUWZidXVWazRCd1hHeW9tYUJUdkdqVXhqMWRHRlRPNjdTMHMvMFBzOVpGM29ONkdZbFRQZDQ2WjYKeHJ5b2FqMXA2V2s3VVhBaGlWd3E3VFhMZEhPcVIxZnl5RFF3eEJLYTArTkI0NHlqcXp6Tmk5TzRuYnovM2hLagpHK1JpcWdFaFFkTUg5MVFkcGVvUUZKeUFmQzVQcnBxUkVoQUY3dG5mUExqOTZRTlJGS0pXeW5MRXFma3cxN0tGCnlvbWFUMmVVZ05Ock11emczdXB4UXNsakp6d2RQLzZxYklmb1Uwa0V1TXo3V3gvQ09WeGtqWDdlckZNT0N2cUIKcWQ0PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
    tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlKS0FJQkFBS0NBZ0VBOHFrVHpzd1N2ZkQxYUZVUUdrU0ZsOWlzRWIyeDNqeXpjYW5IZkhlMFV1cE13bmh4Cis5UkpBT3UvYXdJOVhsTndzZUxhTnpVam1SNCtBQjFURFNwODJPVldyUkhFTVZ4VFdlMEsvNklTUzBweWxCazUKOGovdDNqY2lxS1hNVjhpRzV4bmtZYmMxRC9lRHNOTm1Wc3RLdnNlNnlxSjN6TG5naXpVcFJvOHJpYzBzRUtCbgpnQVpyMS8zRTdLSWk3RUxhNGZrYUxLR1A3aDJqQ2VQVjVQbXl1SkYwUU1SVHM4Y0xoUmNBV0RqRXhZQ3NNR0ltCmQyTHh2alc5S1Y1YzkvcG9Fa2xTME02QVkyRGppaXhXZFdhUU5iU1dteDZsNmJnTTZHaVBGUHl2d2VicXBTdXMKZnRzWm1nS0srZFM3NTVScWx3OGZvVFhyUXNBazlUOFVUVGlnUHVlR3pYbVVmTW8xQmROL2RVL2JQSkZTVG5YTwo3a2pGbmFQZ1NCQmQ1Q0srMitmSkt2R3hPMUtRMC9SSzVhUDVKV3VuelZvVVkxaFJyaHIvUGtRWWhWM1ZLbmIwCnNralMvN2xjRTA3THlJMkFuWTQ5cGRPaU40cUFKWlVlUDg5d3BKalJKMmdtZFZXdHEweTdBUko2dzhYdzI1VWMKUzA4dytYV0U2N3RlN2RlMmJjTzg0N2dyVUdWRzkzTEdVUXhrTkR2VjZwV0oraUF1V1JlS3NsdkVMcUxNMldlYQpqOUNFRnZSWmZyS0tNZ1o5bXIzSnVyNGRENlBWVitJcmtuQ3E2U1VRR2VNaDI3UndVYWpMelNCR3FaNUlHRlRGCnRHclVRcnJJZHpTaDRVZ1l4ZHQxVnRYc1hESHBWaEdGdkRJWjZCcnlrNEg5dlhxSWR2WGxMNzFUTWwwQ0F3RUEKQVFLQ0FnQnNOcTVZcUhVck0wdWRiV0d5OVIvR2FaL0NnWi9TaGF0WVl1aE5QMnl4RlQrSjhnQ1MxMFovSEtMTwphNzlHVTF1TVdLZ0x1cXpYV2I4NGVkdFJvY0x2VHNicWcyUEV4M0Y4UnRPQzBKbnI3WlZQS2pqSEtXOUFpOEh2CkI1RXJESWZzZzRWdmRpNDVvcDJkdTRpRjZENjYrWUw1WHA2aU03cEpHam4vOTFUcExSQWJrZ3pWOFFjaTJVNTYKWUl2R1pNSUx4L1MrTm9aakgrQlhScjFhVVdnOEd0R0hHSVpqUTc2RmFZNkR5VDBtL296TFB0bjhuNmxDcytCWgpsSFZOT09RMFUzS1ZINkh0cjRXSlZ1QnZsbjkxRThXZUEzcmwwV2dnTkpDcVFVMTM0U3grNEEwYXZVYWJnY3JNClF1eFJCOFRJL0x1VVB1RmRjU2FLSDhsRDdwNjIwR1dzcDBXditMMWJlY3N6YkphcHRhMzhsUnNrLy8rUmpCMW8KUWszRXRkNnZWblZxVTF1Q0FDdlYzcWplY25Kd244QkJ1OElyOXdXMHFSR1RQUUhKak5pMzdYVC95RVJGNjJtVApHMEVodmVjQnZqdE5QcVE5OUdGc2l0YUtFeGhSK3FWTWZDZktVQ2dMZmRGYTFvU0x3NFVJY2NrZzZ1Ymxoc2YyCmxIaHBDWlpzL3piQzFxbDJob3NHRHR2YnNzOEIwbTJWdW9HalFuMHF2SEFZeEMrZG9QK3FZTlB6VjRxS1VweFUKV0RXZEZ5QXRvZ0M4bVgyMXZvbHNvU3grRWdrVW5ZV0Z6WmViQXpleTFuZGxXNnVQcmJBQUM0aVQ0VUFRZko1aAo3Q0hIUlo4M2V4ZGo4L1Fnd0FBZHByZXplV3VTNFdyUVJYOVl1WGJudXE3OEFLWEFtUUtDQVFFQSs3a3l1Z3lVCmxRYXlsNFV4Qmp5ajdSTCtTWGErbXYwWkhUQVd5dXhra2FsSHNCYms1U0hNT2RBbWFrR1hJdEhTckxZMEd3Yi8KRys2WC9YQWYybFZtZUlBRUM3WlJiRk9kRHE1TXBZZkdSWmpBZWNRamRJQjQvMWNMNnExT2dRMjZtaFNaalJKegpFQnVocVQwNHJIcmovQ2lyT2lER3dMUDJlejg2aXViOHB6YWtkeEMwMERsLzliTDNPWWJmWG1wL29BcDZYem9pCnExcUpzdzIzTFJSNlNkd29xQmlZdHczZ1hJK3dPV3ozci9uNUFJOHNuSUVwdGNrTjVLYWQ0NnlYOEpzSFYvVjcKNnBqeWU3cWpkRVp3TktGRGlDeWhVVFA0bit1WW5TblhjeTdteTUwNkh0V2M4SHNNekRyclFFeFZsSkY3UitPMQo3ZmhnbG55V0tBcGVDd0tDQVFFQTlzaDJXb2hyOWpVUlk1M0s2NVFpa0JxQkUySCs2aGhSc0pXaGhBbUlwUUlmCmFTVVorNThIL0JmMGx5czdCNTUxU24xVTlWNmxQTFNOQm5lb25OWnIzZ0hwaGNhN2o5Rm45Y2tpdHVTZzhDN3MKSGg1RzEvOVptSDlvRTdPRjRwSzdCT1JVUGVHRUdsbE1QdWpNYVV4a3BpamJhdmFXd2htU2NNN1E4bVFxKzhYUwpIVkpoMW5uUmpwY1B0ZzBwSVFnZUtkTFViMytRZ1RXV3B6RWg1V2NKNjY2OCtNWDBJRENoWUdOOVFINklWNDNmCm9ZZE1reE1WajVoQXQ3dVZ4UzdrQmY4a2o0RndmbmhIc3hta3NpMUp0aTBMUUZJWUhLaEh3RjhRTjYzM1RWNFUKbENNcm9Sb3NzWVpwbGRUR0c3TE5IYm05NFd6MStJUVlIRkxNL0crNk53S0NBUUJIY1FCY25VVnVKa0I1a0d1aApnWVJrdkljL0FseUdVRjdZVWRXbU1nRTQ5ZnBLbDdUTzh4Q3JOOUF2Ui81RSs1ZjNQSjc5TExjcWprVEV3UlhKCk9ZT2puM0dHZ1hBS2RwQ1VvaE5PeDRJV0xvd3lBdUN0SitrdlR0MHE5WlRhTzdOQ0Y2YWN5eWVzNHFxM0JaRVkKSXFpaDRFajRibVQ2UEJrd1VYbWtBbnFpV25mQzh4TGVKZk9USC8vYWE5VHBUd0I1dzMrSGwxQlBvWnFESHRsbQpDZkhMRkpqVlhHVzdUa3ptK0VEamkzR2dtQ2w3WnljYUkrNWFrWDFINzZIUUJDUDdQWVNRQ2pQcEdROTQzamVWCndJZ0g1OXpxd1AvbnRBQlVJdUZsZVlLVVJqTnFobTBBWTAvdlVIMEpXWTk2NkM5Qnd5aGg5dGFqZTJLdVV4MnEKcjRhUkFvSUJBRTQvaWFkekVpaGk4enlPejhTYWw4cnhYSFAwNG9yL1l3ZUdxa3ZmYWdCSUNBV2l5ZlpLbXBHSQpWdm1IcjVQZTNubmIrNUJCamtzTlJKb0VYdVk3NXIvaUExVnppZzB6N0s2Mk05ZWg3cFc1aXd5UnRRelAzbXpJCkdRd0dKREdQTE5XRVFHSE9tOEJ2Q0FuNmJyWUVqdlZRaHlJSFJnNE5aYTEzSmpGMUtWdmpnWmZpZ3pzcUxSUDEKT2ZvVGVqTUxDK1ZmQUJUS2NkdEdUcHA4cmE2N1dSM3RyZVdEZnNDbUtzVVJScW1vZFRxdHRYYldHNldXcTRONApWeXpxd2JaZ0E5VVM3VmpEUmhRVHQwaEduVDRSdGdtWmhyUENVL3JpTUw0Q1puWUJKeVRVNjlsOHZWeTdtK2crCklrUnJ6dFVCZ0tBb1FOaTFYOWlJcWN4eFJLWFdGMGtDZ2dFQkFNQXVjWEs4eGo5VEJuWkdGMW5mR2pOWlFQWkcKb2UvMEdOaWpHVUtUZkN1dXZNcU16Tm1JeVY2VlVFNXhiaWVFNElIbE5EQXROSG5uYTBnNWUxb0cycDdISWo2cApWNG10QVlMYUU0dTg4c3hFMFdKNHVEUVN3aWg3b0Q5UWkrTUN1R1ZZRTY3LzNDbElZdFhaK2dpSWRRY3ZEcDNwCk9WVzlXYjhENk1mSTQ0Ukp4RlFQb2hheU9LazAvOXVuck1vQlAzZXdpcVRLY2EwNFZ2V2dONHVaU3pMQ0wwaDQKeUJiL3AwdUNlcW1XOUhLSWlsdXc0NmN3OVdOZnZPVDZYZHB4b25LNysya2RieTQ4VFk0QVpNa3JEU21JYzkwTApXeFFlb2NlYWkxUlRIRG9Ock44dzB5Qk1WOGNKYjdsRW9yY21xV0tteEZ6cjNoQjRyRktrc3hlZE93ST0KLS0tLS1FTkQgUlNBIFBSSVZBVEUgS0VZLS0tLS0K
  kind: Secret
  metadata:
    creationTimestamp: "2020-09-24T14:21:09Z"
    generateName: sealed-secrets-key
    labels:
      sealedsecrets.bitnami.com/sealed-secrets-key: active
    name: sealed-secrets-key4wlwz
    namespace: kube-system
    resourceVersion: "3522389"
    selfLink: /api/v1/namespaces/kube-system/secrets/sealed-secrets-key4wlwz
    uid: 1fb9a39d-1b5f-4a4b-a47f-ebd56d02303d
  type: kubernetes.io/tls
kind: List
metadata:
  resourceVersion: ""
  selfLink: ""

local環境用の鍵はそのままコミットしても構わないと思いますが、ステージングや本番などの鍵はそうはいきません。
何もせずにコミットしてしまうと、SealedSecretからSecretを生成できてしますので、SealedSecretを使う意味がなくなってしまいます。
KMSなどを使用して暗号化してからコミットしましょう。

バックアップから証明書を取得

上記のsealed-secrets-key.yamlから証明書を取得します。
ここでは、yqコマンドを使用します。

$ yq r sealed-secrets-key.yaml 'items.*.data."tls.crt"' | base64 --decode > cert.pem

鍵のリストア

バックアップした鍵をクラスターにリストアします。
ここでは、鍵のSecretがない場合の手順を紹介しますが、残っている場合は事前に削除してください。

$ # 鍵のSecretを生成
$ kubectl apply -f sealed-secrets-key.yaml

$ # Secretを生成しただけでは反映されないので、ControllerのPodを再生成する
$ # このPodはDeploymentによって生成されているので、Podを削除するとすぐに生成される
$ kubectl delete pods -n kube-system -l name=sealed-secrets-controller

$ # ControllerのPodが作り直されたことを確認
$ kubectl get pods -n kube-system -l name=sealed-secrets-controller
NAME                                         READY   STATUS    RESTARTS   AGE
sealed-secrets-controller-867447b788-zhrg4   1/1     Running   0          18s

Secretの内容を更新する

運用していると、Secretを更新したい場面があると思います。
しかし、SecretはSealedSecretとして暗号化されているので、簡単には更新できません。
そこで、SealedSecretを復号してから更新する手順を紹介します。
もちろんクラスターからSecretを取得しても構いません。

$ # バックアップした鍵のyamlを渡して、SealedSecretを復号する
$ kubeseal \
--controller-namespace=kube-system \
--controller-name=sealed-secrets-controller \
< sealed-secret.yaml \
--recovery-unseal \
--recovery-private-key sealed-secrets-key.yaml -o yaml > secret.yaml

$ echo -n 'user' | base64
dXNlcg==

Secretの値を上記の値で更新し、再度SealedSecretのyamlを生成すれば完了です。

環境毎にSealedSecretを管理する

Kustomizeを使用することで、環境毎にSealedSecretを管理できます。
Kustomizeの使い方に関しては、公式のドキュメント等をご覧ください。

前提として、以下のディレクトリが作成されているとします。

パス 説明
base/ 全環境共通のyamlを置く。
overlays/local/ local環境用のyamlを置く。
namenamespaceが一致するオブジェクトの値を上書くことができる。

base/には、namenamespaceを定義したyamlを置きます。

base/sealed-secret.yaml
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  name: sample-secret
  namespace: default

overlays/local/には、local環境用の具体的な値を持つSealedSecretのyamlを置きます。

overlays/local/sealed-secret.yaml
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  creationTimestamp: null
  name: sample-secret
  namespace: default
spec:
  encryptedData:
    MYSQL_HOST: AgBPhntyTvUOYPtjJrdZk0fbVyljZFQExAVKAHYjE0rtTNsqaNXygninGs5CPPALfIf/BgmONK35xepHnH7kqc+WNm2nt4MueAMHAtrHQ/8SCkzXbQGLuiyUUds2Q2xVu/rBAVwGfWaKkwi9Db8Y7u6UOLE2wjgk2hpfqnf0KCgR4+u4q936tszBlqQrzk6+ZCqn7oPkOHI3iHJIi4Myo+Xh51Cx0ItEcq18huAK04tTgPoNDJgjLZZnWt83eOJcdqs6mNuI2khsi+/sXizA/naRRDJjiZat5//v/e73ejPRYLJkLCxmbiqBkZUmzBcTgj9PNvOPd+Be/VlKV0I4oO23e4f4CTp+xsq/hxje1qo38zL5UEalbYvtWVCAQCHyA5zcacpD7Sbl0hDSn4JCmXgktrtmgp4iADw2dGDqj7yGyvgQt18VUyWwqIzdhdih9a9LLvqK+gEf9pHs6p6j+rSlvIhKWM9k+vp44h9GLv5ViXB0jFD6Ig5kuyrne6OPceJgj1kuoGQbGdwCvd6thjNytWwBuPujKo5AK4VepLUUh7q7zZwqBv0yXMwSkmFDpJMTmMuy6W32HZvmJw1m/ajaAqlltDKl2pbyDClRLJ/K1n9QDiOEgIdLX3t9C5tJL1G9b1bMTpTfqqYcQbTQnDBEJJ7du4eYQRoT9EP4vI0eyab8beuvBvwIdPvsPWwY+uf7TzuyX0ve6/8=
    MYSQL_PASSWORD: AgAV4O7rOmyqL2JtPZ/XtUIMNW5GPgSMhc8zU0zU7LyaNfu5PPFfZrqmyvV4u0XjiNL0nH0rxCEnbm8ZlYtDQCDTMAZMgQkDSWVq7RGnL0txkE3mtsgzctNvIk54lvoVi7fzsBUHXLWsXu58NgQsGhc/VG25zMIq2ycEKd0nEFoa3EK1EoNsb3gKNS4qGM7hqkS5y7ufOcUR5mVuvXlihrfn9nGcucFsRsMbxsTYWUauM8x0wqxrnw7o74EkA0Gyhu2FI43n4mleubC8pzOEYvurfyfd1UmS7H6j/Cm4VuPtiX/uynvKyb2iVJX3vkeyKnmvhP6AcegfI1UYSD1nLg6taDOUFDdScCRsdI97lF0ehYauinLLp94rRQIXOFWYaSqq8VNbQzApXTDAgSzjazdx17p1IVrspEc7xCzD1Dcn3RiiiSpGnYEbG4rF4pFu8AcKH5j/MSydKN4iJgQ2utOgJ7pUXNeShgRrNf/BI67uus44fgeIXnkG8+LG7/WGhsjnYTRsECZD4g+q2vB3tYeBSFQ7ojUFVN5ChsZgTS+uziIp3jzxu3sWfSt4puGx+BlFp+dxv0Zp1YrRKuje+dBWlCkrf9Sa16SR7DCqKdX+1G0W6rwYYEyXwRW9Y0s6lUZwfpd0kOVcYZABohfH3RkWmxgKjF76xFlyFfHILVtdGJbe1nz1144wAMQeE3jvk/ypeAGy+I8lbg==
    MYSQL_USER: AgDjq7qBJR1DDOWapYF4AhtQi1sQgeglSRhu6tZ+JHPTPPdNFBV+zQk3xzKKRldZeeIlXQ5ZquEdc3lMWYKqg5xvL2e21YhgPv4oWADDvsWxWdklZVLDl0SGPQT64v6K+lPVR5Gia6zIpocvv4v3GbMcCXFkFK/b5HeSU6FcxrAhrQ+vdxSWKfkezO8vrS1wJ3MYLgLSwtiRDsibjpYwVTg0sLDcpTlrDk95IEnmcY8bE6qGJ3BO8mUBPhsBnk5yLJO2MZeOofA9IbpUDIpUJSFcaaXc+PK3C+vKxxJ2Ia94Y07UshUOrl72dTrp9aJv1mdkVfzZ7EHQpHh+2k7b2U1nMFVFwK2QV193vJVSQDM21IzIttE4QOxllv0ei48FVMVw3pJ0ABJMKiYta6KAZvKQ5EJkEIVVHtVqRH6plpBgU72dAPVuJAmdlAQ3I844lj1AKwfydylFRJP/olkHegB5Lu3ScwblicPZt4Q+f+IV79YefKR+Bf8zwLB5bd5uFOVx5Jj3dkiSfTN9P60FE6uA1axxyECafqwcgEmxafODtRpP+94rbAUboWCCXTAqjL+KyclCkXg0lKKLDHBmIzhsV3+eDwlApHWfvs1/x2dyW17Rjs7Nqb6vi/xX+YXT0d67TzNEUFfYFABRN9ABN/YSCWsVxIJXvMulg4RpsGD4g5E4GG5gwj3Jjjfr0yEtsu9fS5Zn
  template:
    metadata:
      creationTimestamp: null
      name: sample-secret
      namespace: default

このように配置し、kustomization.yamlを作成した上で、kustomize build overlays/local/ | kubectl apply -f -を実行すると、local環境用にSealedSecretが上書かれてSecretが生成されるようになります。
stagingやproductionなどの環境に対しても同様の方法で、環境毎のSealedSecretを定義できます。

まとめ

SecretをGit管理できるようになったので、安心してGitOpsを導入できるようになりました。
Secretの扱いに関しては他にもツールがあるので、各プロジェクトに応じて選択していただければと思います。

参考

SecretをGitHubに登録したくないのでSealedSecretを使う