IAM Roles for Service Accounts (IRSA) は、KubernetesのServiceAccountに対してAWSのIAMロールを関連付ける機能です。これにより、PodからAWSサービスに対する認証をより安全に行うことができます。
EKSでは標準でIRSAが利用できますが、他のKubernetes環境でAPI Serverに追加の設定をすれば利用可能です。この記事では、MicroK8sでIRSAを有効にする手順を説明します。
目次
- OIDC Issuerの準備
- MicroK8sの設定
- Service Account Tokenの確認
- AWS OIDC Identity Providerの作成
- IAMロールの作成
- IAMポリシーの設定
- テスト用Podの作成
- AWSサービスアクセスの動作確認
OIDC Issuerの準備
IRSAを利用するためには、まずOIDC Issuerを準備する必要があります。
必要なパブリックエンドポイントは
- OpenID Connect Discovery (
.well-known/openid-configuration) - JSON Web Key Set (上記のJSONのうち
jwks_uriで指定されるもの)
だけなので、JSONファイルを静的に配置するだけでもIRSAで使用するためのOIDC Issuerとして機能します。
今回はGitHubレポジトリにこの2ファイルを置き、OIDC Issuerとして使用します。
まず、作業ディレクトリを作成します:
mkdir oidc-issuer
cd oidc-issuer
echo private_key.pem > .gitignore
次に、OIDC Issuerで使用する公開鍵・秘密鍵のペアを生成します:
openssl genrsa -out private.pem 2048
openssl rsa -in private.pem -outform PEM -pubout -out public.pem
openssl pkcs8 -topk8 -inform PEM -in private.pem -out private_key.pem -nocrypt
rm -f private.pem
生成した公開鍵をJWKS形式に変換します。このために、専用のツールを使用します (要: Java 17+):
git clone https://github.com/making/pem-to-jwks.git
cd pem-to-jwks && ./mvnw clean package -DskipTests && cd -
java -jar pem-to-jwks/target/pem-to-jwks-0.0.1-SNAPSHOT.jar
echo pem-to-jwks >> .gitignore
生成されたjwks.jsonをGitHubリポジトリに公開します。:
GITHIB_REPO=making/oidc-issuer
git init
git add -A
git commit -m "Initial commit"
git branch -M main
git remote add origin git@github.com:${GITHIB_REPO}.git
git push -u origin main
JWKSファイルがGitHub上で正しく公開されているか確認します:
curl -s https://raw.githubusercontent.com/${GITHIB_REPO}/refs/heads/main/jwks.json
次にOpenID Connect Discoveryの設定ファイルを作成します:
mkdir -p .well-known
cat << EOF > .well-known/openid-configuration
{
"response_types_supported": [
"id_token"
],
"issuer": "https://raw.githubusercontent.com/${GITHIB_REPO}/refs/heads/main",
"id_token_signing_alg_values_supported": [
"RS256"
],
"subject_types_supported": [
"public"
],
"jwks_uri": "https://raw.githubusercontent.com/${GITHIB_REPO}/refs/heads/main/jwks.json"
}
EOF
.well-known/openid-configurationをリポジトリに追加してプッシュします:
git add -A
git commit -m "Add OpenID Connect configuration"
git push origin main
OpenID Connect Discoveryの設定が正しく公開されているか確認します:
curl -s https://raw.githubusercontent.com/${GITHIB_REPO}/refs/heads/main/.well-known/openid-configuration
MicroK8sの設定
MicroK8sでIRSAを使用するために、APIサーバーの設定を変更します。まず、先ほど生成した鍵ファイルをMicroK8sのホスト上に場所にコピーします。ここでは以下のパスに配置したことにします。:
- /opt/k8s/public.pem
- /opt/k8s/private_key.pem
次に、MicroK8sのAPIサーバー設定を変更してこの鍵とIssuerを使ってService Account Tokenが生成されるようにします:
GITHIB_REPO=making/oidc-issuer
cat <<EOF | sudo tee -a /var/snap/microk8s/current/args/kube-apiserver
--api-audiences=https://kubernetes.default.svc
--service-account-issuer=https://raw.githubusercontent.com/${GITHIB_REPO}/refs/heads/main
--service-account-key-file=/opt/k8s/public.pem
--service-account-signing-key-file=/opt/k8s/private_key.pem
EOF
--service-account-****オプションはデフォルトで設定されており、重複して設定すると先勝ちになるので、デフォルトの設定行をコメントアウトします:
#--service-account-key-file=${SNAP_DATA}/certs/serviceaccount.key
...
#--service-account-issuer='https://kubernetes.default.svc'
#--service-account-signing-key-file=${SNAP_DATA}/certs/serviceaccount.key
設定を反映させるためにMicroK8sを再起動します:
sudo snap restart microk8s
既存のPodのService Account Tokenを再生成するために、乱暴ですがすべてのPodを再作成します:
kubectl delete pod -A --all --force
全てのPodがReadyになるまで待ちます。
Service Account Tokenの確認
設定が正しく適用されているかを確認するため、テスト用のPodを起動してService Account Tokenを確認します:
kubectl run -it netshoot --image=nicolaka/netshoot --rm --restart=Never --command=true -- bash
Pod内で、Service Account Tokenの内容をデコードして確認します:
$ cat /run/secrets/kubernetes.io/serviceaccount/token | cut -d'.' -f2 | sed 's/$/===/' | base64 -d | jq .
{
"aud": [
"https://kubernetes.default.svc"
],
"exp": 1781800378,
"iat": 1750264378,
"iss": "https://raw.githubusercontent.com/making/oidc-issuer/refs/heads/main",
"jti": "dc1712b8-db40-4b1a-89cd-8b69e26ad4ca",
"kubernetes.io": {
"namespace": "default",
"node": {
"name": "apricot",
"uid": "e6edbd10-b5c9-461f-83f5-eaf92559ef07"
},
"pod": {
"name": "netshoot",
"uid": "0d1dadab-331a-442d-9359-d4e79ab24c7f"
},
"serviceaccount": {
"name": "default",
"uid": "aa0d94f9-f02c-4560-bcdf-50f10f5a95f0"
},
"warnafter": 1750267985
},
"nbf": 1750264378,
"sub": "system:serviceaccount:default:default"
}
トークンのissフィールドが設定したOIDC Issuer URLになっていることを確認します。これで、IRSAに必要なトークンの設定が正しく行われています。
確認が完了したら、Podを終了します:
exit
EKSの場合は、/var/run/secrets/eks.amazonaws.com/serviceaccount/tokenに別途Web Identity Tokenが格納されますが、本稿の手法では/run/secrets/kubernetes.io/serviceaccount/tokenのService Account TokenをIAMのWeb Identity Tokenとして使用します。
AWS OIDC Identity Providerの作成
次に、AWSでOIDC Identity Providerを作成します。まず、GitHub(raw.githubusercontent.com)のSSL証明書のフィンガープリントを取得します:
FINGERPRINT=$(openssl s_client -servername raw.githubusercontent.com -showcerts -connect raw.githubusercontent.com:443 </dev/null 2>/dev/null | openssl x509 -fingerprint -sha1 -noout | sed 's/sha1 Fingerprint=//' | sed 's/://g')
取得したフィンガープリントを使用してOIDC Identity Providerの設定ファイルを作成し、AWSに登録します:
cat <<EOF > oidc-provider.json
{
"Url": "https://raw.githubusercontent.com/${GITHIB_REPO}/refs/heads/main",
"ClientIDList": [
"https://kubernetes.default.svc"
],
"ThumbprintList": [
"${FINGERPRINT}"
]
}
EOF
aws iam create-open-id-connect-provider --cli-input-json file://oidc-provider.json
IAMロールの作成
作成したOIDC Identity ProviderのARNを取得します:
OIDC_PROVIDER_ARN=$(aws iam list-open-id-connect-providers --query "OpenIDConnectProviderList[?ends_with(Arn, 'raw.githubusercontent.com/${GITHIB_REPO}/refs/heads/main')].Arn" --output text)
特定のServiceAccountに対してIAMロールを作成します。ここでは、defaultネームスペースのaws-cli ServiceAccountを使用します:
NAMESPACE=default
SERVICE_ACCOUNT_NAME=aws-cli
cat << EOF > k8s_${NAMESPACE}_${SERVICE_ACCOUNT_NAME}-trust-policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "${OIDC_PROVIDER_ARN}"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringLike": {
"raw.githubusercontent.com/${GITHIB_REPO}/refs/heads/main:sub": "system:serviceaccount:${NAMESPACE}:${SERVICE_ACCOUNT_NAME}",
"raw.githubusercontent.com/${GITHIB_REPO}/refs/heads/main:aud": "https://kubernetes.default.svc"
}
}
}
]
}
EOF
aws iam create-role --role-name k8s_${NAMESPACE}_${SERVICE_ACCOUNT_NAME} --assume-role-policy-document file://k8s_${NAMESPACE}_${SERVICE_ACCOUNT_NAME}-trust-policy.json
IAMポリシーの設定
作成したIAMロールに対して、S3へのアクセス権限を付与します。この例では、特定のプレフィックスを持つS3バケットへのフルアクセス権限を設定します:
export BUCKET_PREFIX=k8s-${NAMESPACE}-${SERVICE_ACCOUNT_NAME}
cat <<EOF > s3-prefix-full-access-${BUCKET_PREFIX}-policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "FullAccessTo$(echo ${BUCKET_PREFIX} | sed 's/\-//g')Buckets",
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::${BUCKET_PREFIX}-*",
"arn:aws:s3:::${BUCKET_PREFIX}-*/*"
]
},
{
"Sid": "ListBucketsForConsoleAccess",
"Effect": "Allow",
"Action": [
"s3:ListAllMyBuckets",
"s3:GetBucketLocation"
],
"Resource": "*"
}
]
}
EOF
aws iam put-role-policy --role-name k8s_${NAMESPACE}_${SERVICE_ACCOUNT_NAME} --policy-name s3-prefix-full-access-${BUCKET_PREFIX} --policy-document file://s3-prefix-full-access-${BUCKET_PREFIX}-policy.json
テスト用Podの作成
IRSAの動作を確認するため、AWS CLIが実行可能なテスト用のPodを作成します:
export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
export AWS_REGION=ap-northeast-1
cat <<EOF > aws-cli.yaml
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: aws-cli
namespace: default
---
apiVersion: v1
kind: Pod
metadata:
name: aws-cli
namespace: default
spec:
serviceAccountName: aws-cli
containers:
- name: aws-cli
image: public.ecr.aws/aws-cli/aws-cli
command: [ "sleep", "infinity" ]
env:
- name: AWS_REGION
value: ${AWS_REGION}
- name: AWS_ROLE_ARN
value: arn:aws:iam::${AWS_ACCOUNT_ID}:role/k8s_default_aws-cli
- name: AWS_WEB_IDENTITY_TOKEN_FILE
value: /run/secrets/kubernetes.io/serviceaccount/token
- name: AWS_ROLE_SESSION_NAME
value: k8s-demo
restartPolicy: Never
---
EOF
kubectl apply -f aws-cli.yaml
AWSサービスアクセスの動作確認
作成したPodに接続してIAMロールでAWSサービスに正しくアクセスできることを確認します:
kubectl exec -ti aws-cli -c aws-cli -- bash
Pod内でAWSの認証情報を確認します。IRSAが正しく設定されていれば、IAMロールを使用した認証が行われます:
aws sts get-caller-identity
次のような出力が得られれば成功です:
{
"UserId": "*********************:k8s-demo",
"Account": "*****",
"Arn": "arn:aws:sts::*****:assumed-role/k8s_default_aws-cli/k8s-demo"
}
設定したIAMポリシーが正しく動作することを確認します。まず、許可されたプレフィックスでS3バケットを作成してみます:
aws s3 mb s3://k8s-default-aws-cli-demo
make_bucket: k8s-default-aws-cli-demo
次に、許可されていないプレフィックスでS3バケットを作成してみます:
aws s3 mb s3://k9s-default-aws-cli-demo
make_bucket failed: s3://k9s-default-aws-cli-demo An error occurred (AccessDenied) when calling the CreateBucket operation: User: arn:aws:sts::*****:assumed-role/k8s_default_aws-cli/k8s-demo is not authorized to perform: s3:CreateBucket on resource: "arn:aws:s3:::k9s-default-aws-cli-demo" because no identity-based policy allows the s3:CreateBucket action
期待通りアクセスが拒否されました。これで、IAMポリシーによる権限制御が正しく機能していることが確認できました。
exit
本稿では、MicroK8sでIRSAを有効にする手順を説明しました。
試してはいませんが、Kindやその他、API Serverの設定が可能なKubernetes環境でも同様の手順でIRSAを有効にできるはずです。
API Serverの設定が許可されてないK8sを使用している場合は、本稿の手法は使えません。代わりにService Account Tokenを外向けに再発行する裏技を使うことで無理やりIRSAを実現することもできました。
興味のある方は https://github.com/categolj/jwt-reissuer のREADMEを参照してください。