Skip to content

Commit

Permalink
handle IMC, Work
Browse files Browse the repository at this point in the history
  • Loading branch information
Arvindthiru committed Oct 19, 2023
1 parent d88b584 commit 4169e93
Show file tree
Hide file tree
Showing 4 changed files with 26 additions and 357 deletions.
37 changes: 16 additions & 21 deletions pkg/webhook/fleetresourcehandler/fleetresourcehandler_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
workv1alpha1 "sigs.k8s.io/work-api/pkg/apis/v1alpha1"

clusterv1beta1 "go.goms.io/fleet/apis/cluster/v1beta1"
placementv1beta1 "go.goms.io/fleet/apis/placement/v1beta1"
fleetv1alpha1 "go.goms.io/fleet/apis/v1alpha1"
"go.goms.io/fleet/pkg/utils"
"go.goms.io/fleet/pkg/webhook/validation"
Expand All @@ -37,14 +38,15 @@ const (
)

var (
crdGVK = metav1.GroupVersionKind{Group: v1.SchemeGroupVersion.Group, Version: v1.SchemeGroupVersion.Version, Kind: "CustomResourceDefinition"}
v1Alpha1MCGVK = metav1.GroupVersionKind{Group: fleetv1alpha1.GroupVersion.Group, Version: fleetv1alpha1.GroupVersion.Version, Kind: "MemberCluster"}
v1Alpha1IMCGVK = metav1.GroupVersionKind{Group: fleetv1alpha1.GroupVersion.Group, Version: fleetv1alpha1.GroupVersion.Version, Kind: "InternalMemberCluster"}
mcGVK = metav1.GroupVersionKind{Group: clusterv1beta1.GroupVersion.Group, Version: clusterv1beta1.GroupVersion.Version, Kind: "MemberCluster"}
imcGVK = metav1.GroupVersionKind{Group: clusterv1beta1.GroupVersion.Group, Version: clusterv1beta1.GroupVersion.Version, Kind: "InternalMemberCluster"}
namespaceGVK = metav1.GroupVersionKind{Group: corev1.SchemeGroupVersion.Group, Version: corev1.SchemeGroupVersion.Version, Kind: "Namespace"}
workGVK = metav1.GroupVersionKind{Group: workv1alpha1.GroupVersion.Group, Version: workv1alpha1.GroupVersion.Version, Kind: "Work"}
eventGVK = metav1.GroupVersionKind{Group: corev1.SchemeGroupVersion.Group, Version: corev1.SchemeGroupVersion.Version, Kind: "Event"}
crdGVK = metav1.GroupVersionKind{Group: v1.SchemeGroupVersion.Group, Version: v1.SchemeGroupVersion.Version, Kind: "CustomResourceDefinition"}
v1Alpha1MCGVK = metav1.GroupVersionKind{Group: fleetv1alpha1.GroupVersion.Group, Version: fleetv1alpha1.GroupVersion.Version, Kind: "MemberCluster"}
v1Alpha1IMCGVK = metav1.GroupVersionKind{Group: fleetv1alpha1.GroupVersion.Group, Version: fleetv1alpha1.GroupVersion.Version, Kind: "InternalMemberCluster"}
v1Alpha1WorkGVK = metav1.GroupVersionKind{Group: workv1alpha1.GroupVersion.Group, Version: workv1alpha1.GroupVersion.Version, Kind: "Work"}
mcGVK = metav1.GroupVersionKind{Group: clusterv1beta1.GroupVersion.Group, Version: clusterv1beta1.GroupVersion.Version, Kind: "MemberCluster"}
imcGVK = metav1.GroupVersionKind{Group: clusterv1beta1.GroupVersion.Group, Version: clusterv1beta1.GroupVersion.Version, Kind: "InternalMemberCluster"}
workGVK = metav1.GroupVersionKind{Group: placementv1beta1.GroupVersion.Group, Version: placementv1beta1.GroupVersion.Version, Kind: "Work"}
namespaceGVK = metav1.GroupVersionKind{Group: corev1.SchemeGroupVersion.Group, Version: corev1.SchemeGroupVersion.Version, Kind: "Namespace"}
eventGVK = metav1.GroupVersionKind{Group: corev1.SchemeGroupVersion.Group, Version: corev1.SchemeGroupVersion.Version, Kind: "Event"}
)

