🙌

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リソースの場合は以下を参考にクラスタへ適用します
https://docs.upbound.io/manuals/packages/providers/provider-families#installing-a-provider-family

---
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などが該当します
https://docs.crossplane.io/latest/packages/providers/#provider-configuration

こちらを参考にIRSA用の設定を行います
https://docs.upbound.io/manuals/packages/providers/authentication#create-a-providerconfig-3

apiVersion: aws.upbound.io/v1beta1
kind: ProviderConfig
metadata:
  name: aws-irsa
spec:
  credentials:
    source: IRSA

DeploymentRuntimeConfig

provider podの設定を追加できるリソースです
ServiceAccountの定義ができるのでIRSA用のannotationを付与します

https://docs.crossplane.io/latest/packages/providers/#runtime-configuration
https://docs.upbound.io/manuals/packages/providers/authentication#create-a-deploymentruntimeconfig

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ポリシー作成テスト

準備が出来たのでテストします
以下を参考にして作るといいでしょう
https://marketplace.upbound.io/providers/upbound/provider-aws-iam/v2.2.0/resources/iam.aws.m.upbound.io/Policy/v1beta1

---
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)

https://docs.crossplane.io/latest/composition/composite-resource-definitions/

作成したい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

https://docs.crossplane.io/latest/composition/composite-resources/

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を自動検索しているらしい(よくわからん)

Function (function-patch-and-transform)

https://docs.crossplane.io/latest/guides/function-patch-and-transform/#transform-a-patch

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リソースに反映する役割です
利用可能な処理はこのあたりにまとまっているため、見てみるのがよいでしょう
https://docs.crossplane.io/latest/guides/function-patch-and-transform/#types-of-patches

ここまで適用すると以下のように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も作ってみたい

参考

公式ドキュメント
https://alexanderhose.com/getting-started-with-crossplane/
https://techblog.hacomono.jp/entry/2025/10/14/110000
https://qiita.com/umanetes/items/81cdde32d10737ae1656

Discussion