はじめに
オンプレミスの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は、以下のプロセスで認証を行います。
- トラストアンカーの登録: AWS側に認証局(CA)のルート証明書を「トラストアンカー」として登録し、信頼関係を確立します。
- 署名付きリクエストの作成: クライアント(オンプレミス側のワークロード)は、自身の秘密鍵を使用して署名付きのリクエストを作成します。
- 認証要求: クライアント証明書と署名を併せて、IAM Roles Anywhereのエンドポイントへ認証を要求します。
- 検証と発行: 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間の認証を仲介する点にあります。
以下は認証フローのステップです。
-
[ESO] AWS SDKの初期化と認証情報のリクエスト
ESOはExternalSecretのspec.secretStoreRefを読み取り、参照先のSecretStoreを特定します。
次に、そのSecretStoreのspec.provider.awsフィールドを認識して、プロバイダーがAWSであると判断し、内部的にAWS SDK for Goを利用してクライアントを作成します。
この時にSecretStoreのspec.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で指定されたエンドポイントに認証情報をリクエストします。 -
[ESO → サイドカー] 擬似メタデータサービスへのリクエスト
AWS_EC2_METADATA_SERVICE_ENDPOINTにサイドカーコンテナがリッスンするhttp://127.0.0.1:9911を指定します。これにより、ESOからのリクエストはAWSのパブリックエンドポイントではなく、同じPod内で稼働するaws_signing_helperサイドカーに送信されます。 -
[サイドカー] 署名付きリクエストの作成と送信
リクエストを受け取ったサイドカーは、Podにボリュームマウントされたクライアント証明書と秘密鍵を使用し、IAM Roles Anywhereのエンドポイントに対する署名付きリクエストを作成・送信します。 -
[AWS → サイドカー] 一時クレデンシャルの取得
IAM Roles Anywhereは、リクエストを検証し、正当であればSTSの一時的なクレデンシャルをサイドカーに返却します。 -
[サイドカー → ESO] IMDSv2形式でのクレデンシャル応答
一時的なクレデンシャルを取得したサイドカーは、その情報をIMDSv2のレスポンス形式に整形して、リクエスト元であるESOのメインコンテナに応答します。 -
[ESO] 一時的なクレデンシャルを利用したAWS APIアクセス
ESOは、サイドカーから受け取った一時的なクレデンシャルを、IMDSから取得したものとして認識します。このクレデンシャルを利用してAWS Secrets Managerへ、APIリクエストを送信し、シークレットの取得を行います。
この一連の流れにより、ESO自身はIAM Roles Anywhereの存在を意識することなく、セキュアにAWSリソースへアクセスすることが可能になります。
動かしてみる
それでは、実際にIAM Roles Anywhereを試すための環境を構築していきます。
今回は、以下のような環境を構築して、AWS Secrets Managerからシークレットを取得するまでを試します。
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リソースへの接続方法に悩んでいる方の一助になれれば幸いです。
ブログ執筆になれておらず、読みづらいところも多々あったと思いますが、最後までお読みいただきありがとうございました!
