feat: add crd gen

Signed-off-by: runzexia <runzexia@yunify.com>
This commit is contained in:
runzexia
2019-07-30 13:36:54 +08:00
parent 9ce52aeb00
commit 1c10391faf
817 changed files with 203429 additions and 27953 deletions

146
vendor/k8s.io/apiserver/pkg/admission/attributes.go generated vendored Normal file
View File

@@ -0,0 +1,146 @@
/*
Copyright 2014 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 admission
import (
"fmt"
"strings"
"sync"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/validation"
"k8s.io/apiserver/pkg/authentication/user"
)
type attributesRecord struct {
kind schema.GroupVersionKind
namespace string
name string
resource schema.GroupVersionResource
subresource string
operation Operation
dryRun bool
object runtime.Object
oldObject runtime.Object
userInfo user.Info
// other elements are always accessed in single goroutine.
// But ValidatingAdmissionWebhook add annotations concurrently.
annotations map[string]string
annotationsLock sync.RWMutex
}
func NewAttributesRecord(object runtime.Object, oldObject runtime.Object, kind schema.GroupVersionKind, namespace, name string, resource schema.GroupVersionResource, subresource string, operation Operation, dryRun bool, userInfo user.Info) Attributes {
return &attributesRecord{
kind: kind,
namespace: namespace,
name: name,
resource: resource,
subresource: subresource,
operation: operation,
dryRun: dryRun,
object: object,
oldObject: oldObject,
userInfo: userInfo,
}
}
func (record *attributesRecord) GetKind() schema.GroupVersionKind {
return record.kind
}
func (record *attributesRecord) GetNamespace() string {
return record.namespace
}
func (record *attributesRecord) GetName() string {
return record.name
}
func (record *attributesRecord) GetResource() schema.GroupVersionResource {
return record.resource
}
func (record *attributesRecord) GetSubresource() string {
return record.subresource
}
func (record *attributesRecord) GetOperation() Operation {
return record.operation
}
func (record *attributesRecord) IsDryRun() bool {
return record.dryRun
}
func (record *attributesRecord) GetObject() runtime.Object {
return record.object
}
func (record *attributesRecord) GetOldObject() runtime.Object {
return record.oldObject
}
func (record *attributesRecord) GetUserInfo() user.Info {
return record.userInfo
}
// getAnnotations implements privateAnnotationsGetter.It's a private method used
// by WithAudit decorator.
func (record *attributesRecord) getAnnotations() map[string]string {
record.annotationsLock.RLock()
defer record.annotationsLock.RUnlock()
if record.annotations == nil {
return nil
}
cp := make(map[string]string, len(record.annotations))
for key, value := range record.annotations {
cp[key] = value
}
return cp
}
func (record *attributesRecord) AddAnnotation(key, value string) error {
if err := checkKeyFormat(key); err != nil {
return err
}
record.annotationsLock.Lock()
defer record.annotationsLock.Unlock()
if record.annotations == nil {
record.annotations = make(map[string]string)
}
if v, ok := record.annotations[key]; ok && v != value {
return fmt.Errorf("admission annotations are not allowd to be overwritten, key:%q, old value: %q, new value:%q", key, record.annotations[key], value)
}
record.annotations[key] = value
return nil
}
func checkKeyFormat(key string) error {
parts := strings.Split(key, "/")
if len(parts) != 2 {
return fmt.Errorf("annotation key has invalid format, the right format is a DNS subdomain prefix and '/' and key name. (e.g. 'podsecuritypolicy.admission.k8s.io/admit-policy')")
}
if msgs := validation.IsQualifiedName(key); len(msgs) != 0 {
return fmt.Errorf("annotation key has invalid format %s. A qualified name like 'podsecuritypolicy.admission.k8s.io/admit-policy' is required.", strings.Join(msgs, ","))
}
return nil
}

95
vendor/k8s.io/apiserver/pkg/admission/audit.go generated vendored Normal file
View File

@@ -0,0 +1,95 @@
/*
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 admission
import (
"fmt"
auditinternal "k8s.io/apiserver/pkg/apis/audit"
"k8s.io/apiserver/pkg/audit"
)
// auditHandler logs annotations set by other admission handlers
type auditHandler struct {
Interface
ae *auditinternal.Event
}
var _ Interface = &auditHandler{}
var _ MutationInterface = &auditHandler{}
var _ ValidationInterface = &auditHandler{}
// WithAudit is a decorator for a admission phase. It saves annotations
// of attribute into the audit event. Attributes passed to the Admit and
// Validate function must be instance of privateAnnotationsGetter or
// AnnotationsGetter, otherwise an error is returned.
func WithAudit(i Interface, ae *auditinternal.Event) Interface {
if i == nil {
return i
}
return &auditHandler{i, ae}
}
func (handler auditHandler) Admit(a Attributes) error {
if !handler.Interface.Handles(a.GetOperation()) {
return nil
}
if err := ensureAnnotationGetter(a); err != nil {
return err
}
var err error
if mutator, ok := handler.Interface.(MutationInterface); ok {
err = mutator.Admit(a)
handler.logAnnotations(a)
}
return err
}
func (handler auditHandler) Validate(a Attributes) error {
if !handler.Interface.Handles(a.GetOperation()) {
return nil
}
if err := ensureAnnotationGetter(a); err != nil {
return err
}
var err error
if validator, ok := handler.Interface.(ValidationInterface); ok {
err = validator.Validate(a)
handler.logAnnotations(a)
}
return err
}
func ensureAnnotationGetter(a Attributes) error {
_, okPrivate := a.(privateAnnotationsGetter)
_, okPublic := a.(AnnotationsGetter)
if okPrivate || okPublic {
return nil
}
return fmt.Errorf("attributes must be an instance of privateAnnotationsGetter or AnnotationsGetter")
}
func (handler auditHandler) logAnnotations(a Attributes) {
switch a := a.(type) {
case privateAnnotationsGetter:
audit.LogAnnotations(handler.ae, a.getAnnotations())
case AnnotationsGetter:
audit.LogAnnotations(handler.ae, a.GetAnnotations())
default:
// this will never happen, because we have already checked it in ensureAnnotationGetter
}
}

68
vendor/k8s.io/apiserver/pkg/admission/chain.go generated vendored Normal file
View File

@@ -0,0 +1,68 @@
/*
Copyright 2014 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 admission
// chainAdmissionHandler is an instance of admission.NamedHandler that performs admission control using
// a chain of admission handlers
type chainAdmissionHandler []Interface
// NewChainHandler creates a new chain handler from an array of handlers. Used for testing.
func NewChainHandler(handlers ...Interface) chainAdmissionHandler {
return chainAdmissionHandler(handlers)
}
// Admit performs an admission control check using a chain of handlers, and returns immediately on first error
func (admissionHandler chainAdmissionHandler) Admit(a Attributes) error {
for _, handler := range admissionHandler {
if !handler.Handles(a.GetOperation()) {
continue
}
if mutator, ok := handler.(MutationInterface); ok {
err := mutator.Admit(a)
if err != nil {
return err
}
}
}
return nil
}
// Validate performs an admission control check using a chain of handlers, and returns immediately on first error
func (admissionHandler chainAdmissionHandler) Validate(a Attributes) error {
for _, handler := range admissionHandler {
if !handler.Handles(a.GetOperation()) {
continue
}
if validator, ok := handler.(ValidationInterface); ok {
err := validator.Validate(a)
if err != nil {
return err
}
}
}
return nil
}
// Handles will return true if any of the handlers handles the given operation
func (admissionHandler chainAdmissionHandler) Handles(operation Operation) bool {
for _, handler := range admissionHandler {
if handler.Handles(operation) {
return true
}
}
return false
}

178
vendor/k8s.io/apiserver/pkg/admission/config.go generated vendored Normal file
View File

@@ -0,0 +1,178 @@
/*
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 admission
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"path"
"path/filepath"
"k8s.io/klog"
"sigs.k8s.io/yaml"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apiserver/pkg/apis/apiserver"
apiserverv1alpha1 "k8s.io/apiserver/pkg/apis/apiserver/v1alpha1"
)
func makeAbs(path, base string) (string, error) {
if filepath.IsAbs(path) {
return path, nil
}
if len(base) == 0 || base == "." {
cwd, err := os.Getwd()
if err != nil {
return "", err
}
base = cwd
}
return filepath.Join(base, path), nil
}
// ReadAdmissionConfiguration reads the admission configuration at the specified path.
// It returns the loaded admission configuration if the input file aligns with the required syntax.
// If it does not align with the provided syntax, it returns a default configuration for the enumerated
// set of pluginNames whose config location references the specified configFilePath.
// It does this to preserve backward compatibility when admission control files were opaque.
// It returns an error if the file did not exist.
func ReadAdmissionConfiguration(pluginNames []string, configFilePath string, configScheme *runtime.Scheme) (ConfigProvider, error) {
if configFilePath == "" {
return configProvider{config: &apiserver.AdmissionConfiguration{}}, nil
}
// a file was provided, so we just read it.
data, err := ioutil.ReadFile(configFilePath)
if err != nil {
return nil, fmt.Errorf("unable to read admission control configuration from %q [%v]", configFilePath, err)
}
codecs := serializer.NewCodecFactory(configScheme)
decoder := codecs.UniversalDecoder()
decodedObj, err := runtime.Decode(decoder, data)
// we were able to decode the file successfully
if err == nil {
decodedConfig, ok := decodedObj.(*apiserver.AdmissionConfiguration)
if !ok {
return nil, fmt.Errorf("unexpected type: %T", decodedObj)
}
baseDir := path.Dir(configFilePath)
for i := range decodedConfig.Plugins {
if decodedConfig.Plugins[i].Path == "" {
continue
}
// we update relative file paths to absolute paths
absPath, err := makeAbs(decodedConfig.Plugins[i].Path, baseDir)
if err != nil {
return nil, err
}
decodedConfig.Plugins[i].Path = absPath
}
return configProvider{
config: decodedConfig,
scheme: configScheme,
}, nil
}
// we got an error where the decode wasn't related to a missing type
if !(runtime.IsMissingVersion(err) || runtime.IsMissingKind(err) || runtime.IsNotRegisteredError(err)) {
return nil, err
}
// Only tolerate load errors if the file appears to be one of the two legacy plugin configs
unstructuredData := map[string]interface{}{}
if err2 := yaml.Unmarshal(data, &unstructuredData); err2 != nil {
return nil, err
}
_, isLegacyImagePolicy := unstructuredData["imagePolicy"]
_, isLegacyPodNodeSelector := unstructuredData["podNodeSelectorPluginConfig"]
if !isLegacyImagePolicy && !isLegacyPodNodeSelector {
return nil, err
}
// convert the legacy format to the new admission control format
// in order to preserve backwards compatibility, we set plugins that
// previously read input from a non-versioned file configuration to the
// current input file.
legacyPluginsWithUnversionedConfig := sets.NewString("ImagePolicyWebhook", "PodNodeSelector")
externalConfig := &apiserverv1alpha1.AdmissionConfiguration{}
for _, pluginName := range pluginNames {
if legacyPluginsWithUnversionedConfig.Has(pluginName) {
externalConfig.Plugins = append(externalConfig.Plugins,
apiserverv1alpha1.AdmissionPluginConfiguration{
Name: pluginName,
Path: configFilePath})
}
}
configScheme.Default(externalConfig)
internalConfig := &apiserver.AdmissionConfiguration{}
if err := configScheme.Convert(externalConfig, internalConfig, nil); err != nil {
return nil, err
}
return configProvider{
config: internalConfig,
scheme: configScheme,
}, nil
}
type configProvider struct {
config *apiserver.AdmissionConfiguration
scheme *runtime.Scheme
}
// GetAdmissionPluginConfigurationFor returns a reader that holds the admission plugin configuration.
func GetAdmissionPluginConfigurationFor(pluginCfg apiserver.AdmissionPluginConfiguration) (io.Reader, error) {
// if there is a nest object, return it directly
if pluginCfg.Configuration != nil {
return bytes.NewBuffer(pluginCfg.Configuration.Raw), nil
}
// there is nothing nested, so we delegate to path
if pluginCfg.Path != "" {
content, err := ioutil.ReadFile(pluginCfg.Path)
if err != nil {
klog.Fatalf("Couldn't open admission plugin configuration %s: %#v", pluginCfg.Path, err)
return nil, err
}
return bytes.NewBuffer(content), nil
}
// there is no special config at all
return nil, nil
}
// ConfigFor returns a reader for the specified plugin.
// If no specific configuration is present, we return a nil reader.
func (p configProvider) ConfigFor(pluginName string) (io.Reader, error) {
// there is no config, so there is no potential config
if p.config == nil {
return nil, nil
}
// look for matching plugin and get configuration
for _, pluginCfg := range p.config.Plugins {
if pluginName != pluginCfg.Name {
continue
}
pluginConfig, err := GetAdmissionPluginConfigurationFor(pluginCfg)
if err != nil {
return nil, err
}
return pluginConfig, nil
}
// there is no registered config that matches on plugin name.
return nil, nil
}

View File

@@ -0,0 +1,166 @@
/*
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 configuration
import (
"fmt"
"sync"
"time"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/wait"
)
const (
defaultInterval = 1 * time.Second
defaultFailureThreshold = 5
defaultBootstrapRetries = 5
defaultBootstrapGraceperiod = 5 * time.Second
)
var (
ErrNotReady = fmt.Errorf("configuration is not ready")
ErrDisabled = fmt.Errorf("disabled")
)
type getFunc func() (runtime.Object, error)
// When running, poller calls `get` every `interval`. If `get` is
// successful, `Ready()` returns ready and `configuration()` returns the
// `mergedConfiguration`; if `get` has failed more than `failureThreshold ` times,
// `Ready()` returns not ready and `configuration()` returns nil configuration.
// In an HA setup, the poller is consistent only if the `get` is
// doing consistent read.
type poller struct {
// a function to consistently read the latest configuration
get getFunc
// consistent read interval
// read-only
interval time.Duration
// if the number of consecutive read failure equals or exceeds the failureThreshold , the
// configuration is regarded as not ready.
// read-only
failureThreshold int
// number of consecutive failures so far.
failures int
// If the poller has passed the bootstrap phase. The poller is considered
// bootstrapped either bootstrapGracePeriod after the first call of
// configuration(), or when setConfigurationAndReady() is called, whichever
// comes first.
bootstrapped bool
// configuration() retries bootstrapRetries times if poller is not bootstrapped
// read-only
bootstrapRetries int
// Grace period for bootstrapping
// read-only
bootstrapGracePeriod time.Duration
once sync.Once
// if the configuration is regarded as ready.
ready bool
mergedConfiguration runtime.Object
lastErr error
// lock must be hold when reading/writing the data fields of poller.
lock sync.RWMutex
}
func newPoller(get getFunc) *poller {
p := poller{
get: get,
interval: defaultInterval,
failureThreshold: defaultFailureThreshold,
bootstrapRetries: defaultBootstrapRetries,
bootstrapGracePeriod: defaultBootstrapGraceperiod,
}
return &p
}
func (a *poller) lastError(err error) {
a.lock.Lock()
defer a.lock.Unlock()
a.lastErr = err
}
func (a *poller) notReady() {
a.lock.Lock()
defer a.lock.Unlock()
a.ready = false
}
func (a *poller) bootstrapping() {
// bootstrapGracePeriod is read-only, so no lock is required
timer := time.NewTimer(a.bootstrapGracePeriod)
go func() {
defer timer.Stop()
<-timer.C
a.lock.Lock()
defer a.lock.Unlock()
a.bootstrapped = true
}()
}
// If the poller is not bootstrapped yet, the configuration() gets a few chances
// to retry. This hides transient failures during system startup.
func (a *poller) configuration() (runtime.Object, error) {
a.once.Do(a.bootstrapping)
a.lock.RLock()
defer a.lock.RUnlock()
retries := 1
if !a.bootstrapped {
retries = a.bootstrapRetries
}
for count := 0; count < retries; count++ {
if count > 0 {
a.lock.RUnlock()
time.Sleep(a.interval)
a.lock.RLock()
}
if a.ready {
return a.mergedConfiguration, nil
}
}
if a.lastErr != nil {
return nil, a.lastErr
}
return nil, ErrNotReady
}
func (a *poller) setConfigurationAndReady(value runtime.Object) {
a.lock.Lock()
defer a.lock.Unlock()
a.bootstrapped = true
a.mergedConfiguration = value
a.ready = true
a.lastErr = nil
}
func (a *poller) Run(stopCh <-chan struct{}) {
go wait.Until(a.sync, a.interval, stopCh)
}
func (a *poller) sync() {
configuration, err := a.get()
if err != nil {
a.failures++
a.lastError(err)
if a.failures >= a.failureThreshold {
a.notReady()
}
return
}
a.failures = 0
a.setConfigurationAndReady(configuration)
}

View File

@@ -0,0 +1,88 @@
/*
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 configuration
import (
"fmt"
"reflect"
"sort"
"k8s.io/klog"
"k8s.io/api/admissionregistration/v1alpha1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)
type InitializerConfigurationLister interface {
List(opts metav1.ListOptions) (*v1alpha1.InitializerConfigurationList, error)
}
type InitializerConfigurationManager struct {
*poller
}
func NewInitializerConfigurationManager(c InitializerConfigurationLister) *InitializerConfigurationManager {
getFn := func() (runtime.Object, error) {
list, err := c.List(metav1.ListOptions{})
if err != nil {
if errors.IsNotFound(err) || errors.IsForbidden(err) {
klog.V(5).Infof("Initializers are disabled due to an error: %v", err)
return nil, ErrDisabled
}
return nil, err
}
return mergeInitializerConfigurations(list), nil
}
return &InitializerConfigurationManager{
newPoller(getFn),
}
}
// Initializers returns the merged InitializerConfiguration.
func (im *InitializerConfigurationManager) Initializers() (*v1alpha1.InitializerConfiguration, error) {
configuration, err := im.poller.configuration()
if err != nil {
return nil, err
}
initializerConfiguration, ok := configuration.(*v1alpha1.InitializerConfiguration)
if !ok {
return nil, fmt.Errorf("expected type %v, got type %v", reflect.TypeOf(initializerConfiguration), reflect.TypeOf(configuration))
}
return initializerConfiguration, nil
}
func (im *InitializerConfigurationManager) Run(stopCh <-chan struct{}) {
im.poller.Run(stopCh)
}
func mergeInitializerConfigurations(initializerConfigurationList *v1alpha1.InitializerConfigurationList) *v1alpha1.InitializerConfiguration {
configurations := initializerConfigurationList.Items
sort.SliceStable(configurations, InitializerConfigurationSorter(configurations).ByName)
var ret v1alpha1.InitializerConfiguration
for _, c := range configurations {
ret.Initializers = append(ret.Initializers, c.Initializers...)
}
return &ret
}
type InitializerConfigurationSorter []v1alpha1.InitializerConfiguration
func (a InitializerConfigurationSorter) ByName(i, j int) bool {
return a[i].Name < a[j].Name
}

View File

@@ -0,0 +1,97 @@
/*
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 configuration
import (
"fmt"
"sort"
"sync/atomic"
"k8s.io/api/admissionregistration/v1beta1"
"k8s.io/apimachinery/pkg/labels"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
"k8s.io/client-go/informers"
admissionregistrationlisters "k8s.io/client-go/listers/admissionregistration/v1beta1"
"k8s.io/client-go/tools/cache"
)
// mutatingWebhookConfigurationManager collects the mutating webhook objects so that they can be called.
type mutatingWebhookConfigurationManager struct {
configuration *atomic.Value
lister admissionregistrationlisters.MutatingWebhookConfigurationLister
hasSynced func() bool
}
var _ generic.Source = &mutatingWebhookConfigurationManager{}
func NewMutatingWebhookConfigurationManager(f informers.SharedInformerFactory) generic.Source {
informer := f.Admissionregistration().V1beta1().MutatingWebhookConfigurations()
manager := &mutatingWebhookConfigurationManager{
configuration: &atomic.Value{},
lister: informer.Lister(),
hasSynced: informer.Informer().HasSynced,
}
// Start with an empty list
manager.configuration.Store(&v1beta1.MutatingWebhookConfiguration{})
// On any change, rebuild the config
informer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(_ interface{}) { manager.updateConfiguration() },
UpdateFunc: func(_, _ interface{}) { manager.updateConfiguration() },
DeleteFunc: func(_ interface{}) { manager.updateConfiguration() },
})
return manager
}
// Webhooks returns the merged MutatingWebhookConfiguration.
func (m *mutatingWebhookConfigurationManager) Webhooks() []v1beta1.Webhook {
return m.configuration.Load().(*v1beta1.MutatingWebhookConfiguration).Webhooks
}
func (m *mutatingWebhookConfigurationManager) HasSynced() bool {
return m.hasSynced()
}
func (m *mutatingWebhookConfigurationManager) updateConfiguration() {
configurations, err := m.lister.List(labels.Everything())
if err != nil {
utilruntime.HandleError(fmt.Errorf("error updating configuration: %v", err))
return
}
m.configuration.Store(mergeMutatingWebhookConfigurations(configurations))
}
func mergeMutatingWebhookConfigurations(configurations []*v1beta1.MutatingWebhookConfiguration) *v1beta1.MutatingWebhookConfiguration {
var ret v1beta1.MutatingWebhookConfiguration
// The internal order of webhooks for each configuration is provided by the user
// but configurations themselves can be in any order. As we are going to run these
// webhooks in serial, they are sorted here to have a deterministic order.
sort.SliceStable(configurations, MutatingWebhookConfigurationSorter(configurations).ByName)
for _, c := range configurations {
ret.Webhooks = append(ret.Webhooks, c.Webhooks...)
}
return &ret
}
type MutatingWebhookConfigurationSorter []*v1beta1.MutatingWebhookConfiguration
func (a MutatingWebhookConfigurationSorter) ByName(i, j int) bool {
return a[i].Name < a[j].Name
}

View File

@@ -0,0 +1,97 @@
/*
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 configuration
import (
"fmt"
"sort"
"sync/atomic"
"k8s.io/api/admissionregistration/v1beta1"
"k8s.io/apimachinery/pkg/labels"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
"k8s.io/client-go/informers"
admissionregistrationlisters "k8s.io/client-go/listers/admissionregistration/v1beta1"
"k8s.io/client-go/tools/cache"
)
// validatingWebhookConfigurationManager collects the validating webhook objects so that they can be called.
type validatingWebhookConfigurationManager struct {
configuration *atomic.Value
lister admissionregistrationlisters.ValidatingWebhookConfigurationLister
hasSynced func() bool
}
var _ generic.Source = &validatingWebhookConfigurationManager{}
func NewValidatingWebhookConfigurationManager(f informers.SharedInformerFactory) generic.Source {
informer := f.Admissionregistration().V1beta1().ValidatingWebhookConfigurations()
manager := &validatingWebhookConfigurationManager{
configuration: &atomic.Value{},
lister: informer.Lister(),
hasSynced: informer.Informer().HasSynced,
}
// Start with an empty list
manager.configuration.Store(&v1beta1.ValidatingWebhookConfiguration{})
// On any change, rebuild the config
informer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(_ interface{}) { manager.updateConfiguration() },
UpdateFunc: func(_, _ interface{}) { manager.updateConfiguration() },
DeleteFunc: func(_ interface{}) { manager.updateConfiguration() },
})
return manager
}
// Webhooks returns the merged ValidatingWebhookConfiguration.
func (v *validatingWebhookConfigurationManager) Webhooks() []v1beta1.Webhook {
return v.configuration.Load().(*v1beta1.ValidatingWebhookConfiguration).Webhooks
}
// HasSynced returns true if the shared informers have synced.
func (v *validatingWebhookConfigurationManager) HasSynced() bool {
return v.hasSynced()
}
func (v *validatingWebhookConfigurationManager) updateConfiguration() {
configurations, err := v.lister.List(labels.Everything())
if err != nil {
utilruntime.HandleError(fmt.Errorf("error updating configuration: %v", err))
return
}
v.configuration.Store(mergeValidatingWebhookConfigurations(configurations))
}
func mergeValidatingWebhookConfigurations(
configurations []*v1beta1.ValidatingWebhookConfiguration,
) *v1beta1.ValidatingWebhookConfiguration {
sort.SliceStable(configurations, ValidatingWebhookConfigurationSorter(configurations).ByName)
var ret v1beta1.ValidatingWebhookConfiguration
for _, c := range configurations {
ret.Webhooks = append(ret.Webhooks, c.Webhooks...)
}
return &ret
}
type ValidatingWebhookConfigurationSorter []*v1beta1.ValidatingWebhookConfiguration
func (a ValidatingWebhookConfigurationSorter) ByName(i, j int) bool {
return a[i].Name < a[j].Name
}

39
vendor/k8s.io/apiserver/pkg/admission/decorator.go generated vendored Normal file
View File

@@ -0,0 +1,39 @@
/*
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 admission
type Decorator interface {
Decorate(handler Interface, name string) Interface
}
type DecoratorFunc func(handler Interface, name string) Interface
func (d DecoratorFunc) Decorate(handler Interface, name string) Interface {
return d(handler, name)
}
type Decorators []Decorator
// Decorate applies the decorator in inside-out order, i.e. the first decorator in the slice is first applied to the given handler.
func (d Decorators) Decorate(handler Interface, name string) Interface {
result := handler
for _, d := range d {
result = d.Decorate(result, name)
}
return result
}

72
vendor/k8s.io/apiserver/pkg/admission/errors.go generated vendored Normal file
View File

@@ -0,0 +1,72 @@
/*
Copyright 2015 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 admission
import (
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime/schema"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
)
func extractResourceName(a Attributes) (name string, resource schema.GroupResource, err error) {
resource = a.GetResource().GroupResource()
if len(a.GetName()) > 0 {
return a.GetName(), resource, nil
}
name = "Unknown"
obj := a.GetObject()
if obj != nil {
accessor, err := meta.Accessor(obj)
if err != nil {
// not all object have ObjectMeta. If we don't, return a name with a slash (always illegal)
return "Unknown/errorGettingName", resource, nil
}
// this is necessary because name object name generation has not occurred yet
if len(accessor.GetName()) > 0 {
name = accessor.GetName()
} else if len(accessor.GetGenerateName()) > 0 {
name = accessor.GetGenerateName()
}
}
return name, resource, nil
}
// NewForbidden is a utility function to return a well-formatted admission control error response
func NewForbidden(a Attributes, internalError error) error {
// do not double wrap an error of same type
if apierrors.IsForbidden(internalError) {
return internalError
}
name, resource, err := extractResourceName(a)
if err != nil {
return apierrors.NewInternalError(utilerrors.NewAggregate([]error{internalError, err}))
}
return apierrors.NewForbidden(resource, name, internalError)
}
// NewNotFound is a utility function to return a well-formatted admission control error response
func NewNotFound(a Attributes) error {
name, resource, err := extractResourceName(a)
if err != nil {
return apierrors.NewInternalError(err)
}
return apierrors.NewNotFound(resource, name)
}

79
vendor/k8s.io/apiserver/pkg/admission/handler.go generated vendored Normal file
View File

@@ -0,0 +1,79 @@
/*
Copyright 2015 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 admission
import (
"time"
"k8s.io/apimachinery/pkg/util/sets"
)
const (
// timeToWaitForReady is the amount of time to wait to let an admission controller to be ready to satisfy a request.
// this is useful when admission controllers need to warm their caches before letting requests through.
timeToWaitForReady = 10 * time.Second
)
// ReadyFunc is a function that returns true if the admission controller is ready to handle requests.
type ReadyFunc func() bool
// Handler is a base for admission control handlers that
// support a predefined set of operations
type Handler struct {
operations sets.String
readyFunc ReadyFunc
}
// Handles returns true for methods that this handler supports
func (h *Handler) Handles(operation Operation) bool {
return h.operations.Has(string(operation))
}
// NewHandler creates a new base handler that handles the passed
// in operations
func NewHandler(ops ...Operation) *Handler {
operations := sets.NewString()
for _, op := range ops {
operations.Insert(string(op))
}
return &Handler{
operations: operations,
}
}
// SetReadyFunc allows late registration of a ReadyFunc to know if the handler is ready to process requests.
func (h *Handler) SetReadyFunc(readyFunc ReadyFunc) {
h.readyFunc = readyFunc
}
// WaitForReady will wait for the readyFunc (if registered) to return ready, and in case of timeout, will return false.
func (h *Handler) WaitForReady() bool {
// there is no ready func configured, so we return immediately
if h.readyFunc == nil {
return true
}
timeout := time.After(timeToWaitForReady)
for !h.readyFunc() {
select {
case <-time.After(100 * time.Millisecond):
case <-timeout:
return h.readyFunc()
}
}
return true
}

View File

@@ -0,0 +1,70 @@
/*
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 initializer
import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
)
type pluginInitializer struct {
externalClient kubernetes.Interface
externalInformers informers.SharedInformerFactory
authorizer authorizer.Authorizer
scheme *runtime.Scheme
}
// New creates an instance of admission plugins initializer.
// TODO(p0lyn0mial): make the parameters public, this construction seems to be redundant.
func New(
extClientset kubernetes.Interface,
extInformers informers.SharedInformerFactory,
authz authorizer.Authorizer,
scheme *runtime.Scheme,
) pluginInitializer {
return pluginInitializer{
externalClient: extClientset,
externalInformers: extInformers,
authorizer: authz,
scheme: scheme,
}
}
// Initialize checks the initialization interfaces implemented by a plugin
// and provide the appropriate initialization data
func (i pluginInitializer) Initialize(plugin admission.Interface) {
if wants, ok := plugin.(WantsExternalKubeClientSet); ok {
wants.SetExternalKubeClientSet(i.externalClient)
}
if wants, ok := plugin.(WantsExternalKubeInformerFactory); ok {
wants.SetExternalKubeInformerFactory(i.externalInformers)
}
if wants, ok := plugin.(WantsAuthorizer); ok {
wants.SetAuthorizer(i.authorizer)
}
if wants, ok := plugin.(WantsScheme); ok {
wants.SetScheme(i.scheme)
}
}
var _ admission.PluginInitializer = pluginInitializer{}

View File

@@ -0,0 +1,49 @@
/*
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 initializer
import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
)
// WantsExternalKubeClientSet defines a function which sets external ClientSet for admission plugins that need it
type WantsExternalKubeClientSet interface {
SetExternalKubeClientSet(kubernetes.Interface)
admission.InitializationValidator
}
// WantsExternalKubeInformerFactory defines a function which sets InformerFactory for admission plugins that need it
type WantsExternalKubeInformerFactory interface {
SetExternalKubeInformerFactory(informers.SharedInformerFactory)
admission.InitializationValidator
}
// WantsAuthorizer defines a function which sets Authorizer for admission plugins that need it.
type WantsAuthorizer interface {
SetAuthorizer(authorizer.Authorizer)
admission.InitializationValidator
}
// WantsScheme defines a function that accepts runtime.Scheme for admission plugins that need it.
type WantsScheme interface {
SetScheme(*runtime.Scheme)
admission.InitializationValidator
}

124
vendor/k8s.io/apiserver/pkg/admission/interfaces.go generated vendored Normal file
View File

@@ -0,0 +1,124 @@
/*
Copyright 2014 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 admission
import (
"io"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/authentication/user"
)
// Attributes is an interface used by AdmissionController to get information about a request
// that is used to make an admission decision.
type Attributes interface {
// GetName returns the name of the object as presented in the request. On a CREATE operation, the client
// may omit name and rely on the server to generate the name. If that is the case, this method will return
// the empty string
GetName() string
// GetNamespace is the namespace associated with the request (if any)
GetNamespace() string
// GetResource is the name of the resource being requested. This is not the kind. For example: pods
GetResource() schema.GroupVersionResource
// GetSubresource is the name of the subresource being requested. This is a different resource, scoped to the parent resource, but it may have a different kind.
// For instance, /pods has the resource "pods" and the kind "Pod", while /pods/foo/status has the resource "pods", the sub resource "status", and the kind "Pod"
// (because status operates on pods). The binding resource for a pod though may be /pods/foo/binding, which has resource "pods", subresource "binding", and kind "Binding".
GetSubresource() string
// GetOperation is the operation being performed
GetOperation() Operation
// IsDryRun indicates that modifications will definitely not be persisted for this request. This is to prevent
// admission controllers with side effects and a method of reconciliation from being overwhelmed.
// However, a value of false for this does not mean that the modification will be persisted, because it
// could still be rejected by a subsequent validation step.
IsDryRun() bool
// GetObject is the object from the incoming request prior to default values being applied
GetObject() runtime.Object
// GetOldObject is the existing object. Only populated for UPDATE requests.
GetOldObject() runtime.Object
// GetKind is the type of object being manipulated. For example: Pod
GetKind() schema.GroupVersionKind
// GetUserInfo is information about the requesting user
GetUserInfo() user.Info
// AddAnnotation sets annotation according to key-value pair. The key should be qualified, e.g., podsecuritypolicy.admission.k8s.io/admit-policy, where
// "podsecuritypolicy" is the name of the plugin, "admission.k8s.io" is the name of the organization, "admit-policy" is the key name.
// An error is returned if the format of key is invalid. When trying to overwrite annotation with a new value, an error is returned.
// Both ValidationInterface and MutationInterface are allowed to add Annotations.
AddAnnotation(key, value string) error
}
// privateAnnotationsGetter is a private interface which allows users to get annotations from Attributes.
type privateAnnotationsGetter interface {
getAnnotations() map[string]string
}
// AnnotationsGetter allows users to get annotations from Attributes. An alternate Attribute should implement
// this interface.
type AnnotationsGetter interface {
GetAnnotations() map[string]string
}
// Interface is an abstract, pluggable interface for Admission Control decisions.
type Interface interface {
// Handles returns true if this admission controller can handle the given operation
// where operation can be one of CREATE, UPDATE, DELETE, or CONNECT
Handles(operation Operation) bool
}
type MutationInterface interface {
Interface
// Admit makes an admission decision based on the request attributes
Admit(a Attributes) (err error)
}
// ValidationInterface is an abstract, pluggable interface for Admission Control decisions.
type ValidationInterface interface {
Interface
// Validate makes an admission decision based on the request attributes. It is NOT allowed to mutate
Validate(a Attributes) (err error)
}
// Operation is the type of resource operation being checked for admission control
type Operation string
// Operation constants
const (
Create Operation = "CREATE"
Update Operation = "UPDATE"
Delete Operation = "DELETE"
Connect Operation = "CONNECT"
)
// PluginInitializer is used for initialization of shareable resources between admission plugins.
// After initialization the resources have to be set separately
type PluginInitializer interface {
Initialize(plugin Interface)
}
// InitializationValidator holds ValidateInitialization functions, which are responsible for validation of initialized
// shared resources and should be implemented on admission plugins
type InitializationValidator interface {
ValidateInitialization() error
}
// ConfigProvider provides a way to get configuration for an admission plugin based on its name
type ConfigProvider interface {
ConfigFor(pluginName string) (io.Reader, error)
}

View File

@@ -0,0 +1,214 @@
/*
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 metrics
import (
"fmt"
"strconv"
"time"
"github.com/prometheus/client_golang/prometheus"
"k8s.io/apiserver/pkg/admission"
)
const (
namespace = "apiserver"
subsystem = "admission"
)
var (
// Use buckets ranging from 25 ms to ~2.5 seconds.
latencyBuckets = prometheus.ExponentialBuckets(25000, 2.5, 5)
latencySummaryMaxAge = 5 * time.Hour
// Metrics provides access to all admission metrics.
Metrics = newAdmissionMetrics()
)
// ObserverFunc is a func that emits metrics.
type ObserverFunc func(elapsed time.Duration, rejected bool, attr admission.Attributes, stepType string, extraLabels ...string)
const (
stepValidate = "validate"
stepAdmit = "admit"
)
// WithControllerMetrics is a decorator for named admission handlers.
func WithControllerMetrics(i admission.Interface, name string) admission.Interface {
return WithMetrics(i, Metrics.ObserveAdmissionController, name)
}
// WithStepMetrics is a decorator for a whole admission phase, i.e. admit or validation.admission step.
func WithStepMetrics(i admission.Interface) admission.Interface {
return WithMetrics(i, Metrics.ObserveAdmissionStep)
}
// WithMetrics is a decorator for admission handlers with a generic observer func.
func WithMetrics(i admission.Interface, observer ObserverFunc, extraLabels ...string) admission.Interface {
return &pluginHandlerWithMetrics{
Interface: i,
observer: observer,
extraLabels: extraLabels,
}
}
// pluginHandlerWithMetrics decorates a admission handler with metrics.
type pluginHandlerWithMetrics struct {
admission.Interface
observer ObserverFunc
extraLabels []string
}
// Admit performs a mutating admission control check and emit metrics.
func (p pluginHandlerWithMetrics) Admit(a admission.Attributes) error {
mutatingHandler, ok := p.Interface.(admission.MutationInterface)
if !ok {
return nil
}
start := time.Now()
err := mutatingHandler.Admit(a)
p.observer(time.Since(start), err != nil, a, stepAdmit, p.extraLabels...)
return err
}
// Validate performs a non-mutating admission control check and emits metrics.
func (p pluginHandlerWithMetrics) Validate(a admission.Attributes) error {
validatingHandler, ok := p.Interface.(admission.ValidationInterface)
if !ok {
return nil
}
start := time.Now()
err := validatingHandler.Validate(a)
p.observer(time.Since(start), err != nil, a, stepValidate, p.extraLabels...)
return err
}
// AdmissionMetrics instruments admission with prometheus metrics.
type AdmissionMetrics struct {
step *metricSet
controller *metricSet
webhook *metricSet
}
// newAdmissionMetrics create a new AdmissionMetrics, configured with default metric names.
func newAdmissionMetrics() *AdmissionMetrics {
// Admission metrics for a step of the admission flow. The entire admission flow is broken down into a series of steps
// Each step is identified by a distinct type label value.
step := newMetricSet("step",
[]string{"type", "operation", "rejected"},
"Admission sub-step %s, broken out for each operation and API resource and step type (validate or admit).", true)
// Built-in admission controller metrics. Each admission controller is identified by name.
controller := newMetricSet("controller",
[]string{"name", "type", "operation", "rejected"},
"Admission controller %s, identified by name and broken out for each operation and API resource and type (validate or admit).", false)
// Admission webhook metrics. Each webhook is identified by name.
webhook := newMetricSet("webhook",
[]string{"name", "type", "operation", "rejected"},
"Admission webhook %s, identified by name and broken out for each operation and API resource and type (validate or admit).", false)
step.mustRegister()
controller.mustRegister()
webhook.mustRegister()
return &AdmissionMetrics{step: step, controller: controller, webhook: webhook}
}
func (m *AdmissionMetrics) reset() {
m.step.reset()
m.controller.reset()
m.webhook.reset()
}
// ObserveAdmissionStep records admission related metrics for a admission step, identified by step type.
func (m *AdmissionMetrics) ObserveAdmissionStep(elapsed time.Duration, rejected bool, attr admission.Attributes, stepType string, extraLabels ...string) {
m.step.observe(elapsed, append(extraLabels, stepType, string(attr.GetOperation()), strconv.FormatBool(rejected))...)
}
// ObserveAdmissionController records admission related metrics for a built-in admission controller, identified by it's plugin handler name.
func (m *AdmissionMetrics) ObserveAdmissionController(elapsed time.Duration, rejected bool, attr admission.Attributes, stepType string, extraLabels ...string) {
m.controller.observe(elapsed, append(extraLabels, stepType, string(attr.GetOperation()), strconv.FormatBool(rejected))...)
}
// ObserveWebhook records admission related metrics for a admission webhook.
func (m *AdmissionMetrics) ObserveWebhook(elapsed time.Duration, rejected bool, attr admission.Attributes, stepType string, extraLabels ...string) {
m.webhook.observe(elapsed, append(extraLabels, stepType, string(attr.GetOperation()), strconv.FormatBool(rejected))...)
}
type metricSet struct {
latencies *prometheus.HistogramVec
latenciesSummary *prometheus.SummaryVec
}
func newMetricSet(name string, labels []string, helpTemplate string, hasSummary bool) *metricSet {
var summary *prometheus.SummaryVec
if hasSummary {
summary = prometheus.NewSummaryVec(
prometheus.SummaryOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: fmt.Sprintf("%s_admission_latencies_seconds_summary", name),
Help: fmt.Sprintf(helpTemplate, "latency summary"),
MaxAge: latencySummaryMaxAge,
},
labels,
)
}
return &metricSet{
latencies: prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: fmt.Sprintf("%s_admission_latencies_seconds", name),
Help: fmt.Sprintf(helpTemplate, "latency histogram"),
Buckets: latencyBuckets,
},
labels,
),
latenciesSummary: summary,
}
}
// MustRegister registers all the prometheus metrics in the metricSet.
func (m *metricSet) mustRegister() {
prometheus.MustRegister(m.latencies)
if m.latenciesSummary != nil {
prometheus.MustRegister(m.latenciesSummary)
}
}
// Reset resets all the prometheus metrics in the metricSet.
func (m *metricSet) reset() {
m.latencies.Reset()
if m.latenciesSummary != nil {
m.latenciesSummary.Reset()
}
}
// Observe records an observed admission event to all metrics in the metricSet.
func (m *metricSet) observe(elapsed time.Duration, labels ...string) {
elapsedMicroseconds := float64(elapsed / time.Microsecond)
m.latencies.WithLabelValues(labels...).Observe(elapsedMicroseconds)
if m.latenciesSummary != nil {
m.latenciesSummary.WithLabelValues(labels...).Observe(elapsedMicroseconds)
}
}

View File

@@ -0,0 +1,369 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package initialization
import (
"fmt"
"io"
"strings"
"k8s.io/klog"
"k8s.io/api/admissionregistration/v1alpha1"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/api/validation"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/admission/configuration"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/features"
utilfeature "k8s.io/apiserver/pkg/util/feature"
clientset "k8s.io/client-go/kubernetes"
)
const (
// Name of admission plug-in
PluginName = "Initializers"
)
// Register registers a plugin
func Register(plugins *admission.Plugins) {
plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
return NewInitializer(), nil
})
}
type initializerOptions struct {
Initializers []string
}
// InitializationConfig specifies initialization config
type InitializationConfig interface {
Run(stopCh <-chan struct{})
Initializers() (*v1alpha1.InitializerConfiguration, error)
}
type initializer struct {
config InitializationConfig
authorizer authorizer.Authorizer
}
// NewInitializer creates a new initializer plugin which assigns newly created resources initializers
// based on configuration loaded from the admission API group.
// FUTURE: this may be moved to the storage layer of the apiserver, but for now this is an alpha feature
// that can be disabled.
func NewInitializer() admission.Interface {
return &initializer{}
}
// ValidateInitialization implements the InitializationValidator interface.
func (i *initializer) ValidateInitialization() error {
if i.config == nil {
return fmt.Errorf("the Initializer admission plugin requires a Kubernetes client to be provided")
}
if i.authorizer == nil {
return fmt.Errorf("the Initializer admission plugin requires an authorizer to be provided")
}
if !utilfeature.DefaultFeatureGate.Enabled(features.Initializers) {
if err := utilfeature.DefaultFeatureGate.Set(string(features.Initializers) + "=true"); err != nil {
klog.Errorf("error enabling Initializers feature as part of admission plugin setup: %v", err)
} else {
klog.Infof("enabled Initializers feature as part of admission plugin setup")
}
}
i.config.Run(wait.NeverStop)
return nil
}
// SetExternalKubeClientSet implements the WantsExternalKubeClientSet interface.
func (i *initializer) SetExternalKubeClientSet(client clientset.Interface) {
i.config = configuration.NewInitializerConfigurationManager(client.AdmissionregistrationV1alpha1().InitializerConfigurations())
}
// SetAuthorizer implements the WantsAuthorizer interface.
func (i *initializer) SetAuthorizer(a authorizer.Authorizer) {
i.authorizer = a
}
var initializerFieldPath = field.NewPath("metadata", "initializers")
// readConfig holds requests instead of failing them if the server is not yet initialized
// or is unresponsive. It formats the returned error for client use if necessary.
func (i *initializer) readConfig(a admission.Attributes) (*v1alpha1.InitializerConfiguration, error) {
// read initializers from config
config, err := i.config.Initializers()
if err == nil {
return config, nil
}
// if initializer configuration is disabled, fail open
if err == configuration.ErrDisabled {
return &v1alpha1.InitializerConfiguration{}, nil
}
e := errors.NewServerTimeout(a.GetResource().GroupResource(), "create", 1)
if err == configuration.ErrNotReady {
e.ErrStatus.Message = fmt.Sprintf("Waiting for initialization configuration to load: %v", err)
e.ErrStatus.Reason = "LoadingConfiguration"
e.ErrStatus.Details.Causes = append(e.ErrStatus.Details.Causes, metav1.StatusCause{
Type: "InitializerConfigurationPending",
Message: "The server is waiting for the initializer configuration to be loaded.",
})
} else {
e.ErrStatus.Message = fmt.Sprintf("Unable to refresh the initializer configuration: %v", err)
e.ErrStatus.Reason = "LoadingConfiguration"
e.ErrStatus.Details.Causes = append(e.ErrStatus.Details.Causes, metav1.StatusCause{
Type: "InitializerConfigurationFailure",
Message: "An error has occurred while refreshing the initializer configuration, no resources can be created until a refresh succeeds.",
})
}
return nil, e
}
// Admit checks for create requests to add initializers, or update request to enforce invariants.
// The admission controller fails open if the object doesn't have ObjectMeta (can't be initialized).
// A client with sufficient permission ("initialize" verb on resource) can specify its own initializers
// or an empty initializers struct (which bypasses initialization). Only clients with the initialize verb
// can update objects that have not completed initialization. Sub resources can still be modified on
// resources that are undergoing initialization.
// TODO: once this logic is ready for beta, move it into the REST storage layer.
func (i *initializer) Admit(a admission.Attributes) (err error) {
switch a.GetOperation() {
case admission.Create, admission.Update:
default:
return nil
}
// TODO: should sub-resource action should be denied until the object is initialized?
if len(a.GetSubresource()) > 0 {
return nil
}
switch a.GetOperation() {
case admission.Create:
accessor, err := meta.Accessor(a.GetObject())
if err != nil {
// objects without meta accessor cannot be checked for initialization, and it is possible to make calls
// via our API that don't have ObjectMeta
return nil
}
existing := accessor.GetInitializers()
if existing != nil {
klog.V(5).Infof("Admin bypassing initialization for %s", a.GetResource())
// it must be possible for some users to bypass initialization - for now, check the initialize operation
if err := i.canInitialize(a, "create with initializers denied"); err != nil {
return err
}
// allow administrators to bypass initialization by setting an empty initializers struct
if len(existing.Pending) == 0 && existing.Result == nil {
accessor.SetInitializers(nil)
return nil
}
} else {
klog.V(5).Infof("Checking initialization for %s", a.GetResource())
config, err := i.readConfig(a)
if err != nil {
return err
}
// Mirror pods are exempt from initialization because they are created and initialized
// on the Kubelet before they appear in the API.
// TODO: once this moves to REST storage layer, this becomes a pod specific concern
if a.GetKind().GroupKind() == v1.SchemeGroupVersion.WithKind("Pod").GroupKind() {
accessor, err := meta.Accessor(a.GetObject())
if err != nil {
return err
}
annotations := accessor.GetAnnotations()
if _, isMirror := annotations[v1.MirrorPodAnnotationKey]; isMirror {
return nil
}
}
names := findInitializers(config, a.GetResource())
if len(names) == 0 {
klog.V(5).Infof("No initializers needed")
return nil
}
klog.V(5).Infof("Found initializers for %s: %v", a.GetResource(), names)
accessor.SetInitializers(newInitializers(names))
}
case admission.Update:
accessor, err := meta.Accessor(a.GetObject())
if err != nil {
// objects without meta accessor cannot be checked for initialization, and it is possible to make calls
// via our API that don't have ObjectMeta
return nil
}
updated := accessor.GetInitializers()
// controllers deployed with an empty initializers.pending have their initializers set to nil
// but should be able to update without changing their manifest
if updated != nil && len(updated.Pending) == 0 && updated.Result == nil {
accessor.SetInitializers(nil)
updated = nil
}
existingAccessor, err := meta.Accessor(a.GetOldObject())
if err != nil {
// if the old object does not have an accessor, but the new one does, error out
return fmt.Errorf("initialized resources must be able to set initializers (%T): %v", a.GetOldObject(), err)
}
existing := existingAccessor.GetInitializers()
// updates on initialized resources are allowed
if updated == nil && existing == nil {
return nil
}
klog.V(5).Infof("Modifying uninitialized resource %s", a.GetResource())
// because we are called before validation, we need to ensure the update transition is valid.
if errs := validation.ValidateInitializersUpdate(updated, existing, initializerFieldPath); len(errs) > 0 {
return errors.NewInvalid(a.GetKind().GroupKind(), a.GetName(), errs)
}
// caller must have the ability to mutate un-initialized resources
if err := i.canInitialize(a, "update to uninitialized resource denied"); err != nil {
return err
}
// TODO: restrict initialization list changes to specific clients?
}
return nil
}
func (i *initializer) canInitialize(a admission.Attributes, message string) error {
// caller must have the ability to mutate un-initialized resources
decision, reason, err := i.authorizer.Authorize(authorizer.AttributesRecord{
Name: a.GetName(),
ResourceRequest: true,
User: a.GetUserInfo(),
Verb: "initialize",
Namespace: a.GetNamespace(),
APIGroup: a.GetResource().Group,
APIVersion: a.GetResource().Version,
Resource: a.GetResource().Resource,
})
if err != nil {
return err
}
if decision != authorizer.DecisionAllow {
return errors.NewForbidden(a.GetResource().GroupResource(), a.GetName(), fmt.Errorf("%s: %s", message, reason))
}
return nil
}
// Handles returns true if this admission controller can handle the given operation
// where operation can be one of CREATE, UPDATE, DELETE, or CONNECT
func (i *initializer) Handles(op admission.Operation) bool {
return op == admission.Create || op == admission.Update
}
// newInitializers populates an Initializers struct.
func newInitializers(names []string) *metav1.Initializers {
if len(names) == 0 {
return nil
}
var init []metav1.Initializer
for _, name := range names {
init = append(init, metav1.Initializer{Name: name})
}
return &metav1.Initializers{
Pending: init,
}
}
// findInitializers returns the list of initializer names that apply to a config. It returns an empty list
// if no initializers apply.
func findInitializers(initializers *v1alpha1.InitializerConfiguration, gvr schema.GroupVersionResource) []string {
var names []string
for _, init := range initializers.Initializers {
if !matchRule(init.Rules, gvr) {
continue
}
names = append(names, init.Name)
}
return names
}
// matchRule returns true if any rule matches the provided group version resource.
func matchRule(rules []v1alpha1.Rule, gvr schema.GroupVersionResource) bool {
for _, rule := range rules {
if !hasGroup(rule.APIGroups, gvr.Group) {
return false
}
if !hasVersion(rule.APIVersions, gvr.Version) {
return false
}
if !hasResource(rule.Resources, gvr.Resource) {
return false
}
}
return len(rules) > 0
}
func hasGroup(groups []string, group string) bool {
if groups[0] == "*" {
return true
}
for _, g := range groups {
if g == group {
return true
}
}
return false
}
func hasVersion(versions []string, version string) bool {
if versions[0] == "*" {
return true
}
for _, v := range versions {
if v == version {
return true
}
}
return false
}
func hasResource(resources []string, resource string) bool {
if resources[0] == "*" || resources[0] == "*/*" {
return true
}
for _, r := range resources {
if strings.Contains(r, "/") {
continue
}
if r == resource {
return true
}
}
return false
}

