こちらはAmazon EKS #1 Advent Calendar 2019 4日目の記事です。
こんにちは、かたいなかです。株式会社リブセンスに入社してはや一ヶ月弱が経過しました。
私が所属する転職会議事業部では、ECSからEKSへの移行を進めています。その中で現在議論が白熱しているのがクレデンシャルの管理方法についてです。検討の中で「GCPにはBerglasという便利ツールがあり、Podに渡す環境変数をよしなに指定するとSecret Managerにあるクレデンシャルをいい感じに展開してくれる」という話を同僚に聞きました。
そこで、今回はMutationWebhookの実装の素振りを兼ねてEKS上でAWS Secrets ManagerからBerglas同様にクレデンシャルを取得するしくみを作ってみましたのでご紹介します。
TL;DR
Beglas風にAWS Secrets Managerからクレデンシャルを取ってきてくれるツール(Not Production Ready)を試験的に作成。MutationWebhookは便利だが劇薬なので用法用量注意が必要。
Berglasとは
BerglasはGCPが提供しているGCP上に保存されたクレデンシャルを便利に扱うツールです。
BerglasのCLIにはberglas exec --
というコマンドで実行するラッパーの機能があります。このコマンドを実行する際、berglas://<credentialへの参照>
という形式の値を持つ環境変数があると、当該の環境変数を参照先から取得した実際の値に置き換えてラップされたコマンドを実行します。
$ export API_KEY="berglas://<credentialへの参照>" #"API_KEY"の値が参照先のクレデンシャルの値で置き換わった状態で"--"の後ろに指定したコマンドを実行 $ berglas exec -- some command
MutationWebhook
前節で紹介したCLIツールをどのようにKubernetesと連携させればよいかについては、BerglasのGitHubリポジトリにKubernetesのMutationWebhookのサンプル実装が置かれています。(https://github.com/GoogleCloudPlatform/berglas/tree/master/examples/kubernetes)
このサンプル実装では、Podの作成時にberglas://<credentialへの参照>
の環境変数が設定されている場合、initContainer
でBergalsのコンテナからemptyDir
のボリュームにバイナリをコピーする設定を追加します((1)の部分)。そして、commandを書き換え、もともと指定されていたコマンドをberglas exec --
でラップするように設定します((2)の部分)。以下のようなYAMLが作成されます。
apiVersion: v1 kind: Pod metadata: name: envserver spec: # ボリュームにberglasのbinを流し込むinitContainersを追加(1) initContainers: - name: copy-berglas-bin image: gcr.io/berglas/berglas:latest imagePullPolicy: IfNotPresent command: ["sh", "-c", "cp /bin/berglas /berglas/bin/"] # berglasのバイナリ流し込み用ボリューム volumeMounts: - name: berglas-bin mountPath: /berglas/bin/ containers: - name: server image: katainaka0503/server imagePullPolicy: Always # もとのPodで指定していたcommandを「"berglas exec --"でラップして実行するように修正(2) # command: ["/bin/server"] command: ["/berglas/bin/berglas"] args: ["exec", "--", "/bin/server"] env: # berglas://の形式の環境変数がある場合にPodを編集 - name: API_KEY value: berglas://berglas-test-secrets/api-key - name: TLS_KEY value: berglas://berglas-test-secrets/tls-key?destination=tempfile # berglasのバイナリ流し込み用ボリューム volumeMounts: - name: berglas-bin mountPath: /berglas/bin/ volumes: # berglasのバイナリ流し込み用ボリューム - name: berglas-bin emptyDir: medium: Memory
これにより以下のような利便性が提供されます。
- 環境変数でGCP上に保存されたSecretの参照を指定するかたちのため、リポジトリ上でのYAMLの暗号化等は不要
- 実行時に
emptyDir
ボリューム経由でラッパーのバイナリを埋め込むことで、アプリケーションのDockerfileに手を入れることなくKubernetesデプロイ時にラッパーを実行 - Podのenv等にクレデンシャルが展開されたりもしないので機密性が高い
ただし、Podの設定のcommand
, args
で指定しているコマンドをラッパースクリプトの中で実行させる仕組みであるため、command
, args
がPodのYAMLの中で明示的に指定されている必要があります。(Dockerfileで指定したENTRYPOINT, CMDをそのまま使わせることができない)
AWSでもBerglas風にクレデンシャルを管理したい
さて、ここからが本題です。GCPでできることはAWS上でも同じようにやりたくなるのが自然な心理です。そこで、今回はBerglasと同様の仕組みでSecretsManagerから値を取得できるようなWebhookおよびCLIを写経を兼ねて実装してみました。
今回のコードは私が自身の学習用に作成したものです。継続的にメンテナンスするつもりは現在のところないので、そのまま使用することはおすすめしません。
動作
今回作成したものをデプロイした状態で以下のようなYAMLでPodを作成しようとすると、
apiVersion: v1 kind: Pod metadata: name: server annotations: eks.amazonaws.com/role-arn: hogehoge spec: containers: - name: server image: server:any-tag command: ["/bin/server"] # berglas-aws://<Secrets ManagerのARN>の形式の環境変数がある場合にPodを編集 env: - name: API_KEY value: berglas-aws://arn:aws:secretsmanager:<REGION>:<ACCOUNT_ID>:secret:<SECRET_ID>
以下のようなYAMLに修正されてPodが作成されます。
apiVersion: v1 kind: Pod metadata: name: server annotations: eks.amazonaws.com/role-arn: hogehoge spec: # ボリュームにberglas-awsのbinを流し込むinitContainersのを追加 initContainers: - name: copy-berglas-aws-bin image: katainaka0503/berglas-aws:latest imagePullPolicy: IfNotPresent command: ["sh", "-c", "cp $(which berglas-aws) /berglas-aws/bin/"] # berglas-awsのバイナリ流し込み用ボリューム volumeMounts: - name: berglas-aws-bin mountPath: /berglas-aws/bin/ containers: - name: server image: katainaka0503/server imagePullPolicy: Always # もとのPodで指定していたcommandを「"berglas exec --"でラップして実行するように修正(2) # command: ["/bin/server"] command: ["/berglas-aws/bin/berglas-aws"] args: ["exec", "--", "/bin/server"] env: # berglas-aws://<Secrets ManagerのARN>の形式の環境変数がある場合にPodを編集 - name: API_KEY value: berglas-aws://arn:aws:secretsmanager:<REGION>:<ACCOUNT_ID>:secret:<SECRET_ID> # berglas-awsのバイナリ流し込み用ボリューム volumeMounts: - name: berglas-aws-bin mountPath: /berglas-aws/bin/ # berglas-awsのバイナリ流し込み用ボリューム volumes: - name: berglas-aws-bin emptyDir: medium: Memory
これにより、特定の形式で環境変数を指定するとAWS Secrets Managerに保存した値を取ってきて環境変数に設定した状態でコマンドを実行してくれるようになります。
CLI
CLI部分はBerglasの実装を参考に実装しました。
https://github.com/katainaka0503/berglas-aws
本家Berglasと同様に、ラップされたコマンドを実行する際に、berglas-aws://<SecretsManagerのARN>
という形式の環境変数があればSecretManagerから取得した実際のシークレットの値で当該環境変数を置き換えます。
$ export API_KEY="berglas-aws://<SecretsManagerのARN>" #"APK_KEY"の値が参照先のAWS Secrets Managerに保存された値で置き換わった状態で"--"の後ろに指定したコマンドを実行 $ berglas-aws exec -- some command
Webhookの実装
Webhookの部分も同様にBerglasのKubernetesへのデプロイのサンプルを参考にkube-webhook
というフレームワークを使用して作成しました。
https://github.com/katainaka0503/berglas-aws-webhook
こちらも本家と同様の仕組みで、Pod作成時にberglas-aws://<SecretsManagerのARN>
という形式の環境変数がある場合にcommand
とargs
をいじってberglas-aws exec --
でラップして実行されるように修正します。
Kubernetesへのデプロイ
最後にWebhookをKubernetesにデプロイする部分です。
https://github.com/katainaka0503/berglas-aws-webhook/tree/master/deploy
MutationWebhookを設定する際には、Webhookサーバ用の証明書とkube-apiserverがWebhookサーバが提供する証明書を検証するためのルート証明書が必要です。今回はCertManagerを使用して証明書を発行しました。それ以外は概ね通常のDeploymentとServiceを使用したAPIサーバの構成です。
まとめ
Secret管理方法の検討とMutationWebhookの学習を兼ねてBerglas風にAWS Secrets Managerからクレデンシャルを取得できるツールを作成して遊んでみました。想像の数倍容易に実装できたので意外でした。実装したツールを継続的にメンテナンスしていける組織であれば、このようなWebhookの仕組みを用意する方法がシークレットの管理方法として選択肢に入ってくるのかもしれません。
今回のコード
参考資料
補足: ECSでは
ちなみにECSでは、ここまで紹介してきたようなECSのタスク起動時にクレデンシャルの値をとってきて環境変数に設定する機能がすでに提供されています。タスク定義内のコンテナ定義のsecretsという属性にAWS Secrets Manager/AWS Systems ManagerパラメータストアのARNを指定すれば、参照先から保存された値を取ってきてくれます。