Crossplane’s RBAC controls who can orchestrate cloud infrastructure by managing permissions on Crossplane’s Custom Resources (CRs) and their underlying infrastructure.
Let’s imagine we have a team that needs to provision PostgreSQL instances on AWS. We want to give them the ability to create RDSInstance resources, but only in a specific region and with specific instance classes. We also want to prevent them from deleting instances they didn’t create.
Here’s how we can set that up using Kubernetes RBAC and Crossplane’s CompositeResourceDefinition (XRD) and CompositeResource (Composite) concepts.
First, let’s define a CompositeResourceDefinition (XRD) that models our desired PostgreSQL instance. This XRD will be the blueprint for what our users can provision.
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
name: rdsinstances.rds.example.org
spec:
group: rds.example.org
names:
kind: RDSInstance
plural: rdsinstances
claimNames:
kind: RDSClaim
plural: rdsclaims
versions:
- name: v1alpha1
served: true
referenceable: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
parameters:
type: object
properties:
storageGB:
type: integer
description: "Storage in GB"
instanceClass:
type: string
description: "AWS RDS instance class"
required:
- storageGB
- instanceClass
required:
- parameters
Next, we’ll create a CompositeResource (Composite) that uses this XRD to define the actual structure of what can be provisioned. This Composite will tell Crossplane how to translate the user’s RDSClaim into a specific RDSInstance.
apiVersion: rds.example.org/v1alpha1
kind: RDSInstance
metadata:
name: managed-postgres-template
spec:
parameters:
storageGB: 20
instanceClass: db.t3.small
Now, we need to create a CompositeResourceClaim (Claim) that our team will use to request a PostgreSQL instance. This is the resource they will interact with directly.
apiVersion: rds.example.org/v1alpha1
kind: RDSClaim
metadata:
name: my-team-postgres
namespace: team-a
spec:
parameters:
storageGB: 30
instanceClass: db.r5.large
With these resources in place, let’s talk about RBAC. Crossplane leverages Kubernetes RBAC to control access. The key is understanding which resources Crossplane creates and how to grant permissions on them.
When a RDSClaim is created in the team-a namespace, Crossplane’s Kubernetes controller (specifically the KubernetesRuntime or KubernetesPlatform if you’re using those) will typically create a CompositeResource (e.g., RDSInstance) and then provision the underlying cloud provider resource (e.g., an AWS RDS instance).
The most common way to control who can provision what is by granting permissions on the CompositeResource kinds and their corresponding CompositeResourceClaim kinds within specific namespaces.
Cause 1: Users can’t create claims.
- Diagnosis: Check if the user has
createpermission onrdsclaims.rds.example.orgin their target namespace.kubectl auth can-i create rdsclaims.rds.example.org --namespace team-a - Fix: Create a
RoleorClusterRolethat grants this permission.apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: rds-provisioner-role namespace: team-a rules: - apiGroups: ["rds.example.org"] resources: ["rdsclaims"] verbs: ["create", "get", "list", "watch"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: rds-provisioner-binding namespace: team-a subjects: - kind: User name: team-a-developer@example.com # Or a ServiceAccount apiGroup: rbac.authorization.k8s.io roleRef: kind: Role name: rds-provisioner-role apiGroup: rbac.authorization.k8s.io - Why it works: This grants the user the ability to request the creation of an RDS instance via the
RDSClaimobject.
Cause 2: Users can create claims but can’t see the underlying composite resource.
- Diagnosis: The
CompositeResource(e.g.,RDSInstance) is created by Crossplane, often in a different namespace (or the same one, depending on configuration). Users need toget,list, andwatchthese.kubectl auth can-i get rdsinstances.rds.example.org --namespace team-a - Fix: Grant
get,list,watchpermissions on theCompositeResourcekind.# Add to the existing Role or create a new one - apiGroups: ["rds.example.org"] resources: ["rdsinstances"] verbs: ["get", "list", "watch"] - Why it works: This allows users to view the status and details of the
CompositeResourcethat represents their provisioned infrastructure, which is crucial for debugging and monitoring.
Cause 3: Users can create claims but can’t see the actual cloud provider resource (e.g., AWS RDS instance).
- Diagnosis: Crossplane provisions actual cloud resources. The RBAC for these resources is managed by the cloud provider’s IAM system, not Kubernetes RBAC. However, Crossplane’s ability to manage these resources is controlled by the
ProviderConfigand the permissions of the service account running the Crossplane controller. If a user wants to see the Kubernetes representation of the managed resource (e.g., theRDSInstancein our example), they need permissions on that. - Fix: Ensure the
CompositeResource(e.g.,RDSInstance) is visible to the user. This is usually covered by Cause 2, as theRDSInstanceis the Kubernetes object representing the cloud resource. To see the actual cloud resource, they’d need IAM permissions in AWS. - Why it works: The
RDSInstanceobject in Kubernetes mirrors the state of the actual AWS RDS instance. RBAC on theRDSInstancecontrols visibility into this mirrored state.
Cause 4: Users can delete claims they didn’t create.
- Diagnosis: The default
deletepermission onrdsclaimsmight be too broad.kubectl auth can-i delete rdsclaims.rds.example.org --namespace team-a - Fix: Restrict
deletepermissions to only the specific claims a user or service account owns. This is achieved by usingResourceInstanceselectors inRoleBindingor by creatingRoleobjects scoped to individualRDSClaimresources (though this is less common and harder to manage at scale). A more practical approach is to limit who can delete by namespace and potentially by label selectors if you have a convention.
Note:# Example: Limit delete to specific labels if you have a naming convention # This is a simplified example; true ownership control is complex. apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: rds-delete-limiter namespace: team-a rules: - apiGroups: ["rds.example.org"] resources: ["rdsclaims"] verbs: ["get", "list", "watch"] # Allow viewing - apiGroups: ["rds.example.org"] resources: ["rdsclaims"] verbs: ["delete"] resourceNames: ["my-team-postgres"] # Only allow deleting this specific oneresourceNamesis powerful but requires explicit management for each resource. Often, you’d grantdeleteonly to cluster admins or a dedicated operations team. - Why it works: By restricting
deleteto specificresourceNamesor by carefully managing namespace-level delete permissions, you ensure that users can only remove the infrastructure they are authorized to manage.
Cause 5: Users can’t modify specific parameters of their claims.
- Diagnosis: The
CompositeResourceDefinition’s schema and theCompositeResource’s structure dictate what can be changed. RBAC can limit which fields can be updated.# This is conceptually checked by admission controllers, not directly by `kubectl auth can-i` # You'd see a validation error during `kubectl apply` or `kubectl edit`. - Fix: Use Kubernetes Admission Controllers (like ValidatingAdmissionWebhook) to enforce policies. Crossplane’s
CompositeResourceDefinitionschema also enforces basic type and required field validation. For finer-grained control over who can change what parameter, you’d typically use an admission controller that inspects thespec.parametersof theRDSClaim.# Example of a policy that might be enforced by an admission controller # This is not a direct RBAC rule but a policy it enables. # The admission controller would reject changes to instanceClass if not allowed. - Why it works: Admission controllers intercept requests to the Kubernetes API server before they are persisted. They can validate the request payload against custom rules, preventing unauthorized modifications to specific fields within a resource.
Cause 6: Users can’t provision resources outside of specific namespaces or with disallowed configurations.
- Diagnosis: Check if the
RoleBindingis correctly scoped to the namespace where the user is trying to provision. Also, check theCompositeResourceDefinitionfor schema constraints. - Fix: Ensure
RoleBindingobjects are namespace-scoped correctly. For disallowed configurations (e.g., instance types not in a predefined list), useValidatingAdmissionWebhooks or leverage the schema validation within theCompositeResourceDefinitionitself to reject invalidspec.parameters.# Example: Limiting instanceClass via XRD schema # ... inside RDSInstance spec.versions[0].schema.openAPIV3Schema.properties.spec.properties.parameters.properties.instanceClass enum: ["db.t3.small", "db.r5.large"] - Why it works: Namespace-scoped RBAC restricts actions to specific namespaces. Schema validation in the XRD prevents the creation of
CompositeResources that don’t conform to defined rules, including valid parameter values.
The next thing you’ll likely run into is managing the lifecycle of these resources, specifically how to handle updates and deletions gracefully, which often involves understanding Crossplane’s Composition and ResourceSelector configurations.