Crossplane in EKS

Crossplane is a CNCF project which can be used to manage AWS resources lifecycle and access them from the application pods. This allows developers to quickly use S3 buckets, SNS or SES resources for their applications and delete them along with the app. Crossplane is a great way to manage AWS resources from a common Kubernetes control-plane instead of trying to figure out what resources you have up etc. Read more here.

Installing Crossplane on a cluster

I have provisioned the EKS cluster using terraform module and enabled OIDC.

Outputs:

cluster_endpoint = "https://C3512060B49A940D946C54B5FF5F4535.gr7.us-east-2.eks.amazonaws.com"
cluster_name = "education-eks-uWa4XrtJ"
cluster_security_group_id = "sg-012181f8810cb1e5b"
region = "us-east-2"

OpenID Connect (OIDC) is an authentication layer built on the OAuth protocol, extending OAuth's authorization abilities to include authentication. OIDC issues an "ID token," a JSON Web Token (JWT) containing user information, akin to an ID card. These ID tokens are proof of authentication, unlike access tokens which can be acquired multiple ways and are not directly tied to user authentication. OIDC standardizes scopes, defining a protected resource that contains common user information. The protocol issues an ID token alongside an access token when the 'openid' scope is requested, easing the process of accessing basic user data.

Ref: https://goteleport.com/blog/how-oidc-authentication-works/

You can install crossplane on the cluster using single helm command

➜  learn-terraform-provision-eks-cluster git:(main) ✗ helm install crossplane \
crossplane-stable/crossplane \
--namespace crossplane-system \
--create-namespace

NAME: crossplane
LAST DEPLOYED: Sat Feb 24 14:25:58 2024
NAMESPACE: crossplane-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
Release: crossplane

Chart Name: crossplane
Chart Description: Crossplane is an open source Kubernetes add-on that enables platform teams to assemble infrastructure from multiple vendors, and expose higher level self-service APIs for application teams to consume.
Chart Version: 1.15.0
Chart Application Version: 1.15.0

Verify the crossplane running by checking the pods in crossplane-system namespace

➜  ~ k get pods -n crossplane-system 
NAME                                                        READY   STATUS    RESTARTS   AGE
crossplane-5b87fcbc66-h8d6g                                 1/1     Running   0          117m
crossplane-rbac-manager-b884bb468-2n9cp                     1/1     Running   0          117m
provider-aws-iam-28cb496c46b8-65c866b566-lp2pf              1/1     Running   0          116m
provider-aws-s3-13463c1ac7d2-5f85d45dfd-6h8ww               1/1     Running   0          116m
upbound-provider-family-aws-3756efeec089-78df6446f6-kdcxc   1/1     Running   0          116m

You may not have these number of pods but you must have the first 2. Now you have to add a crossplane Provider to your cluster to create/manage your resources

Provider is a Crossplane CRD which installs all the required CRDs for that resource.

Here I have installed S3 and IAM providers using definition file

apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
  name: provider-aws-s3
spec:
  package: xpkg.upbound.io/upbound/provider-aws-s3:v1.1.0
---
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
  name: provider-aws-iam
spec:
  package: xpkg.upbound.io/upbound/provider-aws-iam:v1.1.0

These provides installs CRDs to create AWS resources directly inside the cluster.

➜  crossplane k get crds |grep s3
bucketaccelerateconfigurations.s3.aws.upbound.io             2024-02-24T20:27:16Z
bucketacls.s3.aws.upbound.io                                 2024-02-24T20:27:16Z
bucketanalyticsconfigurations.s3.aws.upbound.io              2024-02-24T20:27:16Z
bucketcorsconfigurations.s3.aws.upbound.io                   2024-02-24T20:27:16Z
bucketintelligenttieringconfigurations.s3.aws.upbound.io     2024-02-24T20:27:16Z
bucketinventories.s3.aws.upbound.io                          2024-02-24T20:27:16Z
bucketlifecycleconfigurations.s3.aws.upbound.io              2024-02-24T20:27:16Z
bucketloggings.s3.aws.upbound.io                             2024-02-24T20:27:16Z
bucketmetrics.s3.aws.upbound.io                              2024-02-24T20:27:16Z
bucketnotifications.s3.aws.upbound.io                        2024-02-24T20:27:16Z
bucketobjectlockconfigurations.s3.aws.upbound.io             2024-02-24T20:27:17Z
bucketobjects.s3.aws.upbound.io                              2024-02-24T20:27:17Z
bucketownershipcontrols.s3.aws.upbound.io                    2024-02-24T20:27:17Z
bucketpolicies.s3.aws.upbound.io                             2024-02-24T20:27:17Z
bucketpublicaccessblocks.s3.aws.upbound.io                   2024-02-24T20:27:17Z
bucketreplicationconfigurations.s3.aws.upbound.io            2024-02-24T20:27:17Z
bucketrequestpaymentconfigurations.s3.aws.upbound.io         2024-02-24T20:27:17Z
buckets.s3.aws.upbound.io                                    2024-02-24T20:27:17Z
bucketserversideencryptionconfigurations.s3.aws.upbound.io   2024-02-24T20:27:17Z
bucketversionings.s3.aws.upbound.io                          2024-02-24T20:27:17Z
bucketwebsiteconfigurations.s3.aws.upbound.io                2024-02-24T20:27:17Z
objectcopies.s3.aws.upbound.io                               2024-02-24T20:27:17Z
objects.s3.aws.upbound.io                                    2024-02-24T20:27:17Z

