Amazon Web Services ブログ

AWS Secrets Controller PoC: AWS Secrets Manager と Kubernetes の統合

 

はじめに

Kubernetes では、API キーや証明書などのシークレットオブジェクトを使用して、podSpec の外部に機密情報を保存して管理できます。概念的には、これにより、他のタイプの Kubernetes オブジェクトとは異なる方法でシークレットを処理できます。それにもかかわらず、多くの顧客は、最初に導入されたときにカスタマーマネージドキーによる強力な暗号化オプションが含まれていなかったため、シークレット素材の保存用に Kubernetes Secrets を使用することを避けました。これが、この PoC を作成する動機となりました。Kubernetes 動的アドミッションコントローラを使用して、外部サービス (AWS Secrets Manager) からシークレットを使用する方法を示します。

EKS の最近の進歩

最近、EKS は Kubernetes Secrets の KMS エンベロープ暗号化のサポートを追加しました。エンベロープ暗号化では、カスタマーマネージドの AWS KMS キーを使用して、Kubernetes がシークレットの暗号化に使用するデータキーを暗号化できます。これにより、Kubernetes の外部に保存されている別のキーに依存するため、全体的なセキュリティ体制を強化できます。AWS が etcd に保持されているデータを保護するために、既に使用しているフルボリューム暗号化に追加されます。Kubernetes Secrets データ暗号化の詳細については、暗号化データのドキュメントをご覧ください。

エンベロープ暗号化により、Kubernetes Secrets はシークレット素材を保存するための実行可能なオプションになりますが、これにはいくつかの欠点があります。まず、Kubernetes ポッドとシークレットのスコープは名前空間です。ポッドとシークレットが名前空間を共有している場合、ポッドはその名前空間で作成されたすべてのシークレットを読み取ることができます。次に、Kubernetes Secrets は自動的にローテーションされません。シークレットを定期的にローテーションする必要がある場合は、手動でローテーションを行う必要があります。

Kubernetes Secrets の代案

従来からお客様は、詳細な権限とシークレットの自動ローテーションの両方をサポートする Hashicorp の Vault などの外部シークレットプロバイダーを使用して、Kubernetes Secrets の欠点に対処してきました。また、Kubernetes サービスアカウントおよび変更ウェブフックを介して Kubernetes と統合します。サービスアカウントはポッドに ID を割り当てます。ポッドは Vault のシークレットへのアクセス権を付与するために使用されますが、ウェブフックはシークレットを Vault から一時的なボリュームにマウントするポッドに init コンテナを挿入するために使用されます。これらを組み合わせると、Kubernetes 内から Vault シークレットを簡単に使用できます。

私たちが開発した概念実証は同様のアプローチを利用しており、バックエンドとして Vault を使用する代わりに、シークレットが AWS Secrets Manager で保存および管理されます。ネイティブの Kubernetes Secrets と比較して、Secrets Manager を使用するといくつかの利点があります。最初に、データベースの認証情報、API キー、その他のシークレットを、それらのライフサイクルを通じて簡単にローテーション、管理、および取得することができます。次に、Amazon RDS、Amazon Redshift、および Amazon DocumentDB などの複数の AWS サービスに組み込みのシークレットローテーションを提供します。また、他のタイプのシークレットをローテーションで使用できるという点でも拡張可能です。最後に、詳細権限を使用してシークレットへのアクセスをコントロールし、AWS クラウドのリソース、およびオンプレミスで実行されるサードパーティーサービスとリソースのシークレットローテーションを集中監査する機能を提供します。

ソリューションの概要

