1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-28 10:28:36 +00:00

refactor: user/groups exclusions (#6357)

* refactor: user/groups exclusions

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

* wildcard

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

* fix

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

---------

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
Signed-off-by: Charles-Edouard Brétéché <charled.breteche@gmail.com>
This commit is contained in:
Charles-Edouard Brétéché 2023-02-22 11:08:41 +01:00 committed by GitHub
parent 4a489b8979
commit 9e4ca53c3c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 82 additions and 61 deletions

View file

@ -71,7 +71,7 @@ func NewServer(
"POST",
config.CleanupValidatingWebhookServicePath,
handlers.FromAdmissionFunc("VALIDATE", validationHandler).
WithDump(debugModeOpts.DumpPayload, nil, nil, nil).
WithDump(debugModeOpts.DumpPayload, nil, nil).
WithSubResourceFilter().
WithMetrics(policyLogger, metricsConfig.Config(), metrics.WebhookValidating).
WithAdmission(policyLogger.WithName("validate")).

View file

@ -95,8 +95,10 @@ var (
kyvernoPodName = osutils.GetEnvWithFallback("KYVERNO_POD_NAME", "kyverno")
// kyvernoConfigMapName is the Kyverno configmap name
kyvernoConfigMapName = osutils.GetEnvWithFallback("INIT_CONFIG", "kyverno")
// defaultExcludeGroupRole ...
defaultExcludeGroupRole []string = []string{"system:serviceaccounts:kube-system", "system:nodes", "system:kube-scheduler"}
// defaultExcludedUsernames are the usernames excluded by default when matching an incoming admission request
defaultExcludedUsernames []string = []string{"system:kube-scheduler"}
// defaultExcludedGroups are the groups excluded by default when matching an incoming admission request
defaultExcludedGroups []string = []string{"system:serviceaccounts:kube-system", "system:nodes"}
// kyvernoDryRunNamespace is the namespace for DryRun option of YAML verification
kyvernoDryrunNamespace = osutils.GetEnvWithFallback("KYVERNO_DRYRUN_NAMESPACE", "kyverno-dryrun")
)
@ -137,10 +139,10 @@ 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
// GetExcludeGroupRole return exclude roles
GetExcludeGroupRole() []string
// GetExcludeUsername return exclude username
GetExcludeUsername() []string
// GetExcludedGroups return exclude groups
GetExcludedGroups() []string
// GetExcludedUsernames return exclude usernames
GetExcludedUsernames() []string
// GetGenerateSuccessEvents return if should generate success events
GetGenerateSuccessEvents() bool
// FilterNamespaces filters exclude namespace
@ -155,8 +157,8 @@ type Configuration interface {
type configuration struct {
defaultRegistry string
enableDefaultRegistryMutation bool
excludeGroupRole []string
excludeUsername []string
excludedGroups []string
excludedUsernames []string
filters []filter
generateSuccessEvents bool
mux sync.RWMutex
@ -168,7 +170,8 @@ func NewDefaultConfiguration() *configuration {
return &configuration{
defaultRegistry: "docker.io",
enableDefaultRegistryMutation: true,
excludeGroupRole: defaultExcludeGroupRole,
excludedGroups: defaultExcludedGroups,
excludedUsernames: defaultExcludedUsernames,
}
}
@ -202,12 +205,6 @@ func (cd *configuration) ToFilter(kind, namespace, name string) bool {
return false
}
func (cd *configuration) GetExcludeGroupRole() []string {
cd.mux.RLock()
defer cd.mux.RUnlock()
return cd.excludeGroupRole
}
func (cd *configuration) GetDefaultRegistry() string {
cd.mux.RLock()
defer cd.mux.RUnlock()
@ -220,10 +217,16 @@ func (cd *configuration) GetEnableDefaultRegistryMutation() bool {
return cd.enableDefaultRegistryMutation
}
func (cd *configuration) GetExcludeUsername() []string {
func (cd *configuration) GetExcludedUsernames() []string {
cd.mux.RLock()
defer cd.mux.RUnlock()
return cd.excludeUsername
return cd.excludedUsernames
}
func (cd *configuration) GetExcludedGroups() []string {
cd.mux.RLock()
defer cd.mux.RUnlock()
return cd.excludedGroups
}
func (cd *configuration) GetGenerateSuccessEvents() bool {
@ -265,10 +268,12 @@ func (cd *configuration) load(cm *corev1.ConfigMap) {
defer cd.mux.Unlock()
// reset
cd.filters = []filter{}
cd.excludeGroupRole = []string{}
cd.excludeUsername = []string{}
cd.excludedUsernames = []string{}
cd.excludedGroups = []string{}
cd.generateSuccessEvents = false
cd.webhooks = nil
cd.excludedGroups = append(cd.excludedGroups, defaultExcludedGroups...)
cd.excludedUsernames = append(cd.excludedUsernames, defaultExcludedUsernames...)
// load filters
cd.filters = parseKinds(cm.Data["resourceFilters"])
newDefaultRegistry, ok := cm.Data["defaultRegistry"]
@ -294,10 +299,19 @@ func (cd *configuration) load(cm *corev1.ConfigMap) {
cd.enableDefaultRegistryMutation = newEnableDefaultRegistryMutation
}
// load excludeGroupRole
cd.excludeGroupRole = append(cd.excludeGroupRole, parseRbac(cm.Data["excludeGroupRole"])...)
cd.excludeGroupRole = append(cd.excludeGroupRole, defaultExcludeGroupRole...)
excludedGroups, ok := cm.Data["excludeGroupRole"]
if !ok {
logger.V(6).Info("configuration: No excludeGroupRole defined in ConfigMap")
} else {
cd.excludedGroups = parseRbac(excludedGroups)
}
// load excludeUsername
cd.excludeUsername = append(cd.excludeUsername, parseRbac(cm.Data["excludeUsername"])...)
excludedUsernames, ok := cm.Data["excludeUsername"]
if !ok {
logger.V(6).Info("configuration: No excludeUsername defined in ConfigMap")
} else {
cd.excludedUsernames = parseRbac(excludedUsernames)
}
// load generateSuccessEvents
generateSuccessEvents, ok := cm.Data["generateSuccessEvents"]
if ok {
@ -326,9 +340,10 @@ func (cd *configuration) unload() {
cd.filters = []filter{}
cd.defaultRegistry = "docker.io"
cd.enableDefaultRegistryMutation = true
cd.excludeGroupRole = []string{}
cd.excludeUsername = []string{}
cd.excludedUsernames = []string{}
cd.excludedGroups = []string{}
cd.generateSuccessEvents = false
cd.webhooks = nil
cd.excludeGroupRole = append(cd.excludeGroupRole, defaultExcludeGroupRole...)
cd.excludedGroups = append(cd.excludedGroups, defaultExcludedGroups...)
cd.excludedUsernames = append(cd.excludedUsernames, defaultExcludedUsernames...)
}

View file

@ -93,7 +93,7 @@ func (e *engine) filterRule(
oldResource := policyContext.OldResource()
admissionInfo := policyContext.AdmissionInfo()
ctx := policyContext.JSONContext()
excludeGroupRole := e.configuration.GetExcludeGroupRole()
excludeGroupRole := e.configuration.GetExcludedGroups()
namespaceLabels := policyContext.NamespaceLabels()
if err := MatchesResourceDescription(subresourceGVKToAPIResource, newResource, rule, admissionInfo, excludeGroupRole, namespaceLabels, "", policyContext.SubResource()); err != nil {

View file

@ -60,7 +60,7 @@ func matchesException(
subresourceGVKToAPIResource,
policyContext.SubResource(),
policyContext.AdmissionInfo(),
cfg.GetExcludeGroupRole(),
cfg.GetExcludedGroups(),
)
// if there's no error it means a match
if err == nil {

View file

@ -57,8 +57,8 @@ func (e *engine) mutate(
func(ctx context.Context, span trace.Span) {
logger := internal.LoggerWithRule(logger, rule)
var excludeResource []string
if len(e.configuration.GetExcludeGroupRole()) > 0 {
excludeResource = e.configuration.GetExcludeGroupRole()
if len(e.configuration.GetExcludedGroups()) > 0 {
excludeResource = e.configuration.GetExcludedGroups()
}
kindsInPolicy := append(rule.MatchResources.GetKinds(), rule.ExcludeResources.GetKinds()...)

View file

@ -514,13 +514,13 @@ func matches(
subresourceGVKToAPIResource map[string]*metav1.APIResource,
cfg config.Configuration,
) bool {
err := MatchesResourceDescription(subresourceGVKToAPIResource, ctx.NewResource(), *rule, ctx.AdmissionInfo(), cfg.GetExcludeGroupRole(), ctx.NamespaceLabels(), "", ctx.SubResource())
err := MatchesResourceDescription(subresourceGVKToAPIResource, ctx.NewResource(), *rule, ctx.AdmissionInfo(), cfg.GetExcludedGroups(), ctx.NamespaceLabels(), "", ctx.SubResource())
if err == nil {
return true
}
if !reflect.DeepEqual(ctx.OldResource, unstructured.Unstructured{}) {
err := MatchesResourceDescription(subresourceGVKToAPIResource, ctx.OldResource(), *rule, ctx.AdmissionInfo(), cfg.GetExcludeGroupRole(), ctx.NamespaceLabels(), "", ctx.SubResource())
err := MatchesResourceDescription(subresourceGVKToAPIResource, ctx.OldResource(), *rule, ctx.AdmissionInfo(), cfg.GetExcludedGroups(), ctx.NamespaceLabels(), "", ctx.SubResource())
if err == nil {
return true
}

View file

@ -3,8 +3,6 @@ package userinfo
import (
"fmt"
"github.com/kyverno/kyverno/pkg/config"
datautils "github.com/kyverno/kyverno/pkg/utils/data"
admissionv1 "k8s.io/api/admission/v1"
authenticationv1 "k8s.io/api/authentication/v1"
rbacv1 "k8s.io/api/rbac/v1"
@ -18,11 +16,7 @@ const (
)
// GetRoleRef gets the list of roles and cluster roles for the incoming api-request
func GetRoleRef(rbLister rbacv1listers.RoleBindingLister, crbLister rbacv1listers.ClusterRoleBindingLister, request *admissionv1.AdmissionRequest, dynamicConfig config.Configuration) ([]string, []string, error) {
keys := append(request.UserInfo.Groups, request.UserInfo.Username)
if datautils.SliceContains(keys, dynamicConfig.GetExcludeGroupRole()...) {
return nil, nil, nil
}
func GetRoleRef(rbLister rbacv1listers.RoleBindingLister, crbLister rbacv1listers.ClusterRoleBindingLister, request *admissionv1.AdmissionRequest) ([]string, []string, error) {
// rolebindings
roleBindings, err := rbLister.List(labels.Everything())
if err != nil {

View file

@ -6,7 +6,6 @@ import (
"time"
"github.com/go-logr/logr"
"github.com/kyverno/kyverno/pkg/config"
"github.com/kyverno/kyverno/pkg/userinfo"
admissionutils "github.com/kyverno/kyverno/pkg/utils/admission"
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
@ -22,22 +21,20 @@ func (inner AdmissionHandler) WithDump(
enabled bool,
rbLister rbacv1listers.RoleBindingLister,
crbLister rbacv1listers.ClusterRoleBindingLister,
configuration config.Configuration,
) AdmissionHandler {
if !enabled {
return inner
}
return inner.withDump(rbLister, crbLister, configuration).WithTrace("DUMP")
return inner.withDump(rbLister, crbLister).WithTrace("DUMP")
}
func (inner AdmissionHandler) withDump(
rbLister rbacv1listers.RoleBindingLister,
crbLister rbacv1listers.ClusterRoleBindingLister,
configuration config.Configuration,
) AdmissionHandler {
return func(ctx context.Context, logger logr.Logger, request *admissionv1.AdmissionRequest, startTime time.Time) *admissionv1.AdmissionResponse {
response := inner(ctx, logger, request, startTime)
dumpPayload(logger, rbLister, crbLister, configuration, request, response)
dumpPayload(logger, rbLister, crbLister, request, response)
return response
}
}
@ -46,11 +43,10 @@ func dumpPayload(
logger logr.Logger,
rbLister rbacv1listers.RoleBindingLister,
crbLister rbacv1listers.ClusterRoleBindingLister,
configuration config.Configuration,
request *admissionv1.AdmissionRequest,
response *admissionv1.AdmissionResponse,
) {
reqPayload, err := newAdmissionRequestPayload(request, rbLister, crbLister, configuration)
reqPayload, err := newAdmissionRequestPayload(request, rbLister, crbLister)
if err != nil {
logger.Error(err, "Failed to extract resources")
} else {
@ -82,7 +78,11 @@ type admissionRequestPayload struct {
Options unstructured.Unstructured `json:"options,omitempty"`
}
func newAdmissionRequestPayload(request *admissionv1.AdmissionRequest, rbLister rbacv1listers.RoleBindingLister, crbLister rbacv1listers.ClusterRoleBindingLister, configuration config.Configuration) (*admissionRequestPayload, error) {
func newAdmissionRequestPayload(
request *admissionv1.AdmissionRequest,
rbLister rbacv1listers.RoleBindingLister,
crbLister rbacv1listers.ClusterRoleBindingLister,
) (*admissionRequestPayload, error) {
newResource, oldResource, err := admissionutils.ExtractResources(nil, request)
if err != nil {
return nil, err
@ -95,8 +95,8 @@ func newAdmissionRequestPayload(request *admissionv1.AdmissionRequest, rbLister
}
}
var roles, clusterRoles []string
if rbLister != nil && crbLister != nil && configuration != nil {
if r, cr, err := userinfo.GetRoleRef(rbLister, crbLister, request, configuration); err != nil {
if rbLister != nil && crbLister != nil {
if r, cr, err := userinfo.GetRoleRef(rbLister, crbLister, request); err != nil {
return nil, err
} else {
roles = r

View file

@ -141,7 +141,7 @@ func Test_RedactPayload(t *testing.T) {
req := new(admissionv1.AdmissionRequest)
err := json.Unmarshal(c.requestPayload, req)
assert.NilError(t, err)
payload, err := newAdmissionRequestPayload(req, nil, nil, nil)
payload, err := newAdmissionRequestPayload(req, nil, nil)
assert.NilError(t, err)
if payload.Object.Object != nil {
data, err := datautils.ToMap(payload.Object.Object["data"])

View file

@ -6,6 +6,7 @@ import (
"github.com/go-logr/logr"
"github.com/kyverno/kyverno/pkg/config"
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"
@ -25,14 +26,25 @@ func (inner AdmissionHandler) WithSubResourceFilter(subresources ...string) Admi
func (inner AdmissionHandler) withFilter(c config.Configuration) AdmissionHandler {
return func(ctx context.Context, logger logr.Logger, request *admissionv1.AdmissionRequest, startTime time.Time) *admissionv1.AdmissionResponse {
if c.ToFilter(request.Kind.Kind, request.Namespace, request.Name) {
return nil
}
for _, username := range c.GetExcludeUsername() {
if request.UserInfo.Username == username {
// filter by username
for _, username := range c.GetExcludedUsernames() {
if wildcard.Match(username, request.UserInfo.Username) {
return nil
}
}
// filter by groups
for _, group := range c.GetExcludedGroups() {
for _, candidate := range request.UserInfo.Groups {
if wildcard.Match(group, candidate) {
return nil
}
}
}
// filter by resource filters
if c.ToFilter(request.Kind.Kind, request.Namespace, request.Name) {
return nil
}
// filter kyverno resources
if webhookutils.ExcludeKyvernoResources(request.Kind.Kind) {
return nil
}

View file

@ -96,7 +96,7 @@ func NewServer(
return handler.
WithFilter(configuration).
WithProtection(toggle.ProtectManagedResources.Enabled()).
WithDump(debugModeOpts.DumpPayload, rbLister, crbLister, configuration).
WithDump(debugModeOpts.DumpPayload, rbLister, crbLister).
WithOperationFilter(admissionv1.Create, admissionv1.Update, admissionv1.Connect).
WithMetrics(resourceLogger, metricsConfig.Config(), metrics.WebhookMutating).
WithAdmission(resourceLogger.WithName("mutate"))
@ -111,7 +111,7 @@ func NewServer(
return handler.
WithFilter(configuration).
WithProtection(toggle.ProtectManagedResources.Enabled()).
WithDump(debugModeOpts.DumpPayload, rbLister, crbLister, configuration).
WithDump(debugModeOpts.DumpPayload, rbLister, crbLister).
WithMetrics(resourceLogger, metricsConfig.Config(), metrics.WebhookValidating).
WithAdmission(resourceLogger.WithName("validate"))
},
@ -120,7 +120,7 @@ func NewServer(
"POST",
config.PolicyMutatingWebhookServicePath,
handlers.FromAdmissionFunc("MUTATE", policyHandlers.Mutate).
WithDump(debugModeOpts.DumpPayload, rbLister, crbLister, configuration).
WithDump(debugModeOpts.DumpPayload, rbLister, crbLister).
WithMetrics(policyLogger, metricsConfig.Config(), metrics.WebhookMutating).
WithAdmission(policyLogger.WithName("mutate")).
ToHandlerFunc(),
@ -129,7 +129,7 @@ func NewServer(
"POST",
config.PolicyValidatingWebhookServicePath,
handlers.FromAdmissionFunc("VALIDATE", policyHandlers.Validate).
WithDump(debugModeOpts.DumpPayload, rbLister, crbLister, configuration).
WithDump(debugModeOpts.DumpPayload, rbLister, crbLister).
WithSubResourceFilter().
WithMetrics(policyLogger, metricsConfig.Config(), metrics.WebhookValidating).
WithAdmission(policyLogger.WithName("validate")).
@ -139,7 +139,7 @@ func NewServer(
"POST",
config.ExceptionValidatingWebhookServicePath,
handlers.FromAdmissionFunc("VALIDATE", exceptionHandlers.Validate).
WithDump(debugModeOpts.DumpPayload, rbLister, crbLister, configuration).
WithDump(debugModeOpts.DumpPayload, rbLister, crbLister).
WithSubResourceFilter().
WithMetrics(exceptionLogger, metricsConfig.Config(), metrics.WebhookValidating).
WithAdmission(exceptionLogger.WithName("validate")).

View file

@ -39,7 +39,7 @@ func (b *policyContextBuilder) Build(request *admissionv1.AdmissionRequest) (*en
userRequestInfo := kyvernov1beta1.RequestInfo{
AdmissionUserInfo: *request.UserInfo.DeepCopy(),
}
if roles, clusterRoles, err := userinfo.GetRoleRef(b.rbLister, b.crbLister, request, b.configuration); err != nil {
if roles, clusterRoles, err := userinfo.GetRoleRef(b.rbLister, b.crbLister, request); err != nil {
return nil, fmt.Errorf("failed to fetch RBAC information for request: %w", err)
} else {
userRequestInfo.Roles = roles