Update to kube v1.17

Signed-off-by: Humble Chirammal <hchiramm@redhat.com>
This commit is contained in:
Humble Chirammal
2020-01-14 16:08:55 +05:30
committed by mergify[bot]
parent 327fcd1b1b
commit 3af1e26d7c
1710 changed files with 289562 additions and 168638 deletions

View 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/v1"
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() v1.WebhookClientConfig
// GetRules gets the webhook Rules field.
GetRules() []v1.RuleWithOperations
// GetFailurePolicy gets the webhook FailurePolicy field.
GetFailurePolicy() *v1.FailurePolicyType
// GetMatchPolicy gets the webhook MatchPolicy field.
GetMatchPolicy() *v1.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() *v1.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() (*v1.MutatingWebhook, bool)
// GetValidatingWebhook if the accessor contains a ValidatingWebhook, returns it and true, else returns false.
GetValidatingWebhook() (*v1.ValidatingWebhook, bool)
}
// NewMutatingWebhookAccessor creates an accessor for a MutatingWebhook.
func NewMutatingWebhookAccessor(uid, configurationName string, h *v1.MutatingWebhook) WebhookAccessor {
return &mutatingWebhookAccessor{uid: uid, configurationName: configurationName, MutatingWebhook: h}
}
type mutatingWebhookAccessor struct {
*v1.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() v1.WebhookClientConfig {
return m.ClientConfig
}
func (m *mutatingWebhookAccessor) GetRules() []v1.RuleWithOperations {
return m.Rules
}
func (m *mutatingWebhookAccessor) GetFailurePolicy() *v1.FailurePolicyType {
return m.FailurePolicy
}
func (m *mutatingWebhookAccessor) GetMatchPolicy() *v1.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() *v1.SideEffectClass {
return m.SideEffects
}
func (m *mutatingWebhookAccessor) GetTimeoutSeconds() *int32 {
return m.TimeoutSeconds
}
func (m *mutatingWebhookAccessor) GetAdmissionReviewVersions() []string {
return m.AdmissionReviewVersions
}
func (m *mutatingWebhookAccessor) GetMutatingWebhook() (*v1.MutatingWebhook, bool) {
return m.MutatingWebhook, true
}
func (m *mutatingWebhookAccessor) GetValidatingWebhook() (*v1.ValidatingWebhook, bool) {
return nil, false
}
// NewValidatingWebhookAccessor creates an accessor for a ValidatingWebhook.
func NewValidatingWebhookAccessor(uid, configurationName string, h *v1.ValidatingWebhook) WebhookAccessor {
return &validatingWebhookAccessor{uid: uid, configurationName: configurationName, ValidatingWebhook: h}
}
type validatingWebhookAccessor struct {
*v1.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() v1.WebhookClientConfig {
return v.ClientConfig
}
func (v *validatingWebhookAccessor) GetRules() []v1.RuleWithOperations {
return v.Rules
}
func (v *validatingWebhookAccessor) GetFailurePolicy() *v1.FailurePolicyType {
return v.FailurePolicy
}
func (v *validatingWebhookAccessor) GetMatchPolicy() *v1.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() *v1.SideEffectClass {
return v.SideEffects
}
func (v *validatingWebhookAccessor) GetTimeoutSeconds() *int32 {
return v.TimeoutSeconds
}
func (v *validatingWebhookAccessor) GetAdmissionReviewVersions() []string {
return v.AdmissionReviewVersions
}
func (v *validatingWebhookAccessor) GetMutatingWebhook() (*v1.MutatingWebhook, bool) {
return nil, false
}
func (v *validatingWebhookAccessor) GetValidatingWebhook() (*v1.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
}

View File

@ -0,0 +1,19 @@
/*
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.
*/
// +k8s:deepcopy-gen=package
package webhookadmission

View File

@ -0,0 +1,53 @@
/*
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 webhookadmission
import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
var (
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
AddToScheme = SchemeBuilder.AddToScheme
)
// GroupName is the group name use in this package
const GroupName = "apiserver.config.k8s.io"
// SchemeGroupVersion is group version used to register these objects
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal}
// Kind takes an unqualified kind and returns a Group qualified GroupKind
func Kind(kind string) schema.GroupKind {
return SchemeGroupVersion.WithKind(kind).GroupKind()
}
// Resource takes an unqualified resource and returns a Group qualified GroupResource
func Resource(resource string) schema.GroupResource {
return SchemeGroupVersion.WithResource(resource).GroupResource()
}
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&WebhookAdmission{},
)
scheme.AddKnownTypeWithName(SchemeGroupVersion.WithKind("WebhookAdmissionConfiguration"),
&WebhookAdmission{},
)
return nil
}

View File

@ -0,0 +1,29 @@
/*
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 webhookadmission
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// WebhookAdmission provides configuration for the webhook admission controller.
type WebhookAdmission struct {
metav1.TypeMeta
// KubeConfigFile is the path to the kubeconfig file.
KubeConfigFile string
}

View File

@ -0,0 +1,23 @@
/*
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.
*/
// +k8s:deepcopy-gen=package
// +k8s:conversion-gen=k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission
// +k8s:defaulter-gen=TypeMeta
// +groupName=apiserver.config.k8s.io
// Package v1 is the v1 version of the API.
package v1

View File

@ -0,0 +1,50 @@
/*
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 v1
import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// GroupName is the group name use in this package
const GroupName = "apiserver.config.k8s.io"
// SchemeGroupVersion is group version used to register these objects
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1"}
var (
// TODO: move SchemeBuilder with zz_generated.deepcopy.go to k8s.io/api.
// localSchemeBuilder and AddToScheme will stay in k8s.io/kubernetes.
SchemeBuilder runtime.SchemeBuilder
localSchemeBuilder = &SchemeBuilder
AddToScheme = localSchemeBuilder.AddToScheme
)
func init() {
// We only register manually written functions here. The registration of the
// generated functions takes place in the generated files. The separation
// makes the code compile even when the generated files are missing.
localSchemeBuilder.Register(addKnownTypes)
}
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypeWithName(SchemeGroupVersion.WithKind("WebhookAdmissionConfiguration"),
&WebhookAdmission{},
)
return nil
}

View File

@ -0,0 +1,29 @@
/*
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 v1
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// WebhookAdmission provides configuration for the webhook admission controller.
type WebhookAdmission struct {
metav1.TypeMeta `json:",inline"`
// KubeConfigFile is the path to the kubeconfig file.
KubeConfigFile string `json:"kubeConfigFile"`
}

View File

@ -0,0 +1,67 @@
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by conversion-gen. DO NOT EDIT.
package v1
import (
conversion "k8s.io/apimachinery/pkg/conversion"
runtime "k8s.io/apimachinery/pkg/runtime"
webhookadmission "k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission"
)
func init() {
localSchemeBuilder.Register(RegisterConversions)
}
// RegisterConversions adds conversion functions to the given scheme.
// Public to allow building arbitrary schemes.
func RegisterConversions(s *runtime.Scheme) error {
if err := s.AddGeneratedConversionFunc((*WebhookAdmission)(nil), (*webhookadmission.WebhookAdmission)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1_WebhookAdmission_To_webhookadmission_WebhookAdmission(a.(*WebhookAdmission), b.(*webhookadmission.WebhookAdmission), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*webhookadmission.WebhookAdmission)(nil), (*WebhookAdmission)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_webhookadmission_WebhookAdmission_To_v1_WebhookAdmission(a.(*webhookadmission.WebhookAdmission), b.(*WebhookAdmission), scope)
}); err != nil {
return err
}
return nil
}
func autoConvert_v1_WebhookAdmission_To_webhookadmission_WebhookAdmission(in *WebhookAdmission, out *webhookadmission.WebhookAdmission, s conversion.Scope) error {
out.KubeConfigFile = in.KubeConfigFile
return nil
}
// Convert_v1_WebhookAdmission_To_webhookadmission_WebhookAdmission is an autogenerated conversion function.
func Convert_v1_WebhookAdmission_To_webhookadmission_WebhookAdmission(in *WebhookAdmission, out *webhookadmission.WebhookAdmission, s conversion.Scope) error {
return autoConvert_v1_WebhookAdmission_To_webhookadmission_WebhookAdmission(in, out, s)
}
func autoConvert_webhookadmission_WebhookAdmission_To_v1_WebhookAdmission(in *webhookadmission.WebhookAdmission, out *WebhookAdmission, s conversion.Scope) error {
out.KubeConfigFile = in.KubeConfigFile
return nil
}
// Convert_webhookadmission_WebhookAdmission_To_v1_WebhookAdmission is an autogenerated conversion function.
func Convert_webhookadmission_WebhookAdmission_To_v1_WebhookAdmission(in *webhookadmission.WebhookAdmission, out *WebhookAdmission, s conversion.Scope) error {
return autoConvert_webhookadmission_WebhookAdmission_To_v1_WebhookAdmission(in, out, s)
}

View File

@ -0,0 +1,50 @@
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by deepcopy-gen. DO NOT EDIT.
package v1
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *WebhookAdmission) DeepCopyInto(out *WebhookAdmission) {
*out = *in
out.TypeMeta = in.TypeMeta
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebhookAdmission.
func (in *WebhookAdmission) DeepCopy() *WebhookAdmission {
if in == nil {
return nil
}
out := new(WebhookAdmission)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *WebhookAdmission) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}

View File

@ -0,0 +1,32 @@
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by defaulter-gen. DO NOT EDIT.
package v1
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// RegisterDefaults adds defaulters functions to the given scheme.
// Public to allow building arbitrary schemes.
// All generated defaulters are covering - they call all nested defaulters.
func RegisterDefaults(scheme *runtime.Scheme) error {
return nil
}

View File

@ -0,0 +1,23 @@
/*
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.
*/
// +k8s:deepcopy-gen=package
// +k8s:conversion-gen=k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission
// +k8s:defaulter-gen=TypeMeta
// +groupName=apiserver.config.k8s.io
// Package v1alpha1 is the v1alpha1 version of the API.
package v1alpha1

View File

@ -0,0 +1,50 @@
/*
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 v1alpha1
import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// GroupName is the group name use in this package
const GroupName = "apiserver.config.k8s.io"
// SchemeGroupVersion is group version used to register these objects
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"}
var (
// TODO: move SchemeBuilder with zz_generated.deepcopy.go to k8s.io/api.
// localSchemeBuilder and AddToScheme will stay in k8s.io/kubernetes.
SchemeBuilder runtime.SchemeBuilder
localSchemeBuilder = &SchemeBuilder
AddToScheme = localSchemeBuilder.AddToScheme
)
func init() {
// We only register manually written functions here. The registration of the
// generated functions takes place in the generated files. The separation
// makes the code compile even when the generated files are missing.
localSchemeBuilder.Register(addKnownTypes)
}
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&WebhookAdmission{},
)
return nil
}

View File

@ -0,0 +1,29 @@
/*
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 v1alpha1
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// WebhookAdmission provides configuration for the webhook admission controller.
type WebhookAdmission struct {
metav1.TypeMeta `json:",inline"`
// KubeConfigFile is the path to the kubeconfig file.
KubeConfigFile string `json:"kubeConfigFile"`
}

View File

@ -0,0 +1,67 @@
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by conversion-gen. DO NOT EDIT.
package v1alpha1
import (
conversion "k8s.io/apimachinery/pkg/conversion"
runtime "k8s.io/apimachinery/pkg/runtime"
webhookadmission "k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission"
)
func init() {
localSchemeBuilder.Register(RegisterConversions)
}
// RegisterConversions adds conversion functions to the given scheme.
// Public to allow building arbitrary schemes.
func RegisterConversions(s *runtime.Scheme) error {
if err := s.AddGeneratedConversionFunc((*WebhookAdmission)(nil), (*webhookadmission.WebhookAdmission)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1alpha1_WebhookAdmission_To_webhookadmission_WebhookAdmission(a.(*WebhookAdmission), b.(*webhookadmission.WebhookAdmission), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*webhookadmission.WebhookAdmission)(nil), (*WebhookAdmission)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_webhookadmission_WebhookAdmission_To_v1alpha1_WebhookAdmission(a.(*webhookadmission.WebhookAdmission), b.(*WebhookAdmission), scope)
}); err != nil {
return err
}
return nil
}
func autoConvert_v1alpha1_WebhookAdmission_To_webhookadmission_WebhookAdmission(in *WebhookAdmission, out *webhookadmission.WebhookAdmission, s conversion.Scope) error {
out.KubeConfigFile = in.KubeConfigFile
return nil
}
// Convert_v1alpha1_WebhookAdmission_To_webhookadmission_WebhookAdmission is an autogenerated conversion function.
func Convert_v1alpha1_WebhookAdmission_To_webhookadmission_WebhookAdmission(in *WebhookAdmission, out *webhookadmission.WebhookAdmission, s conversion.Scope) error {
return autoConvert_v1alpha1_WebhookAdmission_To_webhookadmission_WebhookAdmission(in, out, s)
}
func autoConvert_webhookadmission_WebhookAdmission_To_v1alpha1_WebhookAdmission(in *webhookadmission.WebhookAdmission, out *WebhookAdmission, s conversion.Scope) error {
out.KubeConfigFile = in.KubeConfigFile
return nil
}
// Convert_webhookadmission_WebhookAdmission_To_v1alpha1_WebhookAdmission is an autogenerated conversion function.
func Convert_webhookadmission_WebhookAdmission_To_v1alpha1_WebhookAdmission(in *webhookadmission.WebhookAdmission, out *WebhookAdmission, s conversion.Scope) error {
return autoConvert_webhookadmission_WebhookAdmission_To_v1alpha1_WebhookAdmission(in, out, s)
}

View File

@ -0,0 +1,50 @@
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by deepcopy-gen. DO NOT EDIT.
package v1alpha1
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *WebhookAdmission) DeepCopyInto(out *WebhookAdmission) {
*out = *in
out.TypeMeta = in.TypeMeta
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebhookAdmission.
func (in *WebhookAdmission) DeepCopy() *WebhookAdmission {
if in == nil {
return nil
}
out := new(WebhookAdmission)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *WebhookAdmission) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}

View File

@ -0,0 +1,32 @@
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by defaulter-gen. DO NOT EDIT.
package v1alpha1
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// RegisterDefaults adds defaulters functions to the given scheme.
// Public to allow building arbitrary schemes.
// All generated defaulters are covering - they call all nested defaulters.
func RegisterDefaults(scheme *runtime.Scheme) error {
return nil
}

View File

@ -0,0 +1,50 @@
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by deepcopy-gen. DO NOT EDIT.
package webhookadmission
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *WebhookAdmission) DeepCopyInto(out *WebhookAdmission) {
*out = *in
out.TypeMeta = in.TypeMeta
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebhookAdmission.
func (in *WebhookAdmission) DeepCopy() *WebhookAdmission {
if in == nil {
return nil
}
out := new(WebhookAdmission)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *WebhookAdmission) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}

View File

@ -0,0 +1,71 @@
/*
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 config
import (
"fmt"
"io"
"io/ioutil"
"path"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission"
"k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1"
"k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1"
)
var (
scheme = runtime.NewScheme()
codecs = serializer.NewCodecFactory(scheme)
)
func init() {
utilruntime.Must(webhookadmission.AddToScheme(scheme))
utilruntime.Must(v1.AddToScheme(scheme))
utilruntime.Must(v1alpha1.AddToScheme(scheme))
}
// LoadConfig extract the KubeConfigFile from configFile
func LoadConfig(configFile io.Reader) (string, error) {
var kubeconfigFile string
if configFile != nil {
// we have a config so parse it.
data, err := ioutil.ReadAll(configFile)
if err != nil {
return "", err
}
decoder := codecs.UniversalDecoder()
decodedObj, err := runtime.Decode(decoder, data)
if err != nil {
return "", err
}
config, ok := decodedObj.(*webhookadmission.WebhookAdmission)
if !ok {
return "", fmt.Errorf("unexpected type: %T", decodedObj)
}
if !path.IsAbs(config.KubeConfigFile) {
return "", field.Invalid(field.NewPath("kubeConfigFile"), config.KubeConfigFile, "must be an absolute file path")
}
kubeconfigFile = config.KubeConfigFile
}
return kubeconfigFile, nil
}

View File

@ -0,0 +1,18 @@
/*
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 errors contains utilities for admission webhook specific errors
package errors // import "k8s.io/apiserver/pkg/admission/plugin/webhook/errors"

View File

@ -0,0 +1,63 @@
/*
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 errors
import (
"fmt"
"net/http"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// ToStatusErr returns a StatusError with information about the webhook plugin
func ToStatusErr(webhookName string, result *metav1.Status) *apierrors.StatusError {
deniedBy := fmt.Sprintf("admission webhook %q denied the request", webhookName)
const noExp = "without explanation"
if result == nil {
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)
case len(result.Reason) > 0:
result.Message = fmt.Sprintf("%s: %s", deniedBy, result.Reason)
default:
result.Message = fmt.Sprintf("%s %s", deniedBy, noExp)
}
return &apierrors.StatusError{
ErrStatus: *result,
}
}
// NewDryRunUnsupportedErr returns a StatusError with information about the webhook plugin
func NewDryRunUnsupportedErr(webhookName string) *apierrors.StatusError {
reason := fmt.Sprintf("admission webhook %q does not support dry run", webhookName)
return apierrors.NewBadRequest(reason)
}

View File

@ -0,0 +1,112 @@
/*
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 generic
import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/admission"
)
// ConvertToGVK converts object to the desired gvk.
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 := o.GetObjectCreater().New(gvk)
if err != nil {
return nil, err
}
err = o.GetObjectConvertor().Convert(obj, out, nil)
if err != nil {
return nil, err
}
// Explicitly set the GVK
out.GetObjectKind().SetGroupVersionKind(gvk)
return out, nil
}
// 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
}

View File

@ -0,0 +1,75 @@
/*
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 generic
import (
"context"
"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() []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 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. 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
}

View File

@ -0,0 +1,223 @@
/*
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 generic
import (
"context"
"fmt"
"io"
admissionv1 "k8s.io/api/admission/v1"
admissionv1beta1 "k8s.io/api/admission/v1beta1"
"k8s.io/api/admissionregistration/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"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"
webhookutil "k8s.io/apiserver/pkg/util/webhook"
"k8s.io/client-go/informers"
clientset "k8s.io/client-go/kubernetes"
)
// Webhook is an abstract admission plugin with all the infrastructure to define Admit or Validate on-top.
type Webhook struct {
*admission.Handler
sourceFactory sourceFactory
hookSource Source
clientManager *webhookutil.ClientManager
namespaceMatcher *namespace.Matcher
objectMatcher *object.Matcher
dispatcher Dispatcher
}
var (
_ genericadmissioninit.WantsExternalKubeClientSet = &Webhook{}
_ admission.Interface = &Webhook{}
)
type sourceFactory func(f informers.SharedInformerFactory) Source
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) {
kubeconfigFile, err := config.LoadConfig(configFile)
if err != nil {
return nil, err
}
cm, err := webhookutil.NewClientManager(
[]schema.GroupVersion{
admissionv1beta1.SchemeGroupVersion,
admissionv1.SchemeGroupVersion,
},
admissionv1beta1.AddToScheme,
admissionv1.AddToScheme,
)
if err != nil {
return nil, err
}
authInfoResolver, err := webhookutil.NewDefaultAuthenticationInfoResolver(kubeconfigFile)
if err != nil {
return nil, err
}
// Set defaults which may be overridden later.
cm.SetAuthenticationInfoResolver(authInfoResolver)
cm.SetServiceResolver(webhookutil.NewDefaultServiceResolver())
return &Webhook{
Handler: handler,
sourceFactory: sourceFactory,
clientManager: &cm,
namespaceMatcher: &namespace.Matcher{},
objectMatcher: &object.Matcher{},
dispatcher: dispatcherFactory(&cm),
}, nil
}
// SetAuthenticationInfoResolverWrapper sets the
// AuthenticationInfoResolverWrapper.
// TODO find a better way wire this, but keep this pull small for now.
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 webhookutil.ServiceResolver) {
a.clientManager.SetServiceResolver(sr)
}
// SetExternalKubeClientSet implements the WantsExternalKubeInformerFactory interface.
// It sets external ClientSet for admission plugins that need it
func (a *Webhook) SetExternalKubeClientSet(client clientset.Interface) {
a.namespaceMatcher.Client = client
}
// SetExternalKubeInformerFactory implements the WantsExternalKubeInformerFactory interface.
func (a *Webhook) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) {
namespaceInformer := f.Core().V1().Namespaces()
a.namespaceMatcher.NamespaceLister = namespaceInformer.Lister()
a.hookSource = a.sourceFactory(f)
a.SetReadyFunc(func() bool {
return namespaceInformer.Informer().HasSynced() && a.hookSource.HasSynced()
})
}
// ValidateInitialization implements the InitializationValidator interface.
func (a *Webhook) ValidateInitialization() error {
if a.hookSource == nil {
return fmt.Errorf("kubernetes client is not properly setup")
}
if err := a.namespaceMatcher.Validate(); err != nil {
return fmt.Errorf("namespaceMatcher is not properly setup: %v", err)
}
if err := a.clientManager.Validate(); err != nil {
return fmt.Errorf("clientManager is not properly setup: %v", err)
}
return nil
}
// 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() {
invocation = &WebhookInvocation{
Webhook: h,
Resource: attr.GetResource(),
Subresource: attr.GetSubresource(),
Kind: attr.GetKind(),
}
break
}
}
if invocation == nil && h.GetMatchPolicy() != nil && *h.GetMatchPolicy() == v1.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
}
}
}
}
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(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces) error {
if rules.IsWebhookConfigurationResource(attr) {
return nil
}
if !a.WaitForReady() {
return admission.NewForbidden(attr, fmt.Errorf("not yet ready to handle request"))
}
hooks := a.hookSource.Webhooks()
return a.dispatcher.Dispatch(ctx, attr, o, hooks)
}

View File

@ -0,0 +1,425 @@
/*
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 mutating delegates admission checks to dynamically configured
// mutating webhooks.
package mutating
import (
"context"
"fmt"
"time"
jsonpatch "github.com/evanphx/json-patch"
apiequality "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/klog"
admissionv1 "k8s.io/api/admission/v1"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
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"
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 *webhookutil.ClientManager
plugin *Plugin
}
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 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 v1.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()
round := 0
if reinvokeCtx.IsReinvoke() {
round = 1
}
changed, err := a.callAttrMutatingHook(ctx, hook, invocation, versionedAttr, o, round, i)
ignoreClientCallFailures := hook.FailurePolicy != nil && *hook.FailurePolicy == admissionregistrationv1.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 == admissionregistrationv1.IfNeededReinvocationPolicy {
webhookReinvokeCtx.AddReinvocableWebhookToPreviouslyInvoked(invocation.Webhook.GetUID())
}
if err == nil {
continue
}
if callErr, ok := err.(*webhookutil.ErrCallingWebhook); ok {
if ignoreClientCallFailures {
klog.Warningf("Failed calling webhook, failing open %v: %v", hook.Name, callErr)
utilruntime.HandleError(callErr)
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)
}
if rejectionErr, ok := err.(*webhookutil.ErrWebhookRejection); ok {
return rejectionErr.Status
}
return err
}
// 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 *admissionregistrationv1.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 false, &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook SideEffects is nil")}
}
if !(*h.SideEffects == admissionregistrationv1.SideEffectClassNone || *h.SideEffects == admissionregistrationv1.SideEffectClassNoneOnDryRun) {
return false, webhookerrors.NewDryRunUnsupportedErr(h.Name)
}
}
uid, request, response, err := webhookrequest.CreateAdmissionObjects(attr, invocation)
if err != nil {
return false, &webhookutil.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()
}
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)
}
}
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.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 !result.Allowed {
return false, &webhookutil.ErrWebhookRejection{Status: webhookerrors.ToStatusErr(h.Name, result.Result)}
}
if len(result.Patch) == 0 {
return false, nil
}
patchObj, err := jsonpatch.DecodePatch(result.Patch)
if err != nil {
return false, apierrors.NewInternalError(err)
}
if len(patchObj) == 0 {
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 false, apierrors.NewInternalError(fmt.Errorf("admission webhook %q attempted to modify the object, which is not supported for this operation", h.Name))
}
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
if _, ok := attr.VersionedObject.(*unstructured.Unstructured); ok {
// Custom Resources don't have corresponding Go struct's.
// They are represented as Unstructured.
newVersionedObject = &unstructured.Unstructured{}
} else {
newVersionedObject, err = o.GetObjectCreater().New(attr.VersionedKind)
if err != nil {
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 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
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
}

View File

@ -0,0 +1,19 @@
/*
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 mutating makes calls to mutating webhooks during the admission
// process.
package mutating // import "k8s.io/apiserver/pkg/admission/plugin/webhook/mutating"

View File

@ -0,0 +1,76 @@
/*
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 mutating
import (
"context"
"io"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/admission/configuration"
"k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
)
const (
// PluginName indicates the name of admission plug-in
PluginName = "MutatingAdmissionWebhook"
)
// Register registers a plugin
func Register(plugins *admission.Plugins) {
plugins.Register(PluginName, func(configFile io.Reader) (admission.Interface, error) {
plugin, err := NewMutatingWebhook(configFile)
if err != nil {
return nil, err
}
return plugin, nil
})
}
// Plugin is an implementation of admission.Interface.
type Plugin struct {
*generic.Webhook
}
var _ admission.MutationInterface = &Plugin{}
// NewMutatingWebhook returns a generic admission webhook plugin.
func NewMutatingWebhook(configFile io.Reader) (*Plugin, error) {
handler := admission.NewHandler(admission.Connect, admission.Create, admission.Delete, admission.Update)
p := &Plugin{}
var err error
p.Webhook, err = generic.NewWebhook(handler, configFile, configuration.NewMutatingWebhookConfigurationManager, newMutatingDispatcher(p))
if err != nil {
return nil, err
}
return p, nil
}
// ValidateInitialization implements the InitializationValidator interface.
func (a *Plugin) ValidateInitialization() error {
if err := a.Webhook.ValidateInitialization(); err != nil {
return err
}
return nil
}
// Admit makes an admission decision based on the request attributes.
func (a *Plugin) Admit(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces) error {
return a.Webhook.Dispatch(ctx, attr, o)
}

View 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()
}
}

View File

@ -0,0 +1,20 @@
/*
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 namespace defines the utilities that are used by the webhook
// plugin to decide if a webhook should be applied to an object based on its
// namespace.
package namespace // import "k8s.io/apiserver/pkg/admission/plugin/webhook/namespace"

View File

@ -0,0 +1,120 @@
/*
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 namespace
import (
"fmt"
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"
)
// Matcher decides if a request is exempted by the NamespaceSelector of a
// webhook configuration.
type Matcher struct {
NamespaceLister corelisters.NamespaceLister
Client clientset.Interface
}
// Validate checks if the Matcher has a NamespaceLister and Client.
func (m *Matcher) Validate() error {
var errs []error
if m.NamespaceLister == nil {
errs = append(errs, fmt.Errorf("the namespace matcher requires a namespaceLister"))
}
if m.Client == nil {
errs = append(errs, fmt.Errorf("the namespace matcher requires a namespaceLister"))
}
return utilerrors.NewAggregate(errs)
}
// GetNamespaceLabels gets the labels of the namespace related to the attr.
func (m *Matcher) GetNamespaceLabels(attr admission.Attributes) (map[string]string, error) {
// If the request itself is creating or updating a namespace, then get the
// labels from attr.Object, because namespaceLister doesn't have the latest
// namespace yet.
//
// However, if the request is deleting a namespace, then get the label from
// the namespace in the namespaceLister, because a delete request is not
// going to change the object, and attr.Object will be a DeleteOptions
// rather than a namespace object.
if attr.GetResource().Resource == "namespaces" &&
len(attr.GetSubresource()) == 0 &&
(attr.GetOperation() == admission.Create || attr.GetOperation() == admission.Update) {
accessor, err := meta.Accessor(attr.GetObject())
if err != nil {
return nil, err
}
return accessor.GetLabels(), nil
}
namespaceName := attr.GetNamespace()
namespace, err := m.NamespaceLister.Get(namespaceName)
if err != nil && !apierrors.IsNotFound(err) {
return nil, err
}
if apierrors.IsNotFound(err) {
// in case of latency in our caches, make a call direct to storage to verify that it truly exists or not
namespace, err = m.Client.CoreV1().Namespaces().Get(namespaceName, metav1.GetOptions{})
if err != nil {
return nil, err
}
}
return namespace.Labels, nil
}
// MatchNamespaceSelector decideds whether the request matches the
// namespaceSelctor of the webhook. Only when they match, the webhook is called.
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
// namespace, it is never exempted.
// TODO: figure out a way selective exempt cluster scoped resources.
// 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
if apierrors.IsNotFound(err) {
status, ok := err.(apierrors.APIStatus)
if !ok {
return false, apierrors.NewInternalError(err)
}
return false, &apierrors.StatusError{status.Status()}
}
if err != nil {
return false, apierrors.NewInternalError(err)
}
return selector.Matches(labels.Set(namespaceLabels)), nil
}

View 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"

View 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
}

View File

@ -0,0 +1,280 @@
/*
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 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"
)
// 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),
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 &admissionv1.AdmissionReview{
Request: &admissionv1.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: admissionv1.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(),
},
},
}
}
// 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(),
},
},
}
}

View File

@ -0,0 +1,18 @@
/*
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 request creates admissionReview request based on admission attributes.
package request // import "k8s.io/apiserver/pkg/admission/plugin/webhook/request"

View File

@ -0,0 +1,129 @@
/*
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 rules
import (
"strings"
"k8s.io/api/admissionregistration/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/admission"
)
// Matcher determines if the Attr matches the Rule.
type Matcher struct {
Rule v1.RuleWithOperations
Attr admission.Attributes
}
// Matches returns if the Attr matches the Rule.
func (r *Matcher) Matches() bool {
return r.scope() &&
r.operation() &&
r.group() &&
r.version() &&
r.resource()
}
func exactOrWildcard(items []string, requested string) bool {
for _, item := range items {
if item == "*" {
return true
}
if item == requested {
return true
}
}
return false
}
var namespaceResource = schema.GroupVersionResource{Group: "", Version: "v1", Resource: "namespaces"}
func (r *Matcher) scope() bool {
if r.Rule.Scope == nil || *r.Rule.Scope == v1.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 v1.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 v1.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)
}
func (r *Matcher) version() bool {
return exactOrWildcard(r.Rule.APIVersions, r.Attr.GetResource().Version)
}
func (r *Matcher) operation() bool {
attrOp := r.Attr.GetOperation()
for _, op := range r.Rule.Operations {
if op == v1.OperationAll {
return true
}
// The constants are the same such that this is a valid cast (and this
// is tested).
if op == v1.OperationType(attrOp) {
return true
}
}
return false
}
func splitResource(resSub string) (res, sub string) {
parts := strings.SplitN(resSub, "/", 2)
if len(parts) == 2 {
return parts[0], parts[1]
}
return parts[0], ""
}
func (r *Matcher) resource() bool {
opRes, opSub := r.Attr.GetResource().Resource, r.Attr.GetSubresource()
for _, res := range r.Rule.Resources {
res, sub := splitResource(res)
resMatch := res == "*" || res == opRes
subMatch := sub == "*" || sub == opSub
if resMatch && subMatch {
return true
}
}
return false
}
// IsWebhookConfigurationResource determines if an admission.Attributes object is describing
// the admission of a ValidatingWebhookConfiguration or a MutatingWebhookConfiguration
func IsWebhookConfigurationResource(attr admission.Attributes) bool {
gvk := attr.GetKind()
if gvk.Group == "admissionregistration.k8s.io" {
if gvk.Kind == "ValidatingWebhookConfiguration" || gvk.Kind == "MutatingWebhookConfiguration" {
return true
}
}
return false
}