KubernetesでGitOpsを実践しようと思うときに,Secretsをどうするかという問題は結構大きい.GitOpsをしていなかったとしても,Kubernetesのyamlファイルをgitにコミットしようと思うとき,Secretsをどうするかというのは結構大きな問題ではないだろうか. PrivateRepositoryであれば,そこまで危なくはない.けれどパスワードとかクレデンシャルとかを,base64エンコードしただけの平文でコミットしたくはない.
となると何かしらの方法で暗号化してコミットしたくなる. どうせ普段AWSを触ることが多いのだし,AWS KMSでSecretsを暗号化したら良くない?それ自体はいろいろOSSがあるのだし.
そして, KMSで暗号化したSecretsをそのままapplyできたら良くない?
と思ったので作りました.
使い方
Secretsの代わりに,KMSSecret
というリソースを新たに定義する.
apiVersion: secret.h3poteto.dev/v1beta1 kind: KMSSecret metadata: name: mysecret namespace: mynamespace spec: encryptedData: # AWS KMSで暗号化されたデータ API_KEY: AQICAHh2iCEGE2e6vdC+w6dQ4hRIyahEPE... PASSWORD: AQICAHh2iCEGE2e6vdC+w6dQ4hRIyahEPE... # これはencryptedDataを復号化する鍵が存在するAWS Region region: us-east-1
このときに, spec.encryptedData
に暗号化したデータを突っ込む.
暗号化したデータを得るためには,便利なOSSもいくつかあるのだが(後述する),とりあえず
$ aws kms encrypt --key-id 1asdf3-rsdf... --plaintext "apikey" --query CiphertextBlob --output text AQICAHh2iCEGE2e6vdC+w6dQ4hRIyahEPE...
とすると得られる.
ふつうSecretsに値を定義する際には,base64エンコードしたものを定義する. しかし,ここではbase64エンコードせず,平分のままKMSで暗号化してもらって問題ない.
このリソースをapplyすると,
apiVersion: v1 data: API_KEY: YXBpa2V5 PASSWORD: cGFzc3dvcmQ= kind: Secret metadata: creationTimestamp: "2020-03-18T07:27:06Z" name: mysecret namespace: mynamespace ownerReferences: - apiVersion: secret.h3poteto.dev/v1beta1 blockOwnerDeletion: true controller: true kind: KMSSecret name: mysecret uid: deac9220-68e9-11ea-8182-0658b210029a resourceVersion: "10673189" selfLink: /api/v1/namespaces/mynamespace/secrets/mysecret uid: dec32aea-68e9-11ea-ae9a-0a561536d7cc type: Opaque
というようなリソースが自動的に生成される.
インストール方法
Helm chartを用意してあるので,helm installするだけ.
$ helm repo add h3poteto-stable https://h3poteto.github.io/charts/stable $ helm install h3poteto-stable/kms-secrets --name kms-secrets
namespaceは,適当にどこのnamespaceでも動く.
AWSへのアクセス権
KMSSecretsの内部では当然AWS KMSにアクセスしているので,適切なIAM Roleが振り当てられていないと,復号化に失敗する.
EKSを使っていれば,おそらくIAMへのアクセスは,IAM Role for Service Accountを使っていることだろう.
であれば,ServiceAccountのannotationsに eks.amazonaws.com/role-arn
を付与すれば良い.
これもhelm install時に指定できる.
$ helm install h3poteto-stable/kms-secrets --name kms-secrets --set rbac.serviceAccount.annotations=your-iam-role-name
kube2iamやkiamを使っている場合は,podAnnotations
を付与できるようにしてあるので,そこにrole-arnを書いてもらえば問題ない.
このとき,your-iam-role-name
には,以下のようなPolicyをつけておく.
{ "Version": "2012-10-17", "Statement": [ { "Sid": "Allow use of the key", "Effect": "Allow", "Action": [ "kms:Decrypt", "kms:DescribeKey" ], "Resource": "*" } ] }
KMSの復号化ができれば問題ない程度の権限.
気になるOSSたち
Sealed Secrets
ここまで読んできて,気づいた人も多いと思うけど,この方式はSealedSecretsにかなり似ている. おそらくこの手のもので一番有名なのではないだろうか.
こいつは,CLIと,カスタムコントローラの両方を提供している.CLIでSecretsを暗号化し,そいつをそのままapplyすると,カスタムコントローラが復号化してSecretsとして適用しといてくれる.
すごく良いじゃん!と思ったんだけど,SealedSecretsは暗号化/復号化の鍵をSealedSecretsのコントローラ内部に持っている. そして,どうやらこの鍵,バックアップを取っておく必要がある.
そう考えると,やりたいことは非常に近いのだが,鍵の管理が俺の望むものではない.
kubesec
こいつはCLIで,Secretsの暗号化/復号化ができる.もちろん鍵にはAWS KMSを利用できる.
のだが,暗号化/復号化が楽にできるCLIでしかないので,applyする際には一度decryptしてapplyしてやる必要がある. まぁそうだよね.普通そうなる.
Kubernetes External Secrets
こいつは今までのものとはちょっと違って,かなりAWSにベッタリなものになる.カスタムコントローラではあるのだが,暗号化したものをコミットするのではなく,そもそも秘匿したい情報はすべてAWS Secrets Managerで管理しておくという思想.
その上で, ExternalSecret
として定義するファイル内で,Secrets Managerのアクセス情報等を記述する.
たしかにこうしてしまえば,そもそもKubernetesの定義上で何かを暗号化する必要性すらなくなる.
まとめ
実は全然事前調査とかせずに,「なんかCustom Resource Definitionが作りたい.作るか.」と思って作り始めたので,もしかしたら本当にこれと全く同じことができるCRDが存在しているかもしれない.
あと,もともとjokerさんが作ってたyaml_vaultがかなり好きで,基本的に定義が全部yamlになるKubernetesとは相性いいと思うんだよね.Rubyが必要だけど. なので,yaml_vaultで暗号化したものをそのままapplyできるというのが,望んでいた世界観ではある.
というわけで,今の所,平文で KMSSecret
リソースを作り,
$ bundle exec yaml_vault encrypt raw-kms-secret.yaml -o kms-secret.yaml --key=$.spec.encryptedData --cryptor=aws-kms --aws-kms-key-id=...
みたいにして暗号化している.
生成された kms-secret.yaml
はそのままapplyできる.
$ kubectl apply -f kms-secret.yaml
yaml_vault
自体はAWS KMS以外にGCP KMSにも対応しているので,そのうちAWS KMSだけじゃなくてGoogle Cloud KMSとかも入れるかもしれない.
そのあたりは強い要望とかあればIssueにしてもらえると,考えます.