diff --git a/api/v1alpha1/capsuleconfiguration_types.go b/api/v1alpha1/capsuleconfiguration_types.go index 21b6e9df..b56a44ac 100644 --- a/api/v1alpha1/capsuleconfiguration_types.go +++ b/api/v1alpha1/capsuleconfiguration_types.go @@ -12,6 +12,8 @@ type CapsuleConfigurationSpec struct { // Names of the groups for Capsule users. // +kubebuilder:default={capsule.clastix.io} UserGroups []string `json:"userGroups,omitempty"` + // Names of the groups for Capsule users. + ExcludeUserGroups []string `json:"excludeUserGroups,omitempty"` // Enforces the Tenant owner, during Namespace creation, to name it using the selected Tenant name as prefix, // separated by a dash. This is useful to avoid Namespace name collision in a public CaaS environment. // +kubebuilder:default=false diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index efca1518..a020eb6f 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -150,6 +150,11 @@ func (in *CapsuleConfigurationSpec) DeepCopyInto(out *CapsuleConfigurationSpec) *out = make([]string, len(*in)) copy(*out, *in) } + if in.ExcludeUserGroups != nil { + in, out := &in.ExcludeUserGroups, &out.ExcludeUserGroups + *out = make([]string, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CapsuleConfigurationSpec. diff --git a/bin/controller-gen b/bin/controller-gen new file mode 100755 index 00000000..2872a032 Binary files /dev/null and b/bin/controller-gen differ diff --git a/bin/manager b/bin/manager new file mode 100755 index 00000000..790625ff Binary files /dev/null and b/bin/manager differ diff --git a/charts/capsule/crds/capsuleconfiguration-crd.yaml b/charts/capsule/crds/capsuleconfiguration-crd.yaml index 7665e040..72fb5207 100644 --- a/charts/capsule/crds/capsuleconfiguration-crd.yaml +++ b/charts/capsule/crds/capsuleconfiguration-crd.yaml @@ -30,6 +30,11 @@ spec: spec: description: CapsuleConfigurationSpec defines the Capsule configuration. properties: + excludeUserGroups: + description: Names of the groups for Capsule users. + items: + type: string + type: array forceTenantPrefix: default: false description: Enforces the Tenant owner, during Namespace creation, to name it using the selected Tenant name as prefix, separated by a dash. This is useful to avoid Namespace name collision in a public CaaS environment. diff --git a/charts/capsule/templates/configuration-default.yaml b/charts/capsule/templates/configuration-default.yaml index 3cb897a1..bef5a62c 100644 --- a/charts/capsule/templates/configuration-default.yaml +++ b/charts/capsule/templates/configuration-default.yaml @@ -17,5 +17,9 @@ spec: userGroups: {{- range .Values.manager.options.capsuleUserGroups }} - {{ . }} +{{- end}} + excludeUserGroups: +{{- range .Values.manager.options.capsuleExcludeUserGroups }} + - {{ . }} {{- end}} protectedNamespaceRegex: {{ .Values.manager.options.protectedNamespaceRegex | quote }} diff --git a/charts/capsule/values.yaml b/charts/capsule/values.yaml index 8885801a..eb11621b 100644 --- a/charts/capsule/values.yaml +++ b/charts/capsule/values.yaml @@ -43,6 +43,8 @@ manager: forceTenantPrefix: false # -- Override the Capsule user groups capsuleUserGroups: ["capsule.clastix.io"] + # -- Override the Capsule exclude user groups + capsuleExcludeUserGroups: [] # -- If specified, disallows creation of namespaces matching the passed regexp protectedNamespaceRegex: "" # -- Specifies whether capsule webhooks certificates should be generated by capsule operator diff --git a/config/crd/bases/capsule.clastix.io_capsuleconfigurations.yaml b/config/crd/bases/capsule.clastix.io_capsuleconfigurations.yaml index 5ce5bdfe..875b0041 100644 --- a/config/crd/bases/capsule.clastix.io_capsuleconfigurations.yaml +++ b/config/crd/bases/capsule.clastix.io_capsuleconfigurations.yaml @@ -32,6 +32,11 @@ spec: spec: description: CapsuleConfigurationSpec defines the Capsule configuration. properties: + excludeUserGroups: + description: Names of the groups for Capsule users. + items: + type: string + type: array forceTenantPrefix: default: false description: Enforces the Tenant owner, during Namespace creation, to name it using the selected Tenant name as prefix, separated by a dash. This is useful to avoid Namespace name collision in a public CaaS environment. diff --git a/go.mod b/go.mod index 6414e366..3ca3157e 100644 --- a/go.mod +++ b/go.mod @@ -44,7 +44,6 @@ require ( github.com/modern-go/reflect2 v1.0.1 // indirect github.com/nxadm/tail v1.4.8 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.11.0 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.26.0 // indirect github.com/prometheus/procfs v0.6.0 // indirect diff --git a/go.sum b/go.sum index cfc7b590..73f688b7 100644 --- a/go.sum +++ b/go.sum @@ -695,7 +695,6 @@ golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/pkg/configuration/client.go b/pkg/configuration/client.go index 3a0dd009..6037d0f5 100644 --- a/pkg/configuration/client.go +++ b/pkg/configuration/client.go @@ -131,6 +131,10 @@ func (c capsuleConfiguration) UserGroups() []string { return c.retrievalFn().Spec.UserGroups } +func (c capsuleConfiguration) ExcludeUserGroups() []string { + return c.retrievalFn().Spec.ExcludeUserGroups +} + func (c capsuleConfiguration) hasForbiddenNodeLabelsAnnotations() bool { if _, ok := c.retrievalFn().Annotations[capsulev1alpha1.ForbiddenNodeLabelsAnnotation]; ok { return true diff --git a/pkg/configuration/configuration.go b/pkg/configuration/configuration.go index 5036ac62..38f6ff52 100644 --- a/pkg/configuration/configuration.go +++ b/pkg/configuration/configuration.go @@ -27,6 +27,7 @@ type Configuration interface { ValidatingWebhookConfigurationName() string TenantCRDName() string UserGroups() []string + ExcludeUserGroups() []string ForbiddenUserNodeLabels() *capsulev1beta1.ForbiddenListSpec ForbiddenUserNodeAnnotations() *capsulev1beta1.ForbiddenListSpec } diff --git a/pkg/webhook/namespace/freezed.go b/pkg/webhook/namespace/freezed.go index 290217a4..781037d3 100644 --- a/pkg/webhook/namespace/freezed.go +++ b/pkg/webhook/namespace/freezed.go @@ -69,7 +69,7 @@ func (r *freezedHandler) OnDelete(c client.Client, _ *admission.Decoder, recorde tnt := tntList.Items[0] - if tnt.IsCordoned() && utils.IsCapsuleUser(ctx, req, c, r.configuration.UserGroups()) { + if tnt.IsCordoned() && utils.IsCapsuleUser(ctx, req, c, r.configuration.UserGroups(), r.configuration.ExcludeUserGroups()) { recorder.Eventf(&tnt, corev1.EventTypeWarning, "TenantFreezed", "Namespace %s cannot be deleted, the current Tenant is freezed", req.Name) response := admission.Denied("the selected Tenant is freezed") @@ -101,7 +101,7 @@ func (r *freezedHandler) OnUpdate(c client.Client, decoder *admission.Decoder, r tnt := tntList.Items[0] - if tnt.IsCordoned() && utils.IsCapsuleUser(ctx, req, c, r.configuration.UserGroups()) { + if tnt.IsCordoned() && utils.IsCapsuleUser(ctx, req, c, r.configuration.UserGroups(), r.configuration.ExcludeUserGroups()) { recorder.Eventf(&tnt, corev1.EventTypeWarning, "TenantFreezed", "Namespace %s cannot be updated, the current Tenant is freezed", ns.GetName()) response := admission.Denied("the selected Tenant is freezed") diff --git a/pkg/webhook/ownerreference/patching.go b/pkg/webhook/ownerreference/patching.go index 44e9c1e9..b6bea5f4 100644 --- a/pkg/webhook/ownerreference/patching.go +++ b/pkg/webhook/ownerreference/patching.go @@ -146,7 +146,6 @@ func (h *handler) setOwnerRef(ctx context.Context, req admission.Request, client return &response } - if h.cfg.ForceTenantPrefix() { for _, tnt := range tenants { if strings.HasPrefix(ns.GetName(), fmt.Sprintf("%s-", tnt.GetName())) { diff --git a/pkg/webhook/tenant/cordoning.go b/pkg/webhook/tenant/cordoning.go index cc024ac7..2c2f14c8 100644 --- a/pkg/webhook/tenant/cordoning.go +++ b/pkg/webhook/tenant/cordoning.go @@ -44,7 +44,7 @@ func (h *cordoningHandler) cordonHandler(ctx context.Context, clt client.Client, } tnt := tntList.Items[0] - if tnt.IsCordoned() && utils.IsCapsuleUser(ctx, req, clt, h.configuration.UserGroups()) { + if tnt.IsCordoned() && utils.IsCapsuleUser(ctx, req, clt, h.configuration.UserGroups(), h.configuration.ExcludeUserGroups()) { recorder.Eventf(&tnt, corev1.EventTypeWarning, "TenantFreezed", "%s %s/%s cannot be %sd, current Tenant is freezed", req.Kind.String(), req.Namespace, req.Name, strings.ToLower(string(req.Operation))) response := admission.Denied(fmt.Sprintf("tenant %s is freezed: please, reach out to the system administrator", tnt.GetName())) diff --git a/pkg/webhook/utils/in_capsule_groups.go b/pkg/webhook/utils/in_capsule_groups.go index 9089cec7..c6761aa6 100644 --- a/pkg/webhook/utils/in_capsule_groups.go +++ b/pkg/webhook/utils/in_capsule_groups.go @@ -28,7 +28,7 @@ type handler struct { func (h *handler) OnCreate(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) webhook.Func { return func(ctx context.Context, req admission.Request) *admission.Response { - if !IsCapsuleUser(ctx, req, client, h.configuration.UserGroups()) { + if !IsCapsuleUser(ctx, req, client, h.configuration.UserGroups(), h.configuration.ExcludeUserGroups()) { return nil } @@ -44,7 +44,7 @@ func (h *handler) OnCreate(client client.Client, decoder *admission.Decoder, rec func (h *handler) OnDelete(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) webhook.Func { return func(ctx context.Context, req admission.Request) *admission.Response { - if !IsCapsuleUser(ctx, req, client, h.configuration.UserGroups()) { + if !IsCapsuleUser(ctx, req, client, h.configuration.UserGroups(), h.configuration.ExcludeUserGroups()) { return nil } @@ -60,7 +60,7 @@ func (h *handler) OnDelete(client client.Client, decoder *admission.Decoder, rec func (h *handler) OnUpdate(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) webhook.Func { return func(ctx context.Context, req admission.Request) *admission.Response { - if !IsCapsuleUser(ctx, req, client, h.configuration.UserGroups()) { + if !IsCapsuleUser(ctx, req, client, h.configuration.UserGroups(), h.configuration.ExcludeUserGroups()) { return nil } diff --git a/pkg/webhook/utils/is_capsule_user.go b/pkg/webhook/utils/is_capsule_user.go index 5c163073..629cc712 100644 --- a/pkg/webhook/utils/is_capsule_user.go +++ b/pkg/webhook/utils/is_capsule_user.go @@ -13,14 +13,15 @@ import ( "github.com/clastix/capsule/pkg/utils" ) -func IsCapsuleUser(ctx context.Context, req admission.Request, clt client.Client, userGroups []string) bool { +func IsCapsuleUser(ctx context.Context, req admission.Request, clt client.Client, userGroups []string, excludeUserGroups []string) bool { groupList := utils.NewUserGroupList(req.UserInfo.Groups) - // if the user is a ServiceAccount belonging to the kube-system namespace, definitely, it's not a Capsule user - // and we can skip the check in case of Capsule user group assigned to system:authenticated - // (ref: https://github.com/clastix/capsule/issues/234) - if groupList.Find("system:serviceaccounts:kube-system") { - return false + + for _, group := range excludeUserGroups { + if groupList.Find(group) { + return false + } } + // nolint:nestif if sets.NewString(req.UserInfo.Groups...).Has("system:serviceaccounts") { parts := strings.Split(req.UserInfo.Username, ":")