crossplaneでIRSAを作る
概要
こんにちは
この記事ではEKS環境にCrossplaneをデプロイし、IRSAをk8sマニフェストから作成できるようにした記録をまとめました
マニフェスト1つデプロイするだけでIRSAが利用出来るのでとても便利です
環境
- EKS: 1.32
- Crossplane: 2.1.1
- provider-family-aws: 2.2.0
- provider-aws-iam: 2.2.0
- function-patch-and-transform: 0.8.0
動機
- AWSリソースもk8s manifestで定義することで認知負荷を減らしたい
- k8sリソースと一緒にAWSリソースもデプロイしたい
インストール
argocd appの場合
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: crossplane
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "1"
finalizers:
- resources-finalizer.argocd.argoproj.io/background
spec:
project: default
source:
repoURL: https://charts.crossplane.io/stable
targetRevision: 2.1.1
chart: crossplane
destination:
server: https://kubernetes.default.svc
namespace: crossplane-system
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
まずはIAMポリシーだけ作ってみる
IAMポリシーを作るまでに以下3リソースが必要です
- provider
- provider-config
- DeploymentRuntimeConfig
またprovider-aws-iamがIAMリソースを操作するためのIRSAの作成も必要です
provider
外部サービス(AWS)のリソースをKubernetesから管理できるように設定するリソースです
AWSリソースの場合は以下を参考にクラスタへ適用します
---
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
name: upbound-provider-family-aws
spec:
package: xpkg.upbound.io/upbound/provider-family-aws:v2.2.0
runtimeConfigRef:
name: aws-irsa
---
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
name: upbound-provider-aws-iam
spec:
package: xpkg.upbound.io/upbound/provider-aws-iam:v2.2.0
runtimeConfigRef:
name: aws-irsa
provider-config
providerが外部サービスと接続するための設定です
AWSであればアクセスキー、IRSA、PodIdentityなどが該当します
こちらを参考にIRSA用の設定を行います
apiVersion: aws.upbound.io/v1beta1
kind: ProviderConfig
metadata:
name: aws-irsa
spec:
credentials:
source: IRSA
DeploymentRuntimeConfig
provider podの設定を追加できるリソースです
ServiceAccountの定義ができるのでIRSA用のannotationを付与します
apiVersion: pkg.crossplane.io/v1beta1
kind: DeploymentRuntimeConfig
metadata:
name: aws-irsa
spec:
serviceAccountTemplate:
metadata:
name: provider-aws-iam
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::{{ .Values.awsAccountId }}:role/provider-aws-iam
provider-aws-iamが利用するIRSAの作成
IRSAを作るためのprovider-aws-iam用のIRSAです、紛らわしいですね
terraform public moduleが提供されているのでそのまま作成します
module "crossplane" {
source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
version = "5.59.0"
role_name = "provider-aws-iam"
role_policy_arns = {
iam_full_access = "arn:aws:iam::aws:policy/IAMFullAccess"
}
oidc_providers = {
one = {
provider_arn = aws_iam_openid_connect_provider.main.arn
namespace_service_accounts = ["crossplane-system:provider-aws-iam"]
}
}
}
IAMポリシー作成テスト
準備が出来たのでテストします
以下を参考にして作るといいでしょう
---
apiVersion: iam.aws.upbound.io/v1beta1
kind: Policy
metadata:
name: test-s3-readonly
namespace: crossplane-system
spec:
forProvider:
description: "Test policy for Crossplane - S3 read-only access"
policy: |
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": "*"
}
]
}
providerConfigRef:
name: aws-irsa
適用すると無事作成されました

