inductor's blog

nothing but self note :)

Kubernetesにおける秘匿情報の扱い方を考える

はじめに

KubernetesではWebアプリケーションから業務用のワークフロー(バッチ処理とか)に至るまで様々なアプリケーションを動かすことができるが、現実世界において苦労するポイントの1つは、ワークロードに秘匿情報を渡すための方法である。

例えば、アプリケーションの上でデータベースに接続するために必要なエンドポイントの情報やパスワードなどの認証情報は、アプリケーションのソースコードに直接書くことはご法度だし、コンテナ化する際に内包することも原則タブーである。また環境変数として注入する場合でも、その情報が物理ディスクに残ってしまう場合などを考え最新の注意を払う必要がある。

ここではKubernetes上のワークロードに秘匿情報をできるだけ安全にわたすための方法を運用者・開発者の目線で考える。

Kubernetesが持つ外部情報注入の仕組み

Kubernetesの場合、アプリケーションに情報を外部注入するためにはConfigMapリソースとSecretリソースがある。以下のアプリケーションを持つPodについて考えてみよう。

簡単のためこんな雑なDockerイメージを用意する。

FROM: golang
ENV DB_USER=hoge #override me
ENV DB_PASSWORD=fuga #override me
COPY . .
EXPOSE 8080
CMD ["go", "run"]
apiVersion: v1
kind: Pod
metadata:
  name: secret-app
spec:
  containers:
    - name: secret-app
      image: hoge/secret:latest
      env:
        - name: DB_USER
          valueFrom:
            secretKeyRef:
              name: db-secrets
              key: username
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: db-secrets
              key: password

このときのSecretはこうなるだろう。

apiVersion: v1
kind: Secret
metadata:
  name: db-secrets
type: Opaque
data:
  username: aGFzaGVkdXNlcm5hbWUK
  password: aGFzaGVkcGFzc3dvcmQK

ConfigMapの場合は認証情報などを直接渡す必要があるうえにマウントするデータが物理ディスクに残ってしまう。Secretについてはボリュームとしてマウントしてもtmpfsによって揮発性が保証されることやbase64によるエンコーディングが行われるためConfigMapに比べて安全性はあるものの、以下のような点について注意が必要である。

1. etcdに格納される秘匿情報の取り扱い

Kubernetesにおけるすべての「状態」はetcdが持っている。これはすなわち、Podが持つ構成情報はもちろん、Secretに保存される情報の中身もetcdに格納されるということである。

Kubernetes公式ドキュメントの「Encrypting Secret Data at Rest 」にもあるように、etcdに保存する情報は外部の暗号化プロバイダーによって安全に保存することができるが、デフォルトでは無効である。 KMS(Key Management Service)などを使った暗号化のほか、AES-CBCやAES-GCMなどの暗号化方式にも対応している。

Ref. 大手パブリッククラウドの対応

よって、これらのパブリッククラウドを使っている範囲であれば1つ目の懸念点は問題なさそうである。

2. gitにSecretリソースをcommitするかどうか

おそらく、この記事を読む多くの方が気にしているのはこの2つ目だと思う。

つまり、簡単にデコードが可能なbase64で記述されたリソースのマニフェストファイルを、果たしてgitリポジトリに登録してしまってよいのかという悩みである。

良いか悪いかで言えば良いとは言えないと個人的には思っているが、以下のような妥協策で対応している組織、チームもあるだろう。

  • プライベートリポジトリなのでcommitを許容している
  • シークレットのためのリポジトリを専用で作っており、アクセスを強めに制限している(そのリポジトリの範囲でのみcommitを許容する)
  • シークレットについては別途管理台帳を作成しており、gitには登録しない代わりにKubernetesのみに反映する方式にしている

Kubernetes Secretを管理するためのOSS

Kubernetesだけの仕組みでSecretの管理をするのは難しいと考える場合、なにか代替策のOSSがないかと探るのがまず1つ目の選択肢である。

Sealed Secrets

おそらく、最も有名でlong runningなこの手のOSSプロジェクトは、bitnamiのSealed Secretsだ。

原理は以下の通り。

f:id:inductor:20200329104312p:plain

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

Sealed Secretsをクラスターにデプロイすると、秘密鍵がクラスターに登録される。あとはそれを使って暗号化したデータをSealedSecretとして登録すればよい。

登録されたSealedSecretリソースをカスタムコントローラーが検知して、それを実際のSecretリソースとして登録する、という仕組みである。

このとき、秘密鍵を取得する場合は

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

とかやってやればよい。

メリットとしてはKubernetesだけで全部が完結できる点だが、デメリットとしては鍵の情報をクラスター全体で共有する必要がある点だろうか。実際に使い倒したことがないのと、細かな制御がどこまでできるかは正直筆者は知らないので間違っていたら教えてほしい。

Kubernetes External Secrets

今のところ筆者が最も使いやすそうだなと思っているのがこのKubernetes External Secretsである。ドメインレジストラとしても有名なGoDaddyのリポジトリにあり、Node.js製のコントローラーが動作する。

原理は以下の通り。

f:id:inductor:20200329105316p:plain

クラスター管理者は以下の3つからプロバイダーを選択することができ、そこに秘匿情報を任意の名前で登録しておく

  • Hashicorp Vault
  • AWS Secrets Manager
  • Google Secret Manager

あとはExternalSecretsリソースを作成して、登録した秘匿情報のkeyを登録しておくだけで、コントローラーが実際の値をSecretリソースに反映してくれる。

このプロダクトの素晴らしいところは、秘匿情報を他のサービスと共有できる点だ。

一般に、Kubernetesはパブリッククラウド、プライベートクラウド含め他のマネージドサービスなどと連携して動かすことも多い。このとき、Kubernetesだけの仕組みで秘匿情報を管理すると、誰が秘匿情報を管理するのか、どのタイミングで反映するかなどを二重で考える必要がある。

秘匿情報管理の仕組みを一元化した上でそれに乗っかることができるため、例えばAWSのAuroraを作成し、Secrets Managerで登録した認証情報を使ってアクセス制御をする場合などのときに、追加で作業をしなくてもそのままそのシークレットが使えるので便利である。

apiVersion: kubernetes-client.io/v1
kind: ExternalSecret
metadata:
  name: db-secrets
spec:
  backendType: secretsManager
  data:
    - key: path/to/key/mysql
      name: username
      property: username
    - key: path/to/key/mysql
      name: password
      property: password

Berglas

GCPをメインに使っているのであればBerglasも1つの選択肢になるだろう。

Kubernetesで利用する場合はリポジトリにあるMutationWebhookを仕込む必要はあるが、導入コストは他の2つに比べても低くて使いやすかった(小並感)

特定のプロダクトに寄っているため、そもそもGCPを使っていない場合は選択肢に入れるのが難しいというデメリットはある。

Berglas自体の仕組みはGCS(GCPのS3と思ってもらえれば良い)にKMSで暗号化した秘匿情報を保存しておき、必要なときに復号して取り出すだけのシンプルな作りになっている。berglas exec -- terraformみたいな感じで他のコマンドを引数に持って使うこともできるので便利。


さいごに

秘匿情報の管理はたいへん。