この概念実証 (PoC) は、次の Kubernetes 構成を利用します。

  • アノテーションは、識別できないキーと値のペアの配列です。この例では、アノテーションを使用して init コンテナインジェクターを有効または無効にし、シークレットの AWS ARN を指定します。
  • 下方修正 API は、ポッドに関するメタデータを取得するためのメカニズムです。このソリューションでは、ポッドのアノテーションフィールドで一連のキーと値のペアの値を取得するために使用しています。
  • ポッドが作成されると、変更ウェブフックが呼び出されます。これは、クラスター内で実行されるポッドとして実装されます。注釈フィールドに secret.k8s.aws/sidecarInjectorWebhook: 有効が表示されている場合、ウェブフックは init コンテナをポッドに挿入します。
  • サービスアカウントの IAM ロール (IRSA) は、Kubernetes ポッドに IAM ロールを割り当てる方法です。この PoC は IRSA を使用してポッドにアクセスを許可し、Secrets Manager からシークレットを取得し、KMS キーを使用してそのシークレットを復号化します。ServiceAccount を通じて、Secrets Manager でシークレットへのアクセスを許可できます。
  • init コンテナは、アプリケーションコンテナが起動する前に実行して終了するコンテナです。私たちの PoC では、init コンテナを使用して Secrets Manager からシークレットを取得し、アプリケーションコンテナによって後でマウントされる emptyDir (RAM ディスク) ボリュームに書き込みます。

必要なアノテーションが付いたポッドがクラスターにデプロイされると、ウェブフックはポッドを更新して init コンテナを実行します。podSpec で指定された ServiceAccount が secrets.k8s.aws/secret-arn: <secret arn> で参照されるシークレットにアクセスできる場合、init コンテナは Secrets Manager からシークレットを取得し、RAM ディスクに書き込みます。emptyDir ボリュームのミディアムとしてメモリを使用するように指定すると、行われます。これにより、ポッドの終了後にシークレットがディスクに保持されなくなります。init コンテナが終了すると、アプリケーションコンテナが起動し、RAM ディスクをボリュームとしてマウントします。アプリケーションがシークレットを読み取る必要がある場合は、マウントされたボリュームから読み取ります。次の図は、PoC が辿るウェブフックプロセスフローを示しています。

 

AWS Secrets Admission Controller ウェブフックのデプロイ

AWS Secrets Admission Controller は、Helm チャートを介してデプロイできます。Helm チャートは、次の Kubernetes オブジェクトを作成します。

  • アドミッションコントローラを実行する Kubernetes デプロイメント。
  • 上記のデプロイメントを公開する Kubernetes サービス。
  • アドミッションコントローラの TLS 証明書を含む Kubernetes Secrets。
  • MutatingWebhookConfiguration オブジェクト

Helm のインストール方法について説明が必要な場合は、こちらの Helm の公式ドキュメントをご参照ください。

1.secret-inject アドミッションコントローラウェブフック の Helm チャートを含む Helm リポジトリを追加します。

$ helm repo add secret-inject https://aws-samples.github.io/aws-secret-sidecar-injector/

2.チャートリポジトリは、更新や新規追加により頻繁に変更されます。これらすべての変更で Helm のローカルリストを更新し続けるには、リポジトリ更新コマンドを時々実行する必要があります。

$ helm repo update

3.Helm チャートをインストールして、AWS Secret Controller をインストールします。

$ helm install secret-inject secret-inject/secret-inject

4.関連する Kubernetes オブジェクトが作成されたことを確認します。

$ kubectl get mutatingwebhookconfiguration
NAME CREATED AT
aws-secret-inject 2020-05-10T04:29:20Z

シークレットの作成

ネイティブ AWS API を使用して Secrets Manager でシークレットを作成および管理できますが、Kubernetes から直接 AWS Secrets Manager シークレットを管理することもできます。Native Secrets (NASE) プロジェクトは、サーバーレスの変更ウェブフックです。これは、変更ウェブフックオブジェクトの一部として Kubernetes に登録されている HTTP API エンドポイントを持つ Lambda 関数として実装されます。ネイティブの Kubernetes Secrets を作成および更新する呼び出しはウェブフックに「リダイレクト」され、シークレットマニフェストから Secrets Manager にシークレットを書き込み、シークレットの ARN を Kubernetes に返して、シークレットとして保存します。

使用例のチュートリアル: AWS Secrets Manager からデータベース認証情報へのアクセス

この PoC では、データベースサーバーにアクセスするためにパスワードが必要なモックウェブサーバーをデプロイします。パスワードは、シークレットとして AWS Secrets Manager に保存されます。実際のシークレットは、変更ウェブフックによってポッドに挿入される init コンテナによって取得されます。

