From 298e25069302c53e270d5dcb7419d31ae079af9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Charles-Edouard=20Br=C3=A9t=C3=A9ch=C3=A9?= Date: Tue, 4 Apr 2023 13:59:02 +0200 Subject: [PATCH] feat: add exclude roles/cluster roles support in configmap (#6779) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add exclude roles/cluster roles support in configmap Signed-off-by: Charles-Edouard Brétéché * update chart Signed-off-by: Charles-Edouard Brétéché * filter handler Signed-off-by: Charles-Edouard Brétéché --------- Signed-off-by: Charles-Edouard Brétéché --- CHANGELOG.md | 1 + charts/kyverno/Chart.yaml | 2 + charts/kyverno/README.md | 6 ++- charts/kyverno/README.md.gotmpl | 2 + .../kyverno/templates/config/configmap.yaml | 16 ++++-- charts/kyverno/values.yaml | 8 +-- pkg/config/config.go | 54 +++++++++++++++---- pkg/config/types.go | 2 +- pkg/config/types_test.go | 2 +- pkg/webhooks/handlers/filter.go | 16 ++++++ 10 files changed, 87 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9af26456e3..fc0cae502c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Refactored `kyverno` chart, migration instructions are available in chart `README.md`. - Image references in the json context are not mutated to canonical form anymore, do not assume a registry domain is always present. - Added support for configuring webhook annotations in the config map through `webhookAnnotations` stanza. +- Added `excludeRoles` and `excludeClusterRoles` support in configuration. ## v1.9.0-rc.1 diff --git a/charts/kyverno/Chart.yaml b/charts/kyverno/Chart.yaml index b281fa3d34..2ffa50847e 100644 --- a/charts/kyverno/Chart.yaml +++ b/charts/kyverno/Chart.yaml @@ -40,3 +40,5 @@ annotations: description: allow overriding PDB api version - kind: fixed description: missing image pull secrets in helm hooks + - kind: added + description: support `excludeRoles` and `excludeClusterRoles` in config diff --git a/charts/kyverno/README.md b/charts/kyverno/README.md index 643eb39b99..7bd904bd30 100644 --- a/charts/kyverno/README.md +++ b/charts/kyverno/README.md @@ -174,6 +174,8 @@ In `v3` chart values changed significantly, please read the instructions below t - Image references are now using the `registry` setting, if you override the registry or repository fields please use `registry` (`--set image.registry=ghcr.io --set image.repository=kyverno/kyverno` instead of `--set image.repository=ghcr.io/kyverno/kyverno`). - Admission controller `Deployment` name changed from `kyverno` to `kyverno-admission-controller`. +- `config.excludeUsername` was renamed to `config.excludeUsernames` +- `config.excludeGroupRole` was renamed to `config.excludeGroups` ## Uninstalling the Chart @@ -200,8 +202,8 @@ The command removes all the Kubernetes components associated with the chart and | config.annotations | object | `{}` | Additional annotations to add to the configmap. | | config.enableDefaultRegistryMutation | bool | `true` | Enable registry mutation for container images. Enabled by default. | | config.defaultRegistry | string | `"docker.io"` | The registry hostname used for the image mutation. | -| config.excludeGroupRole | list | `[]` | Exclude group role | -| config.excludeUsername | list | `[]` | Exclude username | +| config.excludeGroups | list | `[]` | Exclude groups | +| config.excludeUsernames | list | `[]` | Exclude usernames | | config.excludeBackgroundUsernames | list | `[]` | Exclude usernames for mutateExisting and generate policies | | config.generateSuccessEvents | bool | `false` | Generate success events. | | config.resourceFilters | list | See [values.yaml](values.yaml) | Resource types to be skipped by the Kyverno policy engine. Make sure to surround each entry in quotes so that it doesn't get parsed as a nested YAML list. These are joined together without spaces, run through `tpl`, and the result is set in the config map. | diff --git a/charts/kyverno/README.md.gotmpl b/charts/kyverno/README.md.gotmpl index 62b9625364..9d76c177f1 100644 --- a/charts/kyverno/README.md.gotmpl +++ b/charts/kyverno/README.md.gotmpl @@ -174,6 +174,8 @@ In `v3` chart values changed significantly, please read the instructions below t - Image references are now using the `registry` setting, if you override the registry or repository fields please use `registry` (`--set image.registry=ghcr.io --set image.repository=kyverno/kyverno` instead of `--set image.repository=ghcr.io/kyverno/kyverno`). - Admission controller `Deployment` name changed from `kyverno` to `kyverno-admission-controller`. +- `config.excludeUsername` was renamed to `config.excludeUsernames` +- `config.excludeGroupRole` was renamed to `config.excludeGroups` ## Uninstalling the Chart diff --git a/charts/kyverno/templates/config/configmap.yaml b/charts/kyverno/templates/config/configmap.yaml index 7c09a0c12a..88c8c0a935 100644 --- a/charts/kyverno/templates/config/configmap.yaml +++ b/charts/kyverno/templates/config/configmap.yaml @@ -16,8 +16,17 @@ data: defaultRegistry: {{ . | quote }} {{- end }} generateSuccessEvents: {{ .Values.config.generateSuccessEvents | quote }} - {{- with .Values.config.excludeGroupRole }} - excludeGroupRole: {{ join "," . | quote }} + {{- with .Values.config.excludeGroups }} + excludeGroups: {{ join "," . | quote }} + {{- end -}} + {{- with .Values.config.excludeUsernames }} + excludeUsernames: {{ join "," . | quote }} + {{- end -}} + {{- with .Values.config.excludeRoles }} + excludeRoles: {{ join "," . | quote }} + {{- end -}} + {{- with .Values.config.excludeClusterRoles }} + excludeClusterRoles: {{ join "," . | quote }} {{- end -}} {{- $backgroundUsernames := (printf "system:serviceaccount:%s:%s" (include "kyverno.namespace" .) (include "kyverno.background-controller.serviceAccountName" .)) }} {{- if .Values.config.excludeBackgroundUsernames }} @@ -26,9 +35,6 @@ data: {{- else }} excludeBackgroundUsernames: {{ $backgroundUsernames }} {{- end -}} - {{- with .Values.config.excludeUsername }} - excludeUsername: {{ join "," . | quote }} - {{- end -}} {{- if .Values.config.resourceFilters }} resourceFilters: {{ include "kyverno.config.resourceFilters" . | quote }} {{- end -}} diff --git a/charts/kyverno/values.yaml b/charts/kyverno/values.yaml index daca6c3c86..36b18ee857 100644 --- a/charts/kyverno/values.yaml +++ b/charts/kyverno/values.yaml @@ -49,11 +49,11 @@ config: # -- The registry hostname used for the image mutation. defaultRegistry: docker.io - # -- Exclude group role - excludeGroupRole: [] + # -- Exclude groups + excludeGroups: [] - # -- Exclude username - excludeUsername: [] + # -- Exclude usernames + excludeUsernames: [] # -- Exclude usernames for mutateExisting and generate policies excludeBackgroundUsernames: [] diff --git a/pkg/config/config.go b/pkg/config/config.go index 5734f590ff..d5e7d5968f 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -139,10 +139,14 @@ type Configuration interface { GetEnableDefaultRegistryMutation() bool // ToFilter checks if the given resource is set to be filtered in the configuration ToFilter(kind, namespace, name string) bool - // GetExcludedGroups return exclude groups + // GetExcludedGroups return excluded groups GetExcludedGroups() []string - // GetExcludedUsernames return exclude usernames + // GetExcludedUsernames return excluded usernames GetExcludedUsernames() []string + // GetExcludedRoles return excluded roles + GetExcludedRoles() []string + // GetExcludedClusterRoles return excluded roles + GetExcludedClusterRoles() []string // GetExcludedBackgroundUsernames return exclude usernames for mutateExisting and generate policies GetExcludedBackgroundUsernames() []string // GetGenerateSuccessEvents return if should generate success events @@ -161,6 +165,8 @@ type configuration struct { enableDefaultRegistryMutation bool excludedGroups []string excludedUsernames []string + excludedRoles []string + excludedClusterRoles []string excludeBackgroundUsernames []string filters []filter generateSuccessEvents bool @@ -227,6 +233,18 @@ func (cd *configuration) GetExcludedUsernames() []string { return cd.excludedUsernames } +func (cd *configuration) GetExcludedRoles() []string { + cd.mux.RLock() + defer cd.mux.RUnlock() + return cd.excludedRoles +} + +func (cd *configuration) GetExcludedClusterRoles() []string { + cd.mux.RLock() + defer cd.mux.RUnlock() + return cd.excludedClusterRoles +} + func (cd *configuration) GetExcludedBackgroundUsernames() []string { cd.mux.RLock() defer cd.mux.RUnlock() @@ -276,6 +294,8 @@ func (cd *configuration) load(cm *corev1.ConfigMap) { cd.filters = []filter{} cd.excludedUsernames = []string{} cd.excludedGroups = []string{} + cd.excludedRoles = []string{} + cd.excludedClusterRoles = []string{} cd.generateSuccessEvents = false cd.webhooks = nil cd.excludedGroups = append(cd.excludedGroups, defaultExcludedGroups...) @@ -305,25 +325,39 @@ func (cd *configuration) load(cm *corev1.ConfigMap) { cd.enableDefaultRegistryMutation = newEnableDefaultRegistryMutation } // load excludeGroupRole - excludedGroups, ok := cm.Data["excludeGroupRole"] + excludedGroups, ok := cm.Data["excludeGroups"] if !ok { - logger.V(6).Info("configuration: No excludeGroupRole defined in ConfigMap") + logger.V(6).Info("configuration: No excludeGroups defined in ConfigMap") } else { - cd.excludedGroups = parseRbac(excludedGroups) + cd.excludedGroups = parseStrings(excludedGroups) } // load excludeUsername - excludedUsernames, ok := cm.Data["excludeUsername"] + excludedUsernames, ok := cm.Data["excludeUsernames"] if !ok { - logger.V(6).Info("configuration: No excludeUsername defined in ConfigMap") + logger.V(6).Info("configuration: No excludeUsernames defined in ConfigMap") } else { - cd.excludedUsernames = parseRbac(excludedUsernames) + cd.excludedUsernames = parseStrings(excludedUsernames) + } + // load excludeRoles + excludedRoles, ok := cm.Data["excludeRoles"] + if !ok { + logger.V(6).Info("configuration: No excludeRoles defined in ConfigMap") + } else { + cd.excludedRoles = parseStrings(excludedRoles) + } + // load excludeClusterRoles + excludedClusterRoles, ok := cm.Data["excludeClusterRoles"] + if !ok { + logger.V(6).Info("configuration: No excludeClusterRoles defined in ConfigMap") + } else { + cd.excludedClusterRoles = parseStrings(excludedClusterRoles) } // load excludeBackgroundUsernames excludeBackgroundUsernames, ok := cm.Data["excludeBackgroundUsernames"] if !ok { logger.V(6).Info("configuration: No excludeBackgroundUsernames defined in ConfigMap") } else { - cd.excludeBackgroundUsernames = parseRbac(excludeBackgroundUsernames) + cd.excludeBackgroundUsernames = parseStrings(excludeBackgroundUsernames) } // load generateSuccessEvents generateSuccessEvents, ok := cm.Data["generateSuccessEvents"] @@ -365,6 +399,8 @@ func (cd *configuration) unload() { cd.enableDefaultRegistryMutation = true cd.excludedUsernames = []string{} cd.excludedGroups = []string{} + cd.excludedRoles = []string{} + cd.excludedClusterRoles = []string{} cd.generateSuccessEvents = false cd.webhooks = nil cd.webhookAnnotations = nil diff --git a/pkg/config/types.go b/pkg/config/types.go index b0a9a9290d..ef9a01593d 100644 --- a/pkg/config/types.go +++ b/pkg/config/types.go @@ -21,7 +21,7 @@ func parseWebhooks(in string) ([]WebhookConfig, error) { return webhookCfgs, nil } -func parseRbac(in string) []string { +func parseStrings(in string) []string { var out []string for _, in := range strings.Split(in, ",") { in := strings.TrimSpace(in) diff --git a/pkg/config/types_test.go b/pkg/config/types_test.go index 4ad054e8c0..bc6b68d4dd 100644 --- a/pkg/config/types_test.go +++ b/pkg/config/types_test.go @@ -37,7 +37,7 @@ func Test_parseRbac(t *testing.T) { }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := parseRbac(tt.args.in); !reflect.DeepEqual(got, tt.want) { + if got := parseStrings(tt.args.in); !reflect.DeepEqual(got, tt.want) { t.Errorf("parseRbac() = %v, want %v", got, tt.want) } }) diff --git a/pkg/webhooks/handlers/filter.go b/pkg/webhooks/handlers/filter.go index 80e167880e..49f6a692eb 100644 --- a/pkg/webhooks/handlers/filter.go +++ b/pkg/webhooks/handlers/filter.go @@ -41,6 +41,22 @@ func (inner AdmissionHandler) withFilter(c config.Configuration) AdmissionHandle } } } + // filter by roles + for _, role := range c.GetExcludedRoles() { + for _, candidate := range request.Roles { + if wildcard.Match(role, candidate) { + return admissionutils.ResponseSuccess(request.UID) + } + } + } + // filter by cluster roles + for _, clusterRole := range c.GetExcludedClusterRoles() { + for _, candidate := range request.ClusterRoles { + if wildcard.Match(clusterRole, candidate) { + return admissionutils.ResponseSuccess(request.UID) + } + } + } // filter by resource filters if c.ToFilter(request.Kind.Kind, request.Namespace, request.Name) { return admissionutils.ResponseSuccess(request.UID)