AWS KMSで暗号化したSecretsをそのままapplyしたい!

KubernetesでGitOpsを実践しようと思うときに,Secretsをどうするかという問題は結構大きい.GitOpsをしていなかったとしても,Kubernetesyamlファイルをgitにコミットしようと思うとき,Secretsをどうするかというのは結構大きな問題ではないだろうか. PrivateRepositoryであれば,そこまで危なくはない.けれどパスワードとかクレデンシャルとかを,base64エンコードしただけの平文でコミットしたくはない.

となると何かしらの方法で暗号化してコミットしたくなる. どうせ普段AWSを触ることが多いのだし,AWS KMSでSecretsを暗号化したら良くない?それ自体はいろいろOSSがあるのだし.

そして, KMSで暗号化したSecretsをそのままapplyできたら良くない?

と思ったので作りました.

github.com

使い方

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にかなり似ている. おそらくこの手のもので一番有名なのではないだろうか.

github.com

こいつは,CLIと,カスタムコントローラの両方を提供している.CLIでSecretsを暗号化し,そいつをそのままapplyすると,カスタムコントローラが復号化してSecretsとして適用しといてくれる.

すごく良いじゃん!と思ったんだけど,SealedSecretsは暗号化/復号化の鍵をSealedSecretsのコントローラ内部に持っている. そして,どうやらこの鍵,バックアップを取っておく必要がある.

qiita.com

そう考えると,やりたいことは非常に近いのだが,鍵の管理が俺の望むものではない.

kubesec

github.com

こいつはCLIで,Secretsの暗号化/復号化ができる.もちろん鍵にはAWS KMSを利用できる.

のだが,暗号化/復号化が楽にできるCLIでしかないので,applyする際には一度decryptしてapplyしてやる必要がある. まぁそうだよね.普通そうなる.

Kubernetes External Secrets

github.com

こいつは今までのものとはちょっと違って,かなり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

f:id:h3poteto:20200331205505p:plain

yaml_vault自体はAWS KMS以外にGCP KMSにも対応しているので,そのうちAWS KMSだけじゃなくてGoogle Cloud KMSとかも入れるかもしれない.

そのあたりは強い要望とかあればIssueにしてもらえると,考えます.