もうずっといなかぐらし

かたいなかのブログ

EKS上でもBerglasみたいにクレデンシャルをいい感じにしたい!!!

こちらはAmazon EKS #1 Advent Calendar 2019 4日目の記事です。

こんにちは、かたいなかです。株式会社リブセンスに入社してはや一ヶ月弱が経過しました。

私が所属する転職会議事業部では、ECSからEKSへの移行を進めています。その中で現在議論が白熱しているのがクレデンシャルの管理方法についてです。検討の中で「GCPにはBerglasという便利ツールがあり、Podに渡す環境変数をよしなに指定するとSecret Managerにあるクレデンシャルをいい感じに展開してくれる」という話を同僚に聞きました。

github.com

そこで、今回は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>という形式の環境変数がある場合にcommandargsをいじって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を指定すれば、参照先から保存された値を取ってきてくれます。

dev.classmethod.jp