mirror of
https://github.com/kyverno/kyverno.git
synced 2024-12-14 11:57:48 +00:00
7170cbb0c2
* add spec.webhookConfigurations Signed-off-by: ShutingZhao <shuting@nirmata.com> * update crd Signed-off-by: ShutingZhao <shuting@nirmata.com> * configure webhook Signed-off-by: ShutingZhao <shuting@nirmata.com> * register webhook handler Signed-off-by: ShutingZhao <shuting@nirmata.com> * skip storing finegrained policies in cache Signed-off-by: ShutingZhao <shuting@nirmata.com> * update resource validate handler Signed-off-by: ShutingZhao <shuting@nirmata.com> * updates Signed-off-by: ShutingZhao <shuting@nirmata.com> * enable mutate resource handler for fine-grained policies Signed-off-by: ShutingZhao <shuting@nirmata.com> * fix: tests Signed-off-by: ShutingZhao <shuting@nirmata.com> --------- Signed-off-by: ShutingZhao <shuting@nirmata.com>
503 lines
17 KiB
Go
503 lines
17 KiB
Go
package config
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strconv"
|
|
"sync"
|
|
|
|
valid "github.com/asaskevich/govalidator"
|
|
"github.com/kyverno/kyverno/ext/wildcard"
|
|
osutils "github.com/kyverno/kyverno/pkg/utils/os"
|
|
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
|
corev1 "k8s.io/api/core/v1"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
)
|
|
|
|
// These constants MUST be equal to the corresponding names in service definition in definitions/install.yaml
|
|
|
|
// webhook configuration names
|
|
const (
|
|
// PolicyValidatingWebhookConfigurationName default policy validating webhook configuration name
|
|
PolicyValidatingWebhookConfigurationName = "kyverno-policy-validating-webhook-cfg"
|
|
// ValidatingWebhookConfigurationName ...
|
|
ValidatingWebhookConfigurationName = "kyverno-resource-validating-webhook-cfg"
|
|
// ExceptionValidatingWebhookConfigurationName ...
|
|
ExceptionValidatingWebhookConfigurationName = "kyverno-exception-validating-webhook-cfg"
|
|
// CleanupValidatingWebhookConfigurationName ...
|
|
CleanupValidatingWebhookConfigurationName = "kyverno-cleanup-validating-webhook-cfg"
|
|
// PolicyMutatingWebhookConfigurationName default policy mutating webhook configuration name
|
|
PolicyMutatingWebhookConfigurationName = "kyverno-policy-mutating-webhook-cfg"
|
|
// MutatingWebhookConfigurationName default resource mutating webhook configuration name
|
|
MutatingWebhookConfigurationName = "kyverno-resource-mutating-webhook-cfg"
|
|
// VerifyMutatingWebhookConfigurationName default verify mutating webhook configuration name
|
|
VerifyMutatingWebhookConfigurationName = "kyverno-verify-mutating-webhook-cfg"
|
|
// TtlValidatingWebhookConfigurationName ttl label validating webhook configuration name
|
|
TtlValidatingWebhookConfigurationName = "kyverno-ttl-validating-webhook-cfg"
|
|
)
|
|
|
|
// webhook names
|
|
const (
|
|
// PolicyValidatingWebhookName default policy validating webhook name
|
|
PolicyValidatingWebhookName = "validate-policy.kyverno.svc"
|
|
// ValidatingWebhookName ...
|
|
ValidatingWebhookName = "validate.kyverno.svc"
|
|
// PolicyMutatingWebhookName default policy mutating webhook name
|
|
PolicyMutatingWebhookName = "mutate-policy.kyverno.svc"
|
|
// MutatingWebhookName default resource mutating webhook name
|
|
MutatingWebhookName = "mutate.kyverno.svc"
|
|
// VerifyMutatingWebhookName default verify mutating webhook name
|
|
VerifyMutatingWebhookName = "monitor-webhooks.kyverno.svc"
|
|
)
|
|
|
|
// paths
|
|
const (
|
|
// PolicyValidatingWebhookServicePath is the path for policy validation webhook(used to validate policy resource)
|
|
PolicyValidatingWebhookServicePath = "/policyvalidate"
|
|
// ValidatingWebhookServicePath is the path for validation webhook
|
|
ValidatingWebhookServicePath = "/validate"
|
|
// ExceptionValidatingWebhookServicePath is the path for policy exception validation webhook(used to validate policy exception resource)
|
|
ExceptionValidatingWebhookServicePath = "/exceptionvalidate"
|
|
// CleanupValidatingWebhookServicePath is the path for cleanup policy validation webhook(used to validate cleanup policy resource)
|
|
CleanupValidatingWebhookServicePath = "/validate"
|
|
// TtlValidatingWebhookServicePath is the path for validation of cleanup.kyverno.io/ttl label value
|
|
TtlValidatingWebhookServicePath = "/verifyttl"
|
|
// PolicyMutatingWebhookServicePath is the path for policy mutation webhook(used to default)
|
|
PolicyMutatingWebhookServicePath = "/policymutate"
|
|
// MutatingWebhookServicePath is the path for mutation webhook
|
|
MutatingWebhookServicePath = "/mutate"
|
|
// VerifyMutatingWebhookServicePath is the path for verify webhook(used to veryfing if admission control is enabled and active)
|
|
VerifyMutatingWebhookServicePath = "/verifymutate"
|
|
// LivenessServicePath is the path for check liveness health
|
|
LivenessServicePath = "/health/liveness"
|
|
// ReadinessServicePath is the path for check readness health
|
|
ReadinessServicePath = "/health/readiness"
|
|
// MetricsPath is the path for exposing metrics
|
|
MetricsPath = "/metrics"
|
|
// FineGrainedWebhookPath is the sub-path for fine-grained webhook configurationss
|
|
FineGrainedWebhookPath = "/finegrained"
|
|
)
|
|
|
|
// keys in config map
|
|
const (
|
|
resourceFilters = "resourceFilters"
|
|
defaultRegistry = "defaultRegistry"
|
|
enableDefaultRegistryMutation = "enableDefaultRegistryMutation"
|
|
excludeGroups = "excludeGroups"
|
|
excludeUsernames = "excludeUsernames"
|
|
excludeRoles = "excludeRoles"
|
|
excludeClusterRoles = "excludeClusterRoles"
|
|
generateSuccessEvents = "generateSuccessEvents"
|
|
webhooks = "webhooks"
|
|
webhookAnnotations = "webhookAnnotations"
|
|
webhookLabels = "webhookLabels"
|
|
matchConditions = "matchConditions"
|
|
)
|
|
|
|
var (
|
|
// kyvernoNamespace is the Kyverno namespace
|
|
kyvernoNamespace = osutils.GetEnvWithFallback("KYVERNO_NAMESPACE", "kyverno")
|
|
// kyvernoServiceAccountName is the Kyverno service account name
|
|
kyvernoServiceAccountName = osutils.GetEnvWithFallback("KYVERNO_SERVICEACCOUNT_NAME", "kyverno")
|
|
// kyvernoDeploymentName is the Kyverno deployment name
|
|
kyvernoDeploymentName = osutils.GetEnvWithFallback("KYVERNO_DEPLOYMENT", "kyverno")
|
|
// kyvernoServiceName is the Kyverno service name
|
|
kyvernoServiceName = osutils.GetEnvWithFallback("KYVERNO_SVC", "kyverno-svc")
|
|
// kyvernoPodName is the Kyverno pod name
|
|
kyvernoPodName = osutils.GetEnvWithFallback("KYVERNO_POD_NAME", "kyverno")
|
|
// kyvernoConfigMapName is the Kyverno configmap name
|
|
kyvernoConfigMapName = osutils.GetEnvWithFallback("INIT_CONFIG", "kyverno")
|
|
// kyvernoMetricsConfigMapName is the Kyverno metrics configmap name
|
|
kyvernoMetricsConfigMapName = osutils.GetEnvWithFallback("METRICS_CONFIG", "kyverno-metrics")
|
|
// kyvernoDryRunNamespace is the namespace for DryRun option of YAML verification
|
|
kyvernoDryrunNamespace = osutils.GetEnvWithFallback("KYVERNO_DRYRUN_NAMESPACE", "kyverno-dryrun")
|
|
)
|
|
|
|
func KyvernoNamespace() string {
|
|
return kyvernoNamespace
|
|
}
|
|
|
|
func KyvernoDryRunNamespace() string {
|
|
return kyvernoDryrunNamespace
|
|
}
|
|
|
|
func KyvernoServiceAccountName() string {
|
|
return kyvernoServiceAccountName
|
|
}
|
|
|
|
func KyvernoDeploymentName() string {
|
|
return kyvernoDeploymentName
|
|
}
|
|
|
|
func KyvernoServiceName() string {
|
|
return kyvernoServiceName
|
|
}
|
|
|
|
func KyvernoPodName() string {
|
|
return kyvernoPodName
|
|
}
|
|
|
|
func KyvernoConfigMapName() string {
|
|
return kyvernoConfigMapName
|
|
}
|
|
|
|
func KyvernoMetricsConfigMapName() string {
|
|
return kyvernoMetricsConfigMapName
|
|
}
|
|
|
|
func KyvernoUserName(serviceaccount string) string {
|
|
return fmt.Sprintf("system:serviceaccount:%s:%s", kyvernoNamespace, serviceaccount)
|
|
}
|
|
|
|
// Configuration to be used by consumer to check filters
|
|
type Configuration interface {
|
|
// GetDefaultRegistry return default image registry
|
|
GetDefaultRegistry() string
|
|
// GetEnableDefaultRegistryMutation returns true if image references should be mutated
|
|
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
|
|
// GetGenerateSuccessEvents return if should generate success events
|
|
GetGenerateSuccessEvents() bool
|
|
// GetWebhooks returns the webhook configs
|
|
GetWebhooks() []WebhookConfig
|
|
// GetWebhookAnnotations returns annotations to set on webhook configs
|
|
GetWebhookAnnotations() map[string]string
|
|
// GetWebhookLabels returns labels to set on webhook configs
|
|
GetWebhookLabels() map[string]string
|
|
// GetMatchConditions returns match conditions to set on webhook configs
|
|
GetMatchConditions() []admissionregistrationv1.MatchCondition
|
|
// Load loads configuration from a configmap
|
|
Load(*corev1.ConfigMap)
|
|
// OnChanged adds a callback to be invoked when the configuration is reloaded
|
|
OnChanged(func())
|
|
}
|
|
|
|
// configuration stores the configuration
|
|
type configuration struct {
|
|
skipResourceFilters bool
|
|
defaultRegistry string
|
|
enableDefaultRegistryMutation bool
|
|
exclusions match
|
|
inclusions match
|
|
filters []filter
|
|
generateSuccessEvents bool
|
|
webhooks []WebhookConfig
|
|
webhookAnnotations map[string]string
|
|
webhookLabels map[string]string
|
|
matchConditions []admissionregistrationv1.MatchCondition
|
|
mux sync.RWMutex
|
|
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{
|
|
skipResourceFilters: skipResourceFilters,
|
|
defaultRegistry: "docker.io",
|
|
enableDefaultRegistryMutation: true,
|
|
}
|
|
}
|
|
|
|
func (cd *configuration) OnChanged(callback func()) {
|
|
cd.mux.Lock()
|
|
defer cd.mux.Unlock()
|
|
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()
|
|
if !cd.skipResourceFilters {
|
|
for _, f := range cd.filters {
|
|
if wildcard.Match(f.Group, gvk.Group) && wildcard.Match(f.Version, gvk.Version) && wildcard.Match(f.Kind, gvk.Kind) && wildcard.Match(f.Subresource, subresource) {
|
|
if wildcard.Match(f.Namespace, namespace) && wildcard.Match(f.Name, name) {
|
|
return true
|
|
}
|
|
// [Namespace,kube-system,*] || [*,kube-system,*]
|
|
if gvk.Group == "" && gvk.Version == "v1" && gvk.Kind == "Namespace" {
|
|
if wildcard.Match(f.Namespace, name) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (cd *configuration) GetDefaultRegistry() string {
|
|
cd.mux.RLock()
|
|
defer cd.mux.RUnlock()
|
|
return cd.defaultRegistry
|
|
}
|
|
|
|
func (cd *configuration) GetEnableDefaultRegistryMutation() bool {
|
|
cd.mux.RLock()
|
|
defer cd.mux.RUnlock()
|
|
return cd.enableDefaultRegistryMutation
|
|
}
|
|
|
|
func (cd *configuration) GetGenerateSuccessEvents() bool {
|
|
cd.mux.RLock()
|
|
defer cd.mux.RUnlock()
|
|
return cd.generateSuccessEvents
|
|
}
|
|
|
|
func (cd *configuration) GetWebhooks() []WebhookConfig {
|
|
cd.mux.RLock()
|
|
defer cd.mux.RUnlock()
|
|
return cd.webhooks
|
|
}
|
|
|
|
func (cd *configuration) GetWebhookAnnotations() map[string]string {
|
|
cd.mux.RLock()
|
|
defer cd.mux.RUnlock()
|
|
return cd.webhookAnnotations
|
|
}
|
|
|
|
func (cd *configuration) GetWebhookLabels() map[string]string {
|
|
cd.mux.RLock()
|
|
defer cd.mux.RUnlock()
|
|
return cd.webhookLabels
|
|
}
|
|
|
|
func (cd *configuration) GetMatchConditions() []admissionregistrationv1.MatchCondition {
|
|
cd.mux.RLock()
|
|
defer cd.mux.RUnlock()
|
|
return cd.matchConditions
|
|
}
|
|
|
|
func (cd *configuration) Load(cm *corev1.ConfigMap) {
|
|
if cm != nil {
|
|
cd.load(cm)
|
|
} else {
|
|
cd.unload()
|
|
}
|
|
}
|
|
|
|
func (cd *configuration) load(cm *corev1.ConfigMap) {
|
|
logger := logger.WithValues("name", cm.Name, "namespace", cm.Namespace)
|
|
cd.mux.Lock()
|
|
defer cd.mux.Unlock()
|
|
defer cd.notify()
|
|
data := cm.Data
|
|
if data == nil {
|
|
data = map[string]string{}
|
|
}
|
|
// reset
|
|
cd.defaultRegistry = "docker.io"
|
|
cd.enableDefaultRegistryMutation = true
|
|
cd.exclusions = match{}
|
|
cd.inclusions = match{}
|
|
cd.filters = []filter{}
|
|
cd.generateSuccessEvents = false
|
|
cd.webhooks = nil
|
|
cd.webhookAnnotations = nil
|
|
cd.webhookLabels = nil
|
|
cd.matchConditions = nil
|
|
// load filters
|
|
cd.filters = parseKinds(data[resourceFilters])
|
|
logger.Info("filters configured", "filters", cd.filters)
|
|
// load defaultRegistry
|
|
defaultRegistry, ok := data[defaultRegistry]
|
|
if !ok {
|
|
logger.Info("defaultRegistry not set")
|
|
} else {
|
|
logger := logger.WithValues("defaultRegistry", defaultRegistry)
|
|
if valid.IsDNSName(defaultRegistry) {
|
|
cd.defaultRegistry = defaultRegistry
|
|
logger.Info("defaultRegistry configured")
|
|
} else {
|
|
logger.Error(errors.New("defaultRegistry is not a valid DNS hostname"), "failed to configure defaultRegistry")
|
|
}
|
|
}
|
|
// load enableDefaultRegistryMutation
|
|
enableDefaultRegistryMutation, ok := data[enableDefaultRegistryMutation]
|
|
if !ok {
|
|
logger.Info("enableDefaultRegistryMutation not set")
|
|
} else {
|
|
logger := logger.WithValues("enableDefaultRegistryMutation", enableDefaultRegistryMutation)
|
|
enableDefaultRegistryMutation, err := strconv.ParseBool(enableDefaultRegistryMutation)
|
|
if err != nil {
|
|
logger.Error(err, "enableDefaultRegistryMutation is not a boolean")
|
|
} else {
|
|
cd.enableDefaultRegistryMutation = enableDefaultRegistryMutation
|
|
logger.Info("enableDefaultRegistryMutation configured")
|
|
}
|
|
}
|
|
// load excludeGroupRole
|
|
excludedGroups, ok := data[excludeGroups]
|
|
if !ok {
|
|
logger.Info("excludeGroups not set")
|
|
} else {
|
|
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.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.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.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]
|
|
if !ok {
|
|
logger.Info("generateSuccessEvents not set")
|
|
} else {
|
|
logger := logger.WithValues("generateSuccessEvents", generateSuccessEvents)
|
|
generateSuccessEvents, err := strconv.ParseBool(generateSuccessEvents)
|
|
if err != nil {
|
|
logger.Error(err, "generateSuccessEvents is not a boolean")
|
|
} else {
|
|
cd.generateSuccessEvents = generateSuccessEvents
|
|
logger.Info("generateSuccessEvents configured")
|
|
}
|
|
}
|
|
// load webhooks
|
|
webhooks, ok := data[webhooks]
|
|
if !ok {
|
|
logger.Info("webhooks not set")
|
|
} else {
|
|
logger := logger.WithValues("webhooks", webhooks)
|
|
webhooks, err := parseWebhooks(webhooks)
|
|
if err != nil {
|
|
logger.Error(err, "failed to parse webhooks")
|
|
} else {
|
|
cd.webhooks = webhooks
|
|
logger.Info("webhooks configured")
|
|
}
|
|
}
|
|
// load webhook annotations
|
|
webhookAnnotations, ok := data[webhookAnnotations]
|
|
if !ok {
|
|
logger.Info("webhookAnnotations not set")
|
|
} else {
|
|
logger := logger.WithValues("webhookAnnotations", webhookAnnotations)
|
|
webhookAnnotations, err := parseWebhookAnnotations(webhookAnnotations)
|
|
if err != nil {
|
|
logger.Error(err, "failed to parse webhook annotations")
|
|
} else {
|
|
cd.webhookAnnotations = webhookAnnotations
|
|
logger.Info("webhookAnnotations configured")
|
|
}
|
|
}
|
|
// load webhook annotations
|
|
webhookLabels, ok := data[webhookLabels]
|
|
if !ok {
|
|
logger.Info("webhookLabels not set")
|
|
} else {
|
|
logger := logger.WithValues("webhookLabels", webhookLabels)
|
|
webhookLabels, err := parseWebhookLabels(webhookLabels)
|
|
if err != nil {
|
|
logger.Error(err, "failed to parse webhook labels")
|
|
} else {
|
|
cd.webhookLabels = webhookLabels
|
|
logger.Info("webhookLabels configured")
|
|
}
|
|
}
|
|
// load match conditions
|
|
matchConditions, ok := data[matchConditions]
|
|
if !ok {
|
|
logger.Info("matchConditions not set")
|
|
} else {
|
|
logger := logger.WithValues("matchConditions", matchConditions)
|
|
matchConditions, err := parseMatchConditions(matchConditions)
|
|
if err != nil {
|
|
logger.Error(err, "failed to parse match conditions")
|
|
} else {
|
|
cd.matchConditions = matchConditions
|
|
logger.Info("matchConditions configured")
|
|
}
|
|
}
|
|
}
|
|
|
|
func (cd *configuration) unload() {
|
|
cd.mux.Lock()
|
|
defer cd.mux.Unlock()
|
|
defer cd.notify()
|
|
cd.defaultRegistry = "docker.io"
|
|
cd.enableDefaultRegistryMutation = true
|
|
cd.exclusions = match{}
|
|
cd.inclusions = match{}
|
|
cd.filters = []filter{}
|
|
cd.generateSuccessEvents = false
|
|
cd.webhooks = nil
|
|
cd.webhookAnnotations = nil
|
|
cd.webhookLabels = nil
|
|
logger.Info("configuration unloaded")
|
|
}
|
|
|
|
func (cd *configuration) notify() {
|
|
for _, callback := range cd.callbacks {
|
|
callback()
|
|
}
|
|
}
|