From d65513854646474aa2722ad9f5d92207fd4dc5e6 Mon Sep 17 00:00:00 2001 From: Christopher Haar Date: Tue, 27 Jan 2026 18:06:48 +0100 Subject: [PATCH] docs(guides): add crossplane with workload-identity guide Signed-off-by: Christopher Haar --- .../crossplane-with-workload-identity.md | 787 ++++++++++++++++++ 1 file changed, 787 insertions(+) create mode 100644 content/v2.1/guides/crossplane-with-workload-identity.md diff --git a/content/v2.1/guides/crossplane-with-workload-identity.md b/content/v2.1/guides/crossplane-with-workload-identity.md new file mode 100644 index 000000000..9513aa707 --- /dev/null +++ b/content/v2.1/guides/crossplane-with-workload-identity.md @@ -0,0 +1,787 @@ +--- +title: Crossplane with Workload Identity +weight: 205 +description: Configure Crossplane to pull packages from cloud provider container registries using workload identity +--- + +When running Crossplane on managed Kubernetes clusters (EKS, AKS, GKE), you can leverage Kubernetes Workload Identity to grant Crossplane access to pull packages from private cloud container registries. This allows Crossplane to install providers, functions, and configurations from registries like AWS ECR, Azure ACR, and Google Artifact Registry without managing static credentials. + +{{< hint "important" >}} +This guide configures the **Crossplane package manager** to pull packages from private registries. However, packages reference container images that run as separate pods (providers and functions). + +**Two-step image pull process:** +1. **Crossplane package manager** pulls the package, extracts the package contents (CRDs, XRDs) and creates deployments +2. **Kubernetes nodes** pull the runtime container images when creating provider/function pods + +This guide only covers step 1. For step 2, ensure your Kubernetes nodes have permissions to pull images from the private registry. This is typically configured at the cluster level: +- **AWS EKS**: Node IAM role with ECR pull permissions +- **Azure AKS**: Kubelet managed identity with `AcrPull` role +- **GCP GKE**: Node service account with Artifact Registry reader role + +Without node-level access, package installation succeeds but pods fail with `ImagePullBackOff`. +{{< /hint >}} + +## Overview + +To enable Crossplane package manager access to private registries, configure service account annotations during installation. The `crossplane` service account in the `crossplane-system` namespace requires specific annotations for each cloud provider: + +- **AWS EKS**: IAM Roles for Service Accounts (IRSA) +- **Azure AKS**: Azure Workload Identity +- **Google Cloud GKE**: GKE Workload Identity + +Select your cloud provider below for detailed setup instructions: + +{{< tabs >}} + +{{< tab "AWS EKS" >}} + +## AWS EKS Integration + +Configure Crossplane to pull packages from Amazon ECR using IAM Roles for Service Accounts (IRSA). + +### Prerequisites + +- An Amazon EKS cluster with OIDC provider enabled +- AWS CLI installed and configured +- `kubectl` configured to access your EKS cluster +- Permissions to create IAM roles and policies + +### Enable OIDC Provider + +If your EKS cluster doesn't have an OIDC provider, enable it: + +```bash +eksctl utils associate-iam-oidc-provider \ + --cluster= \ + --approve +``` + +Verify the OIDC provider: + +```bash +aws eks describe-cluster \ + --name \ + --query "cluster.identity.oidc.issuer" \ + --output text +``` + +### Create IAM Policy for ECR Access + +Create an IAM policy that grants permissions to pull images from ECR: + +```bash +cat > crossplane-ecr-policy.json <::repository/*" + } + ] +} +EOF + +aws iam create-policy \ + --policy-name CrossplaneECRPolicy \ + --policy-document file://crossplane-ecr-policy.json +``` + +{{< hint "note" >}} +Replace `` and `` with your AWS region and account ID. You can restrict the `Resource` to specific repositories if needed. +{{< /hint >}} + +### Create IAM Role with Trust Policy + +Create an IAM role that can be assumed by the Crossplane service account: + +```bash +export CLUSTER_NAME= +export AWS_REGION= +export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text) +export OIDC_PROVIDER=$(aws eks describe-cluster --name $CLUSTER_NAME --region $AWS_REGION --query "cluster.identity.oidc.issuer" --output text | sed -e "s/^https:\/\///") + +cat > trust-policy.json <}} + +{{< tab "Azure AKS" >}} + +## Azure AKS Integration + +Configure Crossplane to pull packages from Azure Container Registry (ACR) using Azure Workload Identity. + +### Prerequisites + +- An AKS cluster with Workload Identity enabled +- Azure CLI installed and configured +- `kubectl` configured to access your AKS cluster +- Permissions to create Azure managed identities and role assignments + +### Enable Workload Identity on AKS + +If your AKS cluster doesn't have Workload Identity enabled, update it: + +```bash +export RESOURCE_GROUP= +export CLUSTER_NAME= + +az aks update \ + --resource-group $RESOURCE_GROUP \ + --name $CLUSTER_NAME \ + --enable-oidc-issuer \ + --enable-workload-identity +``` + +Get the OIDC issuer URL: + +```bash +export AKS_OIDC_ISSUER=$(az aks show \ + --resource-group $RESOURCE_GROUP \ + --name $CLUSTER_NAME \ + --query "oidcIssuerProfile.issuerUrl" \ + --output tsv) + +echo $AKS_OIDC_ISSUER +``` + +### Create Azure Managed Identity + +Create a managed identity for Crossplane: + +```bash +export IDENTITY_NAME=crossplane-acr-identity + +az identity create \ + --name $IDENTITY_NAME \ + --resource-group $RESOURCE_GROUP + +export USER_ASSIGNED_CLIENT_ID=$(az identity show \ + --name $IDENTITY_NAME \ + --resource-group $RESOURCE_GROUP \ + --query 'clientId' \ + --output tsv) + +export USER_ASSIGNED_OBJECT_ID=$(az identity show \ + --name $IDENTITY_NAME \ + --resource-group $RESOURCE_GROUP \ + --query 'principalId' \ + --output tsv) + +echo "Client ID: $USER_ASSIGNED_CLIENT_ID" +echo "Object ID: $USER_ASSIGNED_OBJECT_ID" +``` + +### Assign ACR Pull Role + +Grant the managed identity permission to pull from ACR: + +```bash +export ACR_NAME= + +export ACR_ID=$(az acr show \ + --name $ACR_NAME \ + --query 'id' \ + --output tsv) + +az role assignment create \ + --assignee-object-id $USER_ASSIGNED_OBJECT_ID \ + --assignee-principal-type ServicePrincipal \ + --role AcrPull \ + --scope $ACR_ID +``` + +### Create Federated Identity Credential + +Create a federated identity credential that establishes trust between the managed identity and the Kubernetes service account: + +```bash +az identity federated-credential create \ + --name crossplane-federated-credential \ + --identity-name $IDENTITY_NAME \ + --resource-group $RESOURCE_GROUP \ + --issuer $AKS_OIDC_ISSUER \ + --subject system:serviceaccount:crossplane-system:crossplane \ + --audience api://AzureADTokenExchange +``` + +### Install Crossplane with Workload Identity Configuration + +Get the tenant ID: + +```bash +export AZURE_TENANT_ID=$(az account show --query tenantId --output tsv) +``` + +Install Crossplane with the workload identity annotations and label: + +```bash +helm upgrade --install crossplane \ + crossplane-stable/crossplane \ + --namespace crossplane-system \ + --create-namespace \ + --set "serviceAccount.customAnnotations.azure\.workload\.identity/client-id=$USER_ASSIGNED_CLIENT_ID" \ + --set "serviceAccount.customAnnotations.azure\.workload\.identity/tenant-id=$AZURE_TENANT_ID" \ + --set-string 'customLabels.azure\.workload\.identity/use=true' +``` + +{{< hint "note" >}} +Azure Workload Identity requires: +- Service account annotations for the client ID and tenant ID +- Label `azure.workload.identity/use: "true"` on pods (applied via `customLabels`) + +The `customLabels` setting applies the label to all Crossplane resources. The Azure Workload Identity webhook uses this label on pods to inject the necessary environment variables and token volumes. Use `--set-string` to ensure the value is treated as a string rather than a boolean. +{{< /hint >}} + +### Verify Configuration + +Check that the service account has the correct annotations: + +```bash +kubectl get sa crossplane -n crossplane-system -o yaml +``` + +Expected output should include: + +```yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + annotations: + azure.workload.identity/client-id: + azure.workload.identity/tenant-id: + name: crossplane + namespace: crossplane-system +``` + +Check that the deployment has the required labels: + +```bash +kubectl get deployment crossplane -n crossplane-system -o yaml +``` + +Expected output should include: + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + azure.workload.identity/use: "true" + name: crossplane + namespace: crossplane-system +spec: + template: + metadata: + labels: + azure.workload.identity/use: "true" +``` + +### Test Package Installation from ACR + +Once configured, you can install Crossplane packages (Providers, Functions, Configurations) from your ACR. Here's an example using a Provider: + +```bash +kubectl apply -f - <}} + +{{< tab "Google Cloud GKE" >}} + +## Google Cloud GKE Integration + +Configure Crossplane to pull packages from Google Artifact Registry using GKE Workload Identity. + +### Prerequisites + +- A GKE cluster with Workload Identity enabled +- `gcloud` CLI installed and configured +- `kubectl` configured to access your GKE cluster +- Permissions to create service accounts and IAM bindings + +### Enable Workload Identity on GKE + +If your GKE cluster doesn't have Workload Identity enabled, create a new cluster with it enabled or update an existing cluster: + +**New cluster:** +```bash +export PROJECT_ID= +export CLUSTER_NAME= +export REGION= + +gcloud container clusters create $CLUSTER_NAME \ + --region=$REGION \ + --workload-pool=${PROJECT_ID}.svc.id.goog +``` + +**Existing cluster:** +```bash +gcloud container clusters update $CLUSTER_NAME \ + --region=$REGION \ + --workload-pool=${PROJECT_ID}.svc.id.goog +``` + +### Create Google Service Account + +Create a Google Cloud service account for Crossplane: + +```bash +export GSA_NAME=crossplane-gar-sa + +gcloud iam service-accounts create $GSA_NAME \ + --display-name="Crossplane Artifact Registry Service Account" \ + --project=$PROJECT_ID +``` + +Get the full service account email: + +```bash +export GSA_EMAIL=${GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com +echo $GSA_EMAIL +``` + +### Grant Artifact Registry Permissions + +Grant the service account permissions to read from Artifact Registry: + +```bash +gcloud projects add-iam-policy-binding $PROJECT_ID \ + --member="serviceAccount:${GSA_EMAIL}" \ + --role="roles/artifactregistry.reader" +``` + +For specific repository access, use: + +```bash +export REPOSITORY= +export REPOSITORY_LOCATION= + +gcloud artifacts repositories add-iam-policy-binding $REPOSITORY \ + --location=$REPOSITORY_LOCATION \ + --member="serviceAccount:${GSA_EMAIL}" \ + --role="roles/artifactregistry.reader" +``` + +### Create IAM Policy Binding + +Create an IAM policy binding between the Google service account and the Kubernetes service account: + +```bash +gcloud iam service-accounts add-iam-policy-binding $GSA_EMAIL \ + --role roles/iam.workloadIdentityUser \ + --member "serviceAccount:${PROJECT_ID}.svc.id.goog[crossplane-system/crossplane]" +``` + +### Install Crossplane with Workload Identity Annotation + +Install Crossplane with the service account annotation: + +```bash +helm upgrade --install crossplane \ + crossplane-stable/crossplane \ + --namespace crossplane-system \ + --create-namespace \ + --set "serviceAccount.customAnnotations.iam\.gke\.io/gcp-service-account=$GSA_EMAIL" +``` + +### Verify Configuration + +Check that the service account has the correct annotation: + +```bash +kubectl get sa crossplane -n crossplane-system -o yaml +``` + +Expected output should include: + +```yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + annotations: + iam.gke.io/gcp-service-account: crossplane-gar-sa@project-id.iam.gserviceaccount.com + name: crossplane + namespace: crossplane-system +``` + +### Test Package Installation from Artifact Registry + +Once configured, you can install Crossplane packages (Providers, Functions, Configurations) from your Artifact Registry. Here's an example using a Provider: + +```bash +kubectl apply -f - <}} + +{{< /tabs >}} + +## Configure After Installation + +If Crossplane is already installed, you can update the service account annotations using Helm upgrade with the appropriate `--set` flags shown in your cloud provider's tab above. After updating, restart the Crossplane deployment: + +```bash +kubectl rollout restart deployment/crossplane -n crossplane-system +```