Skip to content

Commit

Permalink
feat(teamrolebindings): deny changing TeamRoleRef and TeamRef in TRB,…
Browse files Browse the repository at this point in the history
… update docs
  • Loading branch information
k.zagorski committed Jan 10, 2025
1 parent aa7140b commit 04460b8
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 4 deletions.
17 changes: 13 additions & 4 deletions docs/user-guides/team/rbac.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ description: >

## Before you begin

This guides describes how to manage roles and permissions in Greenhouse with the help of TeamRoles and TeamRoleBindings.
This guide describes how to manage roles and permissions in Greenhouse with the help of TeamRoles and TeamRoleBindings.

While all members of an organization can see the permissions configured with TeamRoles & TeamRoleBindings, configuration of these requires **OrganizationAdmin privileges**.

Expand All @@ -40,7 +40,7 @@ Common roles including the below `cluster-admin` are pre-defined within each org

### Example

This TeamRole named `pod-read` grants read access to Pods!.
This TeamRole named `pod-read` grants read access to Pods.

```yaml
apiVersion: greenhouse.sap/v1alpha1
Expand Down Expand Up @@ -75,7 +75,7 @@ Greenhouse provides a set of [default `TeamRoles`](./../../../pkg/controllers/or
## Defining TeamRoleBindings

`TeamRoleBindings` define the permissions of a Greenhouse Team within Clusters by linking to a specific `TeamRole`.
TeamRoleBindings have a simple specification that links a Team, a TeamRole, one or more Clusters and optionally a one or more Namespaces together. Once the TeamRoleBinding is created, the Team will have the permissions defined in the TeamRole within the specified Clusters and Namespaces. This allows for fine-grained control over the permissions of each Team within each Cluster.
TeamRoleBindings have a simple specification that links a Team, a TeamRole, one or more Clusters and optionally one or more Namespaces together. Once the TeamRoleBinding is created, the Team will have the permissions defined in the TeamRole within the specified Clusters and Namespaces. This allows for fine-grained control over the permissions of each Team within each Cluster.
The TeamRoleBinding Controller within Greenhouse deploys rbacv1 resources to the targeted Clusters. The referenced TeamRole is created as a rbacv1.ClusterRole. In case the TeamRoleBinding references a Namespace, the Controller will create a rbacv1.RoleBinding which links the Team with the rbacv1.ClusterRole. In case no Namespace is referenced, the Controller will create a rbacv1.ClusterRoleBinding instead.

### Assigning TeamRoles to Teams on a single Cluster
Expand Down Expand Up @@ -124,7 +124,7 @@ More details on the concept of Aggregated ClusterRoles can be found in the Kuber

[!NOTE] A TeamRole is only created on a cluster if it is referenced by a TeamRoleBinding. If a TeamRole is not referenced by a TeamRoleBinding it will not be created on any target cluster. A TeamRoleBinding referencing a TeamRole with an aggregationRule will only provide the correct access, if there is at least one TeamRoleBinding referencing a TeamRole with the corresponding label deployed to the same cluster.

The following example shows how to an AggregationRule can be used with TeamRoles and TeamRoleBindings.
The following example shows how an AggregationRule can be used with TeamRoles and TeamRoleBindings.

This TeamRole specifies `.spec.Labels`. The labels will be applied to the resulting ClusterRole on the target cluster.

Expand Down Expand Up @@ -218,3 +218,12 @@ spec:
matchLabels:
environment: production
```


### Updating TeamRoleBindings