// Add registers the webhook for K8s built-in object types.
Expand Down Expand Up @@ -83,7 +85,7 @@ func (v *fleetResourceValidator) Handle(ctx context.Context, req admission.Reque
case req.Kind == namespaceGVK:
klog.V(2).InfoS("handling namespace resource", "GVK", namespaceGVK, "namespacedName", namespacedName, "operation", req.Operation, "subResource", req.SubResource)
response = v.handleNamespace(req)
case req.Kind == imcGVK || req.Kind == v1Alpha1IMCGVK || req.Kind == workGVK:
case req.Kind == v1Alpha1IMCGVK || req.Kind == v1Alpha1WorkGVK || req.Kind == imcGVK || req.Kind == workGVK:
klog.V(2).InfoS(fmt.Sprintf(handleResourceFmt, req.RequestKind.Kind), "GVK", imcGVK, "namespacedName", namespacedName, "operation", req.Operation, "subResource", req.SubResource)
response = v.handleFleetMemberNamespacedResource(ctx, req)
case req.Kind == eventGVK:
Expand Down Expand Up @@ -163,22 +165,15 @@ func (v *fleetResourceValidator) handleFleetMemberNamespacedResource(ctx context
}

// handleEvent allows/denies request to modify event after validation.
func (v *fleetResourceValidator) handleEvent(ctx context.Context, req admission.Request) admission.Response {
// hub agent creates events for MC which is cluster scoped, only member agent creates events in fleet-member prefixed namespaces.
if strings.HasPrefix(req.Namespace, fleetMemberNamespacePrefix) {
mcName := parseMemberClusterNameFromNamespace(req.Namespace)
return validation.ValidateMCIdentity(ctx, v.client, req, mcName)
}
if strings.HasPrefix(req.Namespace, fleetNamespacePrefix) || strings.HasPrefix(req.Namespace, kubeNamespacePrefix) {
return validation.ValidateUserForResource(req, v.whiteListedUsers)
}
return admission.Allowed("namespace name for this event is not a reserved namespace so we allow all operations for events on these namespaces")
func (v *fleetResourceValidator) handleEvent(_ context.Context, _ admission.Request) admission.Response {
// currently allowing all events will handle events after v1alpha1 resources are removed.
return admission.Allowed("all events are allowed")
}

// handlerNamespace allows/denies request to modify namespace after validation.
func (v *fleetResourceValidator) handleNamespace(req admission.Request) admission.Response {
fleetMatchResult := strings.HasPrefix(req.Name, "fleet")
kubeMatchResult := strings.HasPrefix(req.Name, "kube")
fleetMatchResult := strings.HasPrefix(req.Name, fleetNamespacePrefix)
kubeMatchResult := strings.HasPrefix(req.Name, kubeNamespacePrefix)
if fleetMatchResult || kubeMatchResult {
return validation.ValidateUserForResource(req, v.whiteListedUsers)
}
Expand Down
181 changes: 0 additions & 181 deletions pkg/webhook/fleetresourcehandler/fleetresourcehandler_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -885,184 +885,3 @@ func TestHandleNamespace(t *testing.T) {
})
}
}

