0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

IAM Roles Anywhereを使ってオンプレKubernetesでExternal Secrets Operatorを試してみる

Posted at

はじめに

オンプレミスのKubernetesとAmazon EKSによるハイブリッド構成において、シークレット管理の統合は重要な課題の一つです。セキュリティと運用の一貫性を考慮すると、AWS Secrets Managerをシークレット情報のシングルソースとして利用する構成が有力な選択肢となります。

EKS環境であればIRSA (IAM Roles for Service Accounts) などを利用できますが、オンプレミスでは同様のアプローチは取れないため、IAM Roles Anywhereといった他の方法を検討する必要があります。

IAM Roles Anywhereは、以前から提供されているサービスであるため、既に多くの解説記事が存在します。しかし、IAM Roles Anywhereを利用してAWS Secrets Managerからシークレットを取得する、といった具体的なシナリオに触れた記事は少ないように感じました。

そのため、本記事では、オンプレミスのKubernetesからIAM Roles AnywhereとExternal Secrets Operator(ESO)を利用して、AWS Secrets Managerへアクセスするための具体的な方法について記載します。

私自身これまで、オンプレミスからクラウドリソースへのアクセス方法について、あまり考える機会がなかったので、その備忘も兼ねています。

オンプレミスからのAWSへの認証方法

オンプレミスのワークロードからAWSリソースを利用する場合、認証方法はいくつか存在します。

代表的な方法は、「IAMユーザーのアクセスキー」を利用する方法と、「IAM Roles Anywhere」を利用する方法です。

それぞれの特徴は以下の通りです。

比較項目 IAMユーザーのアクセスキー IAM Roles Anywhere
認証情報の種類 長期的なクレデンシャル (Access Key / Secret Key) 一時的なクレデンシャル (STS) + X.509証明書
有効期限 無期限 (定期的なローテーションが必要) 短期間
漏洩時のリスク 無効化するまで永続的に悪用される可能性がある 有効期限切れと共に無効化される。証明書の失効による対応も可能
管理コスト キーのローテーションや保管方法の厳格な管理が必要 証明書発行基盤(PKI)の構築・管理が必要だが、クレデンシャル自体の管理は不要

KubernetesのSecretとして、有効期限のない長期的なクレデンシャルを保持することは、漏洩してしまった際のリスクが大きく、定期的なローテーションを行うための運用負荷も高くなります。

また、AWSの公式ドキュメント「IAMでのセキュリティのベストプラクティス」でも以下のように推奨されていることから、IAM Roles Anywhereを利用するアプローチがセキュリティ観点でより望ましいと考えられます。

ワークロードが AWS にアクセスする場合に IAM ロールで一時的な資格情報を使用することを必須とする

この方法では、長期的なクレデンシャルを環境内に保持する必要がないため、よりセキュアな運用が期待できます。

IAM Roles Anywhereとは

IAM Roles Anywhereは、AWSの外部で稼働するワークロードに対して、X.509証明書を用いて一時的なAWS認証情報を提供する機能です。

これにより、EKSのIRSAと同様にオンプレミスのPodに対しても、IAMロールの権限を付与することが可能になります。

IAM Roles Anywhereについては、既に他の方の記事も存在するため、詳細は割愛します。

こちらのAWSブログでは、IAM Roles Anywhereを使ってオンプレミスのKubernetesから認証する方法と、IAM Roles Anywhereについても詳しく記載されているため、気になる方は参照してみてください。(私の記事のほとんどの内容は、上記のブログをベースにしています)

IAM Roles Anywhereの仕組み

IAM Roles Anywhereは、以下のプロセスで認証を行います。

  1. トラストアンカーの登録: AWS側に認証局(CA)のルート証明書を「トラストアンカー」として登録し、信頼関係を確立します。
  2. 署名付きリクエストの作成: クライアント(オンプレミス側のワークロード)は、自身の秘密鍵を使用して署名付きのリクエストを作成します。
  3. 認証要求: クライアント証明書と署名を併せて、IAM Roles Anywhereのエンドポイントへ認証を要求します。
  4. 検証と発行: IAM Roles Anywhereは、トラストアンカーに基づいて証明書を検証し、正当であればSTS (Security Token Service) を介して一時的なAWSクレデンシャルを返却します。

ESOにおけるIAM Roles Anywhereの利用方法

本記事の執筆時点においては、ESOは IAM Roles Anywhereをネイティブサポートしていないため、SecretStoreリソースのマニフェストに設定を行い、認証することはできません。

IAM Roles Anywhereのサポートは、2022年から要望されていますが、こちらのGitHub Issueに記載されているように、現時点での解決策はサイドカーパターンを利用した方法となります。

サイドカーを利用した方法