Updating the RoleRef of a ClusterRoleBinding and RoleBinding is not allowed, but requires recreating the binding resources. See [ClusterRoleBinding docs](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#clusterrolebinding-example) for more information.
This is to allow giving out permissions to update the subjects, while avoiding that privileges are changed. Furthermore, changing the role can change the extent of a binding significantly. Therefore it needs to be recreated.

After the TeamRoleBinding has been created, it can be updated with some limitations. Similarly to RoleBindings, the RoleRef and TeamRef may not be changed. Validation webhook denies that.
The TeamRoleBinding's Namespaces may be changed for the bindings to be applied to different namespaces. However, the scope of the TeamRoleBinding cannot be changed. That's why if the TeamRoleBinding has been created with Namespaces specified, it is namespace-scoped, and cannot be changed to cluster-scoped by removing all namespaces from the list. Similarly with the cluster-scoped TeamRoleBinding, which created with empty Namespaces, cannot be changed to namespace-scoped by adding any namespaces to the list.
12 changes: 12 additions & 0 deletions pkg/admission/teamrolebinding_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,18 @@ func ValidateUpdateRoleBinding(ctx context.Context, c client.Client, old, cur ru
Group: oldRB.GroupVersionKind().Group,
Resource: oldRB.Kind,
}, oldRB.Name, field.Forbidden(field.NewPath("spec"), "must contain either spec.clusterName or spec.clusterSelector"))
case oldRB.Spec.TeamRoleRef != curRB.Spec.TeamRoleRef:
return nil, apierrors.NewForbidden(
schema.GroupResource{
Group: oldRB.GroupVersionKind().Group,
Resource: oldRB.Kind,
}, oldRB.Name, field.Forbidden(field.NewPath("spec", "teamRoleRef"), "cannot change TeamRoleRef of an existing TeamRoleBinding"))
case oldRB.Spec.TeamRef != curRB.Spec.TeamRef:
return nil, apierrors.NewForbidden(
schema.GroupResource{
Group: oldRB.GroupVersionKind().Group,
Resource: oldRB.Kind,
}, oldRB.Name, field.Forbidden(field.NewPath("spec", "teamRef"), "cannot change TeamRef of an existing TeamRoleBinding"))
case isClusterScoped(oldRB) && !isClusterScoped(curRB):
return nil, apierrors.NewForbidden(
schema.GroupResource{
Expand Down
66 changes: 66 additions & 0 deletions pkg/admission/teamrolebinding_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,5 +235,71 @@ var _ = Describe("Validate Create RoleBinding", Ordered, func() {
Expect(warns).To(BeNil(), "expected no warnings")
Expect(err).ToNot(HaveOccurred(), "expected no error")
})

It("Should deny changing the TeamRoleRef", func() {
oldRB := &greenhousev1alpha1.TeamRoleBinding{
ObjectMeta: metav1.ObjectMeta{
Namespace: "greenhouse",
Name: "testBinding",
},
Spec: greenhousev1alpha1.TeamRoleBindingSpec{
TeamRoleRef: teamRole.Name,
TeamRef: team.Name,
ClusterName: cluster.Name,
Namespaces: []string{"demoNamespace"},
},
}

curRB := &greenhousev1alpha1.TeamRoleBinding{
ObjectMeta: metav1.ObjectMeta{
Namespace: "greenhouse",
Name: "testBinding",
},
Spec: greenhousev1alpha1.TeamRoleBindingSpec{
TeamRoleRef: "differentTeamRole",
TeamRef: team.Name,
ClusterName: cluster.Name,
Namespaces: []string{"demoNamespace"},
},
}

warns, err := ValidateUpdateRoleBinding(test.Ctx, test.K8sClient, oldRB, curRB)
Expect(warns).To(BeNil(), "expected no warnings")
Expect(err).To(HaveOccurred(), "expected an error")
Expect(err).To(MatchError(ContainSubstring("cannot change TeamRoleRef of an existing TeamRoleBinding")))
})

It("Should deny changing the TeamRef", func() {
oldRB := &greenhousev1alpha1.TeamRoleBinding{
ObjectMeta: metav1.ObjectMeta{
Namespace: "greenhouse",
Name: "testBinding",
},
Spec: greenhousev1alpha1.TeamRoleBindingSpec{
TeamRoleRef: teamRole.Name,
TeamRef: team.Name,
ClusterName: cluster.Name,
Namespaces: []string{},
},
}

curRB := &greenhousev1alpha1.TeamRoleBinding{
ObjectMeta: metav1.ObjectMeta{
Namespace: "greenhouse",
Name: "testBinding",
},
Spec: greenhousev1alpha1.TeamRoleBindingSpec{
TeamRoleRef: teamRole.Name,
TeamRef: "differentTeam",
ClusterName: cluster.Name,
Namespaces: []string{},
},
}

warns, err := ValidateUpdateRoleBinding(test.Ctx, test.K8sClient, oldRB, curRB)
Expect(warns).To(BeNil(), "expected no warnings")
Expect(err).To(HaveOccurred(), "expected an error")
Expect(err).To(MatchError(ContainSubstring("cannot change TeamRef of an existing TeamRoleBinding")))
})
})
})

0 comments on commit 04460b8

Please sign in to comment.