-
Notifications
You must be signed in to change notification settings - Fork 38
feat: Role & Role binding webhook #396
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,13 +29,15 @@ import ( | |
| workv1alpha1 "sigs.k8s.io/work-api/pkg/apis/v1alpha1" | ||
|
|
||
| "go.goms.io/fleet/apis" | ||
| fleetv1beta1 "go.goms.io/fleet/apis/placement/v1beta1" | ||
| fleetv1alpha1 "go.goms.io/fleet/apis/v1alpha1" | ||
| "go.goms.io/fleet/pkg/metrics" | ||
| "go.goms.io/fleet/pkg/utils" | ||
| ) | ||
|
|
||
| const ( | ||
| eventReasonNamespaceCreated = "NamespaceCreated" | ||
| eventReasonNamespaceUpdated = "NamespaceUpdated" | ||
| eventReasonRoleCreated = "RoleCreated" | ||
| eventReasonRoleUpdated = "RoleUpdated" | ||
| eventReasonRoleBindingCreated = "RoleBindingCreated" | ||
|
|
@@ -47,6 +49,9 @@ const ( | |
| reasonMemberClusterJoined = "MemberClusterJoined" | ||
| reasonMemberClusterLeft = "MemberClusterLeft" | ||
| reasonMemberClusterUnknown = "MemberClusterUnknown" | ||
|
|
||
| FleetResourceLabelKey = fleetv1beta1.FleetPrefix + "isFleetResource" | ||
| FleetNamespaceValue = "fleet-namespace" | ||
| ) | ||
|
|
||
| // Reconciler reconciles a MemberCluster object | ||
|
|
@@ -232,6 +237,7 @@ func (r *Reconciler) syncNamespace(ctx context.Context, mc *fleetv1alpha1.Member | |
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: namespaceName, | ||
| OwnerReferences: []metav1.OwnerReference{*toOwnerReference(mc)}, | ||
| Labels: map[string]string{FleetResourceLabelKey: FleetNamespaceValue}, | ||
| }, | ||
| } | ||
|
|
||
|
|
@@ -251,8 +257,17 @@ func (r *Reconciler) syncNamespace(ctx context.Context, mc *fleetv1alpha1.Member | |
| return namespaceName, nil | ||
| } | ||
|
|
||
| // TODO: Update namespace if currentNS != expectedNS. | ||
|
|
||
| // Updates namespace if currentNS != expectedNS. | ||
| if cmp.Equal(currentNS.Labels, expectedNS.Labels) { | ||
| return namespaceName, nil | ||
| } | ||
| currentNS.Labels = expectedNS.Labels | ||
| klog.V(2).InfoS("updating namespace", "memberCluster", klog.KObj(mc), "namespace", namespaceName) | ||
| if err := r.Client.Update(ctx, ¤tNS, client.FieldOwner(utils.MCControllerFieldManagerName)); err != nil { | ||
| return "", fmt.Errorf("failed to update namespace %s: %w", namespaceName, err) | ||
| } | ||
| r.recorder.Event(mc, corev1.EventTypeNormal, eventReasonNamespaceUpdated, "Namespace was updated") | ||
| klog.V(2).InfoS("updated namespace", "memberCluster", klog.KObj(mc), "namespace", namespaceName) | ||
| return namespaceName, nil | ||
| } | ||
|
|
||
|
|
@@ -292,7 +307,7 @@ func (r *Reconciler) syncRole(ctx context.Context, mc *fleetv1alpha1.MemberClust | |
| currentRole.Rules = expectedRole.Rules | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hi Arvind! This is actually about line 304, I know it's not a part of PR, and sorry for pointing it out here, but technically speaking we probably shouldn't use |
||
| klog.V(2).InfoS("updating role", "memberCluster", klog.KObj(mc), "role", roleName) | ||
| if err := r.Client.Update(ctx, ¤tRole, client.FieldOwner(utils.MCControllerFieldManagerName)); err != nil { | ||
| return "", fmt.Errorf("failed to update role %s with rules %+v: %w", roleName, currentRole.Rules, err) | ||
| return "", fmt.Errorf("failed to update role %s: %w", roleName, err) | ||
| } | ||
| r.recorder.Event(mc, corev1.EventTypeNormal, eventReasonRoleUpdated, "role was updated") | ||
| klog.V(2).InfoS("updated role", "memberCluster", klog.KObj(mc), "role", roleName) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,6 +7,7 @@ import ( | |
| "regexp" | ||
|
|
||
| admissionv1 "k8s.io/api/admission/v1" | ||
| rbacv1 "k8s.io/api/rbac/v1" | ||
| v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" | ||
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
| "k8s.io/apimachinery/pkg/runtime" | ||
|
|
@@ -26,6 +27,8 @@ const ( | |
| groupMatch = `^[^.]*\.(.*)` | ||
| crdKind = "CustomResourceDefinition" | ||
| memberClusterKind = "MemberCluster" | ||
| roleKind = "Role" | ||
| roleBindingKind = "RoleBinding" | ||
| ) | ||
|
|
||
| // Add registers the webhook for K8s bulit-in object types. | ||
|
|
@@ -51,6 +54,12 @@ func (v *fleetResourceValidator) Handle(ctx context.Context, req admission.Reque | |
| case createMemberClusterGVK(): | ||
| klog.V(2).InfoS("handling Member cluster resource", "GVK", createMemberClusterGVK()) | ||
| response = v.handleMemberCluster(ctx, req) | ||
| case createRoleGVK(): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hi Arvind! For this one, I am wondering: we are already blocking all requests to Fleet namespaces from non-whitelisted users, i.e., they cannot create anything (incl. roles/rolebindings) in these destinations -> would be this more general rule already cover the cases here?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Makes sense closing this PR and will open a PR to address roles and role bindings along with all other resources in an other PR |
||
| klog.V(2).InfoS("handling Role resource", "GVK", createRoleGVK()) | ||
| response = v.handleRole(req) | ||
| case createRoleBindingGVK(): | ||
| klog.V(2).InfoS("handling Role binding resource", "GVK", createRoleBindingGVK()) | ||
| response = v.handleRoleBinding(req) | ||
| default: | ||
| klog.V(2).InfoS("resource is not monitored by fleet resource validator webhook", "GVK", req.Kind.String()) | ||
| response = admission.Allowed(fmt.Sprintf("user: %s in groups: %v is allowed to modify resource with GVK: %s", req.UserInfo.Username, req.UserInfo.Groups, req.Kind.String())) | ||
|
|
@@ -67,7 +76,7 @@ func (v *fleetResourceValidator) handleCRD(req admission.Request) admission.Resp | |
|
|
||
| // This regex works because every CRD name in kubernetes follows this pattern <plural>.<group>. | ||
| group := regexp.MustCompile(groupMatch).FindStringSubmatch(crd.Name)[1] | ||
| if validation.CheckCRDGroup(group) && !validation.ValidateUserForCRD(v.whiteListedUsers, req.UserInfo) { | ||
| if validation.CheckCRDGroup(group) && !validation.IsMasterGroupUserOrWhiteListedUser(v.whiteListedUsers, req.UserInfo) { | ||
| return admission.Denied(fmt.Sprintf("failed to validate user: %s in groups: %v to modify fleet CRD: %s", req.UserInfo.Username, req.UserInfo.Groups, crd.Name)) | ||
| } | ||
| return admission.Allowed(fmt.Sprintf("user: %s in groups: %v is allowed to modify CRD: %s", req.UserInfo.Username, req.UserInfo.Groups, crd.Name)) | ||
|
|
@@ -86,6 +95,24 @@ func (v *fleetResourceValidator) handleMemberCluster(ctx context.Context, req ad | |
| return admission.Allowed(fmt.Sprintf("user: %s in groups: %v is allowed to modify member cluster: %s", req.UserInfo.Username, req.UserInfo.Groups, mc.Name)) | ||
| } | ||
|
|
||
| func (v *fleetResourceValidator) handleRole(req admission.Request) admission.Response { | ||
| var role rbacv1.Role | ||
| if err := v.decodeRequestObject(req, &role); err != nil { | ||
| return admission.Errored(http.StatusBadRequest, err) | ||
| } | ||
|
|
||
| return validation.ValidateUserForResource(v.whiteListedUsers, req.UserInfo, role.Kind, role.Name) | ||
| } | ||
|
|
||
| func (v *fleetResourceValidator) handleRoleBinding(req admission.Request) admission.Response { | ||
| var rb rbacv1.RoleBinding | ||
| if err := v.decodeRequestObject(req, &rb); err != nil { | ||
| return admission.Errored(http.StatusBadRequest, err) | ||
| } | ||
|
|
||
| return validation.ValidateUserForResource(v.whiteListedUsers, req.UserInfo, rb.Kind, rb.Name) | ||
| } | ||
|
|
||
| func (v *fleetResourceValidator) decodeRequestObject(req admission.Request, obj runtime.Object) error { | ||
| if req.Operation == admissionv1.Delete { | ||
| // req.Object is not populated for delete: https://github.com/kubernetes-sigs/controller-runtime/issues/1762. | ||
|
|
@@ -122,3 +149,19 @@ func createMemberClusterGVK() metav1.GroupVersionKind { | |
| Kind: memberClusterKind, | ||
| } | ||
| } | ||
|
|
||
| func createRoleGVK() metav1.GroupVersionKind { | ||
| return metav1.GroupVersionKind{ | ||
| Group: rbacv1.SchemeGroupVersion.Group, | ||
| Version: rbacv1.SchemeGroupVersion.Version, | ||
| Kind: roleKind, | ||
| } | ||
| } | ||
|
|
||
| func createRoleBindingGVK() metav1.GroupVersionKind { | ||
| return metav1.GroupVersionKind{ | ||
| Group: rbacv1.SchemeGroupVersion.Group, | ||
| Version: rbacv1.SchemeGroupVersion.Version, | ||
| Kind: roleBindingKind, | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi Arvind! I left a comment about this in another PR, PTAL 🙏