Sealed Secretsでk8s上で利用する機密情報をGitなどでセキュアに管理する

はじめに

k8sには、Secretという機密情報を扱うリソースがありますが、こいつは基本情報をBase64エンコードしたもので扱われます。例えばSecretのリソース定義ファイルをGitで管理したいとなった場合定義ファイルに書かれる機密情報はただBase64されてるだけなのでそのままでは管理できない(やりにくい)という問題があります。
こういった問題にたいして、様々な対象方法はあるかと思いますが、その中の1つであるSealed Secretsを試してみたいと思います。

Sealed Secretsとは

Sealed SecretsのGitHubには以下のように書かれています。

Problem: "I can manage all my K8s config in git, except Secrets."

Solution: Encrypt your Secret into a SealedSecret, which is safe to store - even to a public repository. The SealedSecret can be decrypted only by the controller running in the target cluster and nobody else (not even the original author) is able to obtain the original Secret from the SealedSecret.

わかりやすいProblemですね。前述の通りSecretではBase64しただけの機密情報を扱うことになるので、Gitでの管理がなかなか難しくなってきます。それに対して、Sealed Secretでは機密情報を暗号化し、その複合ができるのはクラスターで動くコントローラーのみとなります。
機密情報は暗号化されるため、パブリックのGitリポジトリにPushすることも可能になります。
Sealed Secretsは以下の2つのパートから成り立ちます。

  • クラスターサイド: controller / operator
  • クライアントサイドユーティリティ: kubeseal

kubeseal公開鍵暗号方式で情報を暗号化し、その複合を行えるのはコントローラーだけとなります。
SealedSecretとSecretは、完全に同じではありませんがDeploymentとPodの関係に似ていると説明されています。

kubesealは公開鍵をk8sAPIサーバーから取得し、情報を暗号化します。kubeseal --cert mycert.pemのようにして、Pemファイルを直接指定してオフラインでの暗号化も可能なようです。この場合は、kubeseal --fetch-cert >mycert.pemでPemファイルを取得できるようです。鍵はコントローラーの起動時にログにも出力されるようです。

動かしてみる

環境

今回のk8sクラスターはminikubeを用いて作成します。

$ uname -srvmpio
Linux 5.4.0-99-generic #112-Ubuntu SMP Thu Feb 3 13:50:55 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux


$ lsb_release -a
LSB Version:    core-11.1.0ubuntu2-noarch:security-11.1.0ubuntu2-noarch
Distributor ID: Ubuntu
Description:    Ubuntu 20.04.3 LTS
Release:    20.04
Codename:   focal

$ docker version
Client: Docker Engine - Community
 Version:           20.10.12
 API version:       1.41
 Go version:        go1.16.12
 Git commit:        e91ed57
 Built:             Mon Dec 13 11:45:33 2021
 OS/Arch:           linux/amd64
 Context:           default
 Experimental:      true

Server: Docker Engine - Community
 Engine:
  Version:          20.10.12
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.16.12
  Git commit:       459d0df
  Built:            Mon Dec 13 11:43:42 2021
  OS/Arch:          linux/amd64
  Experimental:     true
 containerd:
  Version:          1.4.12
  GitCommit:        7b11cfaabd73bb80907dd23182b9347b4245eb5d
 runc:
  Version:          1.0.2
  GitCommit:        v1.0.2-0-g52b36a2
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

$ go version
go version go1.17.6 linux/amd64

$ minikube version
minikube version: v1.25.1
commit: 3e64b11ed75e56e4898ea85f96b2e4af0301f43d

$ helm version
version.BuildInfo{Version:"v3.8.0", GitCommit:"d14138609b01886f544b2025f5000351c9eb092e", GitTreeState:"clean", GoVersion:"go1.17.5"}


$ kubectl version -o yaml
clientVersion:
  buildDate: "2021-12-16T11:41:01Z"
  compiler: gc
  gitCommit: 86ec240af8cbd1b60bcc4c03c20da9b98005b92e
  gitTreeState: clean
  gitVersion: v1.23.1
  goVersion: go1.17.5
  major: "1"
  minor: "23"
  platform: linux/amd64
serverVersion:
  buildDate: "2021-12-16T11:34:54Z"
  compiler: gc
  gitCommit: 86ec240af8cbd1b60bcc4c03c20da9b98005b92e
  gitTreeState: clean
  gitVersion: v1.23.1
  goVersion: go1.17.5
  major: "1"
  minor: "23"
  platform: linux/amd64

今後実行するコマンドは、特に指定がない場合はminikubeのコンテキストを指しています。