AWS Secrets Manager ですでにシークレットを作成しています。AWS Secrets Manager でシークレットを作成する方法について説明が必要な場合は、AWS のドキュメントをご参照ください。

$ aws secretsmanager list-secrets 
SecretList:- ARN: arn:aws:secretsmanager:us-east-1:123456789012:secret:database-password-hlRvvF
  Description: Password for the MySQL database
  LastChangedDate: '2020-05-18T00:49:46.912000+00:00'
  Name: database-password
  SecretVersionsToStages:
    bc50ebbf-2811-4561-8b6b-7bc1c564267a:
    - AWSCURRENT
  Tags: []

シークレットの ARN は、後続のステップで使用されるのでご注意ください。

AWS Secrets Manager でシークレットにアクセスするための AWS ロールを作成する

次に、AWS Secrets Manager に保存されているシークレットにアクセスするためにウェブサーバーで使用される IAM ロールを作成します。AWS Secrets Manager からシークレットを読み取るための IAM ポリシーを作成することから始めます。

aws iam create-policy --policy-name webserver-secrets-policy --policy-document file://policy.json

以下は、AWS Secrets Manager からデータベースシークレットを読み取るためのアクセス許可を付与する policy.json ファイルのサンプルです。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "webserversecret",
            "Effect": "Allow",
            "Action": [
                "secretsmanager:GetResourcePolicy",
                "secretsmanager:GetSecretValue",
                "secretsmanager:DescribeSecret",
                "secretsmanager:ListSecretVersionIds"
            ],
            "Resource": "arn:aws:secretsmanager:us-east-1:123456789012:secret:database-password-hlRvvF"
        },
        {
            "Sid": "secretslists",
            "Effect": "Allow",
            "Action": [
                "secretsmanager:GetRandomPassword",
                "secretsmanager:ListSecrets"
            ],
            "Resource": "*"
        }
    ]
}

本番環境では、これを特定のシークレットにスコープする必要があります。

注: 前のステップで作成したシークレットのロール ARN を使用します。

ポリシーを作成したので、ポッドが引き受ける IAM ロールを作成する必要があります。この PoC では、サービスアカウントの IAM ロール (IRSA) を使用して、AWS Secrets Manager からシークレットを読み取るための詳細アクセスを有効にします。IRSA では、IAM の OIDC ID プロバイダーを作成する必要があります。OIDC ID プロバイダーの設定手順については、こちらをご覧ください。

IRSA で使用する IAM ロールを作成するには、次の手順を実行する必要があります。

1.次のコマンドを使用して、AWS アカウント ID を環境変数に設定する

AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text)

2.OIDC ID プロバイダーを環境変数に設定する

OIDC_PROVIDER=$(aws eks describe-cluster --name <cluster-name> --query "cluster.identity.oidc.issuer" --output text | sed -e "s/^https:\/\///")

3.OIDC フェデレーションユーザーがロールを引き受けることができるように、ロールの信頼ポリシーを作成します。次のコードブロックを実行し、namespace および service-account-name プレースホルダーを環境の値に置き換えます。

read -r -d '' TRUST_RELATIONSHIP < trust.json

これにより、trust.json という名前のファイルが作成されます。

4.IAM ロールを作成します。

$ aws iam create-role --role-name webserver-secrets-role --assume-role-policy-document file://trust.json --description "IAM Role to access webserver secret"

5.最初のステップで作成した webserver-secrets-policy をこのロールに添付します。

$ aws iam attach-role-policy --role-name webserver-secrets-role --policy-arn=arn:aws:iam::123456789012:policy/webserver-secret-policy

Kubernetes サービスアカウントの作成

IRSA で使用する IAM ロールを作成したので、サービスアカウントを作成し、それをロールにマップする必要があります。これには、次のタスクを実行する必要があります。

1.Kubernetes サービスアカウントを作成します。

$ kubectl create sa webserver-service-account

2.前に作成した IAM ロールを参照するサービスアカウントのアノテーションを追加します。この事例では、先ほど作成したロールの ARN を使用しています。

