diff --git a/cmd/kyverno/main.go b/cmd/kyverno/main.go index 1176951819..a99a2086f6 100755 --- a/cmd/kyverno/main.go +++ b/cmd/kyverno/main.go @@ -165,21 +165,6 @@ func main() { if err != nil { setupLog.Error(err, "ConfigMap lookup disabled: failed to create resource cache") } - debug := serverIP != "" - webhookCfg := webhookconfig.NewRegister( - clientConfig, - client, - rCache, - serverIP, - int32(webhookTimeout), - debug, - log.Log) - - webhookMonitor, err := webhookconfig.NewMonitor(kubeClient, log.Log.WithName("WebhookMonitor")) - if err != nil { - setupLog.Error(err, "failed to initialize webhookMonitor") - os.Exit(1) - } // KYVERNO CRD INFORMER // watches CRD resources: @@ -231,6 +216,22 @@ func main() { os.Exit(1) } + debug := serverIP != "" + webhookCfg := webhookconfig.NewRegister( + clientConfig, + client, + rCache, + serverIP, + int32(webhookTimeout), + debug, + log.Log) + + webhookMonitor, err := webhookconfig.NewMonitor(kubeClient, log.Log.WithName("WebhookMonitor")) + if err != nil { + setupLog.Error(err, "failed to initialize webhookMonitor") + os.Exit(1) + } + // Configuration Data // dynamically load the configuration from configMap // - resource filters @@ -242,6 +243,7 @@ func main() { excludeGroupRole, excludeUsername, prgen.ReconcileCh, + webhookCfg.UpdateWebhookChan, log.Log.WithName("ConfigData"), ) @@ -350,10 +352,18 @@ func main() { registerWebhookConfigurations := func() { certManager.InitTLSPemPair() + // validate the ConfigMap format + if err := webhookCfg.ValidateWebhookConfigurations(config.KyvernoNamespace, configData.GetInitConfigMapName()); err != nil { + setupLog.Error(err, "invalid format of the Kyverno init ConfigMap, please correct the format of 'data.webhooks'") + os.Exit(1) + } + + go webhookCfg.UpdateWebhookConfigurations(configData) if registrationErr := registerWrapperRetry(); registrationErr != nil { setupLog.Error(err, "Timeout registering admission control webhooks") os.Exit(1) } + webhookCfg.UpdateWebhookChan <- true } // leader election context @@ -366,7 +376,7 @@ func main() { cancel() }() - // register webhooks by the leader, it's a one-time job + // webhookconfigurations are registered by the leader only webhookRegisterLeader, err := leaderelection.New("webhook-register", config.KyvernoNamespace, kubeClient, registerWebhookConfigurations, nil, log.Log.WithName("webhookRegister/LeaderElection")) if err != nil { setupLog.Error(err, "failed to elector leader") diff --git a/go.sum b/go.sum index c6cbb043f7..6a0e8a29cc 100644 --- a/go.sum +++ b/go.sum @@ -741,11 +741,7 @@ github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOms github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -<<<<<<< HEAD -github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= -======= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= ->>>>>>> 2c944c03 (updated verison to latest) github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.0 h1:nfhvjKcUMhBMVqbKHJlk5RPrrfYr/NMo3692g0dwfWU= github.com/sirupsen/logrus v1.8.0/go.mod h1:4GuYW9TZmE769R5STWrRakJc4UqQ3+QQ95fyz7ENv1A= @@ -762,7 +758,6 @@ github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= -github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -779,7 +774,6 @@ github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3 github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -808,7 +802,6 @@ github.com/valyala/fasthttp v1.2.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk github.com/valyala/quicktemplate v1.2.0/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4= github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= -github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70= github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= @@ -878,7 +871,6 @@ golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1115,7 +1107,6 @@ golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4f golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.1.0/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= @@ -1135,7 +1126,6 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -1193,7 +1183,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= @@ -1207,9 +1196,7 @@ gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -1227,7 +1214,6 @@ gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1260,7 +1246,6 @@ k8s.io/apiserver v0.0.0-20190918160949-bfa5e2e684ad/go.mod h1:XPCXEwhjaFN29a8Nld k8s.io/apiserver v0.16.4/go.mod h1:kbLJOak655g6W7C+muqu1F76u9wnEycfKMqbVaXIdAc= k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= k8s.io/apiserver v0.21.0/go.mod h1:w2YSn4/WIwYuxG5zJmcqtRdtqgW/J2JRgFAqps3bBpg= -k8s.io/cli-runtime v0.20.2 h1:W0/FHdbApnl9oB7xdG643c/Zaf7TZT+43I+zKxwqvhU= k8s.io/cli-runtime v0.20.2/go.mod h1:FjH6uIZZZP3XmwrXWeeYCbgxcrD6YXxoAykBaWH0VdM= k8s.io/client-go v0.0.0-20190918160344-1fbdaa4c8d90/go.mod h1:J69/JveO6XESwVgG53q3Uz5OSfgsv4uxpScmmyYOOlk= k8s.io/client-go v0.16.4/go.mod h1:ZgxhFDxSnoKY0J0U2/Y1C8obKDdlhGPZwA7oHH863Ok= diff --git a/pkg/config/dynamicconfig.go b/pkg/config/dynamicconfig.go index 68cd249eaa..3aa5a16f14 100644 --- a/pkg/config/dynamicconfig.go +++ b/pkg/config/dynamicconfig.go @@ -1,6 +1,7 @@ package config import ( + "encoding/json" "os" "reflect" "regexp" @@ -10,6 +11,7 @@ import ( "github.com/go-logr/logr" "github.com/minio/pkg/wildcard" v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" informers "k8s.io/client-go/informers/core/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/cache" @@ -21,6 +23,10 @@ const cmNameEnv string = "INIT_CONFIG" var defaultExcludeGroupRole []string = []string{"system:serviceaccounts:kube-system", "system:nodes", "system:kube-scheduler"} +type WebhookConfig struct { + NamespaceSelector *metav1.LabelSelector `json:"namespaceSelector,omitempty" protobuf:"bytes,5,opt,name=namespaceSelector"` +} + // ConfigData stores the configuration type ConfigData struct { client kubernetes.Interface @@ -30,8 +36,10 @@ type ConfigData struct { excludeGroupRole []string excludeUsername []string restrictDevelopmentUsername []string + webhooks []WebhookConfig cmSycned cache.InformerSynced reconcilePolicyReport chan<- bool + updateWebhookConfigurations chan<- bool log logr.Logger } @@ -87,6 +95,16 @@ func (cd *ConfigData) FilterNamespaces(namespaces []string) []string { return results } +func (cd *ConfigData) GetWebhooks() []WebhookConfig { + cd.mux.RLock() + defer cd.mux.RUnlock() + return cd.webhooks +} + +func (cd *ConfigData) GetInitConfigMapName() string { + return cd.cmName +} + // Interface to be used by consumer to check filters type Interface interface { ToFilter(kind, namespace, name string) bool @@ -94,21 +112,24 @@ type Interface interface { GetExcludeUsername() []string RestrictDevelopmentUsername() []string FilterNamespaces(namespaces []string) []string + GetWebhooks() []WebhookConfig + GetInitConfigMapName() string } // NewConfigData ... -func NewConfigData(rclient kubernetes.Interface, cmInformer informers.ConfigMapInformer, filterK8sResources, excludeGroupRole, excludeUsername string, reconcilePolicyReport chan<- bool, log logr.Logger) *ConfigData { +func NewConfigData(rclient kubernetes.Interface, cmInformer informers.ConfigMapInformer, filterK8sResources, excludeGroupRole, excludeUsername string, reconcilePolicyReport, updateWebhookConfigurations chan<- bool, log logr.Logger) *ConfigData { // environment var is read at start only if cmNameEnv == "" { log.Info("ConfigMap name not defined in env:INIT_CONFIG: loading no default configuration") } cd := ConfigData{ - client: rclient, - cmName: os.Getenv(cmNameEnv), - cmSycned: cmInformer.Informer().HasSynced, - reconcilePolicyReport: reconcilePolicyReport, - log: log, + client: rclient, + cmName: os.Getenv(cmNameEnv), + cmSycned: cmInformer.Informer().HasSynced, + reconcilePolicyReport: reconcilePolicyReport, + updateWebhookConfigurations: updateWebhookConfigurations, + log: log, } cd.restrictDevelopmentUsername = []string{"minikube-user", "kubernetes-admin"} @@ -156,7 +177,6 @@ func (cd *ConfigData) addCM(obj interface{}) { return } cd.load(*cm) - // else load the configuration } func (cd *ConfigData) updateCM(old, cur interface{}) { @@ -165,11 +185,16 @@ func (cd *ConfigData) updateCM(old, cur interface{}) { return } // if data has not changed then dont load configmap - changed := cd.load(*cm) - if changed { + reconcilePolicyReport, updateWebook := cd.load(*cm) + if reconcilePolicyReport { cd.log.Info("resource filters changed, sending reconcile signal to the policy controller") cd.reconcilePolicyReport <- true } + + if updateWebook { + cd.log.Info("webhook configurations changed, updating webhook configurations") + cd.updateWebhookConfigurations <- true + } } func (cd *ConfigData) deleteCM(obj interface{}) { @@ -195,7 +220,7 @@ func (cd *ConfigData) deleteCM(obj interface{}) { cd.unload(*cm) } -func (cd *ConfigData) load(cm v1.ConfigMap) (changed bool) { +func (cd *ConfigData) load(cm v1.ConfigMap) (reconcilePolicyReport, updateWebhook bool) { logger := cd.log.WithValues("name", cm.Name, "namespace", cm.Namespace) if cm.Data == nil { logger.V(4).Info("configuration: No data defined in ConfigMap") @@ -215,7 +240,7 @@ func (cd *ConfigData) load(cm v1.ConfigMap) (changed bool) { } else { logger.V(2).Info("Updated resource filters", "oldFilters", cd.filters, "newFilters", newFilters) cd.filters = newFilters - changed = true + reconcilePolicyReport = true } } @@ -230,7 +255,7 @@ func (cd *ConfigData) load(cm v1.ConfigMap) (changed bool) { } else { logger.V(2).Info("Updated resource excludeGroupRoles", "oldExcludeGroupRole", cd.excludeGroupRole, "newExcludeGroupRole", newExcludeGroupRoles) cd.excludeGroupRole = newExcludeGroupRoles - changed = true + reconcilePolicyReport = true } excludeUsername, ok := cm.Data["excludeUsername"] @@ -243,11 +268,29 @@ func (cd *ConfigData) load(cm v1.ConfigMap) (changed bool) { } else { logger.V(2).Info("Updated resource excludeUsernames", "oldExcludeUsername", cd.excludeUsername, "newExcludeUsername", excludeUsernames) cd.excludeUsername = excludeUsernames - changed = true + reconcilePolicyReport = true } } - return changed + webhooks, ok := cm.Data["webhooks"] + if !ok { + 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 + } + } + return } //TODO: this has been added to backward support command line arguments @@ -330,3 +373,12 @@ func parseKinds(list string) []k8Resource { func parseRbac(list string) []string { return strings.Split(list, ",") } + +func parseWebhooks(webhooks string) ([]WebhookConfig, error) { + webhookCfgs := make([]WebhookConfig, 0, 10) + if err := json.Unmarshal([]byte(webhooks), &webhookCfgs); err != nil { + return nil, err + } + + return webhookCfgs, nil +} diff --git a/pkg/webhookconfig/registration.go b/pkg/webhookconfig/registration.go index 2cdae9daee..7118c44694 100644 --- a/pkg/webhookconfig/registration.go +++ b/pkg/webhookconfig/registration.go @@ -1,7 +1,7 @@ package webhookconfig import ( - "errors" + "encoding/json" "fmt" "strings" "sync" @@ -12,6 +12,7 @@ import ( client "github.com/kyverno/kyverno/pkg/dclient" "github.com/kyverno/kyverno/pkg/resourcecache" "github.com/kyverno/kyverno/pkg/tls" + "github.com/pkg/errors" admregapi "k8s.io/api/admissionregistration/v1beta1" corev1 "k8s.io/api/core/v1" errorsapi "k8s.io/apimachinery/pkg/api/errors" @@ -40,6 +41,8 @@ type Register struct { timeoutSeconds int32 log logr.Logger debug bool + + UpdateWebhookChan chan bool } // NewRegister creates new Register instance @@ -52,13 +55,14 @@ func NewRegister( debug bool, log logr.Logger) *Register { return &Register{ - clientConfig: clientConfig, - client: client, - resCache: resCache, - serverIP: serverIP, - timeoutSeconds: webhookTimeout, - log: log.WithName("Register"), - debug: debug, + clientConfig: clientConfig, + client: client, + resCache: resCache, + serverIP: serverIP, + timeoutSeconds: webhookTimeout, + log: log.WithName("Register"), + debug: debug, + UpdateWebhookChan: make(chan bool), } } @@ -147,6 +151,70 @@ func (wrc *Register) Remove(cleanUp chan<- struct{}) { wrc.removeSecrets() } +// UpdateWebhookConfigurations updates resource webhook configurations dynamically +// base on the UPDATEs of Kyverno init-config ConfigMap +// +// it currently updates namespaceSelector only, can be extend to update other fieids +func (wrc *Register) UpdateWebhookConfigurations(configHandler config.Interface) { + logger := wrc.log.WithName("UpdateWebhookConfigurations") + for { + <-wrc.UpdateWebhookChan + logger.Info("received the signal to update webhook configurations") + + var nsSelector map[string]interface{} + webhookCfgs := configHandler.GetWebhooks() + if webhookCfgs != nil { + selector := webhookCfgs[0].NamespaceSelector + selectorBytes, err := json.Marshal(*selector) + if err != nil { + logger.Error(err, "failed to serialize namespaceSelector") + continue + } + + if err = json.Unmarshal(selectorBytes, &nsSelector); err != nil { + logger.Error(err, "failed to convert namespaceSelector to the map") + continue + } + } + + if err := wrc.updateResourceMutatingWebhookConfiguration(nsSelector); err != nil { + logger.Error(err, "unable to update mutatingWebhookConfigurations", "name", wrc.getResourceMutatingWebhookConfigName()) + } else { + logger.Info("successfully updated mutatingWebhookConfigurations", "name", wrc.getResourceMutatingWebhookConfigName()) + } + + if err := wrc.updateResourceValidatingWebhookConfiguration(nsSelector); err != nil { + logger.Error(err, "unable to update validatingWebhookConfigurations", "name", wrc.getResourceValidatingWebhookConfigName()) + } else { + logger.Info("successfully updated validatingWebhookConfigurations", "name", wrc.getResourceValidatingWebhookConfigName()) + } + } +} + +func (wrc *Register) ValidateWebhookConfigurations(namespace, name string) error { + logger := wrc.log.WithName("ValidateWebhookConfigurations") + + cm, err := wrc.client.GetResource("", "ConfigMap", namespace, name) + if err != nil { + logger.Error(err, "unable to fetch ConfigMap", "namespace", namespace, "name", name) + return nil + } + + webhooks, ok, err := unstructured.NestedString(cm.UnstructuredContent(), "data", "webhooks") + if err != nil { + logger.Error(err, "failed to fetch tag 'webhooks' from the ConfigMap") + return nil + } + + if !ok { + logger.V(4).Info("webhook configurations not defined") + return nil + } + + webhookCfgs := make([]config.WebhookConfig, 0, 10) + return json.Unmarshal([]byte(webhooks), &webhookCfgs) +} + // cleanupKyvernoResource returns true if Kyverno deployment is terminating func (wrc *Register) cleanupKyvernoResource() bool { logger := wrc.log.WithName("cleanupKyvernoResource") @@ -179,9 +247,9 @@ func (wrc *Register) createResourceMutatingWebhookConfiguration(caData []byte) e var config *admregapi.MutatingWebhookConfiguration if wrc.serverIP != "" { - config = wrc.constructDebugMutatingWebhookConfig(caData) + config = wrc.constructDefaultDebugMutatingWebhookConfig(caData) } else { - config = wrc.constructMutatingWebhookConfig(caData) + config = wrc.constructDefaultMutatingWebhookConfig(caData) } logger := wrc.log.WithValues("kind", kindMutating, "name", config.Name) @@ -205,9 +273,9 @@ func (wrc *Register) createResourceValidatingWebhookConfiguration(caData []byte) var config *admregapi.ValidatingWebhookConfiguration if wrc.serverIP != "" { - config = wrc.constructDebugValidatingWebhookConfig(caData) + config = wrc.constructDefaultDebugValidatingWebhookConfig(caData) } else { - config = wrc.constructValidatingWebhookConfig(caData) + config = wrc.constructDefaultValidatingWebhookConfig(caData) } logger := wrc.log.WithValues("kind", kindValidating, "name", config.Name) @@ -550,3 +618,75 @@ func (wrc *Register) checkEndpoint() error { wrc.log.V(3).Info(err.Error(), "ns", config.KyvernoNamespace, "name", config.KyvernoServiceName) return err } + +func (wrc *Register) updateResourceValidatingWebhookConfiguration(nsSelector map[string]interface{}) error { + validatingCache, _ := wrc.resCache.GetGVRCache(kindValidating) + + resourceValidating, err := validatingCache.Lister().Get(wrc.getResourceValidatingWebhookConfigName()) + if err != nil { + return errors.Wrapf(err, "unable to get validatingWebhookConfigurations") + } + + webhooksUntyped, _, err := unstructured.NestedSlice(resourceValidating.UnstructuredContent(), "webhooks") + if err != nil { + return errors.Wrapf(err, "unable to load validatingWebhookConfigurations.webhooks") + } + + var webhooks map[string]interface{} + var ok bool + if webhooksUntyped != nil { + webhooks, ok = webhooksUntyped[0].(map[string]interface{}) + if !ok { + return errors.Wrapf(err, "type mismatched, expected map[string]interface{}, got %T", webhooksUntyped[0]) + } + } + if err = unstructured.SetNestedMap(webhooks, nsSelector, "namespaceSelector"); err != nil { + return errors.Wrapf(err, "unable to set validatingWebhookConfigurations.webhooks[0].namespaceSelector") + } + + if err = unstructured.SetNestedSlice(resourceValidating.UnstructuredContent(), []interface{}{webhooks}, "webhooks"); err != nil { + return errors.Wrapf(err, "unable to set validatingWebhookConfigurations.webhooks") + } + + if _, err := wrc.client.UpdateResource(resourceValidating.GetAPIVersion(), resourceValidating.GetKind(), "", resourceValidating, false); err != nil { + return err + } + + return nil +} + +func (wrc *Register) updateResourceMutatingWebhookConfiguration(nsSelector map[string]interface{}) error { + mutatingCache, _ := wrc.resCache.GetGVRCache(kindMutating) + + resourceMutating, err := mutatingCache.Lister().Get(wrc.getResourceMutatingWebhookConfigName()) + if err != nil { + return errors.Wrapf(err, "unable to get mutatingWebhookConfigurations") + } + + webhooksUntyped, _, err := unstructured.NestedSlice(resourceMutating.UnstructuredContent(), "webhooks") + if err != nil { + return errors.Wrapf(err, "unable to load mutatingWebhookConfigurations.webhooks") + } + + var webhooks map[string]interface{} + var ok bool + if webhooksUntyped != nil { + webhooks, ok = webhooksUntyped[0].(map[string]interface{}) + if !ok { + return errors.Wrapf(err, "type mismatched, expected map[string]interface{}, got %T", webhooksUntyped[0]) + } + } + if err = unstructured.SetNestedMap(webhooks, nsSelector, "namespaceSelector"); err != nil { + return errors.Wrapf(err, "unable to set mutatingWebhookConfigurations.webhooks[0].namespaceSelector") + } + + if err = unstructured.SetNestedSlice(resourceMutating.UnstructuredContent(), []interface{}{webhooks}, "webhooks"); err != nil { + return errors.Wrapf(err, "unable to set mutatingWebhookConfigurations.webhooks") + } + + if _, err := wrc.client.UpdateResource(resourceMutating.GetAPIVersion(), resourceMutating.GetKind(), "", resourceMutating, false); err != nil { + return err + } + + return nil +} diff --git a/pkg/webhookconfig/resource.go b/pkg/webhookconfig/resource.go index 472d47a491..e204c5647d 100644 --- a/pkg/webhookconfig/resource.go +++ b/pkg/webhookconfig/resource.go @@ -11,7 +11,7 @@ import ( v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -func (wrc *Register) constructDebugMutatingWebhookConfig(caData []byte) *admregapi.MutatingWebhookConfiguration { +func (wrc *Register) constructDefaultDebugMutatingWebhookConfig(caData []byte) *admregapi.MutatingWebhookConfiguration { logger := wrc.log url := fmt.Sprintf("https://%s%s", wrc.serverIP, config.MutatingWebhookServicePath) logger.V(4).Info("Debug MutatingWebhookConfig registered", "url", url) @@ -35,7 +35,7 @@ func (wrc *Register) constructDebugMutatingWebhookConfig(caData []byte) *admrega } } -func (wrc *Register) constructMutatingWebhookConfig(caData []byte) *admregapi.MutatingWebhookConfiguration { +func (wrc *Register) constructDefaultMutatingWebhookConfig(caData []byte) *admregapi.MutatingWebhookConfiguration { webhookCfg := generateMutatingWebhook( config.MutatingWebhookName, @@ -94,7 +94,7 @@ func (wrc *Register) removeResourceMutatingWebhookConfiguration(wg *sync.WaitGro logger.Info("webhook configuration deleted") } -func (wrc *Register) constructDebugValidatingWebhookConfig(caData []byte) *admregapi.ValidatingWebhookConfiguration { +func (wrc *Register) constructDefaultDebugValidatingWebhookConfig(caData []byte) *admregapi.ValidatingWebhookConfiguration { url := fmt.Sprintf("https://%s%s", wrc.serverIP, config.ValidatingWebhookServicePath) return &admregapi.ValidatingWebhookConfiguration{ @@ -117,7 +117,7 @@ func (wrc *Register) constructDebugValidatingWebhookConfig(caData []byte) *admre } } -func (wrc *Register) constructValidatingWebhookConfig(caData []byte) *admregapi.ValidatingWebhookConfiguration { +func (wrc *Register) constructDefaultValidatingWebhookConfig(caData []byte) *admregapi.ValidatingWebhookConfiguration { return &admregapi.ValidatingWebhookConfiguration{ ObjectMeta: v1.ObjectMeta{ Name: config.ValidatingWebhookConfigurationName,