インストール

インストールはおおきく以下の2つのことを行う必要があります。

  • Sealed Secretsのクラスターへのデプロイ
  • kubesealedのインストール

先ずは、Sealed Secretsのクラスターへのデプロイを行います。
READMEのInstallationによるとこれを行うには以下の3つの方法が提供されているようです。

  • Kustomize
  • Helm Chart
  • Operator Framework

GKEなどでセットアップする場合はこちらを確認してください。
今回はHelm Chartを使ったデプロイの方法を試してみたいと思います。Sealed SecretsのHelm ChartはGitHubの公式レポジトリでホスティングされています。以下のコマンドを実行し、リポジトリの追加を行います。

$ helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets
"sealed-secrets" has been added to your repositories

ここで少し注意が必要なのは、バージョニングのスキーマで、このHelm Chartはもともとはコミュニティで作成されていたもので、メジャーバージョンが1.x.yで始まっています。しかし、Sealed SecretsのVersionhaまた0なので、以下のような対応関係を持ちます。

  • Sealed SecretsのコントローラーのVersion: 0.X.Y
  • Helm ChatのVersion: 1.X.Y-rZ

と、思いましたが....どうもChartの方は2系がすでに出ているみたいですね...しかもバージョニングスキーマも説明されるものとは少し違うようです。

$ helm search hub sealed-secrets
URL                                                 CHART VERSION   APP VERSION DESCRIPTION                                  
https://artifacthub.io/packages/helm/bitnami-la...  2.1.2           v0.17.3     Helm chart for the sealed-secrets controller.
https://artifacthub.io/packages/helm/wenerme/se...  2.1.2           v0.17.3     Helm chart for the sealed-secrets controller.
https://artifacthub.io/packages/helm/openinfrad...  1.16.1          v0.16.0     Helm chart for the sealed-secrets controller.
https://artifacthub.io/packages/helm/cloudnativ...  1.0.2           0.7.0       A Helm chart for Sealed Secrets              
https://artifacthub.io/packages/helm/redhat-cop...  1.10.2          0.12.1      A Helm chart for Sealed Secrets     

この辺は、もしかしたらドキュメントが少し古くなってるのかも知れません。ひとまずSealedSecretsのバージョンとHelm Chartのバージョンは完璧には対応づかないようなので注意が必要です。
今回は最新のChartを使ってデプロイを行いたいと思います。デプロイはこちらを参考に以下のコマンドを実行します。

$ helm install --namespace kube-system my-release sealed-secrets/sealed-secrets
NAME: my-release
LAST DEPLOYED: Sat Feb 12 10:26:21 2022
NAMESPACE: kube-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
(省略)

これでデプロイは完了です。my-releaseのところは任意のものを入れます。もしクリーンアップしたい場合はhelm --namespace kube-system delete my-release のコマンドで実行できます。
kubectlコマンドで諸々がデプロイされていることを確認します。

$ kubectl -n kube-system get all
NAME                                             READY   STATUS    RESTARTS      AGE
pod/coredns-64897985d-qfl8f                      1/1     Running   0             34m
pod/etcd-minikube                                1/1     Running   1             34m
pod/kube-apiserver-minikube                      1/1     Running   1             34m
pod/kube-controller-manager-minikube             1/1     Running   1             34m
pod/kube-proxy-vlq5q                             1/1     Running   0             34m
pod/kube-scheduler-minikube                      1/1     Running   1             34m
pod/my-release-sealed-secrets-559446f98f-52szw   1/1     Running   0             83s
pod/storage-provisioner                          1/1     Running   1 (33m ago)   34m

NAME                                TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                  AGE
service/kube-dns                    ClusterIP   10.96.0.10       <none>        53/UDP,53/TCP,9153/TCP   34m
service/my-release-sealed-secrets   ClusterIP   10.101.162.188   <none>        8080/TCP                 83s

NAME                        DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR            AGE
daemonset.apps/kube-proxy   1         1         1       1            1           kubernetes.io/os=linux   34m

NAME                                        READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/coredns                     1/1     1            1           34m
deployment.apps/my-release-sealed-secrets   1/1     1            1           83s

NAME                                                   DESIRED   CURRENT   READY   AGE
replicaset.apps/coredns-64897985d                      1         1         1       34m
replicaset.apps/my-release-sealed-secrets-559446f98f   1         1         1       83s

これでデプロイまでは完了しました。
次にクライアント側のインストールです。
READMEにとると、kubesealをインストールするには以下のようなやり方が用意されているようです。

  • Homebrew
  • MacPorts
  • Installation from source