$ kubectl edit sa webserver-service-account
apiVersion: v1
kind: ServiceAccount
metadata:
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/webserver-secrets-role
  creationTimestamp: "2020-05-08T00:17:04Z"
  name: webserver-service-account
  namespace: default
  resourceVersion: "13330471"
  selfLink: /api/v1/namespaces/default/serviceaccounts/webserver-service-account
  uid: eef8b19d-7bd0-4390-94ab-186a5d677fd0
secrets:
- name: webserver-service-account-token-x5t4q

スピンする

データベースに接続する必要があるウェブサーバーポッドをデプロイしましょう。データベースに接続するための認証情報は、AWS Secrets Manager にシークレットとして保存されます。

ウェブサーバー用の Kubernetes デプロイメントオブジェクトを作成します。

cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    run: webserver
  name: webserver
spec:
  replicas: 1
  selector:
    matchLabels:
      run: webserver
  template:
    metadata:
      annotations:
        secrets.k8s.aws/sidecarInjectorWebhook: enabled
        secrets.k8s.aws/secret-arn: arn:aws:secretsmanager:us-east-1:123456789012:secret:database-password-hlRvvF
      labels:
        run: webserver
    spec:
      serviceAccountName: webserver-service-account
      containers:
      - image: busybox:1.28
        name: webserver
        command: ['sh', '-c', 'echo $(cat /tmp/secret) && sleep 3600']
EOF

上記で作成したアノテーションとサービスアカウントは、ポッド仕様で指定されていることにご注意ください。

変更ウェブフックは emptyDir ボリューム secret-vol を作成し、ポッド内のすべてのコンテナで /tmp/ の場所にマウントします。復号化されたシークレットは /tmp/secret に書き込まれます。

実演目的で、ポッドはそのシークレットを STDOUT にも出力します。これは本番環境では推奨されません

 $  kubectl logs webserver-66f6bb988f-x774k
{"username":"admin","password":"P@$$word1024","engine":"mysql","host":"database-1.cluster.us-east-1.rds.amazonaws.com","port":3306,"dbClusterIdentifier":"database-1"}

注意事項

この PoC は、AWS Secrets Manager からシークレットを使用するための Kubernetes ネイティブによる方法を提供しますが、いくつかの注意事項があります。まず、シークレットの保存と取得にはコストがかかります。次に、Secrets Manager には、シークレットのサイズ (64 KB) と、シークレットを取得できる速度 (例: GetSecretValue の制限は、1 秒あたり 2,000) に制限があります。このソリューションを実装する前に、コストと制限を確認してください。

PoC は、ネイティブの Kubernetes Secrets に比べてより複雑です。たとえば、変更ウェブフックをインストールして登録する必要があります。また、Secrets Manager からのシークレットを使用するポッドに正しくアノテーションを付ける必要があります。ポッドは、アノテーションで参照されているシークレットを取得するために必要な権限を持つサービスアカウントを参照する必要があります。とはいえ、シークレットへの詳細アクセスを提供するメカニズムが必要な場合、またはシークレットをローテーションする必要がある場合は、追加のオーバーヘッドのほうが価値があるかもしれません。

最後に、この Poc の目的は、AWS Secrets Manager と Kubernetes の間で達成できる統合のタイプを示すことです。本番環境での使用は想定されていません。

今後の方向性

AWS Secret Controller PoC では、ポッドの起動時に Init コンテナとして実行することにより、AWS Secrets Manager からシークレットにアクセスできます。この PoC の今後の機能拡張には、ポッドでサイドカーコンテナを実行して、シークレットが AWS Secrets Manager によってローテーションされるたびにシークレットを更新し続ける機能が含まれます。

シークレットストアのコンテナストレージインターフェイス (CSI) ドライバーを使用すれば、エンタープライズレベルの外部シークレットストアからのシークレット、パスワード、および証明書をボリュームとしてマウントできます。このプロジェクトはプラグイン可能なプロバイダーインターフェイスを備えており、これを利用して AWS Secrets Manager を Kubernetes Secrets の外部シークレットストアとして統合できます。

まとめ

Kubernetes アプリケーションのシークレットを保護する方法について楽しく学べたでしょうか。このソリューションのソースコードはこちらにあります。ご質問、コメント、ご意見については、GitHub の問題からお気軽にご連絡ください。PR は大歓迎です。