func TestHandleEvent(t *testing.T) {
mockClient := &test.MockClient{
MockGet: func(ctx context.Context, key client.ObjectKey, obj client.Object) error {
if key.Name == mcName {
o := obj.(*fleetv1alpha1.MemberCluster)
*o = fleetv1alpha1.MemberCluster{
ObjectMeta: metav1.ObjectMeta{
Name: mcName,
},
Spec: fleetv1alpha1.MemberClusterSpec{
Identity: rbacv1.Subject{
Name: "test-identity",
},
},
}
return nil
}
return errors.New("cannot find member cluster")
},
}
testCases := map[string]struct {
req admission.Request
resourceValidator fleetResourceValidator
wantResponse admission.Response
}{
"allow user in MC identity to create in fleet member namespace": {
req: admission.Request{
AdmissionRequest: admissionv1.AdmissionRequest{
Name: "test-event",
Namespace: "fleet-member-test-mc",
RequestKind: &eventGVK,
UserInfo: authenticationv1.UserInfo{
Username: "test-identity",
Groups: []string{"test-group"},
},
Operation: admissionv1.Create,
},
},
resourceValidator: fleetResourceValidator{
client: mockClient,
},
wantResponse: admission.Allowed(fmt.Sprintf(resourceAllowedFormat, "test-identity", []string{"test-group"}, admissionv1.Create, &eventGVK, "", types.NamespacedName{Name: "test-event", Namespace: "fleet-member-test-mc"})),
},
"allow hub-agent-sa in MC identity with create in fleet member namespace": {
req: admission.Request{
AdmissionRequest: admissionv1.AdmissionRequest{
Name: "test-event",
Namespace: "fleet-member-test-mc",
RequestKind: &eventGVK,
UserInfo: authenticationv1.UserInfo{
Username: "system:serviceaccount:fleet-system:hub-agent-sa",
Groups: []string{"system:serviceaccounts"},
},
Operation: admissionv1.Create,
},
},
resourceValidator: fleetResourceValidator{
client: &test.MockClient{
MockGet: func(ctx context.Context, key client.ObjectKey, obj client.Object) error {
o := obj.(*fleetv1alpha1.MemberCluster)
*o = fleetv1alpha1.MemberCluster{
ObjectMeta: metav1.ObjectMeta{
Name: mcName,
},
Spec: fleetv1alpha1.MemberClusterSpec{
Identity: rbacv1.Subject{
Name: "hub-agent-sa",
},
},
}
return nil
},
},
},
wantResponse: admission.Allowed(fmt.Sprintf(resourceAllowedFormat, "system:serviceaccount:fleet-system:hub-agent-sa", []string{"system:serviceaccounts"}, admissionv1.Create, &eventGVK, "", types.NamespacedName{Name: "test-event", Namespace: "fleet-member-test-mc"})),
},
"deny user in system:masters group with create in a fleet member cluster namespace": {
req: admission.Request{
AdmissionRequest: admissionv1.AdmissionRequest{
Name: "test-event",
Namespace: "fleet-member-test-mc",
RequestKind: &eventGVK,
UserInfo: authenticationv1.UserInfo{
Username: "testUser",
Groups: []string{"system:masters"},
},
Operation: admissionv1.Create,
},
},
resourceValidator: fleetResourceValidator{
client: mockClient,
},
wantResponse: admission.Denied(fmt.Sprintf(resourceDeniedFormat, "testUser", []string{"system:masters"}, admissionv1.Create, &eventGVK, "", types.NamespacedName{Name: "test-event", Namespace: "fleet-member-test-mc"})),
},
"allow user in system:masters group with create in fleet-system namespace": {
req: admission.Request{
AdmissionRequest: admissionv1.AdmissionRequest{
Name: "test-event",
Namespace: "fleet-system",
RequestKind: &eventGVK,
UserInfo: authenticationv1.UserInfo{
Username: "testUser",
Groups: []string{"system:masters"},
},
Operation: admissionv1.Create,
},
},
wantResponse: admission.Allowed(fmt.Sprintf(resourceAllowedFormat, "testUser", []string{"system:masters"}, admissionv1.Create, &eventGVK, "", types.NamespacedName{Name: "test-event", Namespace: "fleet-system"})),
},
"deny user not is system:masters group with create in fleet-system namespace": {
req: admission.Request{
AdmissionRequest: admissionv1.AdmissionRequest{
Name: "test-event",
Namespace: "te",
RequestKind: &eventGVK,
UserInfo: authenticationv1.UserInfo{
Username: "testUser",
Groups: []string{"testGroup"},
},
Operation: admissionv1.Create,
},
},
wantResponse: admission.Allowed("namespace name for this event is not a reserved namespace so we allow all operations for events on these namespaces"),
},
"allow user in system:masters group with create in kube-system namespace": {
req: admission.Request{
AdmissionRequest: admissionv1.AdmissionRequest{
Name: "test-event",
Namespace: "kube-system",
RequestKind: &eventGVK,
UserInfo: authenticationv1.UserInfo{
Username: "testUser",
Groups: []string{"system:masters"},
},
Operation: admissionv1.Create,
},
},
wantResponse: admission.Allowed(fmt.Sprintf(resourceAllowedFormat, "testUser", []string{"system:masters"}, admissionv1.Create, &eventGVK, "", types.NamespacedName{Name: "test-event", Namespace: "kube-system"})),
},
"allow user not in system:masters group with create in non reserved namespace": {
req: admission.Request{
AdmissionRequest: admissionv1.AdmissionRequest{
Name: "test-event",
Namespace: "test-ns",
RequestKind: &eventGVK,
UserInfo: authenticationv1.UserInfo{
Username: "testUser",
Groups: []string{"testGroup"},
},
Operation: admissionv1.Create,
},
},
wantResponse: admission.Allowed("namespace name for this event is not a reserved namespace so we allow all operations for events on these namespaces"),
},
"allow request if get MC failed with internal server error": {
req: admission.Request{
AdmissionRequest: admissionv1.AdmissionRequest{
Name: "test-event",
Namespace: "fleet-member",
RequestKind: &eventGVK,
UserInfo: authenticationv1.UserInfo{
Username: "testUser",
Groups: []string{"system:masters"},
},
Operation: admissionv1.Create,
},
},
resourceValidator: fleetResourceValidator{
client: mockClient,
},
wantResponse: admission.Allowed(fmt.Sprintf(resourceAllowedGetMCFailed, "testUser", []string{"system:masters"}, admissionv1.Create, &eventGVK, "", types.NamespacedName{Name: "test-event", Namespace: "fleet-member"})),
},
}
for testName, testCase := range testCases {
t.Run(testName, func(t *testing.T) {
gotResult := testCase.resourceValidator.handleEvent(context.Background(), testCase.req)
assert.Equal(t, testCase.wantResponse, gotResult, utils.TestCaseMsg, testName)
})
}
}
16 changes: 10 additions & 6 deletions pkg/webhook/validation/uservalidation.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ import (
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"

clusterv1beta1 "go.goms.io/fleet/apis/cluster/v1beta1"
placementv1beta1 "go.goms.io/fleet/apis/placement/v1beta1"
fleetv1alpha1 "go.goms.io/fleet/apis/v1alpha1"
workv1alpha1 "sigs.k8s.io/work-api/pkg/apis/v1alpha1"
)