この方法では、ESOの機能を拡張するために、AWSと認証を行うサブコンテナを同じPod内に配置します。AWSが提供する IAM Roles Anywhere Credential Helper (aws_signing_helper) をサイドカーコンテナとして利用し、擬似的なメタデータサービスを構築します。

以前はこのaws_signing_helperのコンテナイメージは公式に配布されておらず、利用者がソースからビルドする必要がありましたが、2023年8月頃からAWS公式のAmazon ECR Public Galleryにて公式イメージが公開されているため、導入・運用のコストが軽減されています。

正直、このヘルパーコンテナのためだけに、レジストリを用意してイメージを管理するのは、少しハードルが高い気がしました。やりたことがシンプルなだけに、このイメージ管理の手間という僅かなコストが目立つと思います。
勿論、PKIの導入の方がコスト面も含めてハードルが高いとは思いますが…
なので、公式で提供してくれるのは本当にありがたいです。ありがとうAWS。

サイドカーを利用した認証フローの詳細

この構成のポイントは、aws_signing_helperサイドカーがEC2のインスタンスメタデータサービス(IMDS)の挙動を模倣し、ESOとAWS間の認証を仲介する点にあります。

以下は認証フローのステップです。

  1. [ESO] AWS SDKの初期化と認証情報のリクエスト
    ESOはExternalSecretspec.secretStoreRefを読み取り、参照先のSecretStoreを特定します。
    次に、そのSecretStorespec.provider.awsフィールドを認識して、プロバイダーがAWSであると判断し、内部的にAWS SDK for Goを利用してクライアントを作成します。
    この時にSecretStorespec.provider.aws.authセクションに認証情報が明示的に指定されていないため、AWS SDKは標準の認証情報プロバイダーチェーンに従い、以下の順序で認証情報を取得します。
    1.環境変数(e.g. AWS_SECRET_ACCESS_KEY)
    2.共有設定ファイル(e.g. .aws/credentials)
    3.ECS タスク定義または RunTask API のタスクロール
    4.EC2のIAMロール
    ※この探索順序はAWS SDKのバージョンや設定によって変わる可能性があります。
    今回のサイドカー構成では、上記の方法4「EC2のIAMロール」を利用して、環境変数 AWS_EC2_METADATA_SERVICE_ENDPOINT で指定されたエンドポイントに認証情報をリクエストします。
  2. [ESO → サイドカー] 擬似メタデータサービスへのリクエスト
    AWS_EC2_METADATA_SERVICE_ENDPOINT にサイドカーコンテナがリッスンする http://127.0.0.1:9911 を指定します。これにより、ESOからのリクエストはAWSのパブリックエンドポイントではなく、同じPod内で稼働するaws_signing_helperサイドカーに送信されます。
  3. [サイドカー] 署名付きリクエストの作成と送信
    リクエストを受け取ったサイドカーは、Podにボリュームマウントされたクライアント証明書と秘密鍵を使用し、IAM Roles Anywhereのエンドポイントに対する署名付きリクエストを作成・送信します。
  4. [AWS → サイドカー] 一時クレデンシャルの取得
    IAM Roles Anywhereは、リクエストを検証し、正当であればSTSの一時的なクレデンシャルをサイドカーに返却します。
  5. [サイドカー → ESO] IMDSv2形式でのクレデンシャル応答
    一時的なクレデンシャルを取得したサイドカーは、その情報をIMDSv2のレスポンス形式に整形して、リクエスト元であるESOのメインコンテナに応答します。
  6. [ESO] 一時的なクレデンシャルを利用したAWS APIアクセス
    ESOは、サイドカーから受け取った一時的なクレデンシャルを、IMDSから取得したものとして認識します。このクレデンシャルを利用してAWS Secrets Managerへ、APIリクエストを送信し、シークレットの取得を行います。

この一連の流れにより、ESO自身はIAM Roles Anywhereの存在を意識することなく、セキュアにAWSリソースへアクセスすることが可能になります。

動かしてみる

それでは、実際にIAM Roles Anywhereを試すための環境を構築していきます。

今回は、以下のような環境を構築して、AWS Secrets Managerからシークレットを取得するまでを試します。

image.png

Step 1: PKI基盤の構築 (cert-manager)

まず、cert-managerを用いて、証明書を発行するためのプライベートCAを構築します。

cert-managerのインストール方法は、公式ドキュメントを参照してください。

以下のマニフェストをデプロイして、PKI基盤を構築します。

# ルートCAを発行するための自己署名Issuer
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: selfsigned-issuer
spec:
  selfSigned: {}
---
# 1. ルートCA証明書を作成
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: my-root-ca
  namespace: cert-manager