View File

@@ -0,0 +1,224 @@
/*
Copyright 2015 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 lifecycle
import (
"fmt"
"io"
"time"
"k8s.io/klog"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
utilcache "k8s.io/apimachinery/pkg/util/cache"
"k8s.io/apimachinery/pkg/util/clock"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/admission/initializer"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
corelisters "k8s.io/client-go/listers/core/v1"
)
const (
// PluginName indicates the name of admission plug-in
PluginName = "NamespaceLifecycle"
// how long a namespace stays in the force live lookup cache before expiration.
forceLiveLookupTTL = 30 * time.Second
// how long to wait for a missing namespace before re-checking the cache (and then doing a live lookup)
// this accomplishes two things:
// 1. It allows a watch-fed cache time to observe a namespace creation event
// 2. It allows time for a namespace creation to distribute to members of a storage cluster,
// so the live lookup has a better chance of succeeding even if it isn't performed against the leader.
missingNamespaceWait = 50 * time.Millisecond
)
// Register registers a plugin
func Register(plugins *admission.Plugins) {
plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
return NewLifecycle(sets.NewString(metav1.NamespaceDefault, metav1.NamespaceSystem, metav1.NamespacePublic))
})
}
// Lifecycle is an implementation of admission.Interface.
// It enforces life-cycle constraints around a Namespace depending on its Phase
type Lifecycle struct {
*admission.Handler
client kubernetes.Interface
immortalNamespaces sets.String
namespaceLister corelisters.NamespaceLister
// forceLiveLookupCache holds a list of entries for namespaces that we have a strong reason to believe are stale in our local cache.
// if a namespace is in this cache, then we will ignore our local state and always fetch latest from api server.
forceLiveLookupCache *utilcache.LRUExpireCache
}
var _ = initializer.WantsExternalKubeInformerFactory(&Lifecycle{})
var _ = initializer.WantsExternalKubeClientSet(&Lifecycle{})
// Admit makes an admission decision based on the request attributes
func (l *Lifecycle) Admit(a admission.Attributes) error {
// prevent deletion of immortal namespaces
if a.GetOperation() == admission.Delete && a.GetKind().GroupKind() == v1.SchemeGroupVersion.WithKind("Namespace").GroupKind() && l.immortalNamespaces.Has(a.GetName()) {
return errors.NewForbidden(a.GetResource().GroupResource(), a.GetName(), fmt.Errorf("this namespace may not be deleted"))
}
// always allow non-namespaced resources
if len(a.GetNamespace()) == 0 && a.GetKind().GroupKind() != v1.SchemeGroupVersion.WithKind("Namespace").GroupKind() {
return nil
}
if a.GetKind().GroupKind() == v1.SchemeGroupVersion.WithKind("Namespace").GroupKind() {
// if a namespace is deleted, we want to prevent all further creates into it
// while it is undergoing termination. to reduce incidences where the cache
// is slow to update, we add the namespace into a force live lookup list to ensure
// we are not looking at stale state.
if a.GetOperation() == admission.Delete {
l.forceLiveLookupCache.Add(a.GetName(), true, forceLiveLookupTTL)
}
// allow all operations to namespaces
return nil
}
// always allow deletion of other resources
if a.GetOperation() == admission.Delete {
return nil
}
// always allow access review checks. Returning status about the namespace would be leaking information
if isAccessReview(a) {
return nil
}
// we need to wait for our caches to warm
if !l.WaitForReady() {
return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
}
var (
exists bool
err error
)
namespace, err := l.namespaceLister.Get(a.GetNamespace())
if err != nil {
if !errors.IsNotFound(err) {
return errors.NewInternalError(err)
}
} else {
exists = true
}
if !exists && a.GetOperation() == admission.Create {
// give the cache time to observe the namespace before rejecting a create.
// this helps when creating a namespace and immediately creating objects within it.
time.Sleep(missingNamespaceWait)
namespace, err = l.namespaceLister.Get(a.GetNamespace())
switch {
case errors.IsNotFound(err):
// no-op
case err != nil:
return errors.NewInternalError(err)
default:
exists = true
}
if exists {
klog.V(4).Infof("found %s in cache after waiting", a.GetNamespace())
}
}
// forceLiveLookup if true will skip looking at local cache state and instead always make a live call to server.
forceLiveLookup := false
if _, ok := l.forceLiveLookupCache.Get(a.GetNamespace()); ok {
// we think the namespace was marked for deletion, but our current local cache says otherwise, we will force a live lookup.
forceLiveLookup = exists && namespace.Status.Phase == v1.NamespaceActive
}
// refuse to operate on non-existent namespaces
if !exists || forceLiveLookup {
// as a last resort, make a call directly to storage
namespace, err = l.client.CoreV1().Namespaces().Get(a.GetNamespace(), metav1.GetOptions{})
switch {
case errors.IsNotFound(err):
return err
case err != nil:
return errors.NewInternalError(err)
}
klog.V(4).Infof("found %s via storage lookup", a.GetNamespace())
}
// ensure that we're not trying to create objects in terminating namespaces
if a.GetOperation() == admission.Create {
if namespace.Status.Phase != v1.NamespaceTerminating {
return nil
}
// TODO: This should probably not be a 403
return admission.NewForbidden(a, fmt.Errorf("unable to create new content in namespace %s because it is being terminated", a.GetNamespace()))
}
return nil
}
// NewLifecycle creates a new namespace Lifecycle admission control handler
func NewLifecycle(immortalNamespaces sets.String) (*Lifecycle, error) {
return newLifecycleWithClock(immortalNamespaces, clock.RealClock{})
}
func newLifecycleWithClock(immortalNamespaces sets.String, clock utilcache.Clock) (*Lifecycle, error) {
forceLiveLookupCache := utilcache.NewLRUExpireCacheWithClock(100, clock)
return &Lifecycle{
Handler: admission.NewHandler(admission.Create, admission.Update, admission.Delete),
immortalNamespaces: immortalNamespaces,
forceLiveLookupCache: forceLiveLookupCache,
}, nil
}
// SetExternalKubeInformerFactory implements the WantsExternalKubeInformerFactory interface.
func (l *Lifecycle) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) {
namespaceInformer := f.Core().V1().Namespaces()
l.namespaceLister = namespaceInformer.Lister()
l.SetReadyFunc(namespaceInformer.Informer().HasSynced)
}
// SetExternalKubeClientSet implements the WantsExternalKubeClientSet interface.
func (l *Lifecycle) SetExternalKubeClientSet(client kubernetes.Interface) {
l.client = client
}
// ValidateInitialization implements the InitializationValidator interface.
func (l *Lifecycle) ValidateInitialization() error {
if l.namespaceLister == nil {
return fmt.Errorf("missing namespaceLister")
}
if l.client == nil {
return fmt.Errorf("missing client")
}
return nil
}
// accessReviewResources are resources which give a view into permissions in a namespace. Users must be allowed to create these
// resources because returning "not found" errors allows someone to search for the "people I'm going to fire in 2017" namespace.
var accessReviewResources = map[schema.GroupResource]bool{
{Group: "authorization.k8s.io", Resource: "localsubjectaccessreviews"}: true,
}
func isAccessReview(a admission.Attributes) bool {
return accessReviewResources[a.GetResource().GroupResource()]
}

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,51 @@
/*
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 {
// TODO this will get cleaned up with the scheme types are fixed
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 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 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,69 @@
/*
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/v1alpha1"
)
var (
scheme = runtime.NewScheme()
codecs = serializer.NewCodecFactory(scheme)
)
func init() {
utilruntime.Must(webhookadmission.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,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 errors
import (
"fmt"
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}
}
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,57 @@
/*
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 (
"fmt"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// convertor converts objects to the desired version.
type convertor struct {
Scheme *runtime.Scheme
}
// ConvertToGVK converts object to the desired gvk.
func (c *convertor) ConvertToGVK(obj runtime.Object, gvk schema.GroupVersionKind) (runtime.Object, error) {
// Unlike other resources, custom resources do not have internal version, so
// if obj is a custom resource, it should not need conversion.
if obj.GetObjectKind().GroupVersionKind() == gvk {
return obj, nil
}
out, err := c.Scheme.New(gvk)
if err != nil {
return nil, err
}
err = c.Scheme.Convert(obj, out, nil)
if err != nil {
return nil, err
}
// Explicitly set the GVK
out.GetObjectKind().SetGroupVersionKind(gvk)
return out, nil
}
// Validate checks if the conversion has a scheme.
func (c *convertor) Validate() error {
if c.Scheme == nil {
return fmt.Errorf("the convertor requires a scheme")
}
return nil
}

View File

@@ -0,0 +1,45 @@
/*
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/api/admissionregistration/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/admission"
)
// Source can list dynamic webhook plugins.
type Source interface {
Webhooks() []v1beta1.Webhook
HasSynced() bool
}
// VersionedAttributes is a wrapper around the original admission attributes, adding versioned
// variants of the object and old object.
type VersionedAttributes struct {
admission.Attributes
VersionedOldObject runtime.Object
VersionedObject runtime.Object
}
// Dispatcher dispatches webhook call to a list of webhooks with admission attributes as argument.
type Dispatcher interface {
// Dispatch a request to the webhooks using the given webhooks. A non-nil error means the request is rejected.
Dispatch(ctx context.Context, a *VersionedAttributes, hooks []*v1beta1.Webhook) error
}

View File

@@ -0,0 +1,205 @@
/*
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"
admissionv1beta1 "k8s.io/api/admission/v1beta1"
"k8s.io/api/admissionregistration/v1beta1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/admission"
genericadmissioninit "k8s.io/apiserver/pkg/admission/initializer"
"k8s.io/apiserver/pkg/admission/plugin/webhook/config"
"k8s.io/apiserver/pkg/admission/plugin/webhook/namespace"
"k8s.io/apiserver/pkg/admission/plugin/webhook/rules"
"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 *webhook.ClientManager
convertor *convertor
namespaceMatcher *namespace.Matcher
dispatcher Dispatcher
}
var (
_ genericadmissioninit.WantsExternalKubeClientSet = &Webhook{}
_ admission.Interface = &Webhook{}
)
type sourceFactory func(f informers.SharedInformerFactory) Source
type dispatcherFactory func(cm *webhook.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 := webhook.NewClientManager(admissionv1beta1.SchemeGroupVersion, admissionv1beta1.AddToScheme)
if err != nil {
return nil, err
}
authInfoResolver, err := webhook.NewDefaultAuthenticationInfoResolver(kubeconfigFile)
if err != nil {
return nil, err
}
// Set defaults which may be overridden later.
cm.SetAuthenticationInfoResolver(authInfoResolver)
cm.SetServiceResolver(webhook.NewDefaultServiceResolver())
return &Webhook{
Handler: handler,
sourceFactory: sourceFactory,
clientManager: &cm,
convertor: &convertor{},
namespaceMatcher: &namespace.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 webhook.AuthenticationInfoResolverWrapper) {
a.clientManager.SetAuthenticationInfoResolverWrapper(wrapper)
}
// SetServiceResolver sets a service resolver for the webhook admission plugin.
// Passing a nil resolver does not have an effect, instead a default one will be used.
func (a *Webhook) SetServiceResolver(sr webhook.ServiceResolver) {
a.clientManager.SetServiceResolver(sr)
}
// SetScheme sets a serializer(NegotiatedSerializer) which is derived from the scheme
func (a *Webhook) SetScheme(scheme *runtime.Scheme) {
if scheme != nil {
a.convertor.Scheme = scheme
}
}
// SetExternalKubeClientSet implements the WantsExternalKubeInformerFactory interface.
// It sets external ClientSet for admission plugins that need it
func (a *Webhook) SetExternalKubeClientSet(client clientset.Interface) {
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)
}
if err := a.convertor.Validate(); err != nil {
return fmt.Errorf("convertor is not properly setup: %v", err)
}
return nil
}
// ShouldCallHook makes a decision on whether to call the webhook or not by the attribute.
func (a *Webhook) ShouldCallHook(h *v1beta1.Webhook, attr admission.Attributes) (bool, *apierrors.StatusError) {
var matches bool
for _, r := range h.Rules {
m := rules.Matcher{Rule: r, Attr: attr}
if m.Matches() {
matches = true
break
}
}
if !matches {
return false, nil
}
return a.namespaceMatcher.MatchNamespaceSelector(h, attr)
}
// Dispatch is called by the downstream Validate or Admit methods.
func (a *Webhook) Dispatch(attr admission.Attributes) 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()
// TODO: Figure out if adding one second timeout make sense here.
ctx := context.TODO()
var relevantHooks []*v1beta1.Webhook
for i := range hooks {
call, err := a.ShouldCallHook(&hooks[i], attr)
if err != nil {
return err
}
if call {
relevantHooks = append(relevantHooks, &hooks[i])
}
}
if len(relevantHooks) == 0 {
// no matching hooks
return nil
}
// convert the object to the external version before sending it to the webhook
versionedAttr := VersionedAttributes{
Attributes: attr,
}
if oldObj := attr.GetOldObject(); oldObj != nil {
out, err := a.convertor.ConvertToGVK(oldObj, attr.GetKind())
if err != nil {
return apierrors.NewInternalError(err)
}
versionedAttr.VersionedOldObject = out
}
if obj := attr.GetObject(); obj != nil {
out, err := a.convertor.ConvertToGVK(obj, attr.GetKind())
if err != nil {
return apierrors.NewInternalError(err)
}
versionedAttr.VersionedObject = out
}
return a.dispatcher.Dispatch(ctx, &versionedAttr, relevantHooks)
}

View File

@@ -0,0 +1,166 @@
/*
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"
"k8s.io/klog"
admissionv1beta1 "k8s.io/api/admission/v1beta1"
"k8s.io/api/admissionregistration/v1beta1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
admissionmetrics "k8s.io/apiserver/pkg/admission/metrics"
webhookerrors "k8s.io/apiserver/pkg/admission/plugin/webhook/errors"
"k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
"k8s.io/apiserver/pkg/admission/plugin/webhook/request"
"k8s.io/apiserver/pkg/admission/plugin/webhook/util"
"k8s.io/apiserver/pkg/util/webhook"
)
type mutatingDispatcher struct {
cm *webhook.ClientManager
plugin *Plugin
}
func newMutatingDispatcher(p *Plugin) func(cm *webhook.ClientManager) generic.Dispatcher {
return func(cm *webhook.ClientManager) generic.Dispatcher {
return &mutatingDispatcher{cm, p}
}
}
var _ generic.Dispatcher = &mutatingDispatcher{}
func (a *mutatingDispatcher) Dispatch(ctx context.Context, attr *generic.VersionedAttributes, relevantHooks []*v1beta1.Webhook) error {
for _, hook := range relevantHooks {
t := time.Now()
err := a.callAttrMutatingHook(ctx, hook, attr)
admissionmetrics.Metrics.ObserveWebhook(time.Since(t), err != nil, attr.Attributes, "admit", hook.Name)
if err == nil {
continue
}
ignoreClientCallFailures := hook.FailurePolicy != nil && *hook.FailurePolicy == v1beta1.Ignore
if callErr, ok := err.(*webhook.ErrCallingWebhook); ok {
if ignoreClientCallFailures {
klog.Warningf("Failed calling webhook, failing open %v: %v", hook.Name, callErr)
utilruntime.HandleError(callErr)
continue
}
klog.Warningf("Failed calling webhook, failing closed %v: %v", hook.Name, err)
}
return apierrors.NewInternalError(err)
}
// convert attr.VersionedObject to the internal version in the underlying admission.Attributes
if attr.VersionedObject != nil {
return a.plugin.scheme.Convert(attr.VersionedObject, attr.Attributes.GetObject(), nil)
}
return nil
}
// note that callAttrMutatingHook updates attr
func (a *mutatingDispatcher) callAttrMutatingHook(ctx context.Context, h *v1beta1.Webhook, attr *generic.VersionedAttributes) error {
if attr.IsDryRun() {
if h.SideEffects == nil {
return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook SideEffects is nil")}
}
if !(*h.SideEffects == v1beta1.SideEffectClassNone || *h.SideEffects == v1beta1.SideEffectClassNoneOnDryRun) {
return webhookerrors.NewDryRunUnsupportedErr(h.Name)
}
}
// Make the webhook request
request := request.CreateAdmissionReview(attr)
client, err := a.cm.HookClient(util.HookClientConfigForWebhook(h))
if err != nil {
return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
}
response := &admissionv1beta1.AdmissionReview{}
if err := client.Post().Context(ctx).Body(&request).Do().Into(response); err != nil {
return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
}
if response.Response == nil {
return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook response was absent")}
}
for k, v := range response.Response.AuditAnnotations {
key := h.Name + "/" + k
if err := attr.AddAnnotation(key, v); err != nil {
klog.Warningf("Failed to set admission audit annotation %s to %s for mutating webhook %s: %v", key, v, h.Name, err)
}
}
if !response.Response.Allowed {
return webhookerrors.ToStatusErr(h.Name, response.Response.Result)
}
patchJS := response.Response.Patch
if len(patchJS) == 0 {
return nil
}
patchObj, err := jsonpatch.DecodePatch(patchJS)
if err != nil {
return apierrors.NewInternalError(err)
}
if len(patchObj) == 0 {
return nil
}
// if a non-empty patch was provided, and we have no object we can apply it to (e.g. a DELETE admission operation), error
if attr.VersionedObject == nil {
return apierrors.NewInternalError(fmt.Errorf("admission webhook %q attempted to modify the object, which is not supported for this operation", h.Name))
}
objJS, err := runtime.Encode(a.plugin.jsonSerializer, attr.VersionedObject)
if err != nil {
return apierrors.NewInternalError(err)
}
patchedJS, err := patchObj.Apply(objJS)
if err != nil {
return apierrors.NewInternalError(err)
}
var 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 = a.plugin.scheme.New(attr.GetKind())
if err != nil {
return apierrors.NewInternalError(err)
}
}
// TODO: if we have multiple mutating webhooks, we can remember the json
// instead of encoding and decoding for each one.
if _, _, err := a.plugin.jsonSerializer.Decode(patchedJS, nil, newVersionedObject); err != nil {
return apierrors.NewInternalError(err)
}
attr.VersionedObject = newVersionedObject
a.plugin.scheme.Default(attr.VersionedObject)
return nil
}

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,96 @@
/*
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 (
"fmt"
"io"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer/json"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/admission/configuration"
"k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
)
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
scheme *runtime.Scheme
jsonSerializer *json.Serializer
}
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
}
// SetScheme sets a serializer(NegotiatedSerializer) which is derived from the scheme
func (a *Plugin) SetScheme(scheme *runtime.Scheme) {
a.Webhook.SetScheme(scheme)
if scheme != nil {
a.scheme = scheme
a.jsonSerializer = json.NewSerializer(json.DefaultMetaFactory, scheme, scheme, false)
}
}
// ValidateInitialization implements the InitializationValidator interface.
func (a *Plugin) ValidateInitialization() error {
if err := a.Webhook.ValidateInitialization(); err != nil {
return err
}
if a.scheme == nil {
return fmt.Errorf("scheme is not properly setup")
}
if a.jsonSerializer == nil {
return fmt.Errorf("jsonSerializer is not properly setup")
}
return nil
}
// Admit makes an admission decision based on the request attributes.
func (a *Plugin) Admit(attr admission.Attributes) error {
return a.Webhook.Dispatch(attr)
}

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,117 @@
/*
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"
"k8s.io/api/admissionregistration/v1beta1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apiserver/pkg/admission"
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 *v1beta1.Webhook, 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
}
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)
}
// TODO: adding an LRU cache to cache the translation
selector, err := metav1.LabelSelectorAsSelector(h.NamespaceSelector)
if err != nil {
return false, apierrors.NewInternalError(err)
}
return selector.Matches(labels.Set(namespaceLabels)), nil
}

View File

@@ -0,0 +1,73 @@
/*
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 (
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/util/uuid"
"k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
)
// CreateAdmissionReview creates an AdmissionReview for the provided admission.Attributes
func CreateAdmissionReview(attr *generic.VersionedAttributes) admissionv1beta1.AdmissionReview {
gvk := attr.GetKind()
gvr := attr.GetResource()
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: uuid.NewUUID(),
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: attr.GetSubresource(),
Name: attr.GetName(),
Namespace: attr.GetNamespace(),
Operation: admissionv1beta1.Operation(attr.GetOperation()),
UserInfo: userInfo,
Object: runtime.RawExtension{
Object: attr.VersionedObject,
},
OldObject: runtime.RawExtension{
Object: attr.VersionedOldObject,
},
DryRun: &dryRun,
},
}
}

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,107 @@
/*
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/v1beta1"
"k8s.io/apiserver/pkg/admission"
)
// Matcher determines if the Attr matches the Rule.
type Matcher struct {
Rule v1beta1.RuleWithOperations
Attr admission.Attributes
}
// Matches returns if the Attr matches the Rule.
func (r *Matcher) Matches() bool {
return 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
}
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 == v1beta1.OperationAll {
return true
}
// The constants are the same such that this is a valid cast (and this
// is tested).
if op == v1beta1.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
}

View File

@@ -0,0 +1,42 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package util
import (
"k8s.io/api/admissionregistration/v1beta1"
"k8s.io/apiserver/pkg/util/webhook"
)
// HookClientConfigForWebhook construct a webhook.ClientConfig using a v1beta1.Webhook API object.
// webhook.ClientConfig is used to create a HookClient and the purpose of the config struct is to
// share that with other packages that need to create a HookClient.
func HookClientConfigForWebhook(w *v1beta1.Webhook) webhook.ClientConfig {
ret := webhook.ClientConfig{Name: w.Name, CABundle: w.ClientConfig.CABundle}
if w.ClientConfig.URL != nil {
ret.URL = *w.ClientConfig.URL
}
if w.ClientConfig.Service != nil {
ret.Service = &webhook.ClientConfigService{
Name: w.ClientConfig.Service.Name,
Namespace: w.ClientConfig.Service.Namespace,
}
if w.ClientConfig.Service.Path != nil {
ret.Service.Path = *w.ClientConfig.Service.Path
}
}
return ret
}

View File

@@ -0,0 +1,134 @@
/*
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 validating
import (
"context"
"fmt"
"sync"
"time"
"k8s.io/klog"
admissionv1beta1 "k8s.io/api/admission/v1beta1"
"k8s.io/api/admissionregistration/v1beta1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
admissionmetrics "k8s.io/apiserver/pkg/admission/metrics"
webhookerrors "k8s.io/apiserver/pkg/admission/plugin/webhook/errors"
"k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
"k8s.io/apiserver/pkg/admission/plugin/webhook/request"
"k8s.io/apiserver/pkg/admission/plugin/webhook/util"
"k8s.io/apiserver/pkg/util/webhook"
)
type validatingDispatcher struct {
cm *webhook.ClientManager
}
func newValidatingDispatcher(cm *webhook.ClientManager) generic.Dispatcher {
return &validatingDispatcher{cm}
}
var _ generic.Dispatcher = &validatingDispatcher{}
func (d *validatingDispatcher) Dispatch(ctx context.Context, attr *generic.VersionedAttributes, relevantHooks []*v1beta1.Webhook) error {
wg := sync.WaitGroup{}
errCh := make(chan error, len(relevantHooks))
wg.Add(len(relevantHooks))
for i := range relevantHooks {
go func(hook *v1beta1.Webhook) {
defer wg.Done()
t := time.Now()
err := d.callHook(ctx, hook, attr)
admissionmetrics.Metrics.ObserveWebhook(time.Since(t), err != nil, attr.Attributes, "validating", hook.Name)
if err == nil {
return
}
ignoreClientCallFailures := hook.FailurePolicy != nil && *hook.FailurePolicy == v1beta1.Ignore
if callErr, ok := err.(*webhook.ErrCallingWebhook); ok {
if ignoreClientCallFailures {
klog.Warningf("Failed calling webhook, failing open %v: %v", hook.Name, callErr)
utilruntime.HandleError(callErr)
return
}
klog.Warningf("Failed calling webhook, failing closed %v: %v", hook.Name, err)
errCh <- apierrors.NewInternalError(err)
return
}
klog.Warningf("rejected by webhook %q: %#v", hook.Name, err)
errCh <- err
}(relevantHooks[i])
}
wg.Wait()
close(errCh)
var errs []error
for e := range errCh {
errs = append(errs, e)
}
if len(errs) == 0 {
return nil
}
if len(errs) > 1 {
for i := 1; i < len(errs); i++ {
// TODO: merge status errors; until then, just return the first one.
utilruntime.HandleError(errs[i])
}
}
return errs[0]
}
func (d *validatingDispatcher) callHook(ctx context.Context, h *v1beta1.Webhook, attr *generic.VersionedAttributes) error {
if attr.IsDryRun() {
if h.SideEffects == nil {
return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook SideEffects is nil")}
}
if !(*h.SideEffects == v1beta1.SideEffectClassNone || *h.SideEffects == v1beta1.SideEffectClassNoneOnDryRun) {
return webhookerrors.NewDryRunUnsupportedErr(h.Name)
}
}
// Make the webhook request
request := request.CreateAdmissionReview(attr)
client, err := d.cm.HookClient(util.HookClientConfigForWebhook(h))
if err != nil {
return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
}
response := &admissionv1beta1.AdmissionReview{}
if err := client.Post().Context(ctx).Body(&request).Do().Into(response); err != nil {
return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
}
if response.Response == nil {
return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook response was absent")}
}
for k, v := range response.Response.AuditAnnotations {
key := h.Name + "/" + k
if err := attr.AddAnnotation(key, v); err != nil {
klog.Warningf("Failed to set admission audit annotation %s to %s for validating webhook %s: %v", key, v, h.Name, err)
}
}
if response.Response.Allowed {
return nil
}
return webhookerrors.ToStatusErr(h.Name, response.Response.Result)
}

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 validating makes calls to validating (i.e., non-mutating) webhooks
// during the admission process.
package validating // import "k8s.io/apiserver/pkg/admission/plugin/webhook/validating"

View File

@@ -0,0 +1,64 @@
/*
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 validating
import (
"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 = "ValidatingAdmissionWebhook"
)
// Register registers a plugin
func Register(plugins *admission.Plugins) {
plugins.Register(PluginName, func(configFile io.Reader) (admission.Interface, error) {
plugin, err := NewValidatingAdmissionWebhook(configFile)
if err != nil {
return nil, err
}
return plugin, nil
})
}
// Plugin is an implementation of admission.Interface.
type Plugin struct {
*generic.Webhook
}
var _ admission.ValidationInterface = &Plugin{}
// NewValidatingAdmissionWebhook returns a generic admission webhook plugin.
func NewValidatingAdmissionWebhook(configFile io.Reader) (*Plugin, error) {
handler := admission.NewHandler(admission.Connect, admission.Create, admission.Delete, admission.Update)
webhook, err := generic.NewWebhook(handler, configFile, configuration.NewValidatingWebhookConfigurationManager, newValidatingDispatcher)
if err != nil {
return nil, err
}
return &Plugin{webhook}, nil
}
// Validate makes an admission decision based on the request attributes.
func (a *Plugin) Validate(attr admission.Attributes) error {
return a.Webhook.Dispatch(attr)
}

208
vendor/k8s.io/apiserver/pkg/admission/plugins.go generated vendored Normal file
View File

@@ -0,0 +1,208 @@
/*
Copyright 2014 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 admission
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"reflect"
"sort"
"strings"
"sync"
"k8s.io/klog"
)
// Factory is a function that returns an Interface for admission decisions.
// The config parameter provides an io.Reader handler to the factory in
// order to load specific configurations. If no configuration is provided
// the parameter is nil.
type Factory func(config io.Reader) (Interface, error)
type Plugins struct {
lock sync.Mutex
registry map[string]Factory
}
func NewPlugins() *Plugins {
return &Plugins{}
}
// All registered admission options.
var (
// PluginEnabledFn checks whether a plugin is enabled. By default, if you ask about it, it's enabled.
PluginEnabledFn = func(name string, config io.Reader) bool {
return true
}
)
// PluginEnabledFunc is a function type that can provide an external check on whether an admission plugin may be enabled
type PluginEnabledFunc func(name string, config io.Reader) bool
// Registered enumerates the names of all registered plugins.
func (ps *Plugins) Registered() []string {
ps.lock.Lock()
defer ps.lock.Unlock()
keys := []string{}
for k := range ps.registry {
keys = append(keys, k)
}
sort.Strings(keys)
return keys
}
// Register registers a plugin Factory by name. This
// is expected to happen during app startup.
func (ps *Plugins) Register(name string, plugin Factory) {
ps.lock.Lock()
defer ps.lock.Unlock()
if ps.registry != nil {
_, found := ps.registry[name]
if found {
klog.Fatalf("Admission plugin %q was registered twice", name)
}
} else {
ps.registry = map[string]Factory{}
}
klog.V(1).Infof("Registered admission plugin %q", name)
ps.registry[name] = plugin
}
// getPlugin creates an instance of the named plugin. It returns `false` if the
// the name is not known. The error is returned only when the named provider was
// known but failed to initialize. The config parameter specifies the io.Reader
// handler of the configuration file for the cloud provider, or nil for no configuration.
func (ps *Plugins) getPlugin(name string, config io.Reader) (Interface, bool, error) {
ps.lock.Lock()
defer ps.lock.Unlock()
f, found := ps.registry[name]
if !found {
return nil, false, nil
}
config1, config2, err := splitStream(config)
if err != nil {
return nil, true, err
}
if !PluginEnabledFn(name, config1) {
return nil, true, nil
}
ret, err := f(config2)
return ret, true, err
}
// splitStream reads the stream bytes and constructs two copies of it.
func splitStream(config io.Reader) (io.Reader, io.Reader, error) {
if config == nil || reflect.ValueOf(config).IsNil() {
return nil, nil, nil
}
configBytes, err := ioutil.ReadAll(config)
if err != nil {
return nil, nil, err
}
return bytes.NewBuffer(configBytes), bytes.NewBuffer(configBytes), nil
}
// NewFromPlugins returns an admission.Interface that will enforce admission control decisions of all
// the given plugins.
func (ps *Plugins) NewFromPlugins(pluginNames []string, configProvider ConfigProvider, pluginInitializer PluginInitializer, decorator Decorator) (Interface, error) {
handlers := []Interface{}
mutationPlugins := []string{}
validationPlugins := []string{}
for _, pluginName := range pluginNames {
pluginConfig, err := configProvider.ConfigFor(pluginName)
if err != nil {
return nil, err
}
plugin, err := ps.InitPlugin(pluginName, pluginConfig, pluginInitializer)
if err != nil {
return nil, err
}
if plugin != nil {
if decorator != nil {
handlers = append(handlers, decorator.Decorate(plugin, pluginName))
} else {
handlers = append(handlers, plugin)
}
if _, ok := plugin.(MutationInterface); ok {
mutationPlugins = append(mutationPlugins, pluginName)
}
if _, ok := plugin.(ValidationInterface); ok {
validationPlugins = append(validationPlugins, pluginName)
}
}
}
if len(mutationPlugins) != 0 {
klog.Infof("Loaded %d mutating admission controller(s) successfully in the following order: %s.", len(mutationPlugins), strings.Join(mutationPlugins, ","))
}
if len(validationPlugins) != 0 {
klog.Infof("Loaded %d validating admission controller(s) successfully in the following order: %s.", len(validationPlugins), strings.Join(validationPlugins, ","))
}
return chainAdmissionHandler(handlers), nil
}
// InitPlugin creates an instance of the named interface.
func (ps *Plugins) InitPlugin(name string, config io.Reader, pluginInitializer PluginInitializer) (Interface, error) {
if name == "" {
klog.Info("No admission plugin specified.")
return nil, nil
}
plugin, found, err := ps.getPlugin(name, config)
if err != nil {
return nil, fmt.Errorf("couldn't init admission plugin %q: %v", name, err)
}
if !found {
return nil, fmt.Errorf("unknown admission plugin: %s", name)
}
pluginInitializer.Initialize(plugin)
// ensure that plugins have been properly initialized
if err := ValidateInitialization(plugin); err != nil {
return nil, fmt.Errorf("failed to initialize admission plugin %q: %v", name, err)
}
return plugin, nil
}
// ValidateInitialization will call the InitializationValidate function in each plugin if they implement
// the InitializationValidator interface.
func ValidateInitialization(plugin Interface) error {
if validater, ok := plugin.(InitializationValidator); ok {
err := validater.ValidateInitialization()
if err != nil {
return err
}
}
return nil
}
type PluginInitializers []PluginInitializer
func (pp PluginInitializers) Initialize(plugin Interface) {
for _, p := range pp {
p.Initialize(plugin)
}
}