You need to create secret which contains your cloud credentials

[default]
aws_access_key_id = 
aws_secret_access_key =
crossplane k create secret generic aws-secret -n crossplane-system --from-file=creds=./aws-credentials.txt

Now you need to create providerConfig, which contains your cloud secrets, whithout the providerConfig you cannot create resources.

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

now you can create aws resources like bucket with below definition file.

apiVersion: s3.aws.upbound.io/v1beta1
kind: Bucket
metadata:
  generateName: crossplane-bucket-
spec:
  forProvider:
    region: us-east-2
  providerConfigRef:
    name: default
➜  crossplane k get buckets                                                                                  
NAME                      READY   SYNCED   EXTERNAL-NAME             AGE
crossplane-bucket-jvx8m   True    True     crossplane-bucket-jvx8m   132m

Using IRSA to access AWS resources in pod.

IRSA also called as IAM resources for ServiceAccounts which links the serviceaccount to iam roles. Using this you can have a role for pods and using that role you can use AWS resources like S3, SNS, SES etc.

To demonstrate the same I have created role and attached S3 full access managed policy to it

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:*",
                "s3-object-lambda:*"
            ],
            "Resource": "*"
        }
    ]
}

And in trust policy, you need to include your OIDC config as well.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::17483678901:oidc-provider/oidc.eks.ap-south-1.amazonaws.com/id/D510BCG4F7E27AM04AZNS95G6914V4A0"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "oidc.eks.ap-south-1.amazonaws.com/id/D510BCG4F7E27AM04AZNS95G6914V4A0:aud": "sts.amazonaws.com",
          "oidc.eks.ap-south-1.amazonaws.com/id/D510BCG4F7E27AM04AZNS95G6914V4A0:sub": "system:serviceaccount:demo-s3:demo-sa"
        }
      }
    }
  ]
}

Then you need to create ServiceAccount and annotate the SA with role

➜  crossplane k annotate sa bucket-sa eks.amazonaws.com/role-arn=arn:aws:iam::135227014767:role/bucket-role-for-crossplane
serviceaccount/bucket-sa annotated

So whenever this SA is attached to the pod, JWT token will be mounted in the pod which assumes the assigned IAM role

I have created a deployment to show the same

apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    deployment.kubernetes.io/revision: "1"
  creationTimestamp: "2024-02-24T21:16:50Z"
  generation: 1
  labels:
    app: aws-cli-deploy
  name: aws-cli-deploy
  namespace: default
  resourceVersion: "41708"
  uid: 68a358b1-74c1-45a6-bc50-d547968cbe9a
spec:
  progressDeadlineSeconds: 600
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: aws-cli-deploy
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: aws-cli-deploy
    spec:
      containers:
      - args:
        - "1500"
        command:
        - sleep
        image: amazon/aws-cli
        imagePullPolicy: Always
        name: aws-cli
        resources: {}
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      serviceAccount: bucket-sa
      serviceAccountName: bucket-sa
      terminationGracePeriodSeconds: 30

Once you attach the ServiceAccount to pod, EKS will automatically mount JWT token in the pod at this path

    Environment:
      AWS_STS_REGIONAL_ENDPOINTS:   regional
      AWS_DEFAULT_REGION:           us-east-2
      AWS_REGION:                   us-east-2
      AWS_ROLE_ARN:                 arn:aws:iam::135227014767:role/bucket-role-for-crossplane
      AWS_WEB_IDENTITY_TOKEN_FILE:  /var/run/secrets/eks.amazonaws.com/serviceaccount/token
    Mounts:
      /var/run/secrets/eks.amazonaws.com/serviceaccount from aws-iam-token (ro)
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-lpz8m (ro)

With the role attached, I can now list the S3 resources

bash-4.2# aws s3 ls
2023-10-07 20:13:44 cf-templates-1bjk9y0pvs5y3-us-east-2
2024-02-24 20:29:36 crossplane-bucket-jvx8m

Conclusion

Crossplane is a great alternative for terraform to create, manage resources in Kubernetes paradigm

Did you find this article valuable?

Support Srujan Reddy by becoming a sponsor. Any amount is appreciated!