とことんDevOps | 日本仮想化技術のDevOps技術情報メディア

DevOpsに関連する技術情報を幅広く提供していきます。

日本仮想化技術がお届けする「とことんDevOps」では、DevOpsに関する技術情報や、日々のDevOps業務の中での検証結果、TipsなどDevOpsのお役立ち情報をお届けします。
主なテーマ: DevOps、CI/CD、コンテナ開発、IaCなど

開催予定の勉強会

読者登録と各種SNSのフォローもよろしくお願いいたします。

BitnamiのSealedSecretsをつかってみた

悩ましい問題 Secretの管理

クラウドのサービスを利用するにあたり、アプリケーションの実行などに使うSecret(パスワードやトークン、各種キーなど)の管理は悩みのタネになると思います。

Kubernetesではこれらのような秘密情報は、KubernetesのSecretリソースで管理します。 しかし、Kubernetesの標準のSecretは元の秘密情報をbase64エンコードするだけであり、APIアクセス可能なユーザーであれば、データを取り出してエンコードした秘密情報を復元するのは容易です。

公式のドキュメントにあるように、データストア(etcd)に暗号化されずに保存されており、APIにアクセスできる人は誰でもSecretを取得または変更が可能です。そのため、安全にSecretを暗号化する仕組みが必要と書かれています。

kubernetes.io

今回は、通常のSecretに一手間加えてGitでSecretの管理を安心して行えるツールである、SealedSecretsについて取り上げてみようと思います。

github.com

概要

Sealed Secretsはつぎの2つで構成されています。

  • クラスターサイドのコントローラー/オペレーター
  • クライアントサイドのユーティリティ: kubesealツール

kubesealユーティリティは、非対称暗号を使用して、コントローラーのみが復号化できるシークレットを暗号化します。これは公開鍵を使用し情報を暗号化、対応する秘密鍵を使用して暗号テキストを復号化する仕組みです。

クライアントの導入

macOSの場合はbrew install kubeseal、他のOSの場合はバイナリーをダウンロードしてインストールします。

2022/07/19現在、手元の環境ではv0.18.1がインストールされるのを確認しています。

Kubernetesクラスターの準備

試しに使って見る場合は手元の環境にKubernetesとhelm v3のCLIを準備しましょう。 Rancher Desktopなどを使うと、Rancher Desktopを起動するだけでCLIもクラスターもすぐ実行できるので便利です。

rancherdesktop.io

使い方はRancher Desktopドキュメントをご覧ください。

コントローラー/オペレーターの導入

Sealed Secretsのコントローラーの導入は、Helmチャートを使って次のように行います。 Helm V3が必要です。

指定するバージョンはHelmチャートリポジトリーの情報から、最新版を指定しました。

$ helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets
$ helm repo update

$ helm upgrade --install sealed-secrets sealed-secrets/sealed-secrets \
  --namespace sealed-secrets --create-namespace \
  --version 2.4.0 \
  --wait

次を実行して、Sealed Secrets Podの状況を確認します。

$ kubectl get pod -n sealed-secrets

次を実行して、Sealed Secretsのカスタムリソースが登録されたことを確認します。

$ kubectl get crd

次を実行して、暗号化・復号化を行う鍵(sealing key)がSecretリソースに登録されていることを確認します。この中に公開鍵と秘密鍵のペアが格納されています。

$ kubectl get secret -n sealed-secrets -l sealedsecrets.bitnami.com/sealed-secrets-key

Secretの登録

まず、Secretを作成してみます。「password=special-secret」の部分がSecretですね。

$ kubectl create secret generic test-secret -n default --dry-run=client \
  --from-literal password=special-secret -o yaml > test-secret.yaml

そうすると、次のようなファイルが作成されます。これはKubernetes Secretのものです。データはBase64エンコードされているだけです。

% cat test-secret.yaml 
apiVersion: v1
data:
  password: c3BlY2lhbC1zZWNyZXQ=
kind: Secret
metadata:
  creationTimestamp: null
  name: test-secret
  namespace: default

