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:
parent
6a95b305c3
commit
7583aad6fd
7 changed files with 127 additions and 114 deletions
|
@ -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. |
|
||||
|
|
|
@ -59,8 +59,8 @@ config:
|
|||
- system:nodes
|
||||
|
||||
# -- Exclude usernames
|
||||
excludeUsernames: []
|
||||
# - system:kube-scheduler
|
||||
excludeUsernames:
|
||||
- '!system:kube-scheduler'
|
||||
|
||||
# -- Exclude roles
|
||||
excludeRoles: []
|
||||
|
|
|
@ -77,6 +77,7 @@ data:
|
|||
defaultRegistry: "docker.io"
|
||||
generateSuccessEvents: "false"
|
||||
excludeGroups: "system:serviceaccounts:kube-system,system:nodes"
|
||||
excludeUsernames: "!system:kube-scheduler"
|
||||
resourceFilters: >-
|
||||
[*/*,kyverno,*]
|
||||
[Event,*,*]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Add table
Reference in a new issue