1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-04-09 10:42:22 +00:00

feat: add config inclusions support (#7082)

* feat: add config inclusions support

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* tests

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

---------

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
This commit is contained in:
Charles-Edouard Brétéché 2023-05-03 18:40:36 +02:00 committed by GitHub
parent 6a95b305c3
commit 7583aad6fd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 127 additions and 114 deletions

View file

@ -210,7 +210,7 @@ The chart values are organised per component.
| 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.excludeGroups | list | `["system:serviceaccounts:kube-system","system:nodes"]` | Exclude groups |
| config.excludeUsernames | list | `[]` | Exclude usernames |
| config.excludeUsernames | list | `["!system:kube-scheduler"]` | Exclude usernames |
| config.excludeRoles | list | `[]` | Exclude roles |
| config.excludeClusterRoles | list | `[]` | Exclude roles |
| config.generateSuccessEvents | bool | `false` | Generate success events. |

View file

@ -59,8 +59,8 @@ config:
- system:nodes
# -- Exclude usernames
excludeUsernames: []
# - system:kube-scheduler
excludeUsernames:
- '!system:kube-scheduler'
# -- Exclude roles
excludeRoles: []

View file

@ -77,6 +77,7 @@ data:
defaultRegistry: "docker.io"
generateSuccessEvents: "false"
excludeGroups: "system:serviceaccounts:kube-system,system:nodes"
excludeUsernames: "!system:kube-scheduler"
resourceFilters: >-
[*/*,kyverno,*]
[Event,*,*]

View file

@ -146,16 +146,10 @@ type Configuration interface {
GetDefaultRegistry() string
// GetEnableDefaultRegistryMutation return if should mutate image registry
GetEnableDefaultRegistryMutation() bool
// IsExcluded checks exlusions/inclusions to determine if the admission request should be excluded or not
IsExcluded(username string, groups []string, roles []string, clusterroles []string) bool
// ToFilter checks if the given resource is set to be filtered in the configuration
ToFilter(kind schema.GroupVersionKind, subresource, namespace, name string) bool
// GetExcludedGroups return excluded groups
GetExcludedGroups() []string
// GetExcludedUsernames return excluded usernames
GetExcludedUsernames() []string
// GetExcludedRoles return excluded roles
GetExcludedRoles() []string
// GetExcludedClusterRoles return excluded roles
GetExcludedClusterRoles() []string
// GetGenerateSuccessEvents return if should generate success events
GetGenerateSuccessEvents() bool
// GetWebhooks returns the webhook configs
@ -173,10 +167,8 @@ type configuration struct {
skipResourceFilters bool
defaultRegistry string
enableDefaultRegistryMutation bool
excludedGroups []string
excludedUsernames []string
excludedRoles []string
excludedClusterRoles []string
exclusions match
inclusions match
filters []filter
generateSuccessEvents bool
webhooks []WebhookConfig
@ -185,6 +177,47 @@ type configuration struct {
callbacks []func()
}
type match struct {
groups []string
usernames []string
roles []string
clusterroles []string
}
func (c match) matches(username string, groups []string, roles []string, clusterroles []string) bool {
// filter by username
for _, pattern := range c.usernames {
if wildcard.Match(pattern, username) {
return true
}
}
// filter by groups
for _, pattern := range c.groups {
for _, candidate := range groups {
if wildcard.Match(pattern, candidate) {
return true
}
}
}
// filter by roles
for _, pattern := range c.roles {
for _, candidate := range roles {
if wildcard.Match(pattern, candidate) {
return true
}
}
}
// filter by cluster roles
for _, pattern := range c.clusterroles {
for _, candidate := range clusterroles {
if wildcard.Match(pattern, candidate) {
return true
}
}
}
return false
}
// NewDefaultConfiguration ...
func NewDefaultConfiguration(skipResourceFilters bool) *configuration {
return &configuration{
@ -200,6 +233,13 @@ func (cd *configuration) OnChanged(callback func()) {
cd.callbacks = append(cd.callbacks, callback)
}
func (c *configuration) IsExcluded(username string, groups []string, roles []string, clusterroles []string) bool {
if c.inclusions.matches(username, groups, roles, clusterroles) {
return false
}
return c.exclusions.matches(username, groups, roles, clusterroles)
}
func (cd *configuration) ToFilter(gvk schema.GroupVersionKind, subresource, namespace, name string) bool {
cd.mux.RLock()
defer cd.mux.RUnlock()
@ -233,30 +273,6 @@ func (cd *configuration) GetEnableDefaultRegistryMutation() bool {
return cd.enableDefaultRegistryMutation
}
func (cd *configuration) GetExcludedUsernames() []string {
cd.mux.RLock()
defer cd.mux.RUnlock()
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) GetExcludedGroups() []string {
cd.mux.RLock()
defer cd.mux.RUnlock()
return cd.excludedGroups
}
func (cd *configuration) GetGenerateSuccessEvents() bool {
cd.mux.RLock()
defer cd.mux.RUnlock()
@ -295,10 +311,8 @@ func (cd *configuration) load(cm *corev1.ConfigMap) {
// reset
cd.defaultRegistry = "docker.io"
cd.enableDefaultRegistryMutation = true
cd.excludedUsernames = []string{}
cd.excludedGroups = []string{}
cd.excludedRoles = []string{}
cd.excludedClusterRoles = []string{}
cd.exclusions = match{}
cd.inclusions = match{}
cd.filters = []filter{}
cd.generateSuccessEvents = false
cd.webhooks = nil
@ -338,32 +352,32 @@ func (cd *configuration) load(cm *corev1.ConfigMap) {
if !ok {
logger.Info("excludeGroups not set")
} else {
cd.excludedGroups = parseStrings(excludedGroups)
logger.Info("excludedGroups configured", "excludeGroups", cd.excludedGroups)
cd.exclusions.groups, cd.inclusions.groups = parseExclusions(excludedGroups)
logger.Info("excludedGroups configured", "excludeGroups", cd.exclusions.groups, "includeGroups", cd.inclusions.groups)
}
// load excludeUsername
excludedUsernames, ok := data[excludeUsernames]
if !ok {
logger.Info("excludeUsernames not set")
} else {
cd.excludedUsernames = parseStrings(excludedUsernames)
logger.Info("excludedUsernames configured", "excludeUsernames", cd.excludedUsernames)
cd.exclusions.usernames, cd.inclusions.usernames = parseExclusions(excludedUsernames)
logger.Info("excludedUsernames configured", "excludeUsernames", cd.exclusions.usernames, "includeUsernames", cd.inclusions.usernames)
}
// load excludeRoles
excludedRoles, ok := data[excludeRoles]
if !ok {
logger.Info("excludeRoles not set")
} else {
cd.excludedRoles = parseStrings(excludedRoles)
logger.Info("excludedRoles configured", "excludeRoles", cd.excludedRoles)
cd.exclusions.roles, cd.inclusions.roles = parseExclusions(excludedRoles)
logger.Info("excludedRoles configured", "excludeRoles", cd.exclusions.roles, "includeRoles", cd.inclusions.roles)
}
// load excludeClusterRoles
excludedClusterRoles, ok := data[excludeClusterRoles]
if !ok {
logger.Info("excludeClusterRoles not set")
} else {
cd.excludedClusterRoles = parseStrings(excludedClusterRoles)
logger.Info("excludedClusterRoles configured", "excludeClusterRoles", cd.excludedClusterRoles)
cd.exclusions.clusterroles, cd.inclusions.clusterroles = parseExclusions(excludedClusterRoles)
logger.Info("excludedClusterRoles configured", "excludeClusterRoles", cd.exclusions.clusterroles, "includeClusterRoles", cd.inclusions.clusterroles)
}
// load generateSuccessEvents
generateSuccessEvents, ok := data[generateSuccessEvents]
@ -415,10 +429,8 @@ func (cd *configuration) unload() {
defer cd.notify()
cd.defaultRegistry = "docker.io"
cd.enableDefaultRegistryMutation = true
cd.excludedUsernames = []string{}
cd.excludedGroups = []string{}
cd.excludedRoles = []string{}
cd.excludedClusterRoles = []string{}
cd.exclusions = match{}
cd.inclusions = match{}
cd.filters = []filter{}
cd.generateSuccessEvents = false
cd.webhooks = nil

View file

@ -22,15 +22,26 @@ func parseWebhooks(in string) ([]WebhookConfig, error) {
return webhookCfgs, nil
}
func parseStrings(in string) []string {
var out []string
func parseExclusions(in string) (exclusions, inclusions []string) {
for _, in := range strings.Split(in, ",") {
in := strings.TrimSpace(in)
if in != "" {
out = append(out, in)
if in == "" {
continue
}
inclusion := strings.HasPrefix(in, "!")
if inclusion {
in = strings.TrimSpace(in[1:])
if in == "" {
continue
}
}
if inclusion {
inclusions = append(inclusions, in)
} else {
exclusions = append(exclusions, in)
}
}
return out
return
}
func parseWebhookAnnotations(in string) (map[string]string, error) {

View file

@ -5,40 +5,60 @@ import (
"testing"
)
func Test_parseRbac(t *testing.T) {
func Test_parseExclusions(t *testing.T) {
type args struct {
in string
}
tests := []struct {
name string
args args
want []string
name string
args args
wantExclusions []string
wantInclusions []string
}{{
args: args{""},
want: nil,
args: args{""},
wantExclusions: nil,
}, {
args: args{"abc"},
want: []string{"abc"},
args: args{"abc"},
wantExclusions: []string{"abc"},
}, {
args: args{" abc "},
want: []string{"abc"},
args: args{" abc "},
wantExclusions: []string{"abc"},
}, {
args: args{"abc,def"},
want: []string{"abc", "def"},
args: args{"abc,def"},
wantExclusions: []string{"abc", "def"},
}, {
args: args{"abc,,,def,"},
want: []string{"abc", "def"},
args: args{"abc,,,def,"},
wantExclusions: []string{"abc", "def"},
}, {
args: args{"abc, def"},
want: []string{"abc", "def"},
args: args{"abc, def"},
wantExclusions: []string{"abc", "def"},
}, {
args: args{"abc ,def "},
want: []string{"abc", "def"},
args: args{"abc ,def "},
wantExclusions: []string{"abc", "def"},
}, {
args: args{"abc,!def"},
wantExclusions: []string{"abc"},
wantInclusions: []string{"def"},
}, {
args: args{"!def,abc"},
wantExclusions: []string{"abc"},
wantInclusions: []string{"def"},
}, {
args: args{"!,abc"},
wantExclusions: []string{"abc"},
}, {
args: args{" ! def ,abc"},
wantExclusions: []string{"abc"},
wantInclusions: []string{"def"},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := parseStrings(tt.args.in); !reflect.DeepEqual(got, tt.want) {
t.Errorf("parseRbac() = %v, want %v", got, tt.want)
gotExclusions, gotInclusions := parseExclusions(tt.args.in)
if !reflect.DeepEqual(gotExclusions, tt.wantExclusions) {
t.Errorf("parseExclusions() exclusions = %v, want %v", gotExclusions, tt.wantExclusions)
}
if !reflect.DeepEqual(gotInclusions, tt.wantInclusions) {
t.Errorf("parseExclusions() inclusions = %v, want %v", gotInclusions, tt.wantInclusions)
}
})
}

View file

@ -8,7 +8,6 @@ import (
"github.com/kyverno/kyverno/pkg/config"
"github.com/kyverno/kyverno/pkg/tracing"
admissionutils "github.com/kyverno/kyverno/pkg/utils/admission"
wildcard "github.com/kyverno/kyverno/pkg/utils/wildcard"
webhookutils "github.com/kyverno/kyverno/pkg/webhooks/utils"
admissionv1 "k8s.io/api/admission/v1"
"k8s.io/apimachinery/pkg/util/sets"
@ -34,39 +33,9 @@ func filtered(ctx context.Context, logger logr.Logger, request AdmissionRequest,
func (inner AdmissionHandler) withFilter(c config.Configuration) AdmissionHandler {
return func(ctx context.Context, logger logr.Logger, request AdmissionRequest, startTime time.Time) AdmissionResponse {
// filter by username
excludeUsernames := c.GetExcludedUsernames()
for _, username := range excludeUsernames {
if wildcard.Match(username, request.UserInfo.Username) {
return filtered(ctx, logger, request, "admission request filtered because user is excluded", "config.exlude.usernames", excludeUsernames)
}
}
// filter by groups
excludeGroups := c.GetExcludedGroups()
for _, group := range excludeGroups {
for _, candidate := range request.UserInfo.Groups {
if wildcard.Match(group, candidate) {
return filtered(ctx, logger, request, "admission request filtered because group is excluded", "config.exlude.groups", excludeGroups)
}
}
}
// filter by roles
excludeRoles := c.GetExcludedRoles()
for _, role := range excludeRoles {
for _, candidate := range request.Roles {
if wildcard.Match(role, candidate) {
return filtered(ctx, logger, request, "admission request filtered because role is excluded", "config.exlude.roles", excludeRoles)
}
}
}
// filter by cluster roles
excludeClusterRoles := c.GetExcludedClusterRoles()
for _, clusterRole := range excludeClusterRoles {
for _, candidate := range request.ClusterRoles {
if wildcard.Match(clusterRole, candidate) {
return filtered(ctx, logger, request, "admission request filtered because role is excluded", "config.exlude.cluster-roles", excludeClusterRoles)
}
}
// filter by exclusions/inclusions
if c.IsExcluded(request.UserInfo.Username, request.UserInfo.Groups, request.Roles, request.ClusterRoles) {
return filtered(ctx, logger, request, "admission request filtered")
}
// filter by resource filters
if c.ToFilter(request.GroupVersionKind, request.SubResource, request.Namespace, request.Name) {