const (
Expand All @@ -36,9 +38,11 @@ const (
)

var (
fleetCRDGroups = []string{"networking.fleet.azure.com", "fleet.azure.com", "multicluster.x-k8s.io", "cluster.kubernetes-fleet.io", "placement.kubernetes-fleet.io"}
imcGVK = metav1.GroupVersionKind{Group: clusterv1beta1.GroupVersion.Group, Version: clusterv1beta1.GroupVersion.Version, Kind: "InternalMemberCluster"}
v1Alpha1IMCGVK = metav1.GroupVersionKind{Group: fleetv1alpha1.GroupVersion.Group, Version: fleetv1alpha1.GroupVersion.Version, Kind: "InternalMemberCluster"}
fleetCRDGroups = []string{"networking.fleet.azure.com", "fleet.azure.com", "multicluster.x-k8s.io", "cluster.kubernetes-fleet.io", "placement.kubernetes-fleet.io"}
v1Alpha1IMCGVK = metav1.GroupVersionKind{Group: fleetv1alpha1.GroupVersion.Group, Version: fleetv1alpha1.GroupVersion.Version, Kind: "InternalMemberCluster"}
v1Alpha1WorkGVK = metav1.GroupVersionKind{Group: workv1alpha1.GroupVersion.Group, Version: workv1alpha1.GroupVersion.Version, Kind: "Work"}
imcGVK = metav1.GroupVersionKind{Group: clusterv1beta1.GroupVersion.Group, Version: clusterv1beta1.GroupVersion.Version, Kind: "InternalMemberCluster"}
workGVK = metav1.GroupVersionKind{Group: placementv1beta1.GroupVersion.Group, Version: placementv1beta1.GroupVersion.Version, Kind: "Work"}
)

// ValidateUserForFleetCRD checks to see if user is not allowed to modify fleet CRDs.
Expand Down Expand Up @@ -169,16 +173,16 @@ func ValidateMCIdentity(ctx context.Context, client client.Client, req admission
var identity string
namespacedName := types.NamespacedName{Name: req.Name, Namespace: req.Namespace}
userInfo := req.UserInfo
if *req.RequestKind == v1Alpha1IMCGVK {
if *req.RequestKind == v1Alpha1IMCGVK || *req.RequestKind == v1Alpha1WorkGVK {
var mc fleetv1alpha1.MemberCluster
if err := client.Get(ctx, types.NamespacedName{Name: mcName}, &mc); err != nil {
// fail open, if the webhook cannot get member cluster resources we don't block the request.
klog.V(2).ErrorS(err, fmt.Sprintf("failed to get member cluster resource for request to modify %+v/%s, allowing request to be handled by api server", req.RequestKind, req.SubResource),
klog.V(2).ErrorS(err, fmt.Sprintf("failed to get v1alpha1 member cluster resource for request to modify %+v/%s, allowing request to be handled by api server", req.RequestKind, req.SubResource),
"user", userInfo.Username, "groups", userInfo.Groups, "namespacedName", namespacedName)
return admission.Allowed(fmt.Sprintf(resourceAllowedGetMCFailed, userInfo.Username, userInfo.Groups, req.Operation, req.RequestKind, req.SubResource, namespacedName))
}
identity = mc.Spec.Identity.Name
} else if *req.RequestKind == imcGVK {
} else if *req.RequestKind == imcGVK || *req.RequestKind == workGVK {
var mc clusterv1beta1.MemberCluster
if err := client.Get(ctx, types.NamespacedName{Name: mcName}, &mc); err != nil {
// fail open, if the webhook cannot get member cluster resources we don't block the request.
Expand Down
Loading

0 comments on commit 4169e93

Please sign in to comment.