試しにデコードしてみましょう(macOSの場合)。

$ echo c3BlY2lhbC1zZWNyZXQ= | base64 -D
special-secret

あっという間にバレてしまいました。つまり標準のSecretで生成したYAMLファイルは、そのままのファイルをGitで管理するのはまずいということがわかりました。

ではこれをSealed Secretsで暗号化してみます。

$ cat test-secret.yaml | kubeseal --controller-name=sealed-secrets \
  --controller-namespace=sealed-secrets \
  --format yaml > sealed-secret.yaml

作成されたファイルを確認すると、bitnami.com/v1alpha1というAPIを利用する「パスワード」が暗号化された状態のマニフェストファイルになっていると思います。

この暗号化されたデータを復号化するために必要なキーはクラスターにしかないため、万が一このマニフェストファイルが外部のユーザーの手に渡ったとしても復号化はできません。そのため、GitOps, CI/CDなどで利用するためにこの暗号化されたシークレットを記述したマニフェストをGitHubなどにおいたとしても安全であると言えます。

Secretを使ってアプリケーションを実行してみる

登録したSecretを使って、アプリケーションを実行してみます。 といっても、マニフェストファイルを使って暗号化されたSecretを登録した段階で、復号化済みのSecretがクラスターに登録されているため、扱い方はこれまでのSecretと同じように利用する感じです。

apiVersion: v1
kind: Pod
metadata:
  name: sample-pod
spec:
  containers:
    - name: nginx-container
      image: nginx:1.23.0-alpine
      env:
        - name: SECRET_PASSWORD
          valueFrom:
            secretKeyRef:
              name: test-secret
              key: password

SecretがPodの環境変数に登録されていることを確認します。SECRET_PASSWORDの変数にはBase64エンコードした値ではなく、デコード済みの値が設定されていることがわかります。

% kubectl exec -it sample-pod -- env|grep SECRET_PASSWORD
SECRET_PASSWORD=special-secret

他のKubernetesクラスターに持って行った場合どうか

先程成功したシークレットが書き込まれているマニフェストファイルを使って、他のクラスターで実行した場合の挙動を確認します。

次のように実行すると、シークレットは作成できたと表示されます。

% kubectl apply -f test-sealed-secret.yaml
sealedsecret.bitnami.com/test-secret created

% kubectl get -f test-sealed-secret.yaml
NAME          AGE
test-secret   90m

しかし、「test-secret」はないと出力されます。

% kubectl get secret test-secret -n default -o yaml
Error from server (NotFound): secrets "test-secret" not found

これだけだとわからないので、kubectl describeしてみます。 全部貼り付けると長いので、必要なところだけ抜き出すと...

% kubectl describe -f test-sealed-secret.yaml
...
  Template:
    Data:  <nil>
    Metadata:
      Creation Timestamp:  <nil>
      Name:                test-secret
      Namespace:           default
Status:
  Conditions:
    Last Transition Time:  2022-07-19T04:40:52Z
    Last Update Time:      2022-07-19T04:40:52Z
    Message:               no key could decrypt secret (password)
    Status:                False
    Type:                  Synced
  Observed Generation:     1
Events:
  Type     Reason           Age                From            Message
  ----     ------           ----               ----            -------
  Warning  ErrUnsealFailed  41s (x6 over 42s)  sealed-secrets  Failed to unseal: no key could decrypt secret (password)

復号化できなかったようです。 暗号化・復号化を行う鍵(sealing key)は元のKubernetesクラスターのSecretにあるため、これと一致していないクラスターで暗号化済みマニフェストを適用しても、元の秘密情報は復元できないということです。

まとめ

SealedSecretを使うことで、安全にシークレット情報もGit管理できます。 これによりすべてのIaCをGitで管理するGitOpsが更にはかどります。

とはいえ使い方を誤ると秘密情報の漏洩につながるため、公式のベストプラクティスをみながら、適切にSealedSecretの運用管理をする必要がありそうです。

なお、この記事は次の情報を参考にしています。こちらも合わせてご覧ください。