はじめに
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. 大手パブリッククラウドの対応
- GKE
- デフォルトでEncryption at restに対応している。
- EKS
- 公式ドキュメントの明確な記述は見つからないが、containers-roadmapのIssueや、顧客のカスタムKMSキーに対応している点から見ても同様に対応は行われているだろう。
- AKS
- 筆者はAKSは使っていないが、公式ドキュメントの該当ページに暗号化の記述があるため、こちらも対応されている。
よって、これらのパブリッククラウドを使っている範囲であれば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だ。
原理は以下の通り。
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製のコントローラーが動作する。
原理は以下の通り。
クラスター管理者は以下の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
みたいな感じで他のコマンドを引数に持って使うこともできるので便利。
さいごに
秘匿情報の管理はたいへん。