mirror of
https://github.com/kyverno/kyverno.git
synced 2024-12-14 11:57:48 +00:00
fc1a4601a7
Signed-off-by: Charles-Edouard Brétéché <charled.breteche@gmail.com>
331 lines
13 KiB
Go
331 lines
13 KiB
Go
package config
|
|
|
|
import (
|
|
"context"
|
|
"reflect"
|
|
"strconv"
|
|
"sync"
|
|
|
|
osutils "github.com/kyverno/kyverno/pkg/utils/os"
|
|
wildcard "github.com/kyverno/kyverno/pkg/utils/wildcard"
|
|
corev1 "k8s.io/api/core/v1"
|
|
"k8s.io/apimachinery/pkg/api/errors"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/client-go/kubernetes"
|
|
)
|
|
|
|
// These constants MUST be equal to the corresponding names in service definition in definitions/install.yaml
|
|
const (
|
|
// MutatingWebhookConfigurationName default resource mutating webhook configuration name
|
|
MutatingWebhookConfigurationName = "kyverno-resource-mutating-webhook-cfg"
|
|
// MutatingWebhookConfigurationDebugName default resource mutating webhook configuration name for debug mode
|
|
MutatingWebhookConfigurationDebugName = "kyverno-resource-mutating-webhook-cfg-debug"
|
|
// MutatingWebhookName default resource mutating webhook name
|
|
MutatingWebhookName = "mutate.kyverno.svc"
|
|
// ValidatingWebhookConfigurationName ...
|
|
ValidatingWebhookConfigurationName = "kyverno-resource-validating-webhook-cfg"
|
|
// ValidatingWebhookConfigurationDebugName ...
|
|
ValidatingWebhookConfigurationDebugName = "kyverno-resource-validating-webhook-cfg-debug"
|
|
// ValidatingWebhookName ...
|
|
ValidatingWebhookName = "validate.kyverno.svc"
|
|
// VerifyMutatingWebhookConfigurationName default verify mutating webhook configuration name
|
|
VerifyMutatingWebhookConfigurationName = "kyverno-verify-mutating-webhook-cfg"
|
|
// VerifyMutatingWebhookConfigurationDebugName default verify mutating webhook configuration name for debug mode
|
|
VerifyMutatingWebhookConfigurationDebugName = "kyverno-verify-mutating-webhook-cfg-debug"
|
|
// VerifyMutatingWebhookName default verify mutating webhook name
|
|
VerifyMutatingWebhookName = "monitor-webhooks.kyverno.svc"
|
|
// PolicyValidatingWebhookConfigurationName default policy validating webhook configuration name
|
|
PolicyValidatingWebhookConfigurationName = "kyverno-policy-validating-webhook-cfg"
|
|
// PolicyValidatingWebhookConfigurationDebugName default policy validating webhook configuration name for debug mode
|
|
PolicyValidatingWebhookConfigurationDebugName = "kyverno-policy-validating-webhook-cfg-debug"
|
|
// PolicyValidatingWebhookName default policy validating webhook name
|
|
PolicyValidatingWebhookName = "validate-policy.kyverno.svc"
|
|
// PolicyMutatingWebhookConfigurationName default policy mutating webhook configuration name
|
|
PolicyMutatingWebhookConfigurationName = "kyverno-policy-mutating-webhook-cfg"
|
|
// PolicyMutatingWebhookConfigurationDebugName default policy mutating webhook configuration name for debug mode
|
|
PolicyMutatingWebhookConfigurationDebugName = "kyverno-policy-mutating-webhook-cfg-debug"
|
|
// PolicyMutatingWebhookName default policy mutating webhook name
|
|
PolicyMutatingWebhookName = "mutate-policy.kyverno.svc"
|
|
// Due to kubernetes issue, we must use next literal constants instead of deployment TypeMeta fields
|
|
// Issue: https://github.com/kubernetes/kubernetes/pull/63972
|
|
// When the issue is closed, we should use TypeMeta struct instead of this constants
|
|
// ClusterRoleAPIVersion define the default clusterrole resource apiVersion
|
|
ClusterRoleAPIVersion = "rbac.authorization.k8s.io/v1"
|
|
// ClusterRoleKind define the default clusterrole resource kind
|
|
ClusterRoleKind = "ClusterRole"
|
|
// MutatingWebhookServicePath is the path for mutation webhook
|
|
MutatingWebhookServicePath = "/mutate"
|
|
// ValidatingWebhookServicePath is the path for validation webhook
|
|
ValidatingWebhookServicePath = "/validate"
|
|
// PolicyValidatingWebhookServicePath is the path for policy validation webhook(used to validate policy resource)
|
|
PolicyValidatingWebhookServicePath = "/policyvalidate"
|
|
// PolicyMutatingWebhookServicePath is the path for policy mutation webhook(used to default)
|
|
PolicyMutatingWebhookServicePath = "/policymutate"
|
|
// 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"
|
|
)
|
|
|
|
var (
|
|
// kyvernoNamespace is the Kyverno namespace
|
|
kyvernoNamespace = osutils.GetEnvWithFallback("KYVERNO_NAMESPACE", "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")
|
|
// defaultExcludeGroupRole ...
|
|
defaultExcludeGroupRole []string = []string{"system:serviceaccounts:kube-system", "system:nodes", "system:kube-scheduler"}
|
|
)
|
|
|
|
func KyvernoNamespace() string {
|
|
return kyvernoNamespace
|
|
}
|
|
|
|
func KyvernoDeploymentName() string {
|
|
return kyvernoDeploymentName
|
|
}
|
|
|
|
func KyvernoServiceName() string {
|
|
return kyvernoServiceName
|
|
}
|
|
|
|
func KyvernoPodName() string {
|
|
return kyvernoPodName
|
|
}
|
|
|
|
func KyvernoConfigMapName() string {
|
|
return kyvernoConfigMapName
|
|
}
|
|
|
|
// Configuration to be used by consumer to check filters
|
|
type Configuration interface {
|
|
// 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
|
|
// GetGenerateSuccessEvents return if should generate success events
|
|
GetGenerateSuccessEvents() bool
|
|
// RestrictDevelopmentUsername return exclude development username
|
|
RestrictDevelopmentUsername() []string
|
|
// FilterNamespaces filters exclude namespace
|
|
FilterNamespaces(namespaces []string) []string
|
|
// GetWebhooks returns the webhook configs
|
|
GetWebhooks() []WebhookConfig
|
|
// Load loads configuration from a configmap
|
|
Load(cm *corev1.ConfigMap)
|
|
}
|
|
|
|
// configuration stores the configuration
|
|
type configuration struct {
|
|
mux sync.RWMutex
|
|
filters []filter
|
|
excludeGroupRole []string
|
|
excludeUsername []string
|
|
restrictDevelopmentUsername []string
|
|
webhooks []WebhookConfig
|
|
generateSuccessEvents bool
|
|
reconcilePolicyReport chan<- bool
|
|
updateWebhookConfigurations chan<- bool
|
|
}
|
|
|
|
// NewConfiguration ...
|
|
func NewConfiguration(client kubernetes.Interface, reconcilePolicyReport, updateWebhookConfigurations chan<- bool) (Configuration, error) {
|
|
cd := &configuration{
|
|
reconcilePolicyReport: reconcilePolicyReport,
|
|
updateWebhookConfigurations: updateWebhookConfigurations,
|
|
restrictDevelopmentUsername: []string{"minikube-user", "kubernetes-admin"},
|
|
excludeGroupRole: defaultExcludeGroupRole,
|
|
}
|
|
if cm, err := client.CoreV1().ConfigMaps(kyvernoNamespace).Get(context.TODO(), kyvernoConfigMapName, metav1.GetOptions{}); err != nil {
|
|
if !errors.IsNotFound(err) {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
cd.load(cm)
|
|
}
|
|
return cd, nil
|
|
}
|
|
|
|
func (cd *configuration) ToFilter(kind, namespace, name string) bool {
|
|
cd.mux.RLock()
|
|
defer cd.mux.RUnlock()
|
|
for _, f := range cd.filters {
|
|
if wildcard.Match(f.Kind, kind) && wildcard.Match(f.Namespace, namespace) && wildcard.Match(f.Name, name) {
|
|
return true
|
|
}
|
|
if kind == "Namespace" {
|
|
// [Namespace,kube-system,*] || [*,kube-system,*]
|
|
if (f.Kind == "Namespace" || f.Kind == "*") && wildcard.Match(f.Namespace, name) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (cd *configuration) GetExcludeGroupRole() []string {
|
|
cd.mux.RLock()
|
|
defer cd.mux.RUnlock()
|
|
return cd.excludeGroupRole
|
|
}
|
|
|
|
func (cd *configuration) RestrictDevelopmentUsername() []string {
|
|
cd.mux.RLock()
|
|
defer cd.mux.RUnlock()
|
|
return cd.restrictDevelopmentUsername
|
|
}
|
|
|
|
func (cd *configuration) GetExcludeUsername() []string {
|
|
cd.mux.RLock()
|
|
defer cd.mux.RUnlock()
|
|
return cd.excludeUsername
|
|
}
|
|
|
|
func (cd *configuration) GetGenerateSuccessEvents() bool {
|
|
cd.mux.RLock()
|
|
defer cd.mux.RUnlock()
|
|
return cd.generateSuccessEvents
|
|
}
|
|
|
|
func (cd *configuration) FilterNamespaces(namespaces []string) []string {
|
|
var results []string
|
|
for _, ns := range namespaces {
|
|
if !cd.ToFilter("", ns, "") {
|
|
results = append(results, ns)
|
|
}
|
|
}
|
|
return results
|
|
}
|
|
|
|
func (cd *configuration) GetWebhooks() []WebhookConfig {
|
|
cd.mux.RLock()
|
|
defer cd.mux.RUnlock()
|
|
return cd.webhooks
|
|
}
|
|
|
|
func (cd *configuration) Load(cm *corev1.ConfigMap) {
|
|
reconcilePolicyReport, updateWebhook := true, true
|
|
if cm != nil {
|
|
logger.Info("load config", "name", cm.Name, "namespace", cm.Namespace)
|
|
reconcilePolicyReport, updateWebhook = cd.load(cm)
|
|
} else {
|
|
logger.Info("unload config")
|
|
cd.unload()
|
|
}
|
|
if reconcilePolicyReport {
|
|
logger.Info("resource filters changed, sending reconcile signal to the policy controller")
|
|
cd.reconcilePolicyReport <- true
|
|
}
|
|
if updateWebhook {
|
|
logger.Info("webhook configurations changed, updating webhook configurations")
|
|
cd.updateWebhookConfigurations <- true
|
|
}
|
|
}
|
|
|
|
func (cd *configuration) load(cm *corev1.ConfigMap) (reconcilePolicyReport, updateWebhook bool) {
|
|
logger := logger.WithValues("name", cm.Name, "namespace", cm.Namespace)
|
|
if cm.Data == nil {
|
|
logger.V(4).Info("configuration: No data defined in ConfigMap")
|
|
return
|
|
}
|
|
cd.mux.Lock()
|
|
defer cd.mux.Unlock()
|
|
filters, ok := cm.Data["resourceFilters"]
|
|
if !ok {
|
|
logger.V(4).Info("configuration: No resourceFilters defined in ConfigMap")
|
|
} else {
|
|
newFilters := parseKinds(filters)
|
|
if reflect.DeepEqual(newFilters, cd.filters) {
|
|
logger.V(4).Info("resourceFilters did not change")
|
|
} else {
|
|
logger.V(2).Info("Updated resource filters", "oldFilters", cd.filters, "newFilters", newFilters)
|
|
cd.filters = newFilters
|
|
reconcilePolicyReport = true
|
|
}
|
|
}
|
|
excludeGroupRole, ok := cm.Data["excludeGroupRole"]
|
|
if !ok {
|
|
logger.V(4).Info("configuration: No excludeGroupRole defined in ConfigMap")
|
|
}
|
|
newExcludeGroupRoles := parseRbac(excludeGroupRole)
|
|
newExcludeGroupRoles = append(newExcludeGroupRoles, defaultExcludeGroupRole...)
|
|
if reflect.DeepEqual(newExcludeGroupRoles, cd.excludeGroupRole) {
|
|
logger.V(4).Info("excludeGroupRole did not change")
|
|
} else {
|
|
logger.V(2).Info("Updated resource excludeGroupRoles", "oldExcludeGroupRole", cd.excludeGroupRole, "newExcludeGroupRole", newExcludeGroupRoles)
|
|
cd.excludeGroupRole = newExcludeGroupRoles
|
|
reconcilePolicyReport = true
|
|
}
|
|
excludeUsername, ok := cm.Data["excludeUsername"]
|
|
if !ok {
|
|
logger.V(4).Info("configuration: No excludeUsername defined in ConfigMap")
|
|
} else {
|
|
excludeUsernames := parseRbac(excludeUsername)
|
|
if reflect.DeepEqual(excludeUsernames, cd.excludeUsername) {
|
|
logger.V(4).Info("excludeGroupRole did not change")
|
|
} else {
|
|
logger.V(2).Info("Updated resource excludeUsernames", "oldExcludeUsername", cd.excludeUsername, "newExcludeUsername", excludeUsernames)
|
|
cd.excludeUsername = excludeUsernames
|
|
reconcilePolicyReport = true
|
|
}
|
|
}
|
|
webhooks, ok := cm.Data["webhooks"]
|
|
if !ok {
|
|
if len(cd.webhooks) > 0 {
|
|
cd.webhooks = nil
|
|
updateWebhook = true
|
|
logger.V(4).Info("configuration: Setting namespaceSelector to empty in the webhook configurations")
|
|
} else {
|
|
logger.V(4).Info("configuration: No webhook configurations defined in ConfigMap")
|
|
}
|
|
} else {
|
|
cfgs, err := parseWebhooks(webhooks)
|
|
if err != nil {
|
|
logger.Error(err, "unable to parse webhooks configurations")
|
|
return
|
|
}
|
|
|
|
if reflect.DeepEqual(cfgs, cd.webhooks) {
|
|
logger.V(4).Info("webhooks did not change")
|
|
} else {
|
|
logger.Info("Updated webhooks configurations", "oldWebhooks", cd.webhooks, "newWebhookd", cfgs)
|
|
cd.webhooks = cfgs
|
|
updateWebhook = true
|
|
}
|
|
}
|
|
generateSuccessEvents, ok := cm.Data["generateSuccessEvents"]
|
|
if !ok {
|
|
logger.V(4).Info("configuration: No generateSuccessEvents defined in ConfigMap")
|
|
} else {
|
|
generateSuccessEvents, err := strconv.ParseBool(generateSuccessEvents)
|
|
if err != nil {
|
|
logger.V(4).Info("configuration: generateSuccessEvents must be either true/false")
|
|
} else if generateSuccessEvents == cd.generateSuccessEvents {
|
|
logger.V(4).Info("generateSuccessEvents did not change")
|
|
} else {
|
|
logger.V(2).Info("Updated generateSuccessEvents", "oldGenerateSuccessEvents", cd.generateSuccessEvents, "newGenerateSuccessEvents", generateSuccessEvents)
|
|
cd.generateSuccessEvents = generateSuccessEvents
|
|
reconcilePolicyReport = true
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (cd *configuration) unload() {
|
|
cd.mux.Lock()
|
|
defer cd.mux.Unlock()
|
|
cd.filters = []filter{}
|
|
cd.excludeGroupRole = []string{}
|
|
cd.excludeGroupRole = append(cd.excludeGroupRole, defaultExcludeGroupRole...)
|
|
cd.excludeUsername = []string{}
|
|
cd.generateSuccessEvents = false
|
|
}
|