私の環境はLinuxなので最後のInstallation from sourceの方法を試そうかと思ったのですが、ちょっとうまく行かなかったのでバイナリを直接ダウンロードしてパスを通そうと思います。
現状の最新である0.17.3のリリースページを確認し、以下のコマンドでインストールします。

$ wget https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.17.3/kubeseal-0.17.3-linux-amd64.tar.gz
$ sudo mkdir /opt/kubeseal
$ sudo tar -zxvf kubeseal-0.17.3-linux-amd64.tar.gz -C /opt/kubeseal

あとはええ感じにパスを通して、OKです。

$ kubeseal --version
kubeseal version: 0.17.3

kubesealで作ったSecretsを暗号化する

kubesealでSecretsを暗号化します。
今回はAPIサーバ経由で鍵を取得して、暗号化する方法を試したいと思います。もし、この方法が使えない場合は以下のようなコマンドを実行してPemファイルを取得します。

$ kubeseal --fetch-cert \
--controller-name=my-release-sealed-secrets \
--controller-namespace=kube-system \
> pub-cert.pem

少し脇道にそれましたが、話を戻します。
適当なSecretsを作って、そのJsonファイルを出力します。今回はfoo=barというSecretsを作成します。

$ echo -n bar | kubectl create secret generic mysecret --dry-run=client --from-file=foo=/dev/stdin -o json >mysecret.json

次にそのSecretsのJsonを暗号化します。

$ kubeseal --controller-name=my-release-sealed-secrets --controller-namespace=kube-system <mysecret.json >mysealedsecret.json

ここでのポイントはコントローラーの名前(今回の場合はDeploymentの名前で行けました)とネームスペースをきちんと指定してやることです。

出力されたファイルは暗号化されたデータとなっているので、Gitでも管理できます(Twitterに上げることもできるよ!って書いてあった)。

$ cat mysealedsecret.json 
{
  "kind": "SealedSecret",
  "apiVersion": "bitnami.com/v1alpha1",
  "metadata": {
    "name": "mysecret",
    "namespace": "default",
    "creationTimestamp": null
  },
  "spec": {
    "template": {
      "metadata": {
        "name": "mysecret",
        "namespace": "default",
        "creationTimestamp": null
      },
      "data": null
    },
    "encryptedData": {
      "foo": "AgCHg3oahao+sLw2gPlH+9SaxWlYdG06/M5CZAOZ2hOHXZ9deWFF/bMpt+YRlThK5c1mNIj0rf/NsVxWzWL3N4/LFeNFYmJ/orjSYln3Qu4+2F03kKH30kcz23X8CeTQjpRoIYYsy6S0bLMn+Svs5EB669K/n+nEWNjXb5BmO3438GamQ5jodLlcv5zjZLjEpwrqb31HTs44r3NKhzp7sJZ5DaU5Q28r9IkGQneDDi6Y4dRwF/Kp80uiA9DGXRPcG39l0xfsljdEdwTF9NucazQEZ14eI3VUQD/ofQ2gdpkAUaOKW4nr9pHsQk+KErifBGZQOtvaCxqp2NoFwAG9lXcW0PntzR3m0VY4bqYCslr+Ma5D0kyuYojsOZHWJEmVPnrBu18sSzgdQSHgK5lj85hHgWzMWTB9LCifzyjUMzpHpOkVqvf0RmWAVTIGC5KrT4lbN/w0rCMW6mKfcszYUtUshVZYgaTcTMi+MPNAmSknDUVU2owOIzYTMzyz7iGXaM8zhS3q95h98rJiXiVzJIxLKxxbbmSI6bNuTYalstwXLqx3V5kZqnfLNAfWIS5+Hz2FSjrRlXyZCrCnTKrbij4Q+G97URlKTgekjOKyMfT7XVUpIr6R7dxMwTVDBXveSuvXwqPDsNsyHdF6aSPdUHpkCpAdzCxJLN0F6rzbNbZO4RJmA6kP2PVG90vySur/5sBuMIc="
    }
  }
}

できたSealed Secretsをクラスターにデプロイします。

$ kubectl create -f mysealedsecret.json
sealedsecret.bitnami.com/mysecret created

これで完了です。デプロイされたSealed Secretsはコントローラーによって複合されてSecretsが作成されます。

$ kubectl describe secret mysecret 
Name:         mysecret
Namespace:    default
Labels:       <none>
Annotations:  <none>

Type:  Opaque

Data
====
foo:  3 bytes

後は、普通にSecretsを使う要領で利用できます。