IRSA一式を作成する
今度はIRSA一式(IAMポリシー、IAMロール、その2つの紐付けアタッチメント)をIRSAClaimというリソースで一括作成できることを目指します
作るまでに以下3リソースが必要です
- CompositeResourceDefinition (XRD)
- Composition
- Function (function-patch-and-transform)
CompositeResourceDefinition (XRD)
作成したいComposition(カスタムAPI)のスキーマを定義するためのリソースです
今回はIRSAClaim という新しいKubernetes リソースタイプを定義しました
---
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
name: xirsas.platform.example.com
spec:
group: platform.example.com
names:
kind: XIRSA
plural: xirsas
claimNames:
kind: IRSAClaim
plural: irsaclaims
versions:
- name: v1alpha1
served: true
referenceable: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
serviceAccountName:
type: string
description: "Name of the Kubernetes ServiceAccount"
serviceAccountNamespace:
type: string
description: "Namespace of the Kubernetes ServiceAccount"
default: "default"
policyDocument:
type: string
description: "IAM Policy document in JSON format"
policyDescription:
type: string
description: "Description for the IAM Policy"
default: "Policy created by Crossplane IRSA"
managedPolicyArns:
type: array
description: "List of AWS managed policy ARNs to attach to the role"
items:
type: string
default: []
required:
- serviceAccountName
- serviceAccountNamespace
- policyDocument
status:
type: object
properties:
policyArn:
type: string
description: "ARN of the created IAM Policy"
roleArn:
type: string
description: "ARN of the created IAM Role"
ポイント
- ユーザーが指定できるフィールド (spec) を定義
- serviceAccountName: SAの名前を記載, string, 必須
- serviceAccountNamespace: SAのnamespaceを記載, string, 必須
- managedPolicyArns: ロールに付与するIAMマネージドポリシーArnを記載, string, デフォルトで空配列
- 等々
- システムが返す情報 (status) を定義
- IAM policy, Role Arnを返却する
Composition
CompositeResourceDefinition(XRD)で定義したカスタムAPIで、実際にどのようにリソースを作成するかを定義します
今回はIAM Policy, IAM Role, RolePolicyAttachmentを作成するようにし、それぞれのパラメータをXRDから引っ張ってくるように定義しています(後述)
---
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: irsa-aws
labels:
provider: aws
spec:
compositeTypeRef:
apiVersion: platform.example.com/v1alpha1
kind: XIRSA
mode: Pipeline
pipeline:
- step: patch-and-transform
functionRef:
name: function-patch-and-transform
input:
apiVersion: pt.fn.crossplane.io/v1beta1
kind: Resources
resources:
# IAM Policy
- name: iam-policy
base:
apiVersion: iam.aws.upbound.io/v1beta1
kind: Policy
spec:
forProvider:
policy: ""
providerConfigRef:
name: aws-irsa
patches:
- type: FromCompositeFieldPath
fromFieldPath: spec.policyDocument
toFieldPath: spec.forProvider.policy
- type: FromCompositeFieldPath
fromFieldPath: spec.policyDescription
toFieldPath: spec.forProvider.description
- type: FromCompositeFieldPath
fromFieldPath: spec.serviceAccountName
toFieldPath: metadata.name
transforms:
- type: string
string:
type: Format
fmt: "%s-policy"
- type: ToCompositeFieldPath
fromFieldPath: status.atProvider.arn
toFieldPath: status.policyArn
# IAM Role
- name: iam-role
base:
apiVersion: iam.aws.upbound.io/v1beta1
kind: Role
spec:
forProvider:
assumeRolePolicy: ""
managedPolicyArns: []
providerConfigRef:
name: aws-irsa
patches:
- type: FromCompositeFieldPath
fromFieldPath: spec.serviceAccountName
toFieldPath: metadata.name
transforms:
- type: string
string:
type: Format
fmt: "%s-role"
- type: CombineFromComposite
combine:
variables:
- fromFieldPath: spec.serviceAccountNamespace
- fromFieldPath: spec.serviceAccountName
strategy: string
string:
fmt: |
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::{{ .Values.awsAccountId }}:oidc-provider/{{ .Values.oidcProviderUrl }}"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"{{ .Values.oidcProviderUrl }}:aud": "sts.amazonaws.com",
"{{ .Values.oidcProviderUrl }}:sub": "system:serviceaccount:%s:%s"
}
}
}
]
}
toFieldPath: spec.forProvider.assumeRolePolicy
- type: ToCompositeFieldPath
fromFieldPath: status.atProvider.arn
toFieldPath: status.roleArn
- type: FromCompositeFieldPath
fromFieldPath: spec.managedPolicyArns
toFieldPath: spec.forProvider.managedPolicyArns
# RolePolicyAttachment
- name: role-policy-attachment
base:
apiVersion: iam.aws.upbound.io/v1beta1
kind: RolePolicyAttachment
spec:
forProvider:
policyArnSelector:
matchControllerRef: true
roleSelector:
matchControllerRef: true
providerConfigRef:
name: aws-irsa
patches:
- type: FromCompositeFieldPath
fromFieldPath: spec.serviceAccountName
toFieldPath: metadata.name
transforms:
- type: string
string:
type: Format
fmt: "%s-attachment"
ポイント
- compositeTypeRefでXRDを指定
- Pipeline (実行ステップ)でfunction-patch-and-transformを使用(後述)
- resourcesで3つのリソースを作成
- iam-poclicy
- FromCompositeFieldPathでXRDから値を引っ張ってきて(fromFieldPath)、リソースの値に使う(toFieldPath)
- ポリシー名は<serviceaccount名>-policyとしている
- iam-role
- ロール名は<serviceaccount名>-roleとしている
- CombineFromCompositeでserviceaccount名とserviceaccountのnamespaceを引っ張ってきて、信頼ポリシーを作成している
- role-policy-attachment
- attachment名は<serviceaccount名>-attachmentとしている
- matchControllerRefで同じcomposition内のrole,policyを自動検索しているらしい(よくわからん)
- iam-poclicy
Function (function-patch-and-transform)
Compositionのパッチ処理を実行する関数です
このようにFunctionリソースで用意できます
---
apiVersion: pkg.crossplane.io/v1beta1
kind: Function
metadata:
name: function-patch-and-transform
spec:
package: xpkg.upbound.io/crossplane-contrib/function-patch-and-transform:v0.8.0
入力された内容をAWSリソースに反映する役割です
利用可能な処理はこのあたりにまとまっているため、見てみるのがよいでしょう
ここまで適用すると以下のようにpodがたっているはずです
$ k get pod -n crossplane-system
NAME READY STATUS RESTARTS AGE
crossplane-f9fdf8d7b-g2g5q 1/1 Running 0 31m
crossplane-rbac-manager-687bc87998-b5z2q 1/1 Running 0 31m
function-patch-and-transform-7f558fa06b13-fd778b8bf-swcv9 1/1 Running 0 31m
upbound-provider-aws-iam-8ecb8d0d329b-777f865bd4-rxk9f 1/1 Running 0 31m
upbound-provider-family-aws-0e8a4558ea96-78f784b75-4q4ck 1/1 Running 0 31m
テスト用のIRSAをデプロイ
default namespaceにIRSAClaimを用意します
---
apiVersion: platform.example.com/v1alpha1
kind: IRSAClaim
metadata:
name: s3-readonly
namespace: default
spec:
serviceAccountName: test-app
serviceAccountNamespace: default
policyDescription: "S3 read-only access for test-app"
managedPolicyArns:
- "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess"
policyDocument: |
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:ListAllMyBuckets"
],
"Resource": "*"
}
]
}
すると無事作成されました

