mirror of https://github.com/knative/pkg.git
183 lines
5.6 KiB
Go
183 lines
5.6 KiB
Go
/*
|
|
Copyright 2020 The Knative Authors
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package validation
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/gobuffalo/flect"
|
|
"go.uber.org/zap"
|
|
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
|
corev1 "k8s.io/api/core/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
"k8s.io/client-go/kubernetes"
|
|
admissionlisters "k8s.io/client-go/listers/admissionregistration/v1"
|
|
corelisters "k8s.io/client-go/listers/core/v1"
|
|
"knative.dev/pkg/controller"
|
|
"knative.dev/pkg/kmp"
|
|
"knative.dev/pkg/logging"
|
|
"knative.dev/pkg/ptr"
|
|
pkgreconciler "knative.dev/pkg/reconciler"
|
|
"knative.dev/pkg/system"
|
|
"knative.dev/pkg/webhook"
|
|
certresources "knative.dev/pkg/webhook/certificates/resources"
|
|
"knative.dev/pkg/webhook/resourcesemantics"
|
|
)
|
|
|
|
// reconciler implements the AdmissionController for resources
|
|
type reconciler struct {
|
|
webhook.StatelessAdmissionImpl
|
|
pkgreconciler.LeaderAwareFuncs
|
|
|
|
key types.NamespacedName
|
|
path string
|
|
handlers map[schema.GroupVersionKind]resourcesemantics.GenericCRD
|
|
callbacks map[schema.GroupVersionKind]Callback
|
|
|
|
withContext func(context.Context) context.Context
|
|
|
|
client kubernetes.Interface
|
|
vwhlister admissionlisters.ValidatingWebhookConfigurationLister
|
|
secretlister corelisters.SecretLister
|
|
|
|
disallowUnknownFields bool
|
|
secretName string
|
|
}
|
|
|
|
var _ controller.Reconciler = (*reconciler)(nil)
|
|
var _ pkgreconciler.LeaderAware = (*reconciler)(nil)
|
|
var _ webhook.AdmissionController = (*reconciler)(nil)
|
|
var _ webhook.StatelessAdmissionController = (*reconciler)(nil)
|
|
|
|
// Path implements AdmissionController
|
|
func (ac *reconciler) Path() string {
|
|
return ac.path
|
|
}
|
|
|
|
// Reconcile implements controller.Reconciler
|
|
func (ac *reconciler) Reconcile(ctx context.Context, key string) error {
|
|
logger := logging.FromContext(ctx)
|
|
|
|
if !ac.IsLeaderFor(ac.key) {
|
|
return controller.NewSkipKey(key)
|
|
}
|
|
|
|
// Look up the webhook secret, and fetch the CA cert bundle.
|
|
secret, err := ac.secretlister.Secrets(system.Namespace()).Get(ac.secretName)
|
|
if err != nil {
|
|
logger.Errorw("Error fetching secret", zap.Error(err))
|
|
return err
|
|
}
|
|
caCert, ok := secret.Data[certresources.CACert]
|
|
if !ok {
|
|
return fmt.Errorf("secret %q is missing %q key", ac.secretName, certresources.CACert)
|
|
}
|
|
|
|
// Reconcile the webhook configuration.
|
|
return ac.reconcileValidatingWebhook(ctx, caCert)
|
|
}
|
|
|
|
func (ac *reconciler) reconcileValidatingWebhook(ctx context.Context, caCert []byte) error {
|
|
logger := logging.FromContext(ctx)
|
|
|
|
rules := make([]admissionregistrationv1.RuleWithOperations, 0, len(ac.handlers))
|
|
for gvk := range ac.handlers {
|
|
plural := strings.ToLower(flect.Pluralize(gvk.Kind))
|
|
|
|
rules = append(rules, admissionregistrationv1.RuleWithOperations{
|
|
Operations: []admissionregistrationv1.OperationType{
|
|
admissionregistrationv1.Create,
|
|
admissionregistrationv1.Update,
|
|
admissionregistrationv1.Delete,
|
|
},
|
|
Rule: admissionregistrationv1.Rule{
|
|
APIGroups: []string{gvk.Group},
|
|
APIVersions: []string{gvk.Version},
|
|
Resources: []string{plural, plural + "/status"},
|
|
},
|
|
})
|
|
}
|
|
|
|
// Sort the rules by Group, Version, Kind so that things are deterministically ordered.
|
|
sort.Slice(rules, func(i, j int) bool {
|
|
lhs, rhs := rules[i], rules[j]
|
|
if lhs.APIGroups[0] != rhs.APIGroups[0] {
|
|
return lhs.APIGroups[0] < rhs.APIGroups[0]
|
|
}
|
|
if lhs.APIVersions[0] != rhs.APIVersions[0] {
|
|
return lhs.APIVersions[0] < rhs.APIVersions[0]
|
|
}
|
|
return lhs.Resources[0] < rhs.Resources[0]
|
|
})
|
|
|
|
configuredWebhook, err := ac.vwhlister.Get(ac.key.Name)
|
|
if err != nil {
|
|
return fmt.Errorf("error retrieving webhook: %w", err)
|
|
}
|
|
|
|
current := configuredWebhook.DeepCopy()
|
|
|
|
// Set the owner to namespace.
|
|
ns, err := ac.client.CoreV1().Namespaces().Get(ctx, system.Namespace(), metav1.GetOptions{})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to fetch namespace: %w", err)
|
|
}
|
|
nsRef := *metav1.NewControllerRef(ns, corev1.SchemeGroupVersion.WithKind("Namespace"))
|
|
current.OwnerReferences = []metav1.OwnerReference{nsRef}
|
|
|
|
for i, wh := range current.Webhooks {
|
|
if wh.Name != current.Name {
|
|
continue
|
|
}
|
|
cur := ¤t.Webhooks[i]
|
|
cur.Rules = rules
|
|
|
|
cur.NamespaceSelector = webhook.EnsureLabelSelectorExpressions(
|
|
cur.NamespaceSelector,
|
|
&metav1.LabelSelector{
|
|
MatchExpressions: []metav1.LabelSelectorRequirement{{
|
|
Key: "webhooks.knative.dev/exclude",
|
|
Operator: metav1.LabelSelectorOpDoesNotExist,
|
|
}},
|
|
})
|
|
|
|
cur.ClientConfig.CABundle = caCert
|
|
if cur.ClientConfig.Service == nil {
|
|
return fmt.Errorf("missing service reference for webhook: %s", wh.Name)
|
|
}
|
|
cur.ClientConfig.Service.Path = ptr.String(ac.Path())
|
|
}
|
|
|
|
if ok, err := kmp.SafeEqual(configuredWebhook, current); err != nil {
|
|
return fmt.Errorf("error diffing webhooks: %w", err)
|
|
} else if !ok {
|
|
logger.Info("Updating webhook")
|
|
vwhclient := ac.client.AdmissionregistrationV1().ValidatingWebhookConfigurations()
|
|
if _, err := vwhclient.Update(ctx, current, metav1.UpdateOptions{}); err != nil {
|
|
return fmt.Errorf("failed to update webhook: %w", err)
|
|
}
|
|
} else {
|
|
logger.Info("Webhook is valid")
|
|
}
|
|
return nil
|
|
}
|