Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions pkg/utils/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,10 +242,14 @@ func ShouldPropagateObj(informerManager informer.Manager, uObj *unstructured.Uns
return true, nil
}

// ShouldPropagateNamespace decides if we should propagate the resources in the namespace
// IsReservedNamespace indicates if an argued namespace is reserved.
func IsReservedNamespace(namespace string) bool {
return strings.HasPrefix(namespace, fleetPrefix) || strings.HasPrefix(namespace, kubePrefix)
}

// ShouldPropagateNamespace decides if we should propagate the resources in the namespace.
func ShouldPropagateNamespace(namespace string, skippedNamespaces map[string]bool) bool {
// special case for namespace have the reserved prefix
if strings.HasPrefix(namespace, fleetPrefix) || strings.HasPrefix(namespace, kubePrefix) {
if IsReservedNamespace(namespace) {
return false
}

Expand Down
14 changes: 14 additions & 0 deletions pkg/webhook/add_replicaset.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
Copyright (c) Microsoft Corporation.
Licensed under the MIT license.
*/

package webhook

import (
"go.goms.io/fleet/pkg/webhook/replicaset"
)

func init() {
AddToManagerFuncs = append(AddToManagerFuncs, replicaset.Add)
}
4 changes: 3 additions & 1 deletion pkg/webhook/pod/pod_validating_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import (
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/webhook"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"

"go.goms.io/fleet/pkg/utils"
)

const (
Expand Down Expand Up @@ -43,7 +45,7 @@ func (v *podValidator) Handle(ctx context.Context, req admission.Request) admiss
if err != nil {
return admission.Errored(http.StatusBadRequest, err)
}
if pod.Namespace != "kube-system" && pod.Namespace != "fleet-system" {
if !utils.IsReservedNamespace(pod.Namespace) {
return admission.Denied(fmt.Sprintf("Pod %s/%s creation is disallowed in the fleet hub cluster", pod.Namespace, pod.Name))
}
}
Expand Down
58 changes: 58 additions & 0 deletions pkg/webhook/replicaset/replicaset_validating_webhook.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
Copyright (c) Microsoft Corporation.
Licensed under the MIT license.
*/

package replicaset

import (
"context"
"fmt"
"net/http"

admissionv1 "k8s.io/api/admission/v1"
v1 "k8s.io/api/apps/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/webhook"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"

"go.goms.io/fleet/pkg/utils"
)

const (
// ValidationPath is the webhook service path which admission requests are routed to for validating ReplicaSet resources.
ValidationPath = "/validate-apps-v1-replicaset"
)

type replicaSetValidator struct {
Client client.Client
decoder *admission.Decoder
}

// Add registers the webhook for K8s bulit-in object types.
func Add(mgr manager.Manager) error {
hookServer := mgr.GetWebhookServer()
hookServer.Register(ValidationPath, &webhook.Admission{Handler: &replicaSetValidator{Client: mgr.GetClient()}})
return nil
}

// Handle replicaSetValidator denies all creation requests.
func (v *replicaSetValidator) Handle(ctx context.Context, req admission.Request) admission.Response {
if req.Operation == admissionv1.Create {
rs := &v1.ReplicaSet{}
if err := v.decoder.Decode(req, rs); err != nil {
return admission.Errored(http.StatusBadRequest, err)
}
if !utils.IsReservedNamespace(rs.Namespace) {
return admission.Denied(fmt.Sprintf("ReplicaSet %s/%s creation is disallowed in the fleet hub cluster.", rs.Namespace, rs.Name))
}
}
return admission.Allowed("")
}

// InjectDecoder injects the decoder.
func (v *replicaSetValidator) InjectDecoder(d *admission.Decoder) error {
v.decoder = d
return nil
}
34 changes: 30 additions & 4 deletions pkg/webhook/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"time"

admv1 "k8s.io/api/admissionregistration/v1"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand All @@ -32,6 +33,7 @@ import (
"go.goms.io/fleet/cmd/hubagent/options"
"go.goms.io/fleet/pkg/webhook/clusterresourceplacement"
"go.goms.io/fleet/pkg/webhook/pod"
"go.goms.io/fleet/pkg/webhook/replicaset"
)

const (
Expand Down Expand Up @@ -82,7 +84,7 @@ func NewWebhookConfig(mgr manager.Manager, port int, clientConnectionType *optio
}
caPEM, err := w.genCertificate(certDir)
if err != nil {
return nil, err // TODO
return nil, err
}
w.caPEM = caPEM
return &w, err
Expand All @@ -97,9 +99,9 @@ func (w *Config) Start(ctx context.Context) error {
return nil
}

// createFleetWebhookConfiguration creates the ValidatingWebhookConfiguration object for the webhook
// createFleetWebhookConfiguration creates the ValidatingWebhookConfiguration object for the webhook.
func (w *Config) createFleetWebhookConfiguration(ctx context.Context) error {
failPolicy := admv1.Fail // reject request if the webhook doesn't work
failPolicy := admv1.Fail
sideEffortsNone := admv1.SideEffectClassNone
namespacedScope := admv1.NamespacedScope
clusterScope := admv1.ClusterScope
Expand Down Expand Up @@ -155,6 +157,26 @@ func (w *Config) createFleetWebhookConfiguration(ctx context.Context) error {
},
},
},
{
Name: "fleet.replicaset.validating",
ClientConfig: w.createClientConfig(appsv1.ReplicaSet{}),
FailurePolicy: &failPolicy,
SideEffects: &sideEffortsNone,
AdmissionReviewVersions: []string{"v1", "v1beta1"},
Rules: []admv1.RuleWithOperations{
{
Operations: []admv1.OperationType{
admv1.Create,
},
Rule: admv1.Rule{
APIGroups: []string{"apps"},
APIVersions: []string{"v1"},
Resources: []string{"replicasets"},
Scope: &namespacedScope,
},
},
},
},
},
}

Expand All @@ -178,6 +200,7 @@ func (w *Config) createFleetWebhookConfiguration(ctx context.Context) error {
return nil
}

// createClientConfig generates the client configuration with either service ref or URL for the argued interface.
func (w *Config) createClientConfig(webhookInterface interface{}) admv1.WebhookClientConfig {
serviceRef := admv1.ServiceReference{
Namespace: w.serviceNamespace,
Expand All @@ -192,6 +215,9 @@ func (w *Config) createClientConfig(webhookInterface interface{}) admv1.WebhookC
case fleetv1alpha1.ClusterResourcePlacement:
serviceEndpoint = w.serviceURL + clusterresourceplacement.ValidationPath
serviceRef.Path = pointer.String(clusterresourceplacement.ValidationPath)
case appsv1.ReplicaSet:
serviceEndpoint = w.serviceURL + replicaset.ValidationPath
serviceRef.Path = pointer.String(replicaset.ValidationPath)
}

config := admv1.WebhookClientConfig{
Expand All @@ -206,7 +232,7 @@ func (w *Config) createClientConfig(webhookInterface interface{}) admv1.WebhookC
return config
}

// genCertificate generates the serving cerficiate for the webhook server
// genCertificate generates the serving cerficiate for the webhook server.
func (w *Config) genCertificate(certDir string) ([]byte, error) {
caPEM, certPEM, keyPEM, err := w.genSelfSignedCert()
if err != nil {
Expand Down
22 changes: 18 additions & 4 deletions test/e2e/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,31 @@ var (
imc *v1alpha1.InternalMemberCluster
ctx context.Context

// This namespace will store Member cluster-related CRs, such as v1alpha1.MemberCluster
// The fleet-system namespace.
fleetSystemNamespace = &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "fleet-system",
},
}

// The kube-system namespace
kubeSystemNamespace = &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "kube-system",
},
}

// This namespace will store Member cluster-related CRs, such as v1alpha1.MemberCluster.
memberNamespace = &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf(utils.NamespaceNameFormat, MemberCluster.ClusterName),
Name: fmt.Sprintf("fleet-member-%s", MemberCluster.ClusterName),
},
}

// This namespace in HubCluster will store v1alpha1.Work to simulate Work-related features in Hub Cluster.
workNamespace = &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf(utils.NamespaceNameFormat, MemberCluster.ClusterName),
Name: fmt.Sprintf("fleet-member-%s", MemberCluster.ClusterName),
},
}

Expand Down Expand Up @@ -174,7 +188,7 @@ var _ = BeforeSuite(func() {
identity := rbacv1.Subject{
Name: "member-agent-sa",
Kind: "ServiceAccount",
Namespace: "fleet-system",
Namespace: utils.FleetSystemNamespace,
}
mc = &v1alpha1.MemberCluster{
ObjectMeta: metav1.ObjectMeta{
Expand Down
Loading