spec:
  isCA: true
  commonName: "my-verification-root-ca"
  secretName: my-root-ca-secret
  privateKey:
    algorithm: ECDSA
    size: 256
  issuerRef:
    name: selfsigned-issuer
    kind: ClusterIssuer
    group: cert-manager.io
---
# 2. 作成したルートCAを使ってクライアント証明書を発行するためのIssuer
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: my-ca-issuer
spec:
  ca:
    secretName: my-root-ca-secret

今回はお試し目的なので、Self Signed Issuerを利用していますが、本番環境では、AWS Private CAなどの利用を検討してください。

この構成では、クライアント証明書を発行する認証局(CA)の秘密鍵(今回の例では my-root-ca-secret)が侵害された場合、不正なクライアント証明書が発行されるリスクがあります。そのため、ルートCAの秘密鍵は厳格に管理する必要があります。

Step 2: AWSリソースの構築 (Terraform)

次に、IAM Roles Anywhere、IAMロール、および検証用のAWS Secrets ManagerシークレットをTerraformで構築します。

Step 1で作成したルート証明書(my-root-ca-secret内のca.crt)を以下のコマンドを実行して、cert.pemとして保存し、Terraform実行ディレクトリに配置します。

kubectl get secret my-root-ca-secret -n cert-manager -o jsonpath='{.data.tls\.crt}' | base64 -d > cert.pem

証明書の準備ができたら、以下のterraformコードを実行して、IAM Roles Anywhereやサンプルシークレットを含むSecrets Managerなどのリソースを構築します。

provider "aws" {
  region = "ap-northeast-1"
}

locals {
  ca_pem = file("${path.module}/cert.pem")
}

# 1. IAM Roles Anywhere Trust Anchor (CA登録)
resource "aws_rolesanywhere_trust_anchor" "this" {
  name = "eso-verification-anchor"
  source {
    source_data {
      x509_certificate_data = local.ca_pem
    }
    source_type = "CERTIFICATE_BUNDLE"
  }
  enabled = true
}

# 2. IAM Role (ESOが引き受ける役割)
resource "aws_iam_role" "eso_role" {
  name = "eso-verification-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Principal = {
          Service = "rolesanywhere.amazonaws.com"
        }
        Action = [
          "sts:AssumeRole",
          "sts:TagSession",
          "sts:SetSourceIdentity"
        ]
        Condition = {
          ArnEquals = {
            "aws:SourceArn" = aws_rolesanywhere_trust_anchor.this.arn
          }
        }
      }
    ]
  })
}

# 3. IAM Roles Anywhere Profile
resource "aws_rolesanywhere_profile" "this" {
  name      = "eso-verification-profile"
  role_arns = [aws_iam_role.eso_role.arn]
  enabled   = true
}

# 4. 検証用シークレット (AWS Secrets Manager)
resource "aws_secretsmanager_secret" "target_secret" {
  name                    = "eso-verification-secret-v1"
  recovery_window_in_days = 0 # 検証用なので即時削除
}

resource "aws_secretsmanager_secret_version" "target_secret_val" {
  secret_id     = aws_secretsmanager_secret.target_secret.id
  secret_string = jsonencode({ "username" : "admin", "password" : "verification-success!" })
}

# 5. IAM Policy (Secret読み取り権限)
resource "aws_iam_role_policy" "eso_secret_access" {
  role = aws_iam_role.eso_role.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect   = "Allow"
        Action   = "secretsmanager:GetSecretValue"
        Resource = aws_secretsmanager_secret.target_secret.arn
      }
    ]
  })
}

# 出力 (後述のKubernetesマニフェストで使用)
output "roles_anywhere_profile_arn" {
  value = aws_rolesanywhere_profile.this.arn
}

output "roles_anywhere_role_arn" {
  value = aws_iam_role.eso_role.arn
}

output "roles_anywhere_trust_anchor_arn" {
  value = aws_rolesanywhere_trust_anchor.this.arn
}

Step 3: ESOのデプロイとシークレット同期

最後に、サイドカーを組み込んだESOをデプロイし、シークレットの同期を設定します。

事前準備

ESOをデプロイする前に以下のリソースを作成します。

configMapの以下の値はStep2で、terraform実行後に出力された値を設定します。

  • IAMRA_PROFILE_ARN
  • IAMRA_ROLE_ARN
  • IAMRA_TRUST_ANCHOR_ARN
# 1. ESOを展開するNamespaceを作成
apiVersion: v1
kind: Namespace
metadata:
  name: external-secrets
spec: {}
---
# 2. サイドカーが使用するクライアント証明書を発行
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: eso-client-cert
  namespace: external-secrets # ESOをデプロイしたNamespace
