Kubernetes の Secret を Git 管理可能にする Sealed Secrets を使ってみた

Kubernetes でクレデンシャル情報などを管理する際、Secret という仕組みを利用するだろう。

しかし、この Secret は、単に Base64 でエンコードされているだけで、デコードしてしまえば機密情報が簡単に確認できてしまう。Secret を定義するための YAML ファイルを Git 管理などしてしまったら、簡単にパスワード等が漏洩してしまうことになる。

とはいえ、Secret 以外のリソースは Git 管理しているし、Secret だけが YAML ファイルで管理できないのは分かりにくくて困る…。

そんな時に使えるのが、Sealed Secrets というカスタム・リソースだ。Secret を暗号化して Git 管理しても大丈夫なようにしてくれて、使用する際は通常の Secret として使えるようにしてくれる。

Secret は簡単に復元できる

まずは生の Secret を見てみよう。次のようなコマンドで、Secret を作成したとする。

$ kubectl create secret generic my-secret --from-literal='my-password=THIS_IS_SECRET_PASSWORD' --dry-run=client --output=yaml

こんな YAML ファイルができる。

apiVersion: v1
kind: Secret
metadata:
  creationTimestamp: null
  name: my-secret
data:
  my-password: VEhJU19JU19TRUNSRVRfUEFTU1dPUkQ=

my-password 部分の Base64 文字列をデコードすると、元のパスワード文字列が取得できてしまう。

$ kubectl get secret my-secret --output=json | jq -r '.data["my-password"]' | base64 --decode
THIS_IS_SECRET_PASSWORD

つまり上の YAML ファイルを Git 管理に含めたりしてしまうと、簡単にパスワードが割れてしまうワケである。

クラスタに Sealed Secrets をインストールする

それでは Sealed Secrets を使ってみよう。

まずは Kubernetes クラスタに対し、Sealed Secrets の Controller と Operator をインストールする。インストールには Helm を使うのが楽だと思われ。

$ helm repo add stable https://kubernetes-charts.storage.googleapis.com/
$ helm repo update
$ helm install sealed-secrets --namespace kube-system --name sealed-secrets stable/sealed-secrets

こんな感じで良いかな。

クライアントに kubeseal をインストールする

続いてクライアントサイド、kubectl を実行したりするマシンに、kubeseal というコマンドをインストールする。Mac の場合は Homebrew で入れられる。

$ brew install kubeseal

コレで準備は完了。

秘密鍵をバックアップしておく

先程クラスタにインストールした Sealed Secrets は、クラスタ内に公開鍵 (証明書) と秘密鍵を作っている。

公開鍵は、Secret を基に Sealed Secrets を作成する際に暗号化のために使用する。このあとダウンロードする。

秘密鍵は、登録された Sealed Secrets を復号して、クラスタ内に Secret として自動展開してもらう時に、Sealed Secret 自身が使っている。秘密鍵を我々が普段使用することはないが、クラスタを作り直した時なんかに同じ秘密鍵を投入してあげないと、作成済の Sealed Secrets を正しく復号できなくなってしまうので、予めバックアップを取っておこう。

# 秘密鍵をバックアップしておく
$ kubectl get secret --namespace=kube-system sealed-secrets-key --output=yaml > ./sealed-secrets-private-key.pem

# それぞれの中身を参照するにはこんな感じ
$ kubectl get secret --namespace=kube-system sealed-secrets-key --output=json | jq -r '.["data"]["tls.key"]' | base64 --decode
$ kubectl get secret --namespace=kube-system sealed-secrets-key --output=json | jq -r '.["data"]["tls.crt"]' | base64 --decode

公開鍵をダウンロードする

続いて公開鍵をダウンロードする。コチラはこのあとの暗号化作業 (Sealed Secrets の作成作業) で必要になるのでダウンロード必須。

# 公開鍵証明書を取得する
$ kubeseal --fetch-cert --controller-namespace=kube-system --controller-name=sealed-secrets > ./sealed-secrets-public-cert.pem

Sealed Secrets を登録する

それでは Sealed Secrets を登録してみよう。

仮の Secret を作る

まずは Sealed Secrets を生成するために、Dry-Run で Secret を作っておく。

# Secret の YAML ファイルを Dry-Run で生成する
$ kubectl create secret generic my-credential --namespace=my-namespace --from-literal='my-password=PASSWORD_STRING' --dry-run=client --output=yaml > ./secret-my-credential.yaml

コレで次のような YAML ファイルが生成される。ココまでは Kubernetes クラスタにこの Secret を登録していないだけで、素の Secret そのまま。

apiVersion: v1
data:
  my-password: 【「PASSWORD_STRING」のBase64 文字列】
kind: Secret
metadata:
  creationTimestamp: null
  name: my-credential
  namespace: my-namespace

Sealed Secrets を生成する

素の Secret の YAML ファイルができたら、次の要領で Sealed Secrets の YAML ファイルを生成する。

# Sealed Secrets の YAML ファイルを生成する
$ kubeseal --cert=./sealed-secrets-public-cert.pem --format=yaml < ./secret-my-credential.yaml > ./sealed-secrets-my-credential.yaml

ココで、先程取得した公開鍵ファイルを利用し、kubeseal コマンドで暗号化している。生成されたファイルは次のとおり。

apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  creationTimestamp: null
  name: my-credential
  namespace: my-namespace
spec:
  encryptedData:
    my-password: 【暗号化された文字列】
  template:
    metadata:
      creationTimestamp: null
      name: my-credential
      namespace: my-namespace

Git 管理して良いのはコチラの YAML ファイルのみ。Kubernetes クラスタに登録するのもコチラの YAML ファイルのみとなる。

先程 Dry-Run で作成した素の Secret secret-my-credential.yaml ファイルは、当然 Git 管理もしないし、もう捨ててしまって良い。

Sealed Secrets を登録する

それではこの Sealed Secrets を Kubernetes クラスタに登録しよう。

# Sealed Secrets を登録する
$ kubectl apply -f sealed-secrets-my-credential.yaml

登録が成功すると、まず Sealed Secrets リソースとして確認できる。

# Sealed Secrets が登録された
$ kubectl get sealedsecret

そして、Sealed Secrets は登録された時点で自動的に復号され Secret として登録されるので、Secret にも同名のリソースが出来ていることが分かるだろう。

# 同名の Secret が生成されていることを確認する
$ kubectl get secret

もし Secret が生成されていない場合は、Sealed Secrets を正しく復号できなかった可能性がある。使用した公開鍵と秘密鍵のペアを要確認。

Secret として展開された方は、当然 Base64 デコードすれば内容が確認できる。

$ kubectl get secret my-credential --output=json | jq -r '.["data"]["my-password"]' | base64 --decode

Sealed Secrets を参照するには

登録した Sealed Secrets を Deployment などで使用する場合は、「Sealed Secrets を参照しよう」と考えるのではなく、「Sealed Secrets が自動展開された Secret」を参照すれば良い。つまり素の Secret があるテイで、secretKeyRef を使えば良い。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  namespace: my-namespace
spec:
  selector:
    matchLabels:
      app: my-app
  replicas: 1
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
        - name: my-app
          image: my-image:latest
          env:
            # ↓ Secret を参照する
            - name : MY_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: my-credential
                  key : my-password

こんな感じ!