IAMポリシー、Roleも問題なさそうです
$ kubectl get irsaclaim s3-readonly -n default -o jsonpath='{"Role ARN: "}{.status.roleArn}{"\nPolicy ARN: "}{.status.policyArn}{"\n"}'
Role ARN: arn:aws:iam::<AccountID>:role/test-app-role
Policy ARN: arn:aws:iam::<AccountID>:policy/test-app-policy

テスト用リソースを用意
awsコマンドを実行できるpod, IRSAを利用できるServiceAccountを作成します
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: test-app
namespace: default
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::<AccountID>:role/test-app-role
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: test-app
namespace: default
labels:
app: test-app
spec:
replicas: 1
selector:
matchLabels:
app: test-app
template:
metadata:
labels:
app: test-app
spec:
serviceAccountName: test-app
containers:
- name: app
image: amazon/aws-cli:latest
command:
- /bin/bash
- -c
- |
sleep infinity
こいつのshellに入ってみます
bash-5.2# aws sts get-caller-identity
{
"UserId": "XXXXXXXXXXXXX:botocore-session-0123456789",
"Account": "<AccountID>",
"Arn": "arn:aws:sts::<AccountID>:assumed-role/test-app-role/botocore-session-0123456789"
}
bash-5.2# aws s3 ls
2024-11-12 07:03:07 hogefuga
略
できた!
これでk8sユーザはIRSAClaimを定義すれば各々でIRSAを用意できるようになりました!
最後に
IRSAをIRSAClaimリソースだけで作成できました!
terraformに向かう必要がなく、k8sレイヤだけで完結できるのがとても快適です
ただCompositeResourceDefinition, Compositionが複雑すぎる...(所感)
慣れていきたいところ
次はexternal-secret一式だったり、PodIdentityも作ってみたい
参考
公式ドキュメント
Discussion