A Crossplane Composition can provision AWS IAM Roles, but not in the way you’d initially expect; it doesn’t directly manage IAM entities.

Let’s see Crossplane in action. Imagine you have a Kubernetes cluster and you want to grant a specific application running in that cluster the ability to list S3 buckets in your AWS account. You don’t want to manually create an IAM user, attach policies, and then configure your application’s AWS credentials. Instead, you want to define this as a Kubernetes resource.

First, you’d need Crossplane installed and configured with an AWS provider. This provider allows Crossplane to talk to AWS. Your ProviderConfig might look something like this:

apiVersion: aws.upbound.io/v1beta1
kind: ProviderConfig
metadata:
  name: aws-provider-config
spec:
  credentials:
    source: Secret
    secretRef:
      namespace: crossplane-system
      name: aws-provider-creds
      key: credentials

This tells Crossplane to use credentials stored in a Kubernetes secret named aws-provider-creds in the crossplane-system namespace.

Now, you define a Composition that describes how to create an AWS IAM Role. This Composition is a blueprint. It specifies that when a IAMRole custom resource (which is defined by the AWS provider) is created, Crossplane should provision an AWS::IAM::Role resource in AWS.

Here’s a simplified example of a Composition:

apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: iamrole-s3-list
  labels:
    provider: aws
    purpose: iamrole-s3-list
spec:
  writeConnectionSecretToFieldPaths: ["spec.writeConnectionSecretTo"]
  compositeTypeRef:
    apiVersion: example.crossplane.io/v1alpha1
    kind: XIAMRoleS3List
  resources:
    - name: iamrole
      base:
        apiVersion: iam.aws.upbound.io/v1beta1
        kind: Role
        spec:
          forProvider:
            region: us-east-1
            assumeRolePolicy: |
              {
                "Version": "2012-10-17",
                "Statement": [
                  {
                    "Effect": "Allow",
                    "Principal": {
                      "Service": "ec2.amazonaws.com"
                    },
                    "Action": "sts:AssumeRole"
                  }
                ]
              }
      patches:
        - fromFieldPath: spec.parameters.roleName
          toFieldPath: spec.forProvider.roleName

Notice assumeRolePolicy here. This is crucial. It defines who can assume this role. In this example, it’s set to allow EC2 instances to assume it. For Kubernetes applications, you’d typically adjust this to allow your Kubernetes service account (via an IAM OIDC provider setup) to assume the role.

The XIAMRoleS3List is your CompositeResourceDefinition (XRD). It’s the custom Kubernetes API you’ll interact with. When you create an instance of XIAMRoleS3List, you specify parameters like the desired role name:

apiVersion: example.crossplane.io/v1alpha1
kind: XIAMRoleS3List
metadata:
  name: my-app-s3-reader-role
spec:
  parameters:
    roleName: my-app-s3-reader-role
  writeConnectionSecretTo:
    name: app-s3-reader-creds
    namespace: default

When you kubectl apply this XIAMRoleS3List, Crossplane sees it, finds the iamrole-s3-list Composition, and uses it to create an iam.aws.upbound.io/v1beta1.Role resource. The AWS provider then translates this Kubernetes resource into an actual IAM role in your AWS account.

The writeConnectionSecretTo field is where the magic for your application happens. Crossplane can automatically generate AWS credentials (access key ID and secret access key) for this newly created IAM role and store them in a Kubernetes secret named app-s3-reader-creds. Your Kubernetes application can then reference this secret to authenticate with AWS.

The real power is in how you can abstract the AWS specifics. The XIAMRoleS3List CRD is what your application developers interact with. They don’t need to know about AWS::IAM::Role or assumeRolePolicy. They just declare they need a role named my-app-s3-reader-role that can do specific things. The Composition handles the AWS plumbing.

The assumeRolePolicy is not directly managed by the Composition itself in terms of defining what the policy allows, but rather how it’s structured and passed down to the AWS provider’s Role resource. The actual IAM policies that grant permissions (like listing S3 buckets) are separate IAM policy resources that you would also provision, typically via another Composition or directly managed. The role just assumes a policy, it doesn’t contain the permissions itself. The Composition’s role is to orchestrate the creation of the Role resource and potentially link it to other IAM policy resources.

This setup decouples the application’s need for AWS access from the operational burden of managing IAM entities. You define a canonical XIAMRole resource in Kubernetes, and Crossplane’s Compositions translate that into the specific AWS IAM Role and associated policies.

The next hurdle you’ll likely encounter is managing the IAM policies themselves, ensuring they are attached to the provisioned roles with the correct permissions.

Want structured learning?

Take the full Crossplane course →