diff --git a/pkg/webhook/webhook.go b/pkg/webhook/webhook.go index 3bddefdd0..4ac329a45 100644 --- a/pkg/webhook/webhook.go +++ b/pkg/webhook/webhook.go @@ -52,10 +52,11 @@ import ( ) const ( - FleetWebhookCertFileName = "tls.crt" - FleetWebhookKeyFileName = "tls.key" - FleetWebhookCfgName = "fleet-validating-webhook-configuration" - FleetWebhookSvcName = "fleetwebhook" + fleetWebhookCertFileName = "tls.crt" + fleetWebhookKeyFileName = "tls.key" + fleetValidatingWebhookCfgName = "fleet-validating-webhook-configuration" + fleetGuardRailWebhookCfgName = "fleet-guard-rail-webhook-configuration" + fleetWebhookSvcName = "fleetwebhook" crdResourceName = "customresourcedefinitions" bindingResourceName = "bindings" @@ -94,6 +95,12 @@ const ( var ( admissionReviewVersions = []string{admv1.SchemeGroupVersion.Version, admv1beta1.SchemeGroupVersion.Version} + + failPolicy = admv1.Ignore + sideEffortsNone = admv1.SideEffectClassNone + namespacedScope = admv1.NamespacedScope + clusterScope = admv1.ClusterScope + webhookTimeoutSeconds = pointer.Int32(1) ) var AddToManagerFuncs []func(manager.Manager, []string) error @@ -134,7 +141,7 @@ func NewWebhookConfig(mgr manager.Manager, port int, clientConnectionType *optio mgr: mgr, servicePort: int32(port), serviceNamespace: namespace, - serviceURL: fmt.Sprintf("https://%s.%s.svc.cluster.local:%d", FleetWebhookSvcName, namespace, port), + serviceURL: fmt.Sprintf("https://%s.%s.svc.cluster.local:%d", fleetWebhookSvcName, namespace, port), clientConnectionType: clientConnectionType, enableGuardRail: enableGuardRail, } @@ -157,49 +164,55 @@ func (w *Config) Start(ctx context.Context) error { // createFleetWebhookConfiguration creates the ValidatingWebhookConfiguration object for the webhook. func (w *Config) createFleetWebhookConfiguration(ctx context.Context) error { - whCfg := admv1.ValidatingWebhookConfiguration{ + if err := w.createValidatingWebhookConfiguration(ctx, w.buildFleetValidatingWebhooks(), fleetValidatingWebhookCfgName); err != nil { + return err + } + if w.enableGuardRail { + if err := w.createValidatingWebhookConfiguration(ctx, w.buildFleetGuardRailValidatingWebhooks(), fleetGuardRailWebhookCfgName); err != nil { + return err + } + } + return nil +} + +func (w *Config) createValidatingWebhookConfiguration(ctx context.Context, webhooks []admv1.ValidatingWebhook, configName string) error { + validatingWebhookConfig := admv1.ValidatingWebhookConfiguration{ ObjectMeta: metav1.ObjectMeta{ - Name: FleetWebhookCfgName, + Name: configName, Labels: map[string]string{ "admissions.enforcer/disabled": "true", }, }, - Webhooks: w.buildValidatingWebHooks(), + Webhooks: webhooks, } // We need to ensure this webhook configuration is garbage collected if Fleet is uninstalled from the cluster. // Since the fleet-system namespace is a prerequisite for core Fleet components, we bind to this namespace. - if err := bindWebhookConfigToFleetSystem(ctx, w.mgr.GetClient(), &whCfg); err != nil { + if err := bindWebhookConfigToFleetSystem(ctx, w.mgr.GetClient(), &validatingWebhookConfig); err != nil { return err } - if err := w.mgr.GetClient().Create(ctx, &whCfg); err != nil { + if err := w.mgr.GetClient().Create(ctx, &validatingWebhookConfig); err != nil { if !apierrors.IsAlreadyExists(err) { return err } - klog.V(2).InfoS("validatingwebhookconfiguration exists, need to update", "name", FleetWebhookCfgName) + klog.V(2).InfoS("validating webhook configuration exists, need to overwrite", "name", configName) // Here we simply use delete/create pattern to implement full overwrite - err := w.mgr.GetClient().Delete(ctx, &whCfg) - if err != nil { + if err := w.mgr.GetClient().Delete(ctx, &validatingWebhookConfig); err != nil { return err } - err = w.mgr.GetClient().Create(ctx, &whCfg) - if err != nil { + if err = w.mgr.GetClient().Create(ctx, &validatingWebhookConfig); err != nil { return err } + klog.V(2).InfoS("successfully overwritten validating webhook configuration", "name", configName) return nil } - klog.V(2).InfoS("successfully created validatingwebhookconfiguration", "name", FleetWebhookCfgName) + klog.V(2).InfoS("successfully created validating webhook configuration", "name", configName) return nil } -// buildValidatingWebHooks returns a slice of validating webhook objects the length of slice differs based on whether fleet webhook guard rail is enabled/disabled. -func (w *Config) buildValidatingWebHooks() []admv1.ValidatingWebhook { - failPolicy := admv1.Ignore - sideEffortsNone := admv1.SideEffectClassNone - namespacedScope := admv1.NamespacedScope - clusterScope := admv1.ClusterScope - webhookTimeoutSeconds := pointer.Int32(1) +// buildValidatingWebHooks returns a slice of fleet validating webhook objects. +func (w *Config) buildFleetValidatingWebhooks() []admv1.ValidatingWebhook { webHooks := []admv1.ValidatingWebhook{ { Name: "fleet.pod.validating", @@ -269,212 +282,215 @@ func (w *Config) buildValidatingWebHooks() []admv1.ValidatingWebhook { }, } - if w.enableGuardRail { - // MatchLabels/MatchExpressions values are ANDed to select resources. - fleetMemberNamespaceSelector := &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: placementv1beta1.FleetResourceLabelKey, - Operator: metav1.LabelSelectorOpIn, - Values: []string{"true"}, - }, - }, - } - fleetSystemNamespaceSelector := &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: corev1.LabelMetadataName, - Operator: metav1.LabelSelectorOpIn, - Values: []string{"fleet-system"}, - }, - }, - } - kubeNamespaceSelector := &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: corev1.LabelMetadataName, - Operator: metav1.LabelSelectorOpIn, - Values: []string{"kube-system", "kube-public", "kube-node-lease"}, - }, - }, - } - cudOperations := []admv1.OperationType{ - admv1.Create, - admv1.Update, - admv1.Delete, - } - cuOperations := []admv1.OperationType{ - admv1.Create, - admv1.Update, - } - // we don't monitor lease to prevent the deadlock issue, we also don't monitor events. - namespacedResourcesRules := []admv1.RuleWithOperations{ - // we want to monitor delete operations on all namespaced resources. - { - Operations: []admv1.OperationType{admv1.Delete}, - Rule: createRule([]string{"*"}, []string{"*"}, []string{"*/*"}, &namespacedScope), - }, - // TODO(ArvindThiru): not handling pods, replicasets as part of the fleet guard rail since they have validating webhooks, need to remove validating webhooks before adding these resources to fleet guard rail. - { - Operations: cuOperations, - Rule: createRule([]string{corev1.SchemeGroupVersion.Group}, []string{corev1.SchemeGroupVersion.Version}, []string{bindingResourceName, configMapResourceName, endPointResourceName, - limitRangeResourceName, persistentVolumeClaimsName, persistentVolumeClaimsName + "/status", podTemplateResourceName, - replicationControllerResourceName, replicationControllerResourceName + "/status", resourceQuotaResourceName, resourceQuotaResourceName + "/status", secretResourceName, - serviceAccountResourceName, servicesResourceName, servicesResourceName + "/status"}, &namespacedScope), - }, - { - Operations: cuOperations, - Rule: createRule([]string{appsv1.SchemeGroupVersion.Group}, []string{appsv1.SchemeGroupVersion.Version}, []string{controllerRevisionResourceName, daemonSetResourceName, daemonSetResourceName + "/status", - deploymentResourceName, deploymentResourceName + "/status", statefulSetResourceName, statefulSetResourceName + "/status"}, &namespacedScope), - }, - { - Operations: cuOperations, - Rule: createRule([]string{authorizationv1.SchemeGroupVersion.Group}, []string{authorizationv1.SchemeGroupVersion.Version}, []string{localSubjectAccessReviewResourceName, localSubjectAccessReviewResourceName + "/status"}, &namespacedScope), - }, - { - Operations: cuOperations, - Rule: createRule([]string{autoscalingv1.SchemeGroupVersion.Group}, []string{autoscalingv1.SchemeGroupVersion.Version}, []string{horizontalPodAutoScalerResourceName, horizontalPodAutoScalerResourceName + "/status"}, &namespacedScope), - }, - { - Operations: cuOperations, - Rule: createRule([]string{batchv1.SchemeGroupVersion.Group}, []string{batchv1.SchemeGroupVersion.Version}, []string{cronJobResourceName, cronJobResourceName + "/status", jobResourceName, jobResourceName + "/status"}, &namespacedScope), - }, - { - Operations: cuOperations, - Rule: createRule([]string{discoveryv1.SchemeGroupVersion.Group}, []string{discoveryv1.SchemeGroupVersion.Version}, []string{endPointSlicesResourceName}, &namespacedScope), - }, - { - Operations: cuOperations, - Rule: createRule([]string{networkingv1.SchemeGroupVersion.Group}, []string{networkingv1.SchemeGroupVersion.Version}, []string{ingressResourceName, ingressResourceName + "/status", networkPolicyResourceName, networkPolicyResourceName + "/status"}, &namespacedScope), - }, - { - Operations: cuOperations, - Rule: createRule([]string{policyv1.SchemeGroupVersion.Group}, []string{policyv1.SchemeGroupVersion.Version}, []string{podDisruptionBudgetsResourceName, podDisruptionBudgetsResourceName + "/status"}, &namespacedScope), - }, - { - Operations: cuOperations, - Rule: createRule([]string{rbacv1.SchemeGroupVersion.Group}, []string{rbacv1.SchemeGroupVersion.Version}, []string{roleResourceName, roleBindingResourceName}, &namespacedScope), - }, - // rules for fleet namespaced resources. - { - Operations: cuOperations, - Rule: createRule([]string{storagev1.SchemeGroupVersion.Group}, []string{storagev1.SchemeGroupVersion.Version}, []string{csiStorageCapacityResourceName}, &namespacedScope), - }, - { - Operations: cuOperations, - Rule: createRule([]string{fleetv1alpha1.GroupVersion.Group}, []string{fleetv1alpha1.GroupVersion.Version}, []string{internalMemberClusterResourceName, internalMemberClusterResourceName + "/status"}, &namespacedScope), - }, + return webHooks +} + +// buildFleetGuardRailValidatingWebhooks returns a slice of fleet guard rail validating webhook objects. +func (w *Config) buildFleetGuardRailValidatingWebhooks() []admv1.ValidatingWebhook { + // MatchLabels/MatchExpressions values are ANDed to select resources. + fleetMemberNamespaceSelector := &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ { - Operations: cuOperations, - Rule: createRule([]string{clusterv1beta1.GroupVersion.Group}, []string{clusterv1beta1.GroupVersion.Version}, []string{internalMemberClusterResourceName, internalMemberClusterResourceName + "/status"}, &namespacedScope), + Key: placementv1beta1.FleetResourceLabelKey, + Operator: metav1.LabelSelectorOpIn, + Values: []string{"true"}, }, + }, + } + fleetSystemNamespaceSelector := &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ { - Operations: cuOperations, - Rule: createRule([]string{placementv1beta1.GroupVersion.Group}, []string{placementv1beta1.GroupVersion.Version}, []string{workResourceName, workResourceName + "/status"}, &namespacedScope), + Key: corev1.LabelMetadataName, + Operator: metav1.LabelSelectorOpIn, + Values: []string{"fleet-system"}, }, + }, + } + kubeNamespaceSelector := &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ { - Operations: cuOperations, - Rule: createRule([]string{workv1alpha1.GroupVersion.Group}, []string{workv1alpha1.GroupVersion.Version}, []string{workResourceName, workResourceName + "/status"}, &namespacedScope), + Key: corev1.LabelMetadataName, + Operator: metav1.LabelSelectorOpIn, + Values: []string{"kube-system", "kube-public", "kube-node-lease"}, }, - } - guardRailWebhookConfigurations := []admv1.ValidatingWebhook{ - { - Name: "fleet.customresourcedefinition.validating", - ClientConfig: w.createClientConfig(fleetresourcehandler.ValidationPath), - FailurePolicy: &failPolicy, - SideEffects: &sideEffortsNone, - AdmissionReviewVersions: admissionReviewVersions, - Rules: []admv1.RuleWithOperations{ - { - Operations: cudOperations, - Rule: createRule([]string{apiextensionsv1.SchemeGroupVersion.Group}, []string{apiextensionsv1.SchemeGroupVersion.Version}, []string{crdResourceName}, &clusterScope), - }, + }, + } + cudOperations := []admv1.OperationType{ + admv1.Create, + admv1.Update, + admv1.Delete, + } + cuOperations := []admv1.OperationType{ + admv1.Create, + admv1.Update, + } + // we don't monitor lease to prevent the deadlock issue, we also don't monitor events. + namespacedResourcesRules := []admv1.RuleWithOperations{ + // we want to monitor delete operations on all namespaced resources. + { + Operations: []admv1.OperationType{admv1.Delete}, + Rule: createRule([]string{"*"}, []string{"*"}, []string{"*/*"}, &namespacedScope), + }, + // TODO(ArvindThiru): not handling pods, replicasets as part of the fleet guard rail since they have validating webhooks, need to remove validating webhooks before adding these resources to fleet guard rail. + { + Operations: cuOperations, + Rule: createRule([]string{corev1.SchemeGroupVersion.Group}, []string{corev1.SchemeGroupVersion.Version}, []string{bindingResourceName, configMapResourceName, endPointResourceName, + limitRangeResourceName, persistentVolumeClaimsName, persistentVolumeClaimsName + "/status", podTemplateResourceName, + replicationControllerResourceName, replicationControllerResourceName + "/status", resourceQuotaResourceName, resourceQuotaResourceName + "/status", secretResourceName, + serviceAccountResourceName, servicesResourceName, servicesResourceName + "/status"}, &namespacedScope), + }, + { + Operations: cuOperations, + Rule: createRule([]string{appsv1.SchemeGroupVersion.Group}, []string{appsv1.SchemeGroupVersion.Version}, []string{controllerRevisionResourceName, daemonSetResourceName, daemonSetResourceName + "/status", + deploymentResourceName, deploymentResourceName + "/status", statefulSetResourceName, statefulSetResourceName + "/status"}, &namespacedScope), + }, + { + Operations: cuOperations, + Rule: createRule([]string{authorizationv1.SchemeGroupVersion.Group}, []string{authorizationv1.SchemeGroupVersion.Version}, []string{localSubjectAccessReviewResourceName, localSubjectAccessReviewResourceName + "/status"}, &namespacedScope), + }, + { + Operations: cuOperations, + Rule: createRule([]string{autoscalingv1.SchemeGroupVersion.Group}, []string{autoscalingv1.SchemeGroupVersion.Version}, []string{horizontalPodAutoScalerResourceName, horizontalPodAutoScalerResourceName + "/status"}, &namespacedScope), + }, + { + Operations: cuOperations, + Rule: createRule([]string{batchv1.SchemeGroupVersion.Group}, []string{batchv1.SchemeGroupVersion.Version}, []string{cronJobResourceName, cronJobResourceName + "/status", jobResourceName, jobResourceName + "/status"}, &namespacedScope), + }, + { + Operations: cuOperations, + Rule: createRule([]string{discoveryv1.SchemeGroupVersion.Group}, []string{discoveryv1.SchemeGroupVersion.Version}, []string{endPointSlicesResourceName}, &namespacedScope), + }, + { + Operations: cuOperations, + Rule: createRule([]string{networkingv1.SchemeGroupVersion.Group}, []string{networkingv1.SchemeGroupVersion.Version}, []string{ingressResourceName, ingressResourceName + "/status", networkPolicyResourceName, networkPolicyResourceName + "/status"}, &namespacedScope), + }, + { + Operations: cuOperations, + Rule: createRule([]string{policyv1.SchemeGroupVersion.Group}, []string{policyv1.SchemeGroupVersion.Version}, []string{podDisruptionBudgetsResourceName, podDisruptionBudgetsResourceName + "/status"}, &namespacedScope), + }, + { + Operations: cuOperations, + Rule: createRule([]string{rbacv1.SchemeGroupVersion.Group}, []string{rbacv1.SchemeGroupVersion.Version}, []string{roleResourceName, roleBindingResourceName}, &namespacedScope), + }, + // rules for fleet namespaced resources. + { + Operations: cuOperations, + Rule: createRule([]string{storagev1.SchemeGroupVersion.Group}, []string{storagev1.SchemeGroupVersion.Version}, []string{csiStorageCapacityResourceName}, &namespacedScope), + }, + { + Operations: cuOperations, + Rule: createRule([]string{fleetv1alpha1.GroupVersion.Group}, []string{fleetv1alpha1.GroupVersion.Version}, []string{internalMemberClusterResourceName, internalMemberClusterResourceName + "/status"}, &namespacedScope), + }, + { + Operations: cuOperations, + Rule: createRule([]string{clusterv1beta1.GroupVersion.Group}, []string{clusterv1beta1.GroupVersion.Version}, []string{internalMemberClusterResourceName, internalMemberClusterResourceName + "/status"}, &namespacedScope), + }, + { + Operations: cuOperations, + Rule: createRule([]string{placementv1beta1.GroupVersion.Group}, []string{placementv1beta1.GroupVersion.Version}, []string{workResourceName, workResourceName + "/status"}, &namespacedScope), + }, + { + Operations: cuOperations, + Rule: createRule([]string{workv1alpha1.GroupVersion.Group}, []string{workv1alpha1.GroupVersion.Version}, []string{workResourceName, workResourceName + "/status"}, &namespacedScope), + }, + } + guardRailWebhookConfigurations := []admv1.ValidatingWebhook{ + { + Name: "fleet.customresourcedefinition.validating", + ClientConfig: w.createClientConfig(fleetresourcehandler.ValidationPath), + FailurePolicy: &failPolicy, + SideEffects: &sideEffortsNone, + AdmissionReviewVersions: admissionReviewVersions, + Rules: []admv1.RuleWithOperations{ + { + Operations: cudOperations, + Rule: createRule([]string{apiextensionsv1.SchemeGroupVersion.Group}, []string{apiextensionsv1.SchemeGroupVersion.Version}, []string{crdResourceName}, &clusterScope), }, - TimeoutSeconds: webhookTimeoutSeconds, }, - { - Name: "fleet.membercluster.validating", - ClientConfig: w.createClientConfig(fleetresourcehandler.ValidationPath), - FailurePolicy: &failPolicy, - SideEffects: &sideEffortsNone, - AdmissionReviewVersions: admissionReviewVersions, - Rules: []admv1.RuleWithOperations{ - { - Operations: cudOperations, - Rule: createRule([]string{clusterv1beta1.GroupVersion.Group}, []string{clusterv1beta1.GroupVersion.Version}, []string{memberClusterResourceName, memberClusterResourceName + "/status"}, &clusterScope), - }, + TimeoutSeconds: webhookTimeoutSeconds, + }, + { + Name: "fleet.membercluster.validating", + ClientConfig: w.createClientConfig(fleetresourcehandler.ValidationPath), + FailurePolicy: &failPolicy, + SideEffects: &sideEffortsNone, + AdmissionReviewVersions: admissionReviewVersions, + Rules: []admv1.RuleWithOperations{ + { + Operations: cudOperations, + Rule: createRule([]string{clusterv1beta1.GroupVersion.Group}, []string{clusterv1beta1.GroupVersion.Version}, []string{memberClusterResourceName, memberClusterResourceName + "/status"}, &clusterScope), }, - TimeoutSeconds: webhookTimeoutSeconds, }, - { - Name: "fleet.v1alpha1.membercluster.validating", - ClientConfig: w.createClientConfig(fleetresourcehandler.ValidationPath), - FailurePolicy: &failPolicy, - SideEffects: &sideEffortsNone, - AdmissionReviewVersions: admissionReviewVersions, - Rules: []admv1.RuleWithOperations{ - { - Operations: cudOperations, - Rule: createRule([]string{fleetv1alpha1.GroupVersion.Group}, []string{fleetv1alpha1.GroupVersion.Version}, []string{memberClusterResourceName, memberClusterResourceName + "/status"}, &clusterScope), - }, + TimeoutSeconds: webhookTimeoutSeconds, + }, + { + Name: "fleet.v1alpha1.membercluster.validating", + ClientConfig: w.createClientConfig(fleetresourcehandler.ValidationPath), + FailurePolicy: &failPolicy, + SideEffects: &sideEffortsNone, + AdmissionReviewVersions: admissionReviewVersions, + Rules: []admv1.RuleWithOperations{ + { + Operations: cudOperations, + Rule: createRule([]string{fleetv1alpha1.GroupVersion.Group}, []string{fleetv1alpha1.GroupVersion.Version}, []string{memberClusterResourceName, memberClusterResourceName + "/status"}, &clusterScope), }, - TimeoutSeconds: webhookTimeoutSeconds, - }, - { - Name: "fleet.fleetmembernamespacedresources.validating", - ClientConfig: w.createClientConfig(fleetresourcehandler.ValidationPath), - FailurePolicy: &failPolicy, - SideEffects: &sideEffortsNone, - AdmissionReviewVersions: admissionReviewVersions, - NamespaceSelector: fleetMemberNamespaceSelector, - Rules: namespacedResourcesRules, - TimeoutSeconds: webhookTimeoutSeconds, }, - { - Name: "fleet.fleetsystemnamespacedresources.validating", - ClientConfig: w.createClientConfig(fleetresourcehandler.ValidationPath), - FailurePolicy: &failPolicy, - SideEffects: &sideEffortsNone, - AdmissionReviewVersions: admissionReviewVersions, - NamespaceSelector: fleetSystemNamespaceSelector, - Rules: namespacedResourcesRules, - TimeoutSeconds: webhookTimeoutSeconds, - }, - { - Name: "fleet.kubenamespacedresources.validating", - ClientConfig: w.createClientConfig(fleetresourcehandler.ValidationPath), - FailurePolicy: &failPolicy, - SideEffects: &sideEffortsNone, - AdmissionReviewVersions: admissionReviewVersions, - NamespaceSelector: kubeNamespaceSelector, - Rules: namespacedResourcesRules, - TimeoutSeconds: webhookTimeoutSeconds, - }, - { - Name: "fleet.namespace.validating", - ClientConfig: w.createClientConfig(fleetresourcehandler.ValidationPath), - FailurePolicy: &failPolicy, - SideEffects: &sideEffortsNone, - AdmissionReviewVersions: admissionReviewVersions, - Rules: []admv1.RuleWithOperations{ - { - Operations: cudOperations, - Rule: createRule([]string{corev1.SchemeGroupVersion.Group}, []string{corev1.SchemeGroupVersion.Version}, []string{namespaceResourceName}, &clusterScope), - }, + TimeoutSeconds: webhookTimeoutSeconds, + }, + { + Name: "fleet.fleetmembernamespacedresources.validating", + ClientConfig: w.createClientConfig(fleetresourcehandler.ValidationPath), + FailurePolicy: &failPolicy, + SideEffects: &sideEffortsNone, + AdmissionReviewVersions: admissionReviewVersions, + NamespaceSelector: fleetMemberNamespaceSelector, + Rules: namespacedResourcesRules, + TimeoutSeconds: webhookTimeoutSeconds, + }, + { + Name: "fleet.fleetsystemnamespacedresources.validating", + ClientConfig: w.createClientConfig(fleetresourcehandler.ValidationPath), + FailurePolicy: &failPolicy, + SideEffects: &sideEffortsNone, + AdmissionReviewVersions: admissionReviewVersions, + NamespaceSelector: fleetSystemNamespaceSelector, + Rules: namespacedResourcesRules, + TimeoutSeconds: webhookTimeoutSeconds, + }, + { + Name: "fleet.kubenamespacedresources.validating", + ClientConfig: w.createClientConfig(fleetresourcehandler.ValidationPath), + FailurePolicy: &failPolicy, + SideEffects: &sideEffortsNone, + AdmissionReviewVersions: admissionReviewVersions, + NamespaceSelector: kubeNamespaceSelector, + Rules: namespacedResourcesRules, + TimeoutSeconds: webhookTimeoutSeconds, + }, + { + Name: "fleet.namespace.validating", + ClientConfig: w.createClientConfig(fleetresourcehandler.ValidationPath), + FailurePolicy: &failPolicy, + SideEffects: &sideEffortsNone, + AdmissionReviewVersions: admissionReviewVersions, + Rules: []admv1.RuleWithOperations{ + { + Operations: cudOperations, + Rule: createRule([]string{corev1.SchemeGroupVersion.Group}, []string{corev1.SchemeGroupVersion.Version}, []string{namespaceResourceName}, &clusterScope), }, - TimeoutSeconds: webhookTimeoutSeconds, }, - } - webHooks = append(webHooks, guardRailWebhookConfigurations...) + TimeoutSeconds: webhookTimeoutSeconds, + }, } - return webHooks + + return guardRailWebhookConfigurations } // createClientConfig generates the client configuration with either service ref or URL for the argued interface. func (w *Config) createClientConfig(validationPath string) admv1.WebhookClientConfig { serviceRef := admv1.ServiceReference{ Namespace: w.serviceNamespace, - Name: FleetWebhookSvcName, + Name: fleetWebhookSvcName, Port: pointer.Int32(w.servicePort), } serviceEndpoint := w.serviceURL + validationPath @@ -551,15 +567,15 @@ func (w *Config) genSelfSignedCert() (caPEMByte, certPEMByte, keyPEMByte []byte, caPEMByte = caPEM.Bytes() dnsNames := []string{ - fmt.Sprintf("%s.%s.svc", FleetWebhookSvcName, w.serviceNamespace), - fmt.Sprintf("%s.%s.svc.cluster.local", FleetWebhookSvcName, w.serviceNamespace), + fmt.Sprintf("%s.%s.svc", fleetWebhookSvcName, w.serviceNamespace), + fmt.Sprintf("%s.%s.svc.cluster.local", fleetWebhookSvcName, w.serviceNamespace), } // server cert config cert := &x509.Certificate{ DNSNames: dnsNames, SerialNumber: big.NewInt(2022), Subject: pkix.Name{ - CommonName: fmt.Sprintf("%s.cert.server", FleetWebhookSvcName), + CommonName: fmt.Sprintf("%s.cert.server", fleetWebhookSvcName), OrganizationalUnit: []string{"Azure Kubernetes Service"}, Organization: []string{"Microsoft"}, Locality: []string{"Redmond"}, @@ -615,7 +631,7 @@ func genCertAndKeyFile(certData, keyData []byte, certDir string) error { if err := os.MkdirAll(certDir, 0755); err != nil { return fmt.Errorf("could not create directory %q to store certificates: %w", certDir, err) } - certPath := filepath.Join(certDir, FleetWebhookCertFileName) + certPath := filepath.Join(certDir, fleetWebhookCertFileName) f, err := os.OpenFile(filepath.Clean(certPath), os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0600) if err != nil { return fmt.Errorf("could not open %q: %w", certPath, err) @@ -629,7 +645,7 @@ func genCertAndKeyFile(certData, keyData []byte, certDir string) error { return err } - keyPath := filepath.Join(certDir, FleetWebhookKeyFileName) + keyPath := filepath.Join(certDir, fleetWebhookKeyFileName) kf, err := os.OpenFile(filepath.Clean(keyPath), os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0600) if err != nil { return fmt.Errorf("could not open %q: %w", keyPath, err) diff --git a/pkg/webhook/webhook_test.go b/pkg/webhook/webhook_test.go index 174abfb52..4b9e7b3bd 100644 --- a/pkg/webhook/webhook_test.go +++ b/pkg/webhook/webhook_test.go @@ -9,37 +9,51 @@ import ( "go.goms.io/fleet/pkg/utils" ) -func TestBuildValidatingWebhooks(t *testing.T) { +func TestBuildFleetValidatingWebhooks(t *testing.T) { url := options.WebhookClientConnectionType("url") testCases := map[string]struct { config Config wantLength int }{ - "disable guard rail": { + "valid input": { config: Config{ serviceNamespace: "test-namespace", servicePort: 8080, serviceURL: "test-url", clientConnectionType: &url, - enableGuardRail: false, }, wantLength: 4, }, - "enable guard rail": { + } + + for testName, testCase := range testCases { + t.Run(testName, func(t *testing.T) { + gotResult := testCase.config.buildFleetValidatingWebhooks() + assert.Equal(t, testCase.wantLength, len(gotResult), utils.TestCaseMsg, testName) + }) + } +} + +func TestBuildFleetGuardRailValidatingWebhooks(t *testing.T) { + url := options.WebhookClientConnectionType("url") + testCases := map[string]struct { + config Config + wantLength int + }{ + "valid input": { config: Config{ serviceNamespace: "test-namespace", servicePort: 8080, serviceURL: "test-url", clientConnectionType: &url, - enableGuardRail: true, }, - wantLength: 11, + wantLength: 7, }, } for testName, testCase := range testCases { t.Run(testName, func(t *testing.T) { - gotResult := testCase.config.buildValidatingWebHooks() + gotResult := testCase.config.buildFleetGuardRailValidatingWebhooks() assert.Equal(t, testCase.wantLength, len(gotResult), utils.TestCaseMsg, testName) }) }