1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-06 16:06:56 +00:00
kyverno/pkg/webhookconfig/registration.go
shuting c816cf3d69
Add certificate renewer in webhook registration controller (#1692)
* load TLS pair from existing secret, if applicable

Signed-off-by: Shuting Zhao <shutting06@gmail.com>

* remove Kyverno managed secrets during shutdown

Signed-off-by: Shuting Zhao <shutting06@gmail.com>

* - add certificate renewer; - re-structure certificate package

Signed-off-by: Shuting Zhao <shutting06@gmail.com>

* commit un-saved file

Signed-off-by: Shuting Zhao <shutting06@gmail.com>

* eliminate throttling requests while registering webhook configs

Signed-off-by: Shuting Zhao <shutting06@gmail.com>

* disable webhook monitor (in old pod) during rolling update

Signed-off-by: Shuting Zhao <shutting06@gmail.com>

* remove webhook cleanup logic from init container

Signed-off-by: Shuting Zhao <shutting06@gmail.com>

* update PR template

Signed-off-by: Shuting Zhao <shutting06@gmail.com>

* update link to the website repo

Signed-off-by: Shuting Zhao <shutting06@gmail.com>

* update repo name

Signed-off-by: Shuting Zhao <shutting06@gmail.com>
2021-03-16 11:31:04 -07:00

472 lines
14 KiB
Go

package webhookconfig
import (
"errors"
"fmt"
"strings"
"sync"
"time"
"github.com/go-logr/logr"
"github.com/kyverno/kyverno/pkg/config"
client "github.com/kyverno/kyverno/pkg/dclient"
"github.com/kyverno/kyverno/pkg/resourcecache"
"github.com/kyverno/kyverno/pkg/tls"
admregapi "k8s.io/api/admissionregistration/v1beta1"
errorsapi "k8s.io/apimachinery/pkg/api/errors"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
rest "k8s.io/client-go/rest"
)
const (
kindMutating string = "MutatingWebhookConfiguration"
kindValidating string = "ValidatingWebhookConfiguration"
)
// Register manages webhook registration. There are five webhooks:
// 1. Policy Validation
// 2. Policy Mutation
// 3. Resource Validation
// 4. Resource Mutation
// 5. Webhook Status Mutation
type Register struct {
client *client.Client
clientConfig *rest.Config
resCache resourcecache.ResourceCache
serverIP string // when running outside a cluster
timeoutSeconds int32
log logr.Logger
}
// NewRegister creates new Register instance
func NewRegister(
clientConfig *rest.Config,
client *client.Client,
resCache resourcecache.ResourceCache,
serverIP string,
webhookTimeout int32,
log logr.Logger) *Register {
return &Register{
clientConfig: clientConfig,
client: client,
resCache: resCache,
serverIP: serverIP,
timeoutSeconds: webhookTimeout,
log: log.WithName("Register"),
}
}
// Register clean up the old webhooks and re-creates admission webhooks configs on cluster
func (wrc *Register) Register() error {
logger := wrc.log
if wrc.serverIP != "" {
logger.Info("Registering webhook", "url", fmt.Sprintf("https://%s", wrc.serverIP))
}
wrc.removeWebhookConfigurations()
caData := wrc.readCaData()
if caData == nil {
return errors.New("Unable to extract CA data from configuration")
}
errors := make([]string, 0)
if err := wrc.createVerifyMutatingWebhookConfiguration(caData); err != nil {
errors = append(errors, err.Error())
}
if err := wrc.createPolicyValidatingWebhookConfiguration(caData); err != nil {
errors = append(errors, err.Error())
}
if err := wrc.createPolicyMutatingWebhookConfiguration(caData); err != nil {
errors = append(errors, err.Error())
}
if err := wrc.createResourceValidatingWebhookConfiguration(caData); err != nil {
errors = append(errors, err.Error())
}
if err := wrc.createResourceMutatingWebhookConfiguration(caData); err != nil {
errors = append(errors, err.Error())
}
if len(errors) > 0 {
return fmt.Errorf("%s", strings.Join(errors, ","))
}
return nil
}
// Check returns an error if any of the webhooks are not configured
func (wrc *Register) Check() error {
mutatingCache, _ := wrc.resCache.GetGVRCache(kindMutating)
validatingCache, _ := wrc.resCache.GetGVRCache(kindValidating)
if _, err := mutatingCache.Lister().Get(wrc.getVerifyWebhookMutatingWebhookName()); err != nil {
return err
}
if _, err := mutatingCache.Lister().Get(wrc.getResourceMutatingWebhookConfigName()); err != nil {
return err
}
if _, err := validatingCache.Lister().Get(wrc.getResourceValidatingWebhookConfigName()); err != nil {
return err
}
if _, err := mutatingCache.Lister().Get(wrc.getPolicyMutatingWebhookConfigurationName()); err != nil {
return err
}
if _, err := validatingCache.Lister().Get(wrc.getPolicyValidatingWebhookConfigurationName()); err != nil {
return err
}
return nil
}
// Remove removes all webhook configurations
func (wrc *Register) Remove(cleanUp chan<- struct{}) {
defer close(cleanUp)
if !wrc.cleanupKyvernoResource() {
return
}
wrc.removeWebhookConfigurations()
wrc.removeSecrets()
}
// cleanupKyvernoResource returns true if Kyverno deployment is terminating
func (wrc *Register) cleanupKyvernoResource() bool {
logger := wrc.log.WithName("cleanupKyvernoResource")
deploy, err := wrc.client.GetResource("", "Deployment", deployNamespace, deployName)
if err != nil {
logger.Error(err, "failed to get deployment")
return false
}
if deploy.GetDeletionTimestamp() != nil {
logger.Info("Kyverno is terminating, clean up Kyverno resources")
return true
}
replicas, _, err := unstructured.NestedInt64(deploy.UnstructuredContent(), "spec", "replicas")
if err != nil {
logger.Error(err, "unable to fetch spec.replicas of Kyverno deployment")
}
if replicas == 0 {
logger.Info("Kyverno is scaled to zero, clean up Kyverno resources")
return true
}
logger.Info("updating Kyverno Pod, won't clean up Kyverno resources")
return false
}
func (wrc *Register) createResourceMutatingWebhookConfiguration(caData []byte) error {
var config *admregapi.MutatingWebhookConfiguration
if wrc.serverIP != "" {
config = wrc.constructDebugMutatingWebhookConfig(caData)
} else {
config = wrc.constructMutatingWebhookConfig(caData)
}
logger := wrc.log.WithValues("kind", kindMutating, "name", config.Name)
_, err := wrc.client.CreateResource("", kindMutating, "", *config, false)
if errorsapi.IsAlreadyExists(err) {
logger.V(6).Info("resource mutating webhook configuration already exists", "name", config.Name)
return nil
}
if err != nil {
logger.Error(err, "failed to create resource mutating webhook configuration", "name", config.Name)
return err
}
logger.Info("created webhook")
return nil
}
func (wrc *Register) createResourceValidatingWebhookConfiguration(caData []byte) error {
var config *admregapi.ValidatingWebhookConfiguration
if wrc.serverIP != "" {
config = wrc.constructDebugValidatingWebhookConfig(caData)
} else {
config = wrc.constructValidatingWebhookConfig(caData)
}
logger := wrc.log.WithValues("kind", kindValidating, "name", config.Name)
_, err := wrc.client.CreateResource("", kindValidating, "", *config, false)
if errorsapi.IsAlreadyExists(err) {
logger.V(6).Info("resource validating webhook configuration already exists", "name", config.Name)
return nil
}
if err != nil {
logger.Error(err, "failed to create resource")
return err
}
logger.Info("created webhook")
return nil
}
//registerPolicyValidatingWebhookConfiguration create a Validating webhook configuration for Policy CRD
func (wrc *Register) createPolicyValidatingWebhookConfiguration(caData []byte) error {
var config *admregapi.ValidatingWebhookConfiguration
if wrc.serverIP != "" {
config = wrc.contructDebugPolicyValidatingWebhookConfig(caData)
} else {
config = wrc.contructPolicyValidatingWebhookConfig(caData)
}
if _, err := wrc.client.CreateResource("", kindValidating, "", *config, false); err != nil {
if errorsapi.IsAlreadyExists(err) {
wrc.log.V(6).Info("webhook already exists", "kind", kindValidating, "name", config.Name)
return nil
}
return err
}
wrc.log.Info("created webhook", "kind", kindValidating, "name", config.Name)
return nil
}
func (wrc *Register) createPolicyMutatingWebhookConfiguration(caData []byte) error {
var config *admregapi.MutatingWebhookConfiguration
if wrc.serverIP != "" {
config = wrc.contructDebugPolicyMutatingWebhookConfig(caData)
} else {
config = wrc.contructPolicyMutatingWebhookConfig(caData)
}
// create mutating webhook configuration resource
if _, err := wrc.client.CreateResource("", kindMutating, "", *config, false); err != nil {
if errorsapi.IsAlreadyExists(err) {
wrc.log.V(6).Info("webhook already exists", "kind", kindMutating, "name", config.Name)
return nil
}
return err
}
wrc.log.Info("created webhook", "kind", kindMutating, "name", config.Name)
return nil
}
func (wrc *Register) createVerifyMutatingWebhookConfiguration(caData []byte) error {
var config *admregapi.MutatingWebhookConfiguration
if wrc.serverIP != "" {
config = wrc.constructDebugVerifyMutatingWebhookConfig(caData)
} else {
config = wrc.constructVerifyMutatingWebhookConfig(caData)
}
if _, err := wrc.client.CreateResource("", kindMutating, "", *config, false); err != nil {
if errorsapi.IsAlreadyExists(err) {
wrc.log.V(6).Info("webhook already exists", "kind", kindMutating, "name", config.Name)
return nil
}
return err
}
wrc.log.Info("created webhook", "kind", kindMutating, "name", config.Name)
return nil
}
func (wrc *Register) removeWebhookConfigurations() {
startTime := time.Now()
wrc.log.Info("deleting all webhook configurations")
defer func() {
wrc.log.V(4).Info("removed webhook configurations", "processingTime", time.Since(startTime).String())
}()
var wg sync.WaitGroup
wg.Add(5)
go wrc.removeResourceMutatingWebhookConfiguration(&wg)
go wrc.removeResourceValidatingWebhookConfiguration(&wg)
go wrc.removePolicyMutatingWebhookConfiguration(&wg)
go wrc.removePolicyValidatingWebhookConfiguration(&wg)
go wrc.removeVerifyWebhookMutatingWebhookConfig(&wg)
wg.Wait()
}
func (wrc *Register) removePolicyMutatingWebhookConfiguration(wg *sync.WaitGroup) {
defer wg.Done()
mutatingConfig := wrc.getPolicyMutatingWebhookConfigurationName()
logger := wrc.log.WithValues("kind", kindMutating, "name", mutatingConfig)
err := wrc.client.DeleteResource("", kindMutating, "", mutatingConfig, false)
if errorsapi.IsNotFound(err) {
logger.V(5).Info("policy mutating webhook configuration not found")
return
}
if err != nil {
logger.Error(err, "failed to delete policy mutating webhook configuration")
return
}
logger.Info("webhook configuration deleted")
}
func (wrc *Register) getPolicyMutatingWebhookConfigurationName() string {
var mutatingConfig string
if wrc.serverIP != "" {
mutatingConfig = config.PolicyMutatingWebhookConfigurationDebugName
} else {
mutatingConfig = config.PolicyMutatingWebhookConfigurationName
}
return mutatingConfig
}
func (wrc *Register) removePolicyValidatingWebhookConfiguration(wg *sync.WaitGroup) {
defer wg.Done()
validatingConfig := wrc.getPolicyValidatingWebhookConfigurationName()
logger := wrc.log.WithValues("kind", kindValidating, "name", validatingConfig)
logger.V(4).Info("removing validating webhook configuration")
err := wrc.client.DeleteResource("", kindValidating, "", validatingConfig, false)
if errorsapi.IsNotFound(err) {
logger.V(5).Info("policy validating webhook configuration not found")
return
}
if err != nil {
logger.Error(err, "failed to delete policy validating webhook configuration")
return
}
logger.Info("webhook configuration deleted")
}
func (wrc *Register) getPolicyValidatingWebhookConfigurationName() string {
var validatingConfig string
if wrc.serverIP != "" {
validatingConfig = config.PolicyValidatingWebhookConfigurationDebugName
} else {
validatingConfig = config.PolicyValidatingWebhookConfigurationName
}
return validatingConfig
}
func (wrc *Register) constructVerifyMutatingWebhookConfig(caData []byte) *admregapi.MutatingWebhookConfiguration {
return &admregapi.MutatingWebhookConfiguration{
ObjectMeta: v1.ObjectMeta{
Name: config.VerifyMutatingWebhookConfigurationName,
OwnerReferences: []v1.OwnerReference{
wrc.constructOwner(),
},
},
Webhooks: []admregapi.MutatingWebhook{
generateMutatingWebhook(
config.VerifyMutatingWebhookName,
config.VerifyMutatingWebhookServicePath,
caData,
true,
wrc.timeoutSeconds,
[]string{"deployments/*"},
"apps",
"v1",
[]admregapi.OperationType{admregapi.Update},
),
},
}
}
func (wrc *Register) constructDebugVerifyMutatingWebhookConfig(caData []byte) *admregapi.MutatingWebhookConfiguration {
logger := wrc.log
url := fmt.Sprintf("https://%s%s", wrc.serverIP, config.VerifyMutatingWebhookServicePath)
logger.V(4).Info("Debug VerifyMutatingWebhookConfig is registered with url", "url", url)
return &admregapi.MutatingWebhookConfiguration{
ObjectMeta: v1.ObjectMeta{
Name: config.VerifyMutatingWebhookConfigurationDebugName,
},
Webhooks: []admregapi.MutatingWebhook{
generateDebugMutatingWebhook(
config.VerifyMutatingWebhookName,
url,
caData,
true,
wrc.timeoutSeconds,
[]string{"deployments/*"},
"apps",
"v1",
[]admregapi.OperationType{admregapi.Update},
),
},
}
}
func (wrc *Register) removeVerifyWebhookMutatingWebhookConfig(wg *sync.WaitGroup) {
defer wg.Done()
var err error
mutatingConfig := wrc.getVerifyWebhookMutatingWebhookName()
logger := wrc.log.WithValues("kind", kindMutating, "name", mutatingConfig)
err = wrc.client.DeleteResource("", kindMutating, "", mutatingConfig, false)
if errorsapi.IsNotFound(err) {
logger.V(5).Info("verify webhook configuration not found")
return
}
if err != nil {
logger.Error(err, "failed to delete verify webhook configuration")
return
}
logger.Info("webhook configuration deleted")
}
func (wrc *Register) getVerifyWebhookMutatingWebhookName() string {
var mutatingConfig string
if wrc.serverIP != "" {
mutatingConfig = config.VerifyMutatingWebhookConfigurationDebugName
} else {
mutatingConfig = config.VerifyMutatingWebhookConfigurationName
}
return mutatingConfig
}
// GetWebhookTimeOut returns the value of webhook timeout
func (wrc *Register) GetWebhookTimeOut() time.Duration {
return time.Duration(wrc.timeoutSeconds)
}
// removeSecrets removes Kyverno managed secrets
func (wrc *Register) removeSecrets() {
selector := &v1.LabelSelector{
MatchLabels: map[string]string{
tls.ManagedByLabel: "kyverno",
},
}
secretList, err := wrc.client.ListResource("", "Secret", config.KyvernoNamespace, selector)
if err != nil && errorsapi.IsNotFound(err) {
wrc.log.Error(err, "failed to clean up Kyverno managed secrets")
return
}
for _, secret := range secretList.Items {
if err := wrc.client.DeleteResource("", "Secret", secret.GetNamespace(), secret.GetName(), false); err != nil {
wrc.log.Error(err, "failed to delete secret", "ns", secret.GetNamespace(), "name", secret.GetName())
}
}
}