spec:
  secretName: eso-client-cert-secret
  duration: 24h
  renewBefore: 1h
  issuerRef:
    name: my-ca-issuer # Step 1で作成したIssuer
    kind: ClusterIssuer
  commonName: "eso-client"
---
# 3. ESOが参照する設定を外だし(valuesに直接設定も可)
apiVersion: v1
kind: ConfigMap
metadata:
  name: external-secrets-iamra
  namespace: external-secrets
data:
  # TerraformのOutputから得られた値を設定
  IAMRA_PROFILE_ARN: "arn:aws:rolesanywhere:ap-northeast-1:<AWS_ACCOUNT_ID>:profile/..."
  IAMRA_ROLE_ARN: "arn:aws:iam::<AWS_ACCOUNT_ID>:role/eso-verification-role"
  IAMRA_TRUST_ANCHOR_ARN: "arn:aws:rolesanywhere:ap-northeast-1:<AWS_ACCOUNT_ID>:trust-anchor/..."

ESO Helm values.yaml の設定

ESOのインストール方法は、公式ドキュメントを参照してください。

今回は、HelmでESOをインストールするため、values.yamlに以下の設定を追加します。

この設定は、ESOにサブコンテナとして、aws_signing_helper コンテナを追加し、ESOが認証情報を取得する際にサイドカーを参照させるためのものです。

extraEnv:
  - name: AWS_EC2_METADATA_SERVICE_ENDPOINT
    value: 'http://127.0.0.1:9911'

extraVolumes:
  - name: credentials
    secret:
      secretName: eso-client-cert-secret

extraContainers:
  - name: iamra
    image: public.ecr.aws/rolesanywhere/credential-helper:latest
    command: ['aws_signing_helper']
    args:
      - 'serve'
      - '--certificate'
      - '/mnt/credentials/tls.crt'
      - '--private-key'
      - '/mnt/credentials/tls.key'
      - '--trust-anchor-arn'
      - '$(TRUST_ANCHOR_ARN)'
      - '--profile-arn'
      - '$(PROFILE_ARN)'
      - '--role-arn'
      - '$(ROLE_ARN)'
    env:
      - name: TRUST_ANCHOR_ARN
        valueFrom:
          configMapKeyRef:
            key: IAMRA_TRUST_ANCHOR_ARN
            name: external-secrets-iamra
      - name: PROFILE_ARN
        valueFrom:
          configMapKeyRef:
            key: IAMRA_PROFILE_ARN
            name: external-secrets-iamra
      - name: ROLE_ARN
        valueFrom:
          configMapKeyRef:
            key: IAMRA_ROLE_ARN
            name: external-secrets-iamra
    volumeMounts:
      - mountPath: /mnt/credentials
        name: credentials
    resources:
      requests:
        memory: 64Mi
        cpu: 10m
      limits:
        memory: 128Mi
    imagePullPolicy: IfNotPresent

シークレットの同期設定

ESOのデプロイ後、以下のマニフェストを適用します。

これらのリソースをデプロイすることで、ESOはシークレットの同期先のプロバイダーや対象のシークレット情報を認識します。

# 4. SecretStoreの作成 (authセクションは不要)
apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
  name: aws-backend
  namespace: external-secrets
spec:
  provider:
    aws:
      service: SecretsManager
      region: ap-northeast-1
---
# 5. ExternalSecretでシークレットの同期を定義
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
  name: my-synced-secret
  namespace: external-secrets
spec:
  refreshInterval: 1m
  secretStoreRef:
    name: aws-backend
    kind: SecretStore
  target:
    name: k8s-secret-target
    creationPolicy: Owner
  data:
  - secretKey: password
    remoteRef:
      key: eso-verification-secret-v1 # Step 2で作成したシークレット
      property: password

デプロイ後、k8s-secret-targetが作成され、データが正しく同期されていることを確認します。

$ kubectl -n external-secrets get secret k8s-secret-target -o jsonpath='{.data.password}' | base64 -d
verification-success!

無事にIAM Roles Anywhereを利用して、AWS Secrets Managerからシークレットを取得できていることがわかります。

おわりに

本記事では、IAM Roles AnywhereとESOを利用して、オンプレミスのKubernetes環境からIAMアクセスキーではなく、証明書ベースの認証を行い、AWS Secrets Managerへアクセスする方法を記載しました。

実際に利用する際は、PKIの構築・運用コストなども考慮する必要がありますが、長期クレデンシャルの管理によるセキュリティリスクは軽減できると思います。

この記事が、オンプレミス環境からAWSリソースへの接続方法に悩んでいる方の一助になれれば幸いです。

ブログ執筆になれておらず、読みづらいところも多々あったと思いますが、最後までお読みいただきありがとうございました!

参考資料

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?