use istio client-go library instead of knative (#1661)
use istio client-go library instead of knative bump kubernetes dependency version change code coverage to codecov
This commit is contained in:
369
vendor/k8s.io/apiserver/pkg/admission/plugin/initialization/initialization.go
generated
vendored
369
vendor/k8s.io/apiserver/pkg/admission/plugin/initialization/initialization.go
generated
vendored
@@ -1,369 +0,0 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes 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 initialization
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"k8s.io/klog"
|
||||
|
||||
"k8s.io/api/admissionregistration/v1alpha1"
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/api/validation"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/configuration"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
const (
|
||||
// Name of admission plug-in
|
||||
PluginName = "Initializers"
|
||||
)
|
||||
|
||||
// Register registers a plugin
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
|
||||
return NewInitializer(), nil
|
||||
})
|
||||
}
|
||||
|
||||
type initializerOptions struct {
|
||||
Initializers []string
|
||||
}
|
||||
|
||||
// InitializationConfig specifies initialization config
|
||||
type InitializationConfig interface {
|
||||
Run(stopCh <-chan struct{})
|
||||
Initializers() (*v1alpha1.InitializerConfiguration, error)
|
||||
}
|
||||
|
||||
type initializer struct {
|
||||
config InitializationConfig
|
||||
authorizer authorizer.Authorizer
|
||||
}
|
||||
|
||||
// NewInitializer creates a new initializer plugin which assigns newly created resources initializers
|
||||
// based on configuration loaded from the admission API group.
|
||||
// FUTURE: this may be moved to the storage layer of the apiserver, but for now this is an alpha feature
|
||||
// that can be disabled.
|
||||
func NewInitializer() admission.Interface {
|
||||
return &initializer{}
|
||||
}
|
||||
|
||||
// ValidateInitialization implements the InitializationValidator interface.
|
||||
func (i *initializer) ValidateInitialization() error {
|
||||
if i.config == nil {
|
||||
return fmt.Errorf("the Initializer admission plugin requires a Kubernetes client to be provided")
|
||||
}
|
||||
if i.authorizer == nil {
|
||||
return fmt.Errorf("the Initializer admission plugin requires an authorizer to be provided")
|
||||
}
|
||||
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.Initializers) {
|
||||
if err := utilfeature.DefaultFeatureGate.Set(string(features.Initializers) + "=true"); err != nil {
|
||||
klog.Errorf("error enabling Initializers feature as part of admission plugin setup: %v", err)
|
||||
} else {
|
||||
klog.Infof("enabled Initializers feature as part of admission plugin setup")
|
||||
}
|
||||
}
|
||||
|
||||
i.config.Run(wait.NeverStop)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetExternalKubeClientSet implements the WantsExternalKubeClientSet interface.
|
||||
func (i *initializer) SetExternalKubeClientSet(client clientset.Interface) {
|
||||
i.config = configuration.NewInitializerConfigurationManager(client.AdmissionregistrationV1alpha1().InitializerConfigurations())
|
||||
}
|
||||
|
||||
// SetAuthorizer implements the WantsAuthorizer interface.
|
||||
func (i *initializer) SetAuthorizer(a authorizer.Authorizer) {
|
||||
i.authorizer = a
|
||||
}
|
||||
|
||||
var initializerFieldPath = field.NewPath("metadata", "initializers")
|
||||
|
||||
// readConfig holds requests instead of failing them if the server is not yet initialized
|
||||
// or is unresponsive. It formats the returned error for client use if necessary.
|
||||
func (i *initializer) readConfig(a admission.Attributes) (*v1alpha1.InitializerConfiguration, error) {
|
||||
// read initializers from config
|
||||
config, err := i.config.Initializers()
|
||||
if err == nil {
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// if initializer configuration is disabled, fail open
|
||||
if err == configuration.ErrDisabled {
|
||||
return &v1alpha1.InitializerConfiguration{}, nil
|
||||
}
|
||||
|
||||
e := errors.NewServerTimeout(a.GetResource().GroupResource(), "create", 1)
|
||||
if err == configuration.ErrNotReady {
|
||||
e.ErrStatus.Message = fmt.Sprintf("Waiting for initialization configuration to load: %v", err)
|
||||
e.ErrStatus.Reason = "LoadingConfiguration"
|
||||
e.ErrStatus.Details.Causes = append(e.ErrStatus.Details.Causes, metav1.StatusCause{
|
||||
Type: "InitializerConfigurationPending",
|
||||
Message: "The server is waiting for the initializer configuration to be loaded.",
|
||||
})
|
||||
} else {
|
||||
e.ErrStatus.Message = fmt.Sprintf("Unable to refresh the initializer configuration: %v", err)
|
||||
e.ErrStatus.Reason = "LoadingConfiguration"
|
||||
e.ErrStatus.Details.Causes = append(e.ErrStatus.Details.Causes, metav1.StatusCause{
|
||||
Type: "InitializerConfigurationFailure",
|
||||
Message: "An error has occurred while refreshing the initializer configuration, no resources can be created until a refresh succeeds.",
|
||||
})
|
||||
}
|
||||
return nil, e
|
||||
}
|
||||
|
||||
// Admit checks for create requests to add initializers, or update request to enforce invariants.
|
||||
// The admission controller fails open if the object doesn't have ObjectMeta (can't be initialized).
|
||||
// A client with sufficient permission ("initialize" verb on resource) can specify its own initializers
|
||||
// or an empty initializers struct (which bypasses initialization). Only clients with the initialize verb
|
||||
// can update objects that have not completed initialization. Sub resources can still be modified on
|
||||
// resources that are undergoing initialization.
|
||||
// TODO: once this logic is ready for beta, move it into the REST storage layer.
|
||||
func (i *initializer) Admit(a admission.Attributes) (err error) {
|
||||
switch a.GetOperation() {
|
||||
case admission.Create, admission.Update:
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: should sub-resource action should be denied until the object is initialized?
|
||||
if len(a.GetSubresource()) > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch a.GetOperation() {
|
||||
case admission.Create:
|
||||
accessor, err := meta.Accessor(a.GetObject())
|
||||
if err != nil {
|
||||
// objects without meta accessor cannot be checked for initialization, and it is possible to make calls
|
||||
// via our API that don't have ObjectMeta
|
||||
return nil
|
||||
}
|
||||
existing := accessor.GetInitializers()
|
||||
if existing != nil {
|
||||
klog.V(5).Infof("Admin bypassing initialization for %s", a.GetResource())
|
||||
|
||||
// it must be possible for some users to bypass initialization - for now, check the initialize operation
|
||||
if err := i.canInitialize(a, "create with initializers denied"); err != nil {
|
||||
return err
|
||||
}
|
||||
// allow administrators to bypass initialization by setting an empty initializers struct
|
||||
if len(existing.Pending) == 0 && existing.Result == nil {
|
||||
accessor.SetInitializers(nil)
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
klog.V(5).Infof("Checking initialization for %s", a.GetResource())
|
||||
|
||||
config, err := i.readConfig(a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Mirror pods are exempt from initialization because they are created and initialized
|
||||
// on the Kubelet before they appear in the API.
|
||||
// TODO: once this moves to REST storage layer, this becomes a pod specific concern
|
||||
if a.GetKind().GroupKind() == v1.SchemeGroupVersion.WithKind("Pod").GroupKind() {
|
||||
accessor, err := meta.Accessor(a.GetObject())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
annotations := accessor.GetAnnotations()
|
||||
if _, isMirror := annotations[v1.MirrorPodAnnotationKey]; isMirror {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
names := findInitializers(config, a.GetResource())
|
||||
if len(names) == 0 {
|
||||
klog.V(5).Infof("No initializers needed")
|
||||
return nil
|
||||
}
|
||||
|
||||
klog.V(5).Infof("Found initializers for %s: %v", a.GetResource(), names)
|
||||
accessor.SetInitializers(newInitializers(names))
|
||||
}
|
||||
|
||||
case admission.Update:
|
||||
accessor, err := meta.Accessor(a.GetObject())
|
||||
if err != nil {
|
||||
// objects without meta accessor cannot be checked for initialization, and it is possible to make calls
|
||||
// via our API that don't have ObjectMeta
|
||||
return nil
|
||||
}
|
||||
updated := accessor.GetInitializers()
|
||||
|
||||
// controllers deployed with an empty initializers.pending have their initializers set to nil
|
||||
// but should be able to update without changing their manifest
|
||||
if updated != nil && len(updated.Pending) == 0 && updated.Result == nil {
|
||||
accessor.SetInitializers(nil)
|
||||
updated = nil
|
||||
}
|
||||
|
||||
existingAccessor, err := meta.Accessor(a.GetOldObject())
|
||||
if err != nil {
|
||||
// if the old object does not have an accessor, but the new one does, error out
|
||||
return fmt.Errorf("initialized resources must be able to set initializers (%T): %v", a.GetOldObject(), err)
|
||||
}
|
||||
existing := existingAccessor.GetInitializers()
|
||||
|
||||
// updates on initialized resources are allowed
|
||||
if updated == nil && existing == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
klog.V(5).Infof("Modifying uninitialized resource %s", a.GetResource())
|
||||
|
||||
// because we are called before validation, we need to ensure the update transition is valid.
|
||||
if errs := validation.ValidateInitializersUpdate(updated, existing, initializerFieldPath); len(errs) > 0 {
|
||||
return errors.NewInvalid(a.GetKind().GroupKind(), a.GetName(), errs)
|
||||
}
|
||||
|
||||
// caller must have the ability to mutate un-initialized resources
|
||||
if err := i.canInitialize(a, "update to uninitialized resource denied"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: restrict initialization list changes to specific clients?
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *initializer) canInitialize(a admission.Attributes, message string) error {
|
||||
// caller must have the ability to mutate un-initialized resources
|
||||
decision, reason, err := i.authorizer.Authorize(authorizer.AttributesRecord{
|
||||
Name: a.GetName(),
|
||||
ResourceRequest: true,
|
||||
User: a.GetUserInfo(),
|
||||
Verb: "initialize",
|
||||
Namespace: a.GetNamespace(),
|
||||
APIGroup: a.GetResource().Group,
|
||||
APIVersion: a.GetResource().Version,
|
||||
Resource: a.GetResource().Resource,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if decision != authorizer.DecisionAllow {
|
||||
return errors.NewForbidden(a.GetResource().GroupResource(), a.GetName(), fmt.Errorf("%s: %s", message, reason))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Handles returns true if this admission controller can handle the given operation
|
||||
// where operation can be one of CREATE, UPDATE, DELETE, or CONNECT
|
||||
func (i *initializer) Handles(op admission.Operation) bool {
|
||||
return op == admission.Create || op == admission.Update
|
||||
}
|
||||
|
||||
// newInitializers populates an Initializers struct.
|
||||
func newInitializers(names []string) *metav1.Initializers {
|
||||
if len(names) == 0 {
|
||||
return nil
|
||||
}
|
||||
var init []metav1.Initializer
|
||||
for _, name := range names {
|
||||
init = append(init, metav1.Initializer{Name: name})
|
||||
}
|
||||
return &metav1.Initializers{
|
||||
Pending: init,
|
||||
}
|
||||
}
|
||||
|
||||
// findInitializers returns the list of initializer names that apply to a config. It returns an empty list
|
||||
// if no initializers apply.
|
||||
func findInitializers(initializers *v1alpha1.InitializerConfiguration, gvr schema.GroupVersionResource) []string {
|
||||
var names []string
|
||||
for _, init := range initializers.Initializers {
|
||||
if !matchRule(init.Rules, gvr) {
|
||||
continue
|
||||
}
|
||||
names = append(names, init.Name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// matchRule returns true if any rule matches the provided group version resource.
|
||||
func matchRule(rules []v1alpha1.Rule, gvr schema.GroupVersionResource) bool {
|
||||
for _, rule := range rules {
|
||||
if !hasGroup(rule.APIGroups, gvr.Group) {
|
||||
return false
|
||||
}
|
||||
if !hasVersion(rule.APIVersions, gvr.Version) {
|
||||
return false
|
||||
}
|
||||
if !hasResource(rule.Resources, gvr.Resource) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return len(rules) > 0
|
||||
}
|
||||
|
||||
func hasGroup(groups []string, group string) bool {
|
||||
if groups[0] == "*" {
|
||||
return true
|
||||
}
|
||||
for _, g := range groups {
|
||||
if g == group {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func hasVersion(versions []string, version string) bool {
|
||||
if versions[0] == "*" {
|
||||
return true
|
||||
}
|
||||
for _, v := range versions {
|
||||
if v == version {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func hasResource(resources []string, resource string) bool {
|
||||
if resources[0] == "*" || resources[0] == "*/*" {
|
||||
return true
|
||||
}
|
||||
for _, r := range resources {
|
||||
if strings.Contains(r, "/") {
|
||||
continue
|
||||
}
|
||||
if r == resource {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
5
vendor/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle/admission.go
generated
vendored
5
vendor/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle/admission.go
generated
vendored
@@ -17,13 +17,14 @@ limitations under the License.
|
||||
package lifecycle
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"k8s.io/klog"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
@@ -73,7 +74,7 @@ var _ = initializer.WantsExternalKubeInformerFactory(&Lifecycle{})
|
||||
var _ = initializer.WantsExternalKubeClientSet(&Lifecycle{})
|
||||
|
||||
// Admit makes an admission decision based on the request attributes
|
||||
func (l *Lifecycle) Admit(a admission.Attributes) error {
|
||||
func (l *Lifecycle) Admit(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
|
||||
// prevent deletion of immortal namespaces
|
||||
if a.GetOperation() == admission.Delete && a.GetKind().GroupKind() == v1.SchemeGroupVersion.WithKind("Namespace").GroupKind() && l.immortalNamespaces.Has(a.GetName()) {
|
||||
return errors.NewForbidden(a.GetResource().GroupResource(), a.GetName(), fmt.Errorf("this namespace may not be deleted"))
|
||||
|
||||
297
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/accessors.go
generated
vendored
Normal file
297
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/accessors.go
generated
vendored
Normal file
@@ -0,0 +1,297 @@
|
||||
/*
|
||||
Copyright 2019 The Kubernetes 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 webhook
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"k8s.io/api/admissionregistration/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
webhookutil "k8s.io/apiserver/pkg/util/webhook"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
// WebhookAccessor provides a common interface to both mutating and validating webhook types.
|
||||
type WebhookAccessor interface {
|
||||
// GetUID gets a string that uniquely identifies the webhook.
|
||||
GetUID() string
|
||||
|
||||
// GetConfigurationName gets the name of the webhook configuration that owns this webhook.
|
||||
GetConfigurationName() string
|
||||
|
||||
// GetRESTClient gets the webhook client
|
||||
GetRESTClient(clientManager *webhookutil.ClientManager) (*rest.RESTClient, error)
|
||||
// GetParsedNamespaceSelector gets the webhook NamespaceSelector field.
|
||||
GetParsedNamespaceSelector() (labels.Selector, error)
|
||||
// GetParsedObjectSelector gets the webhook ObjectSelector field.
|
||||
GetParsedObjectSelector() (labels.Selector, error)
|
||||
|
||||
// GetName gets the webhook Name field. Note that the name is scoped to the webhook
|
||||
// configuration and does not provide a globally unique identity, if a unique identity is
|
||||
// needed, use GetUID.
|
||||
GetName() string
|
||||
// GetClientConfig gets the webhook ClientConfig field.
|
||||
GetClientConfig() v1beta1.WebhookClientConfig
|
||||
// GetRules gets the webhook Rules field.
|
||||
GetRules() []v1beta1.RuleWithOperations
|
||||
// GetFailurePolicy gets the webhook FailurePolicy field.
|
||||
GetFailurePolicy() *v1beta1.FailurePolicyType
|
||||
// GetMatchPolicy gets the webhook MatchPolicy field.
|
||||
GetMatchPolicy() *v1beta1.MatchPolicyType
|
||||
// GetNamespaceSelector gets the webhook NamespaceSelector field.
|
||||
GetNamespaceSelector() *metav1.LabelSelector
|
||||
// GetObjectSelector gets the webhook ObjectSelector field.
|
||||
GetObjectSelector() *metav1.LabelSelector
|
||||
// GetSideEffects gets the webhook SideEffects field.
|
||||
GetSideEffects() *v1beta1.SideEffectClass
|
||||
// GetTimeoutSeconds gets the webhook TimeoutSeconds field.
|
||||
GetTimeoutSeconds() *int32
|
||||
// GetAdmissionReviewVersions gets the webhook AdmissionReviewVersions field.
|
||||
GetAdmissionReviewVersions() []string
|
||||
|
||||
// GetMutatingWebhook if the accessor contains a MutatingWebhook, returns it and true, else returns false.
|
||||
GetMutatingWebhook() (*v1beta1.MutatingWebhook, bool)
|
||||
// GetValidatingWebhook if the accessor contains a ValidatingWebhook, returns it and true, else returns false.
|
||||
GetValidatingWebhook() (*v1beta1.ValidatingWebhook, bool)
|
||||
}
|
||||
|
||||
// NewMutatingWebhookAccessor creates an accessor for a MutatingWebhook.
|
||||
func NewMutatingWebhookAccessor(uid, configurationName string, h *v1beta1.MutatingWebhook) WebhookAccessor {
|
||||
return &mutatingWebhookAccessor{uid: uid, configurationName: configurationName, MutatingWebhook: h}
|
||||
}
|
||||
|
||||
type mutatingWebhookAccessor struct {
|
||||
*v1beta1.MutatingWebhook
|
||||
uid string
|
||||
configurationName string
|
||||
|
||||
initObjectSelector sync.Once
|
||||
objectSelector labels.Selector
|
||||
objectSelectorErr error
|
||||
|
||||
initNamespaceSelector sync.Once
|
||||
namespaceSelector labels.Selector
|
||||
namespaceSelectorErr error
|
||||
|
||||
initClient sync.Once
|
||||
client *rest.RESTClient
|
||||
clientErr error
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetUID() string {
|
||||
return m.uid
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetConfigurationName() string {
|
||||
return m.configurationName
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetRESTClient(clientManager *webhookutil.ClientManager) (*rest.RESTClient, error) {
|
||||
m.initClient.Do(func() {
|
||||
m.client, m.clientErr = clientManager.HookClient(hookClientConfigForWebhook(m))
|
||||
})
|
||||
return m.client, m.clientErr
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetParsedNamespaceSelector() (labels.Selector, error) {
|
||||
m.initNamespaceSelector.Do(func() {
|
||||
m.namespaceSelector, m.namespaceSelectorErr = metav1.LabelSelectorAsSelector(m.NamespaceSelector)
|
||||
})
|
||||
return m.namespaceSelector, m.namespaceSelectorErr
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetParsedObjectSelector() (labels.Selector, error) {
|
||||
m.initObjectSelector.Do(func() {
|
||||
m.objectSelector, m.objectSelectorErr = metav1.LabelSelectorAsSelector(m.ObjectSelector)
|
||||
})
|
||||
return m.objectSelector, m.objectSelectorErr
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetName() string {
|
||||
return m.Name
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetClientConfig() v1beta1.WebhookClientConfig {
|
||||
return m.ClientConfig
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetRules() []v1beta1.RuleWithOperations {
|
||||
return m.Rules
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetFailurePolicy() *v1beta1.FailurePolicyType {
|
||||
return m.FailurePolicy
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetMatchPolicy() *v1beta1.MatchPolicyType {
|
||||
return m.MatchPolicy
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetNamespaceSelector() *metav1.LabelSelector {
|
||||
return m.NamespaceSelector
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetObjectSelector() *metav1.LabelSelector {
|
||||
return m.ObjectSelector
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetSideEffects() *v1beta1.SideEffectClass {
|
||||
return m.SideEffects
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetTimeoutSeconds() *int32 {
|
||||
return m.TimeoutSeconds
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetAdmissionReviewVersions() []string {
|
||||
return m.AdmissionReviewVersions
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetMutatingWebhook() (*v1beta1.MutatingWebhook, bool) {
|
||||
return m.MutatingWebhook, true
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetValidatingWebhook() (*v1beta1.ValidatingWebhook, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// NewValidatingWebhookAccessor creates an accessor for a ValidatingWebhook.
|
||||
func NewValidatingWebhookAccessor(uid, configurationName string, h *v1beta1.ValidatingWebhook) WebhookAccessor {
|
||||
return &validatingWebhookAccessor{uid: uid, configurationName: configurationName, ValidatingWebhook: h}
|
||||
}
|
||||
|
||||
type validatingWebhookAccessor struct {
|
||||
*v1beta1.ValidatingWebhook
|
||||
uid string
|
||||
configurationName string
|
||||
|
||||
initObjectSelector sync.Once
|
||||
objectSelector labels.Selector
|
||||
objectSelectorErr error
|
||||
|
||||
initNamespaceSelector sync.Once
|
||||
namespaceSelector labels.Selector
|
||||
namespaceSelectorErr error
|
||||
|
||||
initClient sync.Once
|
||||
client *rest.RESTClient
|
||||
clientErr error
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetUID() string {
|
||||
return v.uid
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetConfigurationName() string {
|
||||
return v.configurationName
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetRESTClient(clientManager *webhookutil.ClientManager) (*rest.RESTClient, error) {
|
||||
v.initClient.Do(func() {
|
||||
v.client, v.clientErr = clientManager.HookClient(hookClientConfigForWebhook(v))
|
||||
})
|
||||
return v.client, v.clientErr
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetParsedNamespaceSelector() (labels.Selector, error) {
|
||||
v.initNamespaceSelector.Do(func() {
|
||||
v.namespaceSelector, v.namespaceSelectorErr = metav1.LabelSelectorAsSelector(v.NamespaceSelector)
|
||||
})
|
||||
return v.namespaceSelector, v.namespaceSelectorErr
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetParsedObjectSelector() (labels.Selector, error) {
|
||||
v.initObjectSelector.Do(func() {
|
||||
v.objectSelector, v.objectSelectorErr = metav1.LabelSelectorAsSelector(v.ObjectSelector)
|
||||
})
|
||||
return v.objectSelector, v.objectSelectorErr
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetName() string {
|
||||
return v.Name
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetClientConfig() v1beta1.WebhookClientConfig {
|
||||
return v.ClientConfig
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetRules() []v1beta1.RuleWithOperations {
|
||||
return v.Rules
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetFailurePolicy() *v1beta1.FailurePolicyType {
|
||||
return v.FailurePolicy
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetMatchPolicy() *v1beta1.MatchPolicyType {
|
||||
return v.MatchPolicy
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetNamespaceSelector() *metav1.LabelSelector {
|
||||
return v.NamespaceSelector
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetObjectSelector() *metav1.LabelSelector {
|
||||
return v.ObjectSelector
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetSideEffects() *v1beta1.SideEffectClass {
|
||||
return v.SideEffects
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetTimeoutSeconds() *int32 {
|
||||
return v.TimeoutSeconds
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetAdmissionReviewVersions() []string {
|
||||
return v.AdmissionReviewVersions
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetMutatingWebhook() (*v1beta1.MutatingWebhook, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetValidatingWebhook() (*v1beta1.ValidatingWebhook, bool) {
|
||||
return v.ValidatingWebhook, true
|
||||
}
|
||||
|
||||
// hookClientConfigForWebhook construct a webhookutil.ClientConfig using a WebhookAccessor to access
|
||||
// v1beta1.MutatingWebhook and v1beta1.ValidatingWebhook API objects. webhookutil.ClientConfig is used
|
||||
// to create a HookClient and the purpose of the config struct is to share that with other packages
|
||||
// that need to create a HookClient.
|
||||
func hookClientConfigForWebhook(w WebhookAccessor) webhookutil.ClientConfig {
|
||||
ret := webhookutil.ClientConfig{Name: w.GetName(), CABundle: w.GetClientConfig().CABundle}
|
||||
if w.GetClientConfig().URL != nil {
|
||||
ret.URL = *w.GetClientConfig().URL
|
||||
}
|
||||
if w.GetClientConfig().Service != nil {
|
||||
ret.Service = &webhookutil.ClientConfigService{
|
||||
Name: w.GetClientConfig().Service.Name,
|
||||
Namespace: w.GetClientConfig().Service.Namespace,
|
||||
}
|
||||
if w.GetClientConfig().Service.Port != nil {
|
||||
ret.Service.Port = *w.GetClientConfig().Service.Port
|
||||
} else {
|
||||
ret.Service.Port = 443
|
||||
}
|
||||
if w.GetClientConfig().Service.Path != nil {
|
||||
ret.Service.Path = *w.GetClientConfig().Service.Path
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
10
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/errors/statuserror.go
generated
vendored
10
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/errors/statuserror.go
generated
vendored
@@ -18,6 +18,7 @@ package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -32,6 +33,15 @@ func ToStatusErr(webhookName string, result *metav1.Status) *apierrors.StatusErr
|
||||
result = &metav1.Status{Status: metav1.StatusFailure}
|
||||
}
|
||||
|
||||
// Make sure we don't return < 400 status codes along with a rejection
|
||||
if result.Code < http.StatusBadRequest {
|
||||
result.Code = http.StatusBadRequest
|
||||
}
|
||||
// Make sure we don't return "" or "Success" status along with a rejection
|
||||
if result.Status == "" || result.Status == metav1.StatusSuccess {
|
||||
result.Status = metav1.StatusFailure
|
||||
}
|
||||
|
||||
switch {
|
||||
case len(result.Message) > 0:
|
||||
result.Message = fmt.Sprintf("%s: %s", deniedBy, result.Message)
|
||||
|
||||
83
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/conversion.go
generated
vendored
83
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/conversion.go
generated
vendored
@@ -17,29 +17,23 @@ limitations under the License.
|
||||
package generic
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
)
|
||||
|
||||
// convertor converts objects to the desired version.
|
||||
type convertor struct {
|
||||
Scheme *runtime.Scheme
|
||||
}
|
||||
|
||||
// ConvertToGVK converts object to the desired gvk.
|
||||
func (c *convertor) ConvertToGVK(obj runtime.Object, gvk schema.GroupVersionKind) (runtime.Object, error) {
|
||||
func ConvertToGVK(obj runtime.Object, gvk schema.GroupVersionKind, o admission.ObjectInterfaces) (runtime.Object, error) {
|
||||
// Unlike other resources, custom resources do not have internal version, so
|
||||
// if obj is a custom resource, it should not need conversion.
|
||||
if obj.GetObjectKind().GroupVersionKind() == gvk {
|
||||
return obj, nil
|
||||
}
|
||||
out, err := c.Scheme.New(gvk)
|
||||
out, err := o.GetObjectCreater().New(gvk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = c.Scheme.Convert(obj, out, nil)
|
||||
err = o.GetObjectConvertor().Convert(obj, out, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -48,10 +42,71 @@ func (c *convertor) ConvertToGVK(obj runtime.Object, gvk schema.GroupVersionKind
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Validate checks if the conversion has a scheme.
|
||||
func (c *convertor) Validate() error {
|
||||
if c.Scheme == nil {
|
||||
return fmt.Errorf("the convertor requires a scheme")
|
||||
// NewVersionedAttributes returns versioned attributes with the old and new object (if non-nil) converted to the requested kind
|
||||
func NewVersionedAttributes(attr admission.Attributes, gvk schema.GroupVersionKind, o admission.ObjectInterfaces) (*VersionedAttributes, error) {
|
||||
// convert the old and new objects to the requested version
|
||||
versionedAttr := &VersionedAttributes{
|
||||
Attributes: attr,
|
||||
VersionedKind: gvk,
|
||||
}
|
||||
if oldObj := attr.GetOldObject(); oldObj != nil {
|
||||
out, err := ConvertToGVK(oldObj, gvk, o)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
versionedAttr.VersionedOldObject = out
|
||||
}
|
||||
if obj := attr.GetObject(); obj != nil {
|
||||
out, err := ConvertToGVK(obj, gvk, o)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
versionedAttr.VersionedObject = out
|
||||
}
|
||||
return versionedAttr, nil
|
||||
}
|
||||
|
||||
// ConvertVersionedAttributes converts VersionedObject and VersionedOldObject to the specified kind, if needed.
|
||||
// If attr.VersionedKind already matches the requested kind, no conversion is performed.
|
||||
// If conversion is required:
|
||||
// * attr.VersionedObject is used as the source for the new object if Dirty=true (and is round-tripped through attr.Attributes.Object, clearing Dirty in the process)
|
||||
// * attr.Attributes.Object is used as the source for the new object if Dirty=false
|
||||
// * attr.Attributes.OldObject is used as the source for the old object
|
||||
func ConvertVersionedAttributes(attr *VersionedAttributes, gvk schema.GroupVersionKind, o admission.ObjectInterfaces) error {
|
||||
// we already have the desired kind, we're done
|
||||
if attr.VersionedKind == gvk {
|
||||
return nil
|
||||
}
|
||||
|
||||
// convert the original old object to the desired GVK
|
||||
if oldObj := attr.Attributes.GetOldObject(); oldObj != nil {
|
||||
out, err := ConvertToGVK(oldObj, gvk, o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
attr.VersionedOldObject = out
|
||||
}
|
||||
|
||||
if attr.VersionedObject != nil {
|
||||
// convert the existing versioned object to internal
|
||||
if attr.Dirty {
|
||||
err := o.GetObjectConvertor().Convert(attr.VersionedObject, attr.Attributes.GetObject(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// and back to external
|
||||
out, err := ConvertToGVK(attr.Attributes.GetObject(), gvk, o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
attr.VersionedObject = out
|
||||
}
|
||||
|
||||
// Remember we converted to this version
|
||||
attr.VersionedKind = gvk
|
||||
attr.Dirty = false
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
40
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/interfaces.go
generated
vendored
40
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/interfaces.go
generated
vendored
@@ -19,27 +19,57 @@ package generic
|
||||
import (
|
||||
"context"
|
||||
|
||||
"k8s.io/api/admissionregistration/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook"
|
||||
)
|
||||
|
||||
// Source can list dynamic webhook plugins.
|
||||
type Source interface {
|
||||
Webhooks() []v1beta1.Webhook
|
||||
Webhooks() []webhook.WebhookAccessor
|
||||
HasSynced() bool
|
||||
}
|
||||
|
||||
// VersionedAttributes is a wrapper around the original admission attributes, adding versioned
|
||||
// variants of the object and old object.
|
||||
type VersionedAttributes struct {
|
||||
// Attributes holds the original admission attributes
|
||||
admission.Attributes
|
||||
// VersionedOldObject holds Attributes.OldObject (if non-nil), converted to VersionedKind.
|
||||
// It must never be mutated.
|
||||
VersionedOldObject runtime.Object
|
||||
VersionedObject runtime.Object
|
||||
// VersionedObject holds Attributes.Object (if non-nil), converted to VersionedKind.
|
||||
// If mutated, Dirty must be set to true by the mutator.
|
||||
VersionedObject runtime.Object
|
||||
// VersionedKind holds the fully qualified kind
|
||||
VersionedKind schema.GroupVersionKind
|
||||
// Dirty indicates VersionedObject has been modified since being converted from Attributes.Object
|
||||
Dirty bool
|
||||
}
|
||||
|
||||
// GetObject overrides the Attributes.GetObject()
|
||||
func (v *VersionedAttributes) GetObject() runtime.Object {
|
||||
if v.VersionedObject != nil {
|
||||
return v.VersionedObject
|
||||
}
|
||||
return v.Attributes.GetObject()
|
||||
}
|
||||
|
||||
// WebhookInvocation describes how to call a webhook, including the resource and subresource the webhook registered for,
|
||||
// and the kind that should be sent to the webhook.
|
||||
type WebhookInvocation struct {
|
||||
Webhook webhook.WebhookAccessor
|
||||
Resource schema.GroupVersionResource
|
||||
Subresource string
|
||||
Kind schema.GroupVersionKind
|
||||
}
|
||||
|
||||
// Dispatcher dispatches webhook call to a list of webhooks with admission attributes as argument.
|
||||
type Dispatcher interface {
|
||||
// Dispatch a request to the webhooks using the given webhooks. A non-nil error means the request is rejected.
|
||||
Dispatch(ctx context.Context, a *VersionedAttributes, hooks []*v1beta1.Webhook) error
|
||||
// Dispatch a request to the webhooks. Dispatcher may choose not to
|
||||
// call a hook, either because the rules of the hook does not match, or
|
||||
// the namespaceSelector or the objectSelector of the hook does not
|
||||
// match. A non-nil error means the request is rejected.
|
||||
Dispatch(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces, hooks []webhook.WebhookAccessor) error
|
||||
}
|
||||
|
||||
154
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/webhook.go
generated
vendored
154
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/webhook.go
generated
vendored
@@ -21,16 +21,19 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
admissionv1beta1 "k8s.io/api/admission/v1beta1"
|
||||
"k8s.io/api/admissionregistration/v1beta1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
genericadmissioninit "k8s.io/apiserver/pkg/admission/initializer"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/config"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/namespace"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/object"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/rules"
|
||||
"k8s.io/apiserver/pkg/util/webhook"
|
||||
webhookutil "k8s.io/apiserver/pkg/util/webhook"
|
||||
"k8s.io/client-go/informers"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
)
|
||||
@@ -42,9 +45,9 @@ type Webhook struct {
|
||||
sourceFactory sourceFactory
|
||||
|
||||
hookSource Source
|
||||
clientManager *webhook.ClientManager
|
||||
convertor *convertor
|
||||
clientManager *webhookutil.ClientManager
|
||||
namespaceMatcher *namespace.Matcher
|
||||
objectMatcher *object.Matcher
|
||||
dispatcher Dispatcher
|
||||
}
|
||||
|
||||
@@ -54,7 +57,7 @@ var (
|
||||
)
|
||||
|
||||
type sourceFactory func(f informers.SharedInformerFactory) Source
|
||||
type dispatcherFactory func(cm *webhook.ClientManager) Dispatcher
|
||||
type dispatcherFactory func(cm *webhookutil.ClientManager) Dispatcher
|
||||
|
||||
// NewWebhook creates a new generic admission webhook.
|
||||
func NewWebhook(handler *admission.Handler, configFile io.Reader, sourceFactory sourceFactory, dispatcherFactory dispatcherFactory) (*Webhook, error) {
|
||||
@@ -63,24 +66,31 @@ func NewWebhook(handler *admission.Handler, configFile io.Reader, sourceFactory
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cm, err := webhook.NewClientManager(admissionv1beta1.SchemeGroupVersion, admissionv1beta1.AddToScheme)
|
||||
cm, err := webhookutil.NewClientManager(
|
||||
[]schema.GroupVersion{
|
||||
admissionv1beta1.SchemeGroupVersion,
|
||||
admissionv1.SchemeGroupVersion,
|
||||
},
|
||||
admissionv1beta1.AddToScheme,
|
||||
admissionv1.AddToScheme,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authInfoResolver, err := webhook.NewDefaultAuthenticationInfoResolver(kubeconfigFile)
|
||||
authInfoResolver, err := webhookutil.NewDefaultAuthenticationInfoResolver(kubeconfigFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Set defaults which may be overridden later.
|
||||
cm.SetAuthenticationInfoResolver(authInfoResolver)
|
||||
cm.SetServiceResolver(webhook.NewDefaultServiceResolver())
|
||||
cm.SetServiceResolver(webhookutil.NewDefaultServiceResolver())
|
||||
|
||||
return &Webhook{
|
||||
Handler: handler,
|
||||
sourceFactory: sourceFactory,
|
||||
clientManager: &cm,
|
||||
convertor: &convertor{},
|
||||
namespaceMatcher: &namespace.Matcher{},
|
||||
objectMatcher: &object.Matcher{},
|
||||
dispatcher: dispatcherFactory(&cm),
|
||||
}, nil
|
||||
}
|
||||
@@ -88,23 +98,16 @@ func NewWebhook(handler *admission.Handler, configFile io.Reader, sourceFactory
|
||||
// SetAuthenticationInfoResolverWrapper sets the
|
||||
// AuthenticationInfoResolverWrapper.
|
||||
// TODO find a better way wire this, but keep this pull small for now.
|
||||
func (a *Webhook) SetAuthenticationInfoResolverWrapper(wrapper webhook.AuthenticationInfoResolverWrapper) {
|
||||
func (a *Webhook) SetAuthenticationInfoResolverWrapper(wrapper webhookutil.AuthenticationInfoResolverWrapper) {
|
||||
a.clientManager.SetAuthenticationInfoResolverWrapper(wrapper)
|
||||
}
|
||||
|
||||
// SetServiceResolver sets a service resolver for the webhook admission plugin.
|
||||
// Passing a nil resolver does not have an effect, instead a default one will be used.
|
||||
func (a *Webhook) SetServiceResolver(sr webhook.ServiceResolver) {
|
||||
func (a *Webhook) SetServiceResolver(sr webhookutil.ServiceResolver) {
|
||||
a.clientManager.SetServiceResolver(sr)
|
||||
}
|
||||
|
||||
// SetScheme sets a serializer(NegotiatedSerializer) which is derived from the scheme
|
||||
func (a *Webhook) SetScheme(scheme *runtime.Scheme) {
|
||||
if scheme != nil {
|
||||
a.convertor.Scheme = scheme
|
||||
}
|
||||
}
|
||||
|
||||
// SetExternalKubeClientSet implements the WantsExternalKubeInformerFactory interface.
|
||||
// It sets external ClientSet for admission plugins that need it
|
||||
func (a *Webhook) SetExternalKubeClientSet(client clientset.Interface) {
|
||||
@@ -132,31 +135,83 @@ func (a *Webhook) ValidateInitialization() error {
|
||||
if err := a.clientManager.Validate(); err != nil {
|
||||
return fmt.Errorf("clientManager is not properly setup: %v", err)
|
||||
}
|
||||
if err := a.convertor.Validate(); err != nil {
|
||||
return fmt.Errorf("convertor is not properly setup: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ShouldCallHook makes a decision on whether to call the webhook or not by the attribute.
|
||||
func (a *Webhook) ShouldCallHook(h *v1beta1.Webhook, attr admission.Attributes) (bool, *apierrors.StatusError) {
|
||||
var matches bool
|
||||
for _, r := range h.Rules {
|
||||
// ShouldCallHook returns invocation details if the webhook should be called, nil if the webhook should not be called,
|
||||
// or an error if an error was encountered during evaluation.
|
||||
func (a *Webhook) ShouldCallHook(h webhook.WebhookAccessor, attr admission.Attributes, o admission.ObjectInterfaces) (*WebhookInvocation, *apierrors.StatusError) {
|
||||
var err *apierrors.StatusError
|
||||
var invocation *WebhookInvocation
|
||||
for _, r := range h.GetRules() {
|
||||
m := rules.Matcher{Rule: r, Attr: attr}
|
||||
if m.Matches() {
|
||||
matches = true
|
||||
invocation = &WebhookInvocation{
|
||||
Webhook: h,
|
||||
Resource: attr.GetResource(),
|
||||
Subresource: attr.GetSubresource(),
|
||||
Kind: attr.GetKind(),
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if !matches {
|
||||
return false, nil
|
||||
if invocation == nil && h.GetMatchPolicy() != nil && *h.GetMatchPolicy() == v1beta1.Equivalent {
|
||||
attrWithOverride := &attrWithResourceOverride{Attributes: attr}
|
||||
equivalents := o.GetEquivalentResourceMapper().EquivalentResourcesFor(attr.GetResource(), attr.GetSubresource())
|
||||
// honor earlier rules first
|
||||
OuterLoop:
|
||||
for _, r := range h.GetRules() {
|
||||
// see if the rule matches any of the equivalent resources
|
||||
for _, equivalent := range equivalents {
|
||||
if equivalent == attr.GetResource() {
|
||||
// exclude attr.GetResource(), which we already checked
|
||||
continue
|
||||
}
|
||||
attrWithOverride.resource = equivalent
|
||||
m := rules.Matcher{Rule: r, Attr: attrWithOverride}
|
||||
if m.Matches() {
|
||||
kind := o.GetEquivalentResourceMapper().KindFor(equivalent, attr.GetSubresource())
|
||||
if kind.Empty() {
|
||||
return nil, apierrors.NewInternalError(fmt.Errorf("unable to convert to %v: unknown kind", equivalent))
|
||||
}
|
||||
invocation = &WebhookInvocation{
|
||||
Webhook: h,
|
||||
Resource: equivalent,
|
||||
Subresource: attr.GetSubresource(),
|
||||
Kind: kind,
|
||||
}
|
||||
break OuterLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return a.namespaceMatcher.MatchNamespaceSelector(h, attr)
|
||||
if invocation == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
matches, err := a.namespaceMatcher.MatchNamespaceSelector(h, attr)
|
||||
if !matches || err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
matches, err = a.objectMatcher.MatchObjectSelector(h, attr)
|
||||
if !matches || err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return invocation, nil
|
||||
}
|
||||
|
||||
type attrWithResourceOverride struct {
|
||||
admission.Attributes
|
||||
resource schema.GroupVersionResource
|
||||
}
|
||||
|
||||
func (a *attrWithResourceOverride) GetResource() schema.GroupVersionResource { return a.resource }
|
||||
|
||||
// Dispatch is called by the downstream Validate or Admit methods.
|
||||
func (a *Webhook) Dispatch(attr admission.Attributes) error {
|
||||
func (a *Webhook) Dispatch(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces) error {
|
||||
if rules.IsWebhookConfigurationResource(attr) {
|
||||
return nil
|
||||
}
|
||||
@@ -164,42 +219,5 @@ func (a *Webhook) Dispatch(attr admission.Attributes) error {
|
||||
return admission.NewForbidden(attr, fmt.Errorf("not yet ready to handle request"))
|
||||
}
|
||||
hooks := a.hookSource.Webhooks()
|
||||
// TODO: Figure out if adding one second timeout make sense here.
|
||||
ctx := context.TODO()
|
||||
|
||||
var relevantHooks []*v1beta1.Webhook
|
||||
for i := range hooks {
|
||||
call, err := a.ShouldCallHook(&hooks[i], attr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if call {
|
||||
relevantHooks = append(relevantHooks, &hooks[i])
|
||||
}
|
||||
}
|
||||
|
||||
if len(relevantHooks) == 0 {
|
||||
// no matching hooks
|
||||
return nil
|
||||
}
|
||||
|
||||
// convert the object to the external version before sending it to the webhook
|
||||
versionedAttr := VersionedAttributes{
|
||||
Attributes: attr,
|
||||
}
|
||||
if oldObj := attr.GetOldObject(); oldObj != nil {
|
||||
out, err := a.convertor.ConvertToGVK(oldObj, attr.GetKind())
|
||||
if err != nil {
|
||||
return apierrors.NewInternalError(err)
|
||||
}
|
||||
versionedAttr.VersionedOldObject = out
|
||||
}
|
||||
if obj := attr.GetObject(); obj != nil {
|
||||
out, err := a.convertor.ConvertToGVK(obj, attr.GetKind())
|
||||
if err != nil {
|
||||
return apierrors.NewInternalError(err)
|
||||
}
|
||||
versionedAttr.VersionedObject = out
|
||||
}
|
||||
return a.dispatcher.Dispatch(ctx, &versionedAttr, relevantHooks)
|
||||
return a.dispatcher.Dispatch(ctx, attr, o, hooks)
|
||||
}
|
||||
|
||||
369
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/dispatcher.go
generated
vendored
369
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/dispatcher.go
generated
vendored
@@ -24,124 +24,285 @@ import (
|
||||
"time"
|
||||
|
||||
jsonpatch "github.com/evanphx/json-patch"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/klog"
|
||||
|
||||
admissionv1beta1 "k8s.io/api/admission/v1beta1"
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
"k8s.io/api/admissionregistration/v1beta1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer/json"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
admissionmetrics "k8s.io/apiserver/pkg/admission/metrics"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook"
|
||||
webhookerrors "k8s.io/apiserver/pkg/admission/plugin/webhook/errors"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/request"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/util"
|
||||
"k8s.io/apiserver/pkg/util/webhook"
|
||||
webhookrequest "k8s.io/apiserver/pkg/admission/plugin/webhook/request"
|
||||
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||
webhookutil "k8s.io/apiserver/pkg/util/webhook"
|
||||
utiltrace "k8s.io/utils/trace"
|
||||
)
|
||||
|
||||
const (
|
||||
// PatchAuditAnnotationPrefix is a prefix for persisting webhook patch in audit annotation.
|
||||
// Audit handler decides whether annotation with this prefix should be logged based on audit level.
|
||||
// Since mutating webhook patches the request body, audit level must be greater or equal to Request
|
||||
// for the annotation to be logged
|
||||
PatchAuditAnnotationPrefix = "patch.webhook.admission.k8s.io/"
|
||||
// MutationAuditAnnotationPrefix is a prefix for presisting webhook mutation existence in audit annotation.
|
||||
MutationAuditAnnotationPrefix = "mutation.webhook.admission.k8s.io/"
|
||||
)
|
||||
|
||||
var encodingjson = json.CaseSensitiveJsonIterator()
|
||||
|
||||
type mutatingDispatcher struct {
|
||||
cm *webhook.ClientManager
|
||||
cm *webhookutil.ClientManager
|
||||
plugin *Plugin
|
||||
}
|
||||
|
||||
func newMutatingDispatcher(p *Plugin) func(cm *webhook.ClientManager) generic.Dispatcher {
|
||||
return func(cm *webhook.ClientManager) generic.Dispatcher {
|
||||
func newMutatingDispatcher(p *Plugin) func(cm *webhookutil.ClientManager) generic.Dispatcher {
|
||||
return func(cm *webhookutil.ClientManager) generic.Dispatcher {
|
||||
return &mutatingDispatcher{cm, p}
|
||||
}
|
||||
}
|
||||
|
||||
var _ generic.Dispatcher = &mutatingDispatcher{}
|
||||
|
||||
func (a *mutatingDispatcher) Dispatch(ctx context.Context, attr *generic.VersionedAttributes, relevantHooks []*v1beta1.Webhook) error {
|
||||
for _, hook := range relevantHooks {
|
||||
func (a *mutatingDispatcher) Dispatch(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces, hooks []webhook.WebhookAccessor) error {
|
||||
reinvokeCtx := attr.GetReinvocationContext()
|
||||
var webhookReinvokeCtx *webhookReinvokeContext
|
||||
if v := reinvokeCtx.Value(PluginName); v != nil {
|
||||
webhookReinvokeCtx = v.(*webhookReinvokeContext)
|
||||
} else {
|
||||
webhookReinvokeCtx = &webhookReinvokeContext{}
|
||||
reinvokeCtx.SetValue(PluginName, webhookReinvokeCtx)
|
||||
}
|
||||
|
||||
if reinvokeCtx.IsReinvoke() && webhookReinvokeCtx.IsOutputChangedSinceLastWebhookInvocation(attr.GetObject()) {
|
||||
// If the object has changed, we know the in-tree plugin re-invocations have mutated the object,
|
||||
// and we need to reinvoke all eligible webhooks.
|
||||
webhookReinvokeCtx.RequireReinvokingPreviouslyInvokedPlugins()
|
||||
}
|
||||
defer func() {
|
||||
webhookReinvokeCtx.SetLastWebhookInvocationOutput(attr.GetObject())
|
||||
}()
|
||||
var versionedAttr *generic.VersionedAttributes
|
||||
for i, hook := range hooks {
|
||||
attrForCheck := attr
|
||||
if versionedAttr != nil {
|
||||
attrForCheck = versionedAttr
|
||||
}
|
||||
invocation, statusErr := a.plugin.ShouldCallHook(hook, attrForCheck, o)
|
||||
if statusErr != nil {
|
||||
return statusErr
|
||||
}
|
||||
if invocation == nil {
|
||||
continue
|
||||
}
|
||||
hook, ok := invocation.Webhook.GetMutatingWebhook()
|
||||
if !ok {
|
||||
return fmt.Errorf("mutating webhook dispatch requires v1beta1.MutatingWebhook, but got %T", hook)
|
||||
}
|
||||
// This means that during reinvocation, a webhook will not be
|
||||
// called for the first time. For example, if the webhook is
|
||||
// skipped in the first round because of mismatching labels,
|
||||
// even if the labels become matching, the webhook does not
|
||||
// get called during reinvocation.
|
||||
if reinvokeCtx.IsReinvoke() && !webhookReinvokeCtx.ShouldReinvokeWebhook(invocation.Webhook.GetUID()) {
|
||||
continue
|
||||
}
|
||||
|
||||
if versionedAttr == nil {
|
||||
// First webhook, create versioned attributes
|
||||
var err error
|
||||
if versionedAttr, err = generic.NewVersionedAttributes(attr, invocation.Kind, o); err != nil {
|
||||
return apierrors.NewInternalError(err)
|
||||
}
|
||||
} else {
|
||||
// Subsequent webhook, convert existing versioned attributes to this webhook's version
|
||||
if err := generic.ConvertVersionedAttributes(versionedAttr, invocation.Kind, o); err != nil {
|
||||
return apierrors.NewInternalError(err)
|
||||
}
|
||||
}
|
||||
|
||||
t := time.Now()
|
||||
err := a.callAttrMutatingHook(ctx, hook, attr)
|
||||
admissionmetrics.Metrics.ObserveWebhook(time.Since(t), err != nil, attr.Attributes, "admit", hook.Name)
|
||||
round := 0
|
||||
if reinvokeCtx.IsReinvoke() {
|
||||
round = 1
|
||||
}
|
||||
changed, err := a.callAttrMutatingHook(ctx, hook, invocation, versionedAttr, o, round, i)
|
||||
ignoreClientCallFailures := hook.FailurePolicy != nil && *hook.FailurePolicy == v1beta1.Ignore
|
||||
rejected := false
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case *webhookutil.ErrCallingWebhook:
|
||||
if !ignoreClientCallFailures {
|
||||
rejected = true
|
||||
admissionmetrics.Metrics.ObserveWebhookRejection(hook.Name, "admit", string(versionedAttr.Attributes.GetOperation()), admissionmetrics.WebhookRejectionCallingWebhookError, 0)
|
||||
}
|
||||
case *webhookutil.ErrWebhookRejection:
|
||||
rejected = true
|
||||
admissionmetrics.Metrics.ObserveWebhookRejection(hook.Name, "admit", string(versionedAttr.Attributes.GetOperation()), admissionmetrics.WebhookRejectionNoError, int(err.Status.ErrStatus.Code))
|
||||
default:
|
||||
rejected = true
|
||||
admissionmetrics.Metrics.ObserveWebhookRejection(hook.Name, "admit", string(versionedAttr.Attributes.GetOperation()), admissionmetrics.WebhookRejectionAPIServerInternalError, 0)
|
||||
}
|
||||
}
|
||||
admissionmetrics.Metrics.ObserveWebhook(time.Since(t), rejected, versionedAttr.Attributes, "admit", hook.Name)
|
||||
if changed {
|
||||
// Patch had changed the object. Prepare to reinvoke all previous webhooks that are eligible for re-invocation.
|
||||
webhookReinvokeCtx.RequireReinvokingPreviouslyInvokedPlugins()
|
||||
reinvokeCtx.SetShouldReinvoke()
|
||||
}
|
||||
if hook.ReinvocationPolicy != nil && *hook.ReinvocationPolicy == v1beta1.IfNeededReinvocationPolicy {
|
||||
webhookReinvokeCtx.AddReinvocableWebhookToPreviouslyInvoked(invocation.Webhook.GetUID())
|
||||
}
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
ignoreClientCallFailures := hook.FailurePolicy != nil && *hook.FailurePolicy == v1beta1.Ignore
|
||||
if callErr, ok := err.(*webhook.ErrCallingWebhook); ok {
|
||||
if callErr, ok := err.(*webhookutil.ErrCallingWebhook); ok {
|
||||
if ignoreClientCallFailures {
|
||||
klog.Warningf("Failed calling webhook, failing open %v: %v", hook.Name, callErr)
|
||||
utilruntime.HandleError(callErr)
|
||||
continue
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// parent context is canceled or timed out, no point in continuing
|
||||
return apierrors.NewTimeoutError("request did not complete within requested timeout", 0)
|
||||
default:
|
||||
// individual webhook timed out, but parent context did not, continue
|
||||
continue
|
||||
}
|
||||
}
|
||||
klog.Warningf("Failed calling webhook, failing closed %v: %v", hook.Name, err)
|
||||
return apierrors.NewInternalError(err)
|
||||
}
|
||||
return apierrors.NewInternalError(err)
|
||||
if rejectionErr, ok := err.(*webhookutil.ErrWebhookRejection); ok {
|
||||
return rejectionErr.Status
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// convert attr.VersionedObject to the internal version in the underlying admission.Attributes
|
||||
if attr.VersionedObject != nil {
|
||||
return a.plugin.scheme.Convert(attr.VersionedObject, attr.Attributes.GetObject(), nil)
|
||||
// convert versionedAttr.VersionedObject to the internal version in the underlying admission.Attributes
|
||||
if versionedAttr != nil && versionedAttr.VersionedObject != nil && versionedAttr.Dirty {
|
||||
return o.GetObjectConvertor().Convert(versionedAttr.VersionedObject, versionedAttr.Attributes.GetObject(), nil)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// note that callAttrMutatingHook updates attr
|
||||
func (a *mutatingDispatcher) callAttrMutatingHook(ctx context.Context, h *v1beta1.Webhook, attr *generic.VersionedAttributes) error {
|
||||
if attr.IsDryRun() {
|
||||
|
||||
func (a *mutatingDispatcher) callAttrMutatingHook(ctx context.Context, h *v1beta1.MutatingWebhook, invocation *generic.WebhookInvocation, attr *generic.VersionedAttributes, o admission.ObjectInterfaces, round, idx int) (bool, error) {
|
||||
configurationName := invocation.Webhook.GetConfigurationName()
|
||||
annotator := newWebhookAnnotator(attr, round, idx, h.Name, configurationName)
|
||||
changed := false
|
||||
defer func() { annotator.addMutationAnnotation(changed) }()
|
||||
if attr.Attributes.IsDryRun() {
|
||||
if h.SideEffects == nil {
|
||||
return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook SideEffects is nil")}
|
||||
return false, &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook SideEffects is nil")}
|
||||
}
|
||||
if !(*h.SideEffects == v1beta1.SideEffectClassNone || *h.SideEffects == v1beta1.SideEffectClassNoneOnDryRun) {
|
||||
return webhookerrors.NewDryRunUnsupportedErr(h.Name)
|
||||
return false, webhookerrors.NewDryRunUnsupportedErr(h.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// Make the webhook request
|
||||
request := request.CreateAdmissionReview(attr)
|
||||
client, err := a.cm.HookClient(util.HookClientConfigForWebhook(h))
|
||||
uid, request, response, err := webhookrequest.CreateAdmissionObjects(attr, invocation)
|
||||
if err != nil {
|
||||
return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
|
||||
return false, &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
|
||||
}
|
||||
response := &admissionv1beta1.AdmissionReview{}
|
||||
if err := client.Post().Context(ctx).Body(&request).Do().Into(response); err != nil {
|
||||
return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
|
||||
// Make the webhook request
|
||||
client, err := invocation.Webhook.GetRESTClient(a.cm)
|
||||
if err != nil {
|
||||
return false, &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
|
||||
}
|
||||
trace := utiltrace.New("Call mutating webhook",
|
||||
utiltrace.Field{"configuration", configurationName},
|
||||
utiltrace.Field{"webhook", h.Name},
|
||||
utiltrace.Field{"resource", attr.GetResource()},
|
||||
utiltrace.Field{"subresource", attr.GetSubresource()},
|
||||
utiltrace.Field{"operation", attr.GetOperation()},
|
||||
utiltrace.Field{"UID", uid})
|
||||
defer trace.LogIfLong(500 * time.Millisecond)
|
||||
|
||||
// if the webhook has a specific timeout, wrap the context to apply it
|
||||
if h.TimeoutSeconds != nil {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ctx, time.Duration(*h.TimeoutSeconds)*time.Second)
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
if response.Response == nil {
|
||||
return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook response was absent")}
|
||||
r := client.Post().Context(ctx).Body(request)
|
||||
|
||||
// if the context has a deadline, set it as a parameter to inform the backend
|
||||
if deadline, hasDeadline := ctx.Deadline(); hasDeadline {
|
||||
// compute the timeout
|
||||
if timeout := time.Until(deadline); timeout > 0 {
|
||||
// if it's not an even number of seconds, round up to the nearest second
|
||||
if truncated := timeout.Truncate(time.Second); truncated != timeout {
|
||||
timeout = truncated + time.Second
|
||||
}
|
||||
// set the timeout
|
||||
r.Timeout(timeout)
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range response.Response.AuditAnnotations {
|
||||
if err := r.Do().Into(response); err != nil {
|
||||
return false, &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
|
||||
}
|
||||
trace.Step("Request completed")
|
||||
|
||||
result, err := webhookrequest.VerifyAdmissionResponse(uid, true, response)
|
||||
if err != nil {
|
||||
return false, &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
|
||||
}
|
||||
|
||||
for k, v := range result.AuditAnnotations {
|
||||
key := h.Name + "/" + k
|
||||
if err := attr.AddAnnotation(key, v); err != nil {
|
||||
if err := attr.Attributes.AddAnnotation(key, v); err != nil {
|
||||
klog.Warningf("Failed to set admission audit annotation %s to %s for mutating webhook %s: %v", key, v, h.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
if !response.Response.Allowed {
|
||||
return webhookerrors.ToStatusErr(h.Name, response.Response.Result)
|
||||
if !result.Allowed {
|
||||
return false, &webhookutil.ErrWebhookRejection{Status: webhookerrors.ToStatusErr(h.Name, result.Result)}
|
||||
}
|
||||
|
||||
patchJS := response.Response.Patch
|
||||
if len(patchJS) == 0 {
|
||||
return nil
|
||||
if len(result.Patch) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
patchObj, err := jsonpatch.DecodePatch(patchJS)
|
||||
patchObj, err := jsonpatch.DecodePatch(result.Patch)
|
||||
if err != nil {
|
||||
return apierrors.NewInternalError(err)
|
||||
return false, apierrors.NewInternalError(err)
|
||||
}
|
||||
|
||||
if len(patchObj) == 0 {
|
||||
return nil
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// if a non-empty patch was provided, and we have no object we can apply it to (e.g. a DELETE admission operation), error
|
||||
if attr.VersionedObject == nil {
|
||||
return apierrors.NewInternalError(fmt.Errorf("admission webhook %q attempted to modify the object, which is not supported for this operation", h.Name))
|
||||
return false, apierrors.NewInternalError(fmt.Errorf("admission webhook %q attempted to modify the object, which is not supported for this operation", h.Name))
|
||||
}
|
||||
|
||||
objJS, err := runtime.Encode(a.plugin.jsonSerializer, attr.VersionedObject)
|
||||
if err != nil {
|
||||
return apierrors.NewInternalError(err)
|
||||
}
|
||||
patchedJS, err := patchObj.Apply(objJS)
|
||||
if err != nil {
|
||||
return apierrors.NewInternalError(err)
|
||||
var patchedJS []byte
|
||||
jsonSerializer := json.NewSerializer(json.DefaultMetaFactory, o.GetObjectCreater(), o.GetObjectTyper(), false)
|
||||
switch result.PatchType {
|
||||
// VerifyAdmissionResponse normalizes to v1 patch types, regardless of the AdmissionReview version used
|
||||
case admissionv1.PatchTypeJSONPatch:
|
||||
objJS, err := runtime.Encode(jsonSerializer, attr.VersionedObject)
|
||||
if err != nil {
|
||||
return false, apierrors.NewInternalError(err)
|
||||
}
|
||||
patchedJS, err = patchObj.Apply(objJS)
|
||||
if err != nil {
|
||||
return false, apierrors.NewInternalError(err)
|
||||
}
|
||||
default:
|
||||
return false, &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("unsupported patch type %q", result.PatchType)}
|
||||
}
|
||||
|
||||
var newVersionedObject runtime.Object
|
||||
@@ -150,17 +311,115 @@ func (a *mutatingDispatcher) callAttrMutatingHook(ctx context.Context, h *v1beta
|
||||
// They are represented as Unstructured.
|
||||
newVersionedObject = &unstructured.Unstructured{}
|
||||
} else {
|
||||
newVersionedObject, err = a.plugin.scheme.New(attr.GetKind())
|
||||
newVersionedObject, err = o.GetObjectCreater().New(attr.VersionedKind)
|
||||
if err != nil {
|
||||
return apierrors.NewInternalError(err)
|
||||
return false, apierrors.NewInternalError(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: if we have multiple mutating webhooks, we can remember the json
|
||||
// instead of encoding and decoding for each one.
|
||||
if _, _, err := a.plugin.jsonSerializer.Decode(patchedJS, nil, newVersionedObject); err != nil {
|
||||
return apierrors.NewInternalError(err)
|
||||
if newVersionedObject, _, err = jsonSerializer.Decode(patchedJS, nil, newVersionedObject); err != nil {
|
||||
return false, apierrors.NewInternalError(err)
|
||||
}
|
||||
|
||||
changed = !apiequality.Semantic.DeepEqual(attr.VersionedObject, newVersionedObject)
|
||||
trace.Step("Patch applied")
|
||||
annotator.addPatchAnnotation(patchObj, result.PatchType)
|
||||
attr.Dirty = true
|
||||
attr.VersionedObject = newVersionedObject
|
||||
a.plugin.scheme.Default(attr.VersionedObject)
|
||||
return nil
|
||||
o.GetObjectDefaulter().Default(attr.VersionedObject)
|
||||
return changed, nil
|
||||
}
|
||||
|
||||
type webhookAnnotator struct {
|
||||
attr *generic.VersionedAttributes
|
||||
patchAnnotationKey string
|
||||
mutationAnnotationKey string
|
||||
webhook string
|
||||
configuration string
|
||||
}
|
||||
|
||||
func newWebhookAnnotator(attr *generic.VersionedAttributes, round, idx int, webhook, configuration string) *webhookAnnotator {
|
||||
return &webhookAnnotator{
|
||||
attr: attr,
|
||||
patchAnnotationKey: fmt.Sprintf("%sround_%d_index_%d", PatchAuditAnnotationPrefix, round, idx),
|
||||
mutationAnnotationKey: fmt.Sprintf("%sround_%d_index_%d", MutationAuditAnnotationPrefix, round, idx),
|
||||
webhook: webhook,
|
||||
configuration: configuration,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *webhookAnnotator) addMutationAnnotation(mutated bool) {
|
||||
if w.attr == nil || w.attr.Attributes == nil {
|
||||
return
|
||||
}
|
||||
value, err := mutationAnnotationValue(w.configuration, w.webhook, mutated)
|
||||
if err != nil {
|
||||
klog.Warningf("unexpected error composing mutating webhook annotation: %v", err)
|
||||
return
|
||||
}
|
||||
if err := w.attr.Attributes.AddAnnotation(w.mutationAnnotationKey, value); err != nil {
|
||||
klog.Warningf("failed to set mutation annotation for mutating webhook key %s to %s: %v", w.mutationAnnotationKey, value, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *webhookAnnotator) addPatchAnnotation(patch interface{}, patchType admissionv1.PatchType) {
|
||||
if w.attr == nil || w.attr.Attributes == nil {
|
||||
return
|
||||
}
|
||||
var value string
|
||||
var err error
|
||||
switch patchType {
|
||||
case admissionv1.PatchTypeJSONPatch:
|
||||
value, err = jsonPatchAnnotationValue(w.configuration, w.webhook, patch)
|
||||
if err != nil {
|
||||
klog.Warningf("unexpected error composing mutating webhook JSON patch annotation: %v", err)
|
||||
return
|
||||
}
|
||||
default:
|
||||
klog.Warningf("unsupported patch type for mutating webhook annotation: %v", patchType)
|
||||
return
|
||||
}
|
||||
if err := w.attr.Attributes.AddAnnotationWithLevel(w.patchAnnotationKey, value, auditinternal.LevelRequest); err != nil {
|
||||
// NOTE: we don't log actual patch in kube-apiserver log to avoid potentially
|
||||
// leaking information
|
||||
klog.Warningf("failed to set patch annotation for mutating webhook key %s; confugiration name: %s, webhook name: %s", w.patchAnnotationKey, w.configuration, w.webhook)
|
||||
}
|
||||
}
|
||||
|
||||
// MutationAuditAnnotation logs if a webhook invocation mutated the request object
|
||||
type MutationAuditAnnotation struct {
|
||||
Configuration string `json:"configuration"`
|
||||
Webhook string `json:"webhook"`
|
||||
Mutated bool `json:"mutated"`
|
||||
}
|
||||
|
||||
// PatchAuditAnnotation logs a patch from a mutating webhook
|
||||
type PatchAuditAnnotation struct {
|
||||
Configuration string `json:"configuration"`
|
||||
Webhook string `json:"webhook"`
|
||||
Patch interface{} `json:"patch,omitempty"`
|
||||
PatchType string `json:"patchType,omitempty"`
|
||||
}
|
||||
|
||||
func mutationAnnotationValue(configuration, webhook string, mutated bool) (string, error) {
|
||||
m := MutationAuditAnnotation{
|
||||
Configuration: configuration,
|
||||
Webhook: webhook,
|
||||
Mutated: mutated,
|
||||
}
|
||||
bytes, err := encodingjson.Marshal(m)
|
||||
return string(bytes), err
|
||||
}
|
||||
|
||||
func jsonPatchAnnotationValue(configuration, webhook string, patch interface{}) (string, error) {
|
||||
p := PatchAuditAnnotation{
|
||||
Configuration: configuration,
|
||||
Webhook: webhook,
|
||||
Patch: patch,
|
||||
PatchType: string(admissionv1.PatchTypeJSONPatch),
|
||||
}
|
||||
bytes, err := encodingjson.Marshal(p)
|
||||
return string(bytes), err
|
||||
}
|
||||
|
||||
26
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/plugin.go
generated
vendored
26
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/plugin.go
generated
vendored
@@ -17,11 +17,9 @@ limitations under the License.
|
||||
package mutating
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer/json"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/configuration"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
|
||||
@@ -47,9 +45,6 @@ func Register(plugins *admission.Plugins) {
|
||||
// Plugin is an implementation of admission.Interface.
|
||||
type Plugin struct {
|
||||
*generic.Webhook
|
||||
|
||||
scheme *runtime.Scheme
|
||||
jsonSerializer *json.Serializer
|
||||
}
|
||||
|
||||
var _ admission.MutationInterface = &Plugin{}
|
||||
@@ -67,30 +62,15 @@ func NewMutatingWebhook(configFile io.Reader) (*Plugin, error) {
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// SetScheme sets a serializer(NegotiatedSerializer) which is derived from the scheme
|
||||
func (a *Plugin) SetScheme(scheme *runtime.Scheme) {
|
||||
a.Webhook.SetScheme(scheme)
|
||||
if scheme != nil {
|
||||
a.scheme = scheme
|
||||
a.jsonSerializer = json.NewSerializer(json.DefaultMetaFactory, scheme, scheme, false)
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateInitialization implements the InitializationValidator interface.
|
||||
func (a *Plugin) ValidateInitialization() error {
|
||||
if err := a.Webhook.ValidateInitialization(); err != nil {
|
||||
return err
|
||||
}
|
||||
if a.scheme == nil {
|
||||
return fmt.Errorf("scheme is not properly setup")
|
||||
}
|
||||
if a.jsonSerializer == nil {
|
||||
return fmt.Errorf("jsonSerializer is not properly setup")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Admit makes an admission decision based on the request attributes.
|
||||
func (a *Plugin) Admit(attr admission.Attributes) error {
|
||||
return a.Webhook.Dispatch(attr)
|
||||
func (a *Plugin) Admit(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces) error {
|
||||
return a.Webhook.Dispatch(ctx, attr, o)
|
||||
}
|
||||
|
||||
68
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/reinvocationcontext.go
generated
vendored
Normal file
68
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/reinvocationcontext.go
generated
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
Copyright 2019 The Kubernetes 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 mutating
|
||||
|
||||
import (
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
type webhookReinvokeContext struct {
|
||||
// lastWebhookOutput holds the result of the last webhook admission plugin call
|
||||
lastWebhookOutput runtime.Object
|
||||
// previouslyInvokedReinvocableWebhooks holds the set of webhooks that have been invoked and
|
||||
// should be reinvoked if a later mutation occurs
|
||||
previouslyInvokedReinvocableWebhooks sets.String
|
||||
// reinvokeWebhooks holds the set of webhooks that should be reinvoked
|
||||
reinvokeWebhooks sets.String
|
||||
}
|
||||
|
||||
func (rc *webhookReinvokeContext) ShouldReinvokeWebhook(webhook string) bool {
|
||||
return rc.reinvokeWebhooks.Has(webhook)
|
||||
}
|
||||
|
||||
func (rc *webhookReinvokeContext) IsOutputChangedSinceLastWebhookInvocation(object runtime.Object) bool {
|
||||
return !apiequality.Semantic.DeepEqual(rc.lastWebhookOutput, object)
|
||||
}
|
||||
|
||||
func (rc *webhookReinvokeContext) SetLastWebhookInvocationOutput(object runtime.Object) {
|
||||
if object == nil {
|
||||
rc.lastWebhookOutput = nil
|
||||
return
|
||||
}
|
||||
rc.lastWebhookOutput = object.DeepCopyObject()
|
||||
}
|
||||
|
||||
func (rc *webhookReinvokeContext) AddReinvocableWebhookToPreviouslyInvoked(webhook string) {
|
||||
if rc.previouslyInvokedReinvocableWebhooks == nil {
|
||||
rc.previouslyInvokedReinvocableWebhooks = sets.NewString()
|
||||
}
|
||||
rc.previouslyInvokedReinvocableWebhooks.Insert(webhook)
|
||||
}
|
||||
|
||||
func (rc *webhookReinvokeContext) RequireReinvokingPreviouslyInvokedPlugins() {
|
||||
if len(rc.previouslyInvokedReinvocableWebhooks) > 0 {
|
||||
if rc.reinvokeWebhooks == nil {
|
||||
rc.reinvokeWebhooks = sets.NewString()
|
||||
}
|
||||
for s := range rc.previouslyInvokedReinvocableWebhooks {
|
||||
rc.reinvokeWebhooks.Insert(s)
|
||||
}
|
||||
rc.previouslyInvokedReinvocableWebhooks = sets.NewString()
|
||||
}
|
||||
}
|
||||
17
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace/matcher.go
generated
vendored
17
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace/matcher.go
generated
vendored
@@ -19,13 +19,13 @@ package namespace
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/api/admissionregistration/v1beta1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
corelisters "k8s.io/client-go/listers/core/v1"
|
||||
)
|
||||
@@ -86,7 +86,7 @@ func (m *Matcher) GetNamespaceLabels(attr admission.Attributes) (map[string]stri
|
||||
|
||||
// MatchNamespaceSelector decideds whether the request matches the
|
||||
// namespaceSelctor of the webhook. Only when they match, the webhook is called.
|
||||
func (m *Matcher) MatchNamespaceSelector(h *v1beta1.Webhook, attr admission.Attributes) (bool, *apierrors.StatusError) {
|
||||
func (m *Matcher) MatchNamespaceSelector(h webhook.WebhookAccessor, attr admission.Attributes) (bool, *apierrors.StatusError) {
|
||||
namespaceName := attr.GetNamespace()
|
||||
if len(namespaceName) == 0 && attr.GetResource().Resource != "namespaces" {
|
||||
// If the request is about a cluster scoped resource, and it is not a
|
||||
@@ -95,6 +95,14 @@ func (m *Matcher) MatchNamespaceSelector(h *v1beta1.Webhook, attr admission.Attr
|
||||
// Also update the comment in types.go
|
||||
return true, nil
|
||||
}
|
||||
selector, err := h.GetParsedNamespaceSelector()
|
||||
if err != nil {
|
||||
return false, apierrors.NewInternalError(err)
|
||||
}
|
||||
if selector.Empty() {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
namespaceLabels, err := m.GetNamespaceLabels(attr)
|
||||
// this means the namespace is not found, for backwards compatibility,
|
||||
// return a 404
|
||||
@@ -108,10 +116,5 @@ func (m *Matcher) MatchNamespaceSelector(h *v1beta1.Webhook, attr admission.Attr
|
||||
if err != nil {
|
||||
return false, apierrors.NewInternalError(err)
|
||||
}
|
||||
// TODO: adding an LRU cache to cache the translation
|
||||
selector, err := metav1.LabelSelectorAsSelector(h.NamespaceSelector)
|
||||
if err != nil {
|
||||
return false, apierrors.NewInternalError(err)
|
||||
}
|
||||
return selector.Matches(labels.Set(namespaceLabels)), nil
|
||||
}
|
||||
|
||||
20
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/object/doc.go
generated
vendored
Normal file
20
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/object/doc.go
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
Copyright 2019 The Kubernetes 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 object defines the utilities that are used by the webhook plugin to
|
||||
// decide if a webhook should run, as long as either the old object or the new
|
||||
// object has labels matching the webhook config's objectSelector.
|
||||
package object // import "k8s.io/apiserver/pkg/admission/plugin/webhook/object"
|
||||
57
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/object/matcher.go
generated
vendored
Normal file
57
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/object/matcher.go
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
Copyright 2019 The Kubernetes 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 object
|
||||
|
||||
import (
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook"
|
||||
"k8s.io/klog"
|
||||
)
|
||||
|
||||
// Matcher decides if a request selected by the ObjectSelector.
|
||||
type Matcher struct {
|
||||
}
|
||||
|
||||
func matchObject(obj runtime.Object, selector labels.Selector) bool {
|
||||
if obj == nil {
|
||||
return false
|
||||
}
|
||||
accessor, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
klog.V(5).Infof("cannot access metadata of %v: %v", obj, err)
|
||||
return false
|
||||
}
|
||||
return selector.Matches(labels.Set(accessor.GetLabels()))
|
||||
|
||||
}
|
||||
|
||||
// MatchObjectSelector decideds whether the request matches the ObjectSelector
|
||||
// of the webhook. Only when they match, the webhook is called.
|
||||
func (m *Matcher) MatchObjectSelector(h webhook.WebhookAccessor, attr admission.Attributes) (bool, *apierrors.StatusError) {
|
||||
selector, err := h.GetParsedObjectSelector()
|
||||
if err != nil {
|
||||
return false, apierrors.NewInternalError(err)
|
||||
}
|
||||
if selector.Empty() {
|
||||
return true, nil
|
||||
}
|
||||
return matchObject(attr.GetObject(), selector) || matchObject(attr.GetOldObject(), selector), nil
|
||||
}
|
||||
235
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/request/admissionreview.go
generated
vendored
235
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/request/admissionreview.go
generated
vendored
@@ -17,18 +17,145 @@ limitations under the License.
|
||||
package request
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
admissionv1beta1 "k8s.io/api/admission/v1beta1"
|
||||
authenticationv1 "k8s.io/api/authentication/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/uuid"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
|
||||
)
|
||||
|
||||
// CreateAdmissionReview creates an AdmissionReview for the provided admission.Attributes
|
||||
func CreateAdmissionReview(attr *generic.VersionedAttributes) admissionv1beta1.AdmissionReview {
|
||||
gvk := attr.GetKind()
|
||||
gvr := attr.GetResource()
|
||||
// AdmissionResponse contains the fields extracted from an AdmissionReview response
|
||||
type AdmissionResponse struct {
|
||||
AuditAnnotations map[string]string
|
||||
Allowed bool
|
||||
Patch []byte
|
||||
PatchType admissionv1.PatchType
|
||||
Result *metav1.Status
|
||||
}
|
||||
|
||||
// VerifyAdmissionResponse checks the validity of the provided admission review object, and returns the
|
||||
// audit annotations, whether the response allowed the request, any provided patch/patchType/status,
|
||||
// or an error if the provided admission review was not valid.
|
||||
func VerifyAdmissionResponse(uid types.UID, mutating bool, review runtime.Object) (*AdmissionResponse, error) {
|
||||
switch r := review.(type) {
|
||||
case *admissionv1.AdmissionReview:
|
||||
if r.Response == nil {
|
||||
return nil, fmt.Errorf("webhook response was absent")
|
||||
}
|
||||
|
||||
// Verify UID matches
|
||||
if r.Response.UID != uid {
|
||||
return nil, fmt.Errorf("expected response.uid=%q, got %q", uid, r.Response.UID)
|
||||
}
|
||||
|
||||
// Verify GVK
|
||||
v1GVK := admissionv1.SchemeGroupVersion.WithKind("AdmissionReview")
|
||||
if r.GroupVersionKind() != v1GVK {
|
||||
return nil, fmt.Errorf("expected webhook response of %v, got %v", v1GVK.String(), r.GroupVersionKind().String())
|
||||
}
|
||||
|
||||
patch := []byte(nil)
|
||||
patchType := admissionv1.PatchType("")
|
||||
|
||||
if mutating {
|
||||
// Ensure a mutating webhook provides both patch and patchType together
|
||||
if len(r.Response.Patch) > 0 && r.Response.PatchType == nil {
|
||||
return nil, fmt.Errorf("webhook returned response.patch but not response.patchType")
|
||||
}
|
||||
if len(r.Response.Patch) == 0 && r.Response.PatchType != nil {
|
||||
return nil, fmt.Errorf("webhook returned response.patchType but not response.patch")
|
||||
}
|
||||
patch = r.Response.Patch
|
||||
if r.Response.PatchType != nil {
|
||||
patchType = *r.Response.PatchType
|
||||
if len(patchType) == 0 {
|
||||
return nil, fmt.Errorf("webhook returned invalid response.patchType of %q", patchType)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Ensure a validating webhook doesn't return patch or patchType
|
||||
if len(r.Response.Patch) > 0 {
|
||||
return nil, fmt.Errorf("validating webhook may not return response.patch")
|
||||
}
|
||||
if r.Response.PatchType != nil {
|
||||
return nil, fmt.Errorf("validating webhook may not return response.patchType")
|
||||
}
|
||||
}
|
||||
|
||||
return &AdmissionResponse{
|
||||
AuditAnnotations: r.Response.AuditAnnotations,
|
||||
Allowed: r.Response.Allowed,
|
||||
Patch: patch,
|
||||
PatchType: patchType,
|
||||
Result: r.Response.Result,
|
||||
}, nil
|
||||
|
||||
case *admissionv1beta1.AdmissionReview:
|
||||
if r.Response == nil {
|
||||
return nil, fmt.Errorf("webhook response was absent")
|
||||
}
|
||||
|
||||
// Response GVK and response.uid were not verified in v1beta1 handling, allow any
|
||||
|
||||
patch := []byte(nil)
|
||||
patchType := admissionv1.PatchType("")
|
||||
if mutating {
|
||||
patch = r.Response.Patch
|
||||
if len(r.Response.Patch) > 0 {
|
||||
// patch type was not verified in v1beta1 admissionreview handling. pin to only supported version if a patch is provided.
|
||||
patchType = admissionv1.PatchTypeJSONPatch
|
||||
}
|
||||
}
|
||||
|
||||
return &AdmissionResponse{
|
||||
AuditAnnotations: r.Response.AuditAnnotations,
|
||||
Allowed: r.Response.Allowed,
|
||||
Patch: patch,
|
||||
PatchType: patchType,
|
||||
Result: r.Response.Result,
|
||||
}, nil
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected response type %T", review)
|
||||
}
|
||||
}
|
||||
|
||||
// CreateAdmissionObjects returns the unique request uid, the AdmissionReview object to send the webhook and to decode the response into,
|
||||
// or an error if the webhook does not support receiving any of the admission review versions we know to send
|
||||
func CreateAdmissionObjects(versionedAttributes *generic.VersionedAttributes, invocation *generic.WebhookInvocation) (uid types.UID, request, response runtime.Object, err error) {
|
||||
for _, version := range invocation.Webhook.GetAdmissionReviewVersions() {
|
||||
switch version {
|
||||
case admissionv1.SchemeGroupVersion.Version:
|
||||
uid := types.UID(uuid.NewUUID())
|
||||
request := CreateV1AdmissionReview(uid, versionedAttributes, invocation)
|
||||
response := &admissionv1.AdmissionReview{}
|
||||
return uid, request, response, nil
|
||||
|
||||
case admissionv1beta1.SchemeGroupVersion.Version:
|
||||
uid := types.UID(uuid.NewUUID())
|
||||
request := CreateV1beta1AdmissionReview(uid, versionedAttributes, invocation)
|
||||
response := &admissionv1beta1.AdmissionReview{}
|
||||
return uid, request, response, nil
|
||||
|
||||
}
|
||||
}
|
||||
return "", nil, nil, fmt.Errorf("webhook does not accept known AdmissionReview versions (v1, v1beta1)")
|
||||
}
|
||||
|
||||
// CreateV1AdmissionReview creates an AdmissionReview for the provided admission.Attributes
|
||||
func CreateV1AdmissionReview(uid types.UID, versionedAttributes *generic.VersionedAttributes, invocation *generic.WebhookInvocation) *admissionv1.AdmissionReview {
|
||||
attr := versionedAttributes.Attributes
|
||||
gvk := invocation.Kind
|
||||
gvr := invocation.Resource
|
||||
subresource := invocation.Subresource
|
||||
requestGVK := attr.GetKind()
|
||||
requestGVR := attr.GetResource()
|
||||
requestSubResource := attr.GetSubresource()
|
||||
aUserInfo := attr.GetUserInfo()
|
||||
userInfo := authenticationv1.UserInfo{
|
||||
Extra: make(map[string]authenticationv1.ExtraValue),
|
||||
@@ -43,9 +170,9 @@ func CreateAdmissionReview(attr *generic.VersionedAttributes) admissionv1beta1.A
|
||||
userInfo.Extra[key] = authenticationv1.ExtraValue(val)
|
||||
}
|
||||
|
||||
return admissionv1beta1.AdmissionReview{
|
||||
Request: &admissionv1beta1.AdmissionRequest{
|
||||
UID: uuid.NewUUID(),
|
||||
return &admissionv1.AdmissionReview{
|
||||
Request: &admissionv1.AdmissionRequest{
|
||||
UID: uid,
|
||||
Kind: metav1.GroupVersionKind{
|
||||
Group: gvk.Group,
|
||||
Kind: gvk.Kind,
|
||||
@@ -56,18 +183,98 @@ func CreateAdmissionReview(attr *generic.VersionedAttributes) admissionv1beta1.A
|
||||
Resource: gvr.Resource,
|
||||
Version: gvr.Version,
|
||||
},
|
||||
SubResource: attr.GetSubresource(),
|
||||
Name: attr.GetName(),
|
||||
Namespace: attr.GetNamespace(),
|
||||
Operation: admissionv1beta1.Operation(attr.GetOperation()),
|
||||
UserInfo: userInfo,
|
||||
SubResource: subresource,
|
||||
RequestKind: &metav1.GroupVersionKind{
|
||||
Group: requestGVK.Group,
|
||||
Kind: requestGVK.Kind,
|
||||
Version: requestGVK.Version,
|
||||
},
|
||||
RequestResource: &metav1.GroupVersionResource{
|
||||
Group: requestGVR.Group,
|
||||
Resource: requestGVR.Resource,
|
||||
Version: requestGVR.Version,
|
||||
},
|
||||
RequestSubResource: requestSubResource,
|
||||
Name: attr.GetName(),
|
||||
Namespace: attr.GetNamespace(),
|
||||
Operation: admissionv1.Operation(attr.GetOperation()),
|
||||
UserInfo: userInfo,
|
||||
Object: runtime.RawExtension{
|
||||
Object: attr.VersionedObject,
|
||||
Object: versionedAttributes.VersionedObject,
|
||||
},
|
||||
OldObject: runtime.RawExtension{
|
||||
Object: attr.VersionedOldObject,
|
||||
Object: versionedAttributes.VersionedOldObject,
|
||||
},
|
||||
DryRun: &dryRun,
|
||||
Options: runtime.RawExtension{
|
||||
Object: attr.GetOperationOptions(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// CreateV1beta1AdmissionReview creates an AdmissionReview for the provided admission.Attributes
|
||||
func CreateV1beta1AdmissionReview(uid types.UID, versionedAttributes *generic.VersionedAttributes, invocation *generic.WebhookInvocation) *admissionv1beta1.AdmissionReview {
|
||||
attr := versionedAttributes.Attributes
|
||||
gvk := invocation.Kind
|
||||
gvr := invocation.Resource
|
||||
subresource := invocation.Subresource
|
||||
requestGVK := attr.GetKind()
|
||||
requestGVR := attr.GetResource()
|
||||
requestSubResource := attr.GetSubresource()
|
||||
aUserInfo := attr.GetUserInfo()
|
||||
userInfo := authenticationv1.UserInfo{
|
||||
Extra: make(map[string]authenticationv1.ExtraValue),
|
||||
Groups: aUserInfo.GetGroups(),
|
||||
UID: aUserInfo.GetUID(),
|
||||
Username: aUserInfo.GetName(),
|
||||
}
|
||||
dryRun := attr.IsDryRun()
|
||||
|
||||
// Convert the extra information in the user object
|
||||
for key, val := range aUserInfo.GetExtra() {
|
||||
userInfo.Extra[key] = authenticationv1.ExtraValue(val)
|
||||
}
|
||||
|
||||
return &admissionv1beta1.AdmissionReview{
|
||||
Request: &admissionv1beta1.AdmissionRequest{
|
||||
UID: uid,
|
||||
Kind: metav1.GroupVersionKind{
|
||||
Group: gvk.Group,
|
||||
Kind: gvk.Kind,
|
||||
Version: gvk.Version,
|
||||
},
|
||||
Resource: metav1.GroupVersionResource{
|
||||
Group: gvr.Group,
|
||||
Resource: gvr.Resource,
|
||||
Version: gvr.Version,
|
||||
},
|
||||
SubResource: subresource,
|
||||
RequestKind: &metav1.GroupVersionKind{
|
||||
Group: requestGVK.Group,
|
||||
Kind: requestGVK.Kind,
|
||||
Version: requestGVK.Version,
|
||||
},
|
||||
RequestResource: &metav1.GroupVersionResource{
|
||||
Group: requestGVR.Group,
|
||||
Resource: requestGVR.Resource,
|
||||
Version: requestGVR.Version,
|
||||
},
|
||||
RequestSubResource: requestSubResource,
|
||||
Name: attr.GetName(),
|
||||
Namespace: attr.GetNamespace(),
|
||||
Operation: admissionv1beta1.Operation(attr.GetOperation()),
|
||||
UserInfo: userInfo,
|
||||
Object: runtime.RawExtension{
|
||||
Object: versionedAttributes.VersionedObject,
|
||||
},
|
||||
OldObject: runtime.RawExtension{
|
||||
Object: versionedAttributes.VersionedOldObject,
|
||||
},
|
||||
DryRun: &dryRun,
|
||||
Options: runtime.RawExtension{
|
||||
Object: attr.GetOperationOptions(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
24
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/rules/rules.go
generated
vendored
24
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/rules/rules.go
generated
vendored
@@ -20,6 +20,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"k8s.io/api/admissionregistration/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
)
|
||||
|
||||
@@ -31,7 +33,8 @@ type Matcher struct {
|
||||
|
||||
// Matches returns if the Attr matches the Rule.
|
||||
func (r *Matcher) Matches() bool {
|
||||
return r.operation() &&
|
||||
return r.scope() &&
|
||||
r.operation() &&
|
||||
r.group() &&
|
||||
r.version() &&
|
||||
r.resource()
|
||||
@@ -50,6 +53,25 @@ func exactOrWildcard(items []string, requested string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
var namespaceResource = schema.GroupVersionResource{Group: "", Version: "v1", Resource: "namespaces"}
|
||||
|
||||
func (r *Matcher) scope() bool {
|
||||
if r.Rule.Scope == nil || *r.Rule.Scope == v1beta1.AllScopes {
|
||||
return true
|
||||
}
|
||||
// attr.GetNamespace() is set to the name of the namespace for requests of the namespace object itself.
|
||||
switch *r.Rule.Scope {
|
||||
case v1beta1.NamespacedScope:
|
||||
// first make sure that we are not requesting a namespace object (namespace objects are cluster-scoped)
|
||||
return r.Attr.GetResource() != namespaceResource && r.Attr.GetNamespace() != metav1.NamespaceNone
|
||||
case v1beta1.ClusterScope:
|
||||
// also return true if the request is for a namespace object (namespace objects are cluster-scoped)
|
||||
return r.Attr.GetResource() == namespaceResource || r.Attr.GetNamespace() == metav1.NamespaceNone
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Matcher) group() bool {
|
||||
return exactOrWildcard(r.Rule.APIGroups, r.Attr.GetResource().Group)
|
||||
}
|
||||
|
||||
42
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/util/client_config.go
generated
vendored
42
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/util/client_config.go
generated
vendored
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes 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 util
|
||||
|
||||
import (
|
||||
"k8s.io/api/admissionregistration/v1beta1"
|
||||
"k8s.io/apiserver/pkg/util/webhook"
|
||||
)
|
||||
|
||||
// HookClientConfigForWebhook construct a webhook.ClientConfig using a v1beta1.Webhook API object.
|
||||
// webhook.ClientConfig is used to create a HookClient and the purpose of the config struct is to
|
||||
// share that with other packages that need to create a HookClient.
|
||||
func HookClientConfigForWebhook(w *v1beta1.Webhook) webhook.ClientConfig {
|
||||
ret := webhook.ClientConfig{Name: w.Name, CABundle: w.ClientConfig.CABundle}
|
||||
if w.ClientConfig.URL != nil {
|
||||
ret.URL = *w.ClientConfig.URL
|
||||
}
|
||||
if w.ClientConfig.Service != nil {
|
||||
ret.Service = &webhook.ClientConfigService{
|
||||
Name: w.ClientConfig.Service.Name,
|
||||
Namespace: w.ClientConfig.Service.Namespace,
|
||||
}
|
||||
if w.ClientConfig.Service.Path != nil {
|
||||
ret.Service.Path = *w.ClientConfig.Service.Path
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
164
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/dispatcher.go
generated
vendored
164
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/dispatcher.go
generated
vendored
@@ -22,47 +22,108 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"k8s.io/klog"
|
||||
|
||||
admissionv1beta1 "k8s.io/api/admission/v1beta1"
|
||||
"k8s.io/api/admissionregistration/v1beta1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
admissionmetrics "k8s.io/apiserver/pkg/admission/metrics"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook"
|
||||
webhookerrors "k8s.io/apiserver/pkg/admission/plugin/webhook/errors"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/request"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/util"
|
||||
"k8s.io/apiserver/pkg/util/webhook"
|
||||
webhookrequest "k8s.io/apiserver/pkg/admission/plugin/webhook/request"
|
||||
webhookutil "k8s.io/apiserver/pkg/util/webhook"
|
||||
"k8s.io/klog"
|
||||
utiltrace "k8s.io/utils/trace"
|
||||
)
|
||||
|
||||
type validatingDispatcher struct {
|
||||
cm *webhook.ClientManager
|
||||
cm *webhookutil.ClientManager
|
||||
plugin *Plugin
|
||||
}
|
||||
|
||||
func newValidatingDispatcher(cm *webhook.ClientManager) generic.Dispatcher {
|
||||
return &validatingDispatcher{cm}
|
||||
func newValidatingDispatcher(p *Plugin) func(cm *webhookutil.ClientManager) generic.Dispatcher {
|
||||
return func(cm *webhookutil.ClientManager) generic.Dispatcher {
|
||||
return &validatingDispatcher{cm, p}
|
||||
}
|
||||
}
|
||||
|
||||
var _ generic.Dispatcher = &validatingDispatcher{}
|
||||
|
||||
func (d *validatingDispatcher) Dispatch(ctx context.Context, attr *generic.VersionedAttributes, relevantHooks []*v1beta1.Webhook) error {
|
||||
func (d *validatingDispatcher) Dispatch(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces, hooks []webhook.WebhookAccessor) error {
|
||||
var relevantHooks []*generic.WebhookInvocation
|
||||
// Construct all the versions we need to call our webhooks
|
||||
versionedAttrs := map[schema.GroupVersionKind]*generic.VersionedAttributes{}
|
||||
for _, hook := range hooks {
|
||||
invocation, statusError := d.plugin.ShouldCallHook(hook, attr, o)
|
||||
if statusError != nil {
|
||||
return statusError
|
||||
}
|
||||
if invocation == nil {
|
||||
continue
|
||||
}
|
||||
relevantHooks = append(relevantHooks, invocation)
|
||||
// If we already have this version, continue
|
||||
if _, ok := versionedAttrs[invocation.Kind]; ok {
|
||||
continue
|
||||
}
|
||||
versionedAttr, err := generic.NewVersionedAttributes(attr, invocation.Kind, o)
|
||||
if err != nil {
|
||||
return apierrors.NewInternalError(err)
|
||||
}
|
||||
versionedAttrs[invocation.Kind] = versionedAttr
|
||||
}
|
||||
|
||||
if len(relevantHooks) == 0 {
|
||||
// no matching hooks
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if the request has already timed out before spawning remote calls
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// parent context is canceled or timed out, no point in continuing
|
||||
return apierrors.NewTimeoutError("request did not complete within requested timeout", 0)
|
||||
default:
|
||||
}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
errCh := make(chan error, len(relevantHooks))
|
||||
wg.Add(len(relevantHooks))
|
||||
for i := range relevantHooks {
|
||||
go func(hook *v1beta1.Webhook) {
|
||||
go func(invocation *generic.WebhookInvocation) {
|
||||
defer wg.Done()
|
||||
|
||||
hook, ok := invocation.Webhook.GetValidatingWebhook()
|
||||
if !ok {
|
||||
utilruntime.HandleError(fmt.Errorf("validating webhook dispatch requires v1beta1.ValidatingWebhook, but got %T", hook))
|
||||
return
|
||||
}
|
||||
versionedAttr := versionedAttrs[invocation.Kind]
|
||||
t := time.Now()
|
||||
err := d.callHook(ctx, hook, attr)
|
||||
admissionmetrics.Metrics.ObserveWebhook(time.Since(t), err != nil, attr.Attributes, "validating", hook.Name)
|
||||
err := d.callHook(ctx, hook, invocation, versionedAttr)
|
||||
ignoreClientCallFailures := hook.FailurePolicy != nil && *hook.FailurePolicy == v1beta1.Ignore
|
||||
rejected := false
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case *webhookutil.ErrCallingWebhook:
|
||||
if !ignoreClientCallFailures {
|
||||
rejected = true
|
||||
admissionmetrics.Metrics.ObserveWebhookRejection(hook.Name, "validating", string(versionedAttr.Attributes.GetOperation()), admissionmetrics.WebhookRejectionCallingWebhookError, 0)
|
||||
}
|
||||
case *webhookutil.ErrWebhookRejection:
|
||||
rejected = true
|
||||
admissionmetrics.Metrics.ObserveWebhookRejection(hook.Name, "validating", string(versionedAttr.Attributes.GetOperation()), admissionmetrics.WebhookRejectionNoError, int(err.Status.ErrStatus.Code))
|
||||
default:
|
||||
rejected = true
|
||||
admissionmetrics.Metrics.ObserveWebhookRejection(hook.Name, "validating", string(versionedAttr.Attributes.GetOperation()), admissionmetrics.WebhookRejectionAPIServerInternalError, 0)
|
||||
}
|
||||
}
|
||||
admissionmetrics.Metrics.ObserveWebhook(time.Since(t), rejected, versionedAttr.Attributes, "validating", hook.Name)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
ignoreClientCallFailures := hook.FailurePolicy != nil && *hook.FailurePolicy == v1beta1.Ignore
|
||||
if callErr, ok := err.(*webhook.ErrCallingWebhook); ok {
|
||||
if callErr, ok := err.(*webhookutil.ErrCallingWebhook); ok {
|
||||
if ignoreClientCallFailures {
|
||||
klog.Warningf("Failed calling webhook, failing open %v: %v", hook.Name, callErr)
|
||||
utilruntime.HandleError(callErr)
|
||||
@@ -74,6 +135,9 @@ func (d *validatingDispatcher) Dispatch(ctx context.Context, attr *generic.Versi
|
||||
return
|
||||
}
|
||||
|
||||
if rejectionErr, ok := err.(*webhookutil.ErrWebhookRejection); ok {
|
||||
err = rejectionErr.Status
|
||||
}
|
||||
klog.Warningf("rejected by webhook %q: %#v", hook.Name, err)
|
||||
errCh <- err
|
||||
}(relevantHooks[i])
|
||||
@@ -97,38 +161,74 @@ func (d *validatingDispatcher) Dispatch(ctx context.Context, attr *generic.Versi
|
||||
return errs[0]
|
||||
}
|
||||
|
||||
func (d *validatingDispatcher) callHook(ctx context.Context, h *v1beta1.Webhook, attr *generic.VersionedAttributes) error {
|
||||
if attr.IsDryRun() {
|
||||
func (d *validatingDispatcher) callHook(ctx context.Context, h *v1beta1.ValidatingWebhook, invocation *generic.WebhookInvocation, attr *generic.VersionedAttributes) error {
|
||||
if attr.Attributes.IsDryRun() {
|
||||
if h.SideEffects == nil {
|
||||
return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook SideEffects is nil")}
|
||||
return &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook SideEffects is nil")}
|
||||
}
|
||||
if !(*h.SideEffects == v1beta1.SideEffectClassNone || *h.SideEffects == v1beta1.SideEffectClassNoneOnDryRun) {
|
||||
return webhookerrors.NewDryRunUnsupportedErr(h.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// Make the webhook request
|
||||
request := request.CreateAdmissionReview(attr)
|
||||
client, err := d.cm.HookClient(util.HookClientConfigForWebhook(h))
|
||||
uid, request, response, err := webhookrequest.CreateAdmissionObjects(attr, invocation)
|
||||
if err != nil {
|
||||
return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
|
||||
return &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
|
||||
}
|
||||
response := &admissionv1beta1.AdmissionReview{}
|
||||
if err := client.Post().Context(ctx).Body(&request).Do().Into(response); err != nil {
|
||||
return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
|
||||
// Make the webhook request
|
||||
client, err := invocation.Webhook.GetRESTClient(d.cm)
|
||||
if err != nil {
|
||||
return &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
|
||||
}
|
||||
trace := utiltrace.New("Call validating webhook",
|
||||
utiltrace.Field{"configuration", invocation.Webhook.GetConfigurationName()},
|
||||
utiltrace.Field{"webhook", h.Name},
|
||||
utiltrace.Field{"resource", attr.GetResource()},
|
||||
utiltrace.Field{"subresource", attr.GetSubresource()},
|
||||
utiltrace.Field{"operation", attr.GetOperation()},
|
||||
utiltrace.Field{"UID", uid})
|
||||
defer trace.LogIfLong(500 * time.Millisecond)
|
||||
|
||||
// if the webhook has a specific timeout, wrap the context to apply it
|
||||
if h.TimeoutSeconds != nil {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ctx, time.Duration(*h.TimeoutSeconds)*time.Second)
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
if response.Response == nil {
|
||||
return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook response was absent")}
|
||||
r := client.Post().Context(ctx).Body(request)
|
||||
|
||||
// if the context has a deadline, set it as a parameter to inform the backend
|
||||
if deadline, hasDeadline := ctx.Deadline(); hasDeadline {
|
||||
// compute the timeout
|
||||
if timeout := time.Until(deadline); timeout > 0 {
|
||||
// if it's not an even number of seconds, round up to the nearest second
|
||||
if truncated := timeout.Truncate(time.Second); truncated != timeout {
|
||||
timeout = truncated + time.Second
|
||||
}
|
||||
// set the timeout
|
||||
r.Timeout(timeout)
|
||||
}
|
||||
}
|
||||
for k, v := range response.Response.AuditAnnotations {
|
||||
|
||||
if err := r.Do().Into(response); err != nil {
|
||||
return &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
|
||||
}
|
||||
trace.Step("Request completed")
|
||||
|
||||
result, err := webhookrequest.VerifyAdmissionResponse(uid, false, response)
|
||||
if err != nil {
|
||||
return &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
|
||||
}
|
||||
|
||||
for k, v := range result.AuditAnnotations {
|
||||
key := h.Name + "/" + k
|
||||
if err := attr.AddAnnotation(key, v); err != nil {
|
||||
if err := attr.Attributes.AddAnnotation(key, v); err != nil {
|
||||
klog.Warningf("Failed to set admission audit annotation %s to %s for validating webhook %s: %v", key, v, h.Name, err)
|
||||
}
|
||||
}
|
||||
if response.Response.Allowed {
|
||||
if result.Allowed {
|
||||
return nil
|
||||
}
|
||||
return webhookerrors.ToStatusErr(h.Name, response.Response.Result)
|
||||
return &webhookutil.ErrWebhookRejection{Status: webhookerrors.ToStatusErr(h.Name, result.Result)}
|
||||
}
|
||||
|
||||
11
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/plugin.go
generated
vendored
11
vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/plugin.go
generated
vendored
@@ -17,6 +17,7 @@ limitations under the License.
|
||||
package validating
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
@@ -51,14 +52,16 @@ var _ admission.ValidationInterface = &Plugin{}
|
||||
// NewValidatingAdmissionWebhook returns a generic admission webhook plugin.
|
||||
func NewValidatingAdmissionWebhook(configFile io.Reader) (*Plugin, error) {
|
||||
handler := admission.NewHandler(admission.Connect, admission.Create, admission.Delete, admission.Update)
|
||||
webhook, err := generic.NewWebhook(handler, configFile, configuration.NewValidatingWebhookConfigurationManager, newValidatingDispatcher)
|
||||
p := &Plugin{}
|
||||
var err error
|
||||
p.Webhook, err = generic.NewWebhook(handler, configFile, configuration.NewValidatingWebhookConfigurationManager, newValidatingDispatcher(p))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Plugin{webhook}, nil
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// Validate makes an admission decision based on the request attributes.
|
||||
func (a *Plugin) Validate(attr admission.Attributes) error {
|
||||
return a.Webhook.Dispatch(attr)
|
||||
func (a *Plugin) Validate(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces) error {
|
||||
return a.Webhook.Dispatch(ctx, attr, o)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user