mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-06-13 10:33:35 +00:00
build: move e2e dependencies into e2e/go.mod
Several packages are only used while running the e2e suite. These packages are less important to update, as the they can not influence the final executable that is part of the Ceph-CSI container-image. By moving these dependencies out of the main Ceph-CSI go.mod, it is easier to identify if a reported CVE affects Ceph-CSI, or only the testing (like most of the Kubernetes CVEs). Signed-off-by: Niels de Vos <ndevos@ibm.com>
This commit is contained in:
committed by
mergify[bot]
parent
15da101b1b
commit
bec6090996
211
e2e/vendor/k8s.io/apiserver/pkg/admission/attributes.go
generated
vendored
Normal file
211
e2e/vendor/k8s.io/apiserver/pkg/admission/attributes.go
generated
vendored
Normal file
@ -0,0 +1,211 @@
|
||||
/*
|
||||
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"
|
||||
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
)
|
||||
|
||||
type attributesRecord struct {
|
||||
kind schema.GroupVersionKind
|
||||
namespace string
|
||||
name string
|
||||
resource schema.GroupVersionResource
|
||||
subresource string
|
||||
operation Operation
|
||||
options runtime.Object
|
||||
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]annotation
|
||||
annotationsLock sync.RWMutex
|
||||
|
||||
reinvocationContext ReinvocationContext
|
||||
}
|
||||
|
||||
type annotation struct {
|
||||
level auditinternal.Level
|
||||
value string
|
||||
}
|
||||
|
||||
func NewAttributesRecord(object runtime.Object, oldObject runtime.Object, kind schema.GroupVersionKind, namespace, name string, resource schema.GroupVersionResource, subresource string, operation Operation, operationOptions runtime.Object, dryRun bool, userInfo user.Info) Attributes {
|
||||
return &attributesRecord{
|
||||
kind: kind,
|
||||
namespace: namespace,
|
||||
name: name,
|
||||
resource: resource,
|
||||
subresource: subresource,
|
||||
operation: operation,
|
||||
options: operationOptions,
|
||||
dryRun: dryRun,
|
||||
object: object,
|
||||
oldObject: oldObject,
|
||||
userInfo: userInfo,
|
||||
reinvocationContext: &reinvocationContext{},
|
||||
}
|
||||
}
|
||||
|
||||
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) GetOperationOptions() runtime.Object {
|
||||
return record.options
|
||||
}
|
||||
|
||||
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(maxLevel auditinternal.Level) 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 {
|
||||
if value.level.Less(maxLevel) || value.level == maxLevel {
|
||||
cp[key] = value.value
|
||||
}
|
||||
}
|
||||
return cp
|
||||
}
|
||||
|
||||
// AddAnnotation adds an annotation to attributesRecord with Metadata audit level
|
||||
func (record *attributesRecord) AddAnnotation(key, value string) error {
|
||||
return record.AddAnnotationWithLevel(key, value, auditinternal.LevelMetadata)
|
||||
}
|
||||
|
||||
func (record *attributesRecord) AddAnnotationWithLevel(key, value string, level auditinternal.Level) error {
|
||||
if err := checkKeyFormat(key); err != nil {
|
||||
return err
|
||||
}
|
||||
if level.Less(auditinternal.LevelMetadata) {
|
||||
return fmt.Errorf("admission annotations are not allowed to be set at audit level lower than Metadata, key: %q, level: %s", key, level)
|
||||
}
|
||||
record.annotationsLock.Lock()
|
||||
defer record.annotationsLock.Unlock()
|
||||
|
||||
if record.annotations == nil {
|
||||
record.annotations = make(map[string]annotation)
|
||||
}
|
||||
annotation := annotation{level: level, value: value}
|
||||
if v, ok := record.annotations[key]; ok && v != annotation {
|
||||
return fmt.Errorf("admission annotations are not allowd to be overwritten, key:%q, old value: %v, new value: %v", key, record.annotations[key], annotation)
|
||||
}
|
||||
record.annotations[key] = annotation
|
||||
return nil
|
||||
}
|
||||
|
||||
func (record *attributesRecord) GetReinvocationContext() ReinvocationContext {
|
||||
return record.reinvocationContext
|
||||
}
|
||||
|
||||
type reinvocationContext struct {
|
||||
// isReinvoke is true when admission plugins are being reinvoked
|
||||
isReinvoke bool
|
||||
// reinvokeRequested is true when an admission plugin requested a re-invocation of the chain
|
||||
reinvokeRequested bool
|
||||
// values stores reinvoke context values per plugin.
|
||||
values map[string]interface{}
|
||||
}
|
||||
|
||||
func (rc *reinvocationContext) IsReinvoke() bool {
|
||||
return rc.isReinvoke
|
||||
}
|
||||
|
||||
func (rc *reinvocationContext) SetIsReinvoke() {
|
||||
rc.isReinvoke = true
|
||||
}
|
||||
|
||||
func (rc *reinvocationContext) ShouldReinvoke() bool {
|
||||
return rc.reinvokeRequested
|
||||
}
|
||||
|
||||
func (rc *reinvocationContext) SetShouldReinvoke() {
|
||||
rc.reinvokeRequested = true
|
||||
}
|
||||
|
||||
func (rc *reinvocationContext) SetValue(plugin string, v interface{}) {
|
||||
if rc.values == nil {
|
||||
rc.values = map[string]interface{}{}
|
||||
}
|
||||
rc.values[plugin] = v
|
||||
}
|
||||
|
||||
func (rc *reinvocationContext) Value(plugin string) interface{} {
|
||||
return rc.values[plugin]
|
||||
}
|
||||
|
||||
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
|
||||
}
|
102
e2e/vendor/k8s.io/apiserver/pkg/admission/audit.go
generated
vendored
Normal file
102
e2e/vendor/k8s.io/apiserver/pkg/admission/audit.go
generated
vendored
Normal file
@ -0,0 +1,102 @@
|
||||
/*
|
||||
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 (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apiserver/pkg/audit"
|
||||
)
|
||||
|
||||
// auditHandler logs annotations set by other admission handlers
|
||||
type auditHandler struct {
|
||||
Interface
|
||||
}
|
||||
|
||||
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) Interface {
|
||||
if i == nil {
|
||||
return i
|
||||
}
|
||||
return &auditHandler{Interface: i}
|
||||
}
|
||||
|
||||
func (handler *auditHandler) Admit(ctx context.Context, a Attributes, o ObjectInterfaces) 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(ctx, a, o)
|
||||
handler.logAnnotations(ctx, a)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (handler *auditHandler) Validate(ctx context.Context, a Attributes, o ObjectInterfaces) 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(ctx, a, o)
|
||||
handler.logAnnotations(ctx, 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(ctx context.Context, a Attributes) {
|
||||
ae := audit.AuditEventFrom(ctx)
|
||||
if ae == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var annotations map[string]string
|
||||
switch a := a.(type) {
|
||||
case privateAnnotationsGetter:
|
||||
annotations = a.getAnnotations(ae.Level)
|
||||
case AnnotationsGetter:
|
||||
annotations = a.GetAnnotations(ae.Level)
|
||||
default:
|
||||
// this will never happen, because we have already checked it in ensureAnnotationGetter
|
||||
}
|
||||
|
||||
audit.AddAuditAnnotationsMap(ctx, annotations)
|
||||
}
|
70
e2e/vendor/k8s.io/apiserver/pkg/admission/chain.go
generated
vendored
Normal file
70
e2e/vendor/k8s.io/apiserver/pkg/admission/chain.go
generated
vendored
Normal file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
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 "context"
|
||||
|
||||
// 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(ctx context.Context, a Attributes, o ObjectInterfaces) error {
|
||||
for _, handler := range admissionHandler {
|
||||
if !handler.Handles(a.GetOperation()) {
|
||||
continue
|
||||
}
|
||||
if mutator, ok := handler.(MutationInterface); ok {
|
||||
err := mutator.Admit(ctx, a, o)
|
||||
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(ctx context.Context, a Attributes, o ObjectInterfaces) error {
|
||||
for _, handler := range admissionHandler {
|
||||
if !handler.Handles(a.GetOperation()) {
|
||||
continue
|
||||
}
|
||||
if validator, ok := handler.(ValidationInterface); ok {
|
||||
err := validator.Validate(ctx, a, o)
|
||||
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
|
||||
}
|
174
e2e/vendor/k8s.io/apiserver/pkg/admission/config.go
generated
vendored
Normal file
174
e2e/vendor/k8s.io/apiserver/pkg/admission/config.go
generated
vendored
Normal file
@ -0,0 +1,174 @@
|
||||
/*
|
||||
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"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
"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"
|
||||
apiserverv1 "k8s.io/apiserver/pkg/apis/apiserver/v1"
|
||||
)
|
||||
|
||||
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 := os.ReadFile(configFilePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to read admission control configuration from %q [%v]", configFilePath, err)
|
||||
}
|
||||
codecs := serializer.NewCodecFactory(configScheme, serializer.EnableStrict)
|
||||
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,
|
||||
}, 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 := &apiserverv1.AdmissionConfiguration{}
|
||||
for _, pluginName := range pluginNames {
|
||||
if legacyPluginsWithUnversionedConfig.Has(pluginName) {
|
||||
externalConfig.Plugins = append(externalConfig.Plugins,
|
||||
apiserverv1.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,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type configProvider struct {
|
||||
config *apiserver.AdmissionConfiguration
|
||||
}
|
||||
|
||||
// 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 := os.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
|
||||
}
|
166
e2e/vendor/k8s.io/apiserver/pkg/admission/configuration/configuration_manager.go
generated
vendored
Normal file
166
e2e/vendor/k8s.io/apiserver/pkg/admission/configuration/configuration_manager.go
generated
vendored
Normal 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)
|
||||
}
|
157
e2e/vendor/k8s.io/apiserver/pkg/admission/configuration/mutating_webhook_manager.go
generated
vendored
Normal file
157
e2e/vendor/k8s.io/apiserver/pkg/admission/configuration/mutating_webhook_manager.go
generated
vendored
Normal file
@ -0,0 +1,157 @@
|
||||
/*
|
||||
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"
|
||||
|
||||
v1 "k8s.io/api/admissionregistration/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
|
||||
"k8s.io/client-go/informers"
|
||||
admissionregistrationlisters "k8s.io/client-go/listers/admissionregistration/v1"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/client-go/tools/cache/synctrack"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
// Type for test injection.
|
||||
type mutatingWebhookAccessorCreator func(uid string, configurationName string, h *v1.MutatingWebhook) webhook.WebhookAccessor
|
||||
|
||||
// mutatingWebhookConfigurationManager collects the mutating webhook objects so that they can be called.
|
||||
type mutatingWebhookConfigurationManager struct {
|
||||
lister admissionregistrationlisters.MutatingWebhookConfigurationLister
|
||||
hasSynced func() bool
|
||||
lazy synctrack.Lazy[[]webhook.WebhookAccessor]
|
||||
configurationsCache sync.Map
|
||||
// createMutatingWebhookAccessor is used to instantiate webhook accessors.
|
||||
// This function is defined as field instead of a struct method to allow injection
|
||||
// during tests
|
||||
createMutatingWebhookAccessor mutatingWebhookAccessorCreator
|
||||
}
|
||||
|
||||
var _ generic.Source = &mutatingWebhookConfigurationManager{}
|
||||
|
||||
func NewMutatingWebhookConfigurationManager(f informers.SharedInformerFactory) generic.Source {
|
||||
informer := f.Admissionregistration().V1().MutatingWebhookConfigurations()
|
||||
manager := &mutatingWebhookConfigurationManager{
|
||||
lister: informer.Lister(),
|
||||
createMutatingWebhookAccessor: webhook.NewMutatingWebhookAccessor,
|
||||
}
|
||||
manager.lazy.Evaluate = manager.getConfiguration
|
||||
|
||||
handle, _ := informer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: func(_ interface{}) { manager.lazy.Notify() },
|
||||
UpdateFunc: func(old, new interface{}) {
|
||||
obj := new.(*v1.MutatingWebhookConfiguration)
|
||||
manager.configurationsCache.Delete(obj.GetName())
|
||||
manager.lazy.Notify()
|
||||
},
|
||||
DeleteFunc: func(obj interface{}) {
|
||||
vwc, ok := obj.(*v1.MutatingWebhookConfiguration)
|
||||
if !ok {
|
||||
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
|
||||
if !ok {
|
||||
klog.V(2).Infof("Couldn't get object from tombstone %#v", obj)
|
||||
return
|
||||
}
|
||||
vwc, ok = tombstone.Obj.(*v1.MutatingWebhookConfiguration)
|
||||
if !ok {
|
||||
klog.V(2).Infof("Tombstone contained object that is not expected %#v", obj)
|
||||
return
|
||||
}
|
||||
}
|
||||
manager.configurationsCache.Delete(vwc.Name)
|
||||
manager.lazy.Notify()
|
||||
},
|
||||
})
|
||||
manager.hasSynced = handle.HasSynced
|
||||
|
||||
return manager
|
||||
}
|
||||
|
||||
// Webhooks returns the merged MutatingWebhookConfiguration.
|
||||
func (m *mutatingWebhookConfigurationManager) Webhooks() []webhook.WebhookAccessor {
|
||||
out, err := m.lazy.Get()
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("error getting webhook configuration: %v", err))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// HasSynced returns true if the initial set of mutating webhook configurations
|
||||
// has been loaded.
|
||||
func (m *mutatingWebhookConfigurationManager) HasSynced() bool { return m.hasSynced() }
|
||||
|
||||
func (m *mutatingWebhookConfigurationManager) getConfiguration() ([]webhook.WebhookAccessor, error) {
|
||||
configurations, err := m.lister.List(labels.Everything())
|
||||
if err != nil {
|
||||
return []webhook.WebhookAccessor{}, err
|
||||
}
|
||||
return m.getMutatingWebhookConfigurations(configurations), nil
|
||||
}
|
||||
|
||||
// getMutatingWebhookConfigurations returns the webhook accessors for a given list of
|
||||
// mutating webhook configurations.
|
||||
//
|
||||
// This function will, first, try to load the webhook accessors from the cache and avoid
|
||||
// recreating them, which can be expessive (requiring CEL expression recompilation).
|
||||
func (m *mutatingWebhookConfigurationManager) getMutatingWebhookConfigurations(configurations []*v1.MutatingWebhookConfiguration) []webhook.WebhookAccessor {
|
||||
// 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)
|
||||
size := 0
|
||||
for _, cfg := range configurations {
|
||||
size += len(cfg.Webhooks)
|
||||
}
|
||||
accessors := make([]webhook.WebhookAccessor, 0, size)
|
||||
|
||||
for _, c := range configurations {
|
||||
cachedConfigurationAccessors, ok := m.configurationsCache.Load(c.Name)
|
||||
if ok {
|
||||
// Pick an already cached webhookAccessor
|
||||
accessors = append(accessors, cachedConfigurationAccessors.([]webhook.WebhookAccessor)...)
|
||||
continue
|
||||
}
|
||||
|
||||
// webhook names are not validated for uniqueness, so we check for duplicates and
|
||||
// add a int suffix to distinguish between them
|
||||
names := map[string]int{}
|
||||
configurationAccessors := make([]webhook.WebhookAccessor, 0, len(c.Webhooks))
|
||||
for i := range c.Webhooks {
|
||||
n := c.Webhooks[i].Name
|
||||
uid := fmt.Sprintf("%s/%s/%d", c.Name, n, names[n])
|
||||
names[n]++
|
||||
configurationAccessor := m.createMutatingWebhookAccessor(uid, c.Name, &c.Webhooks[i])
|
||||
configurationAccessors = append(configurationAccessors, configurationAccessor)
|
||||
}
|
||||
accessors = append(accessors, configurationAccessors...)
|
||||
m.configurationsCache.Store(c.Name, configurationAccessors)
|
||||
}
|
||||
return accessors
|
||||
}
|
||||
|
||||
type MutatingWebhookConfigurationSorter []*v1.MutatingWebhookConfiguration
|
||||
|
||||
func (a MutatingWebhookConfigurationSorter) ByName(i, j int) bool {
|
||||
return a[i].Name < a[j].Name
|
||||
}
|
155
e2e/vendor/k8s.io/apiserver/pkg/admission/configuration/validating_webhook_manager.go
generated
vendored
Normal file
155
e2e/vendor/k8s.io/apiserver/pkg/admission/configuration/validating_webhook_manager.go
generated
vendored
Normal file
@ -0,0 +1,155 @@
|
||||
/*
|
||||
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"
|
||||
|
||||
v1 "k8s.io/api/admissionregistration/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
|
||||
"k8s.io/client-go/informers"
|
||||
admissionregistrationlisters "k8s.io/client-go/listers/admissionregistration/v1"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/client-go/tools/cache/synctrack"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
// Type for test injection.
|
||||
type validatingWebhookAccessorCreator func(uid string, configurationName string, h *v1.ValidatingWebhook) webhook.WebhookAccessor
|
||||
|
||||
// validatingWebhookConfigurationManager collects the validating webhook objects so that they can be called.
|
||||
type validatingWebhookConfigurationManager struct {
|
||||
lister admissionregistrationlisters.ValidatingWebhookConfigurationLister
|
||||
hasSynced func() bool
|
||||
lazy synctrack.Lazy[[]webhook.WebhookAccessor]
|
||||
configurationsCache sync.Map
|
||||
// createValidatingWebhookAccessor is used to instantiate webhook accessors.
|
||||
// This function is defined as field instead of a struct method to allow injection
|
||||
// during tests
|
||||
createValidatingWebhookAccessor validatingWebhookAccessorCreator
|
||||
}
|
||||
|
||||
var _ generic.Source = &validatingWebhookConfigurationManager{}
|
||||
|
||||
func NewValidatingWebhookConfigurationManager(f informers.SharedInformerFactory) generic.Source {
|
||||
informer := f.Admissionregistration().V1().ValidatingWebhookConfigurations()
|
||||
manager := &validatingWebhookConfigurationManager{
|
||||
lister: informer.Lister(),
|
||||
createValidatingWebhookAccessor: webhook.NewValidatingWebhookAccessor,
|
||||
}
|
||||
manager.lazy.Evaluate = manager.getConfiguration
|
||||
|
||||
handle, _ := informer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: func(_ interface{}) { manager.lazy.Notify() },
|
||||
UpdateFunc: func(old, new interface{}) {
|
||||
obj := new.(*v1.ValidatingWebhookConfiguration)
|
||||
manager.configurationsCache.Delete(obj.GetName())
|
||||
manager.lazy.Notify()
|
||||
},
|
||||
DeleteFunc: func(obj interface{}) {
|
||||
vwc, ok := obj.(*v1.ValidatingWebhookConfiguration)
|
||||
if !ok {
|
||||
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
|
||||
if !ok {
|
||||
klog.V(2).Infof("Couldn't get object from tombstone %#v", obj)
|
||||
return
|
||||
}
|
||||
vwc, ok = tombstone.Obj.(*v1.ValidatingWebhookConfiguration)
|
||||
if !ok {
|
||||
klog.V(2).Infof("Tombstone contained object that is not expected %#v", obj)
|
||||
return
|
||||
}
|
||||
}
|
||||
manager.configurationsCache.Delete(vwc.Name)
|
||||
manager.lazy.Notify()
|
||||
},
|
||||
})
|
||||
manager.hasSynced = handle.HasSynced
|
||||
|
||||
return manager
|
||||
}
|
||||
|
||||
// Webhooks returns the merged ValidatingWebhookConfiguration.
|
||||
func (v *validatingWebhookConfigurationManager) Webhooks() []webhook.WebhookAccessor {
|
||||
out, err := v.lazy.Get()
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("error getting webhook configuration: %v", err))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// HasSynced returns true if the initial set of validating webhook configurations
|
||||
// has been loaded.
|
||||
func (v *validatingWebhookConfigurationManager) HasSynced() bool { return v.hasSynced() }
|
||||
|
||||
func (v *validatingWebhookConfigurationManager) getConfiguration() ([]webhook.WebhookAccessor, error) {
|
||||
configurations, err := v.lister.List(labels.Everything())
|
||||
if err != nil {
|
||||
return []webhook.WebhookAccessor{}, err
|
||||
}
|
||||
return v.getValidatingWebhookConfigurations(configurations), nil
|
||||
}
|
||||
|
||||
// getMutatingWebhookConfigurations returns the webhook accessors for a given list of
|
||||
// mutating webhook configurations.
|
||||
//
|
||||
// This function will, first, try to load the webhook accessors from the cache and avoid
|
||||
// recreating them, which can be expessive (requiring CEL expression recompilation).
|
||||
func (v *validatingWebhookConfigurationManager) getValidatingWebhookConfigurations(configurations []*v1.ValidatingWebhookConfiguration) []webhook.WebhookAccessor {
|
||||
sort.SliceStable(configurations, ValidatingWebhookConfigurationSorter(configurations).ByName)
|
||||
size := 0
|
||||
for _, cfg := range configurations {
|
||||
size += len(cfg.Webhooks)
|
||||
}
|
||||
accessors := make([]webhook.WebhookAccessor, 0, size)
|
||||
|
||||
for _, c := range configurations {
|
||||
cachedConfigurationAccessors, ok := v.configurationsCache.Load(c.Name)
|
||||
if ok {
|
||||
// Pick an already cached webhookAccessor
|
||||
accessors = append(accessors, cachedConfigurationAccessors.([]webhook.WebhookAccessor)...)
|
||||
continue
|
||||
}
|
||||
|
||||
// webhook names are not validated for uniqueness, so we check for duplicates and
|
||||
// add a int suffix to distinguish between them
|
||||
names := map[string]int{}
|
||||
configurationAccessors := make([]webhook.WebhookAccessor, 0, len(c.Webhooks))
|
||||
for i := range c.Webhooks {
|
||||
n := c.Webhooks[i].Name
|
||||
uid := fmt.Sprintf("%s/%s/%d", c.Name, n, names[n])
|
||||
names[n]++
|
||||
configurationAccessor := v.createValidatingWebhookAccessor(uid, c.Name, &c.Webhooks[i])
|
||||
configurationAccessors = append(configurationAccessors, configurationAccessor)
|
||||
}
|
||||
accessors = append(accessors, configurationAccessors...)
|
||||
v.configurationsCache.Store(c.Name, configurationAccessors)
|
||||
}
|
||||
|
||||
return accessors
|
||||
}
|
||||
|
||||
type ValidatingWebhookConfigurationSorter []*v1.ValidatingWebhookConfiguration
|
||||
|
||||
func (a ValidatingWebhookConfigurationSorter) ByName(i, j int) bool {
|
||||
return a[i].Name < a[j].Name
|
||||
}
|
136
e2e/vendor/k8s.io/apiserver/pkg/admission/conversion.go
generated
vendored
Normal file
136
e2e/vendor/k8s.io/apiserver/pkg/admission/conversion.go
generated
vendored
Normal file
@ -0,0 +1,136 @@
|
||||
/*
|
||||
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 (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// VersionedAttributes is a wrapper around the original admission attributes, adding versioned
|
||||
// variants of the object and old object.
|
||||
type VersionedAttributes struct {
|
||||
// Attributes holds the original admission attributes
|
||||
Attributes
|
||||
// VersionedOldObject holds Attributes.OldObject (if non-nil), converted to VersionedKind.
|
||||
// It must never be mutated.
|
||||
VersionedOldObject runtime.Object
|
||||
// VersionedObject holds Attributes.Object (if non-nil), converted to VersionedKind.
|
||||
// If mutated, Dirty must be set to true by the mutator.
|
||||
VersionedObject runtime.Object
|
||||
// VersionedKind holds the fully qualified kind
|
||||
VersionedKind schema.GroupVersionKind
|
||||
// Dirty indicates VersionedObject has been modified since being converted from Attributes.Object
|
||||
Dirty bool
|
||||
}
|
||||
|
||||
// GetObject overrides the Attributes.GetObject()
|
||||
func (v *VersionedAttributes) GetObject() runtime.Object {
|
||||
if v.VersionedObject != nil {
|
||||
return v.VersionedObject
|
||||
}
|
||||
return v.Attributes.GetObject()
|
||||
}
|
||||
|
||||
// ConvertToGVK converts object to the desired gvk.
|
||||
func ConvertToGVK(obj runtime.Object, gvk schema.GroupVersionKind, o ObjectInterfaces) (runtime.Object, error) {
|
||||
// Unlike other resources, custom resources do not have internal version, so
|
||||
// if obj is a custom resource, it should not need conversion.
|
||||
if obj.GetObjectKind().GroupVersionKind() == gvk {
|
||||
return obj, nil
|
||||
}
|
||||
out, err := o.GetObjectCreater().New(gvk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = o.GetObjectConvertor().Convert(obj, out, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Explicitly set the GVK
|
||||
out.GetObjectKind().SetGroupVersionKind(gvk)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// NewVersionedAttributes returns versioned attributes with the old and new object (if non-nil) converted to the requested kind
|
||||
func NewVersionedAttributes(attr Attributes, gvk schema.GroupVersionKind, o ObjectInterfaces) (*VersionedAttributes, error) {
|
||||
// convert the old and new objects to the requested version
|
||||
versionedAttr := &VersionedAttributes{
|
||||
Attributes: attr,
|
||||
VersionedKind: gvk,
|
||||
}
|
||||
if oldObj := attr.GetOldObject(); oldObj != nil {
|
||||
out, err := ConvertToGVK(oldObj, gvk, o)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
versionedAttr.VersionedOldObject = out
|
||||
}
|
||||
if obj := attr.GetObject(); obj != nil {
|
||||
out, err := ConvertToGVK(obj, gvk, o)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
versionedAttr.VersionedObject = out
|
||||
}
|
||||
return versionedAttr, nil
|
||||
}
|
||||
|
||||
// ConvertVersionedAttributes converts VersionedObject and VersionedOldObject to the specified kind, if needed.
|
||||
// If attr.VersionedKind already matches the requested kind, no conversion is performed.
|
||||
// If conversion is required:
|
||||
// * attr.VersionedObject is used as the source for the new object if Dirty=true (and is round-tripped through attr.Attributes.Object, clearing Dirty in the process)
|
||||
// * attr.Attributes.Object is used as the source for the new object if Dirty=false
|
||||
// * attr.Attributes.OldObject is used as the source for the old object
|
||||
func ConvertVersionedAttributes(attr *VersionedAttributes, gvk schema.GroupVersionKind, o ObjectInterfaces) error {
|
||||
// we already have the desired kind, we're done
|
||||
if attr.VersionedKind == gvk {
|
||||
return nil
|
||||
}
|
||||
|
||||
// convert the original old object to the desired GVK
|
||||
if oldObj := attr.Attributes.GetOldObject(); oldObj != nil {
|
||||
out, err := ConvertToGVK(oldObj, gvk, o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
attr.VersionedOldObject = out
|
||||
}
|
||||
|
||||
if attr.VersionedObject != nil {
|
||||
// convert the existing versioned object to internal
|
||||
if attr.Dirty {
|
||||
err := o.GetObjectConvertor().Convert(attr.VersionedObject, attr.Attributes.GetObject(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// and back to external
|
||||
out, err := ConvertToGVK(attr.Attributes.GetObject(), gvk, o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
attr.VersionedObject = out
|
||||
}
|
||||
|
||||
// Remember we converted to this version
|
||||
attr.VersionedKind = gvk
|
||||
attr.Dirty = false
|
||||
|
||||
return nil
|
||||
}
|
39
e2e/vendor/k8s.io/apiserver/pkg/admission/decorator.go
generated
vendored
Normal file
39
e2e/vendor/k8s.io/apiserver/pkg/admission/decorator.go
generated
vendored
Normal 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
e2e/vendor/k8s.io/apiserver/pkg/admission/errors.go
generated
vendored
Normal file
72
e2e/vendor/k8s.io/apiserver/pkg/admission/errors.go
generated
vendored
Normal 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
e2e/vendor/k8s.io/apiserver/pkg/admission/handler.go
generated
vendored
Normal file
79
e2e/vendor/k8s.io/apiserver/pkg/admission/handler.go
generated
vendored
Normal 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
|
||||
}
|
95
e2e/vendor/k8s.io/apiserver/pkg/admission/initializer/initializer.go
generated
vendored
Normal file
95
e2e/vendor/k8s.io/apiserver/pkg/admission/initializer/initializer.go
generated
vendored
Normal file
@ -0,0 +1,95 @@
|
||||
/*
|
||||
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/api/meta"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/component-base/featuregate"
|
||||
)
|
||||
|
||||
type pluginInitializer struct {
|
||||
externalClient kubernetes.Interface
|
||||
dynamicClient dynamic.Interface
|
||||
externalInformers informers.SharedInformerFactory
|
||||
authorizer authorizer.Authorizer
|
||||
featureGates featuregate.FeatureGate
|
||||
stopCh <-chan struct{}
|
||||
restMapper meta.RESTMapper
|
||||
}
|
||||
|
||||
// New creates an instance of admission plugins initializer.
|
||||
// This constructor is public with a long param list so that callers immediately know that new information can be expected
|
||||
// during compilation when they update a level.
|
||||
func New(
|
||||
extClientset kubernetes.Interface,
|
||||
dynamicClient dynamic.Interface,
|
||||
extInformers informers.SharedInformerFactory,
|
||||
authz authorizer.Authorizer,
|
||||
featureGates featuregate.FeatureGate,
|
||||
stopCh <-chan struct{},
|
||||
restMapper meta.RESTMapper,
|
||||
) pluginInitializer {
|
||||
return pluginInitializer{
|
||||
externalClient: extClientset,
|
||||
dynamicClient: dynamicClient,
|
||||
externalInformers: extInformers,
|
||||
authorizer: authz,
|
||||
featureGates: featureGates,
|
||||
stopCh: stopCh,
|
||||
restMapper: restMapper,
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize checks the initialization interfaces implemented by a plugin
|
||||
// and provide the appropriate initialization data
|
||||
func (i pluginInitializer) Initialize(plugin admission.Interface) {
|
||||
// First tell the plugin about drained notification, so it can pass it to further initializations.
|
||||
if wants, ok := plugin.(WantsDrainedNotification); ok {
|
||||
wants.SetDrainedNotification(i.stopCh)
|
||||
}
|
||||
|
||||
// Second tell the plugin about enabled features, so it can decide whether to start informers or not
|
||||
if wants, ok := plugin.(WantsFeatures); ok {
|
||||
wants.InspectFeatureGates(i.featureGates)
|
||||
}
|
||||
|
||||
if wants, ok := plugin.(WantsExternalKubeClientSet); ok {
|
||||
wants.SetExternalKubeClientSet(i.externalClient)
|
||||
}
|
||||
|
||||
if wants, ok := plugin.(WantsDynamicClient); ok {
|
||||
wants.SetDynamicClient(i.dynamicClient)
|
||||
}
|
||||
|
||||
if wants, ok := plugin.(WantsExternalKubeInformerFactory); ok {
|
||||
wants.SetExternalKubeInformerFactory(i.externalInformers)
|
||||
}
|
||||
|
||||
if wants, ok := plugin.(WantsAuthorizer); ok {
|
||||
wants.SetAuthorizer(i.authorizer)
|
||||
}
|
||||
if wants, ok := plugin.(WantsRESTMapper); ok {
|
||||
wants.SetRESTMapper(i.restMapper)
|
||||
}
|
||||
}
|
||||
|
||||
var _ admission.PluginInitializer = pluginInitializer{}
|
99
e2e/vendor/k8s.io/apiserver/pkg/admission/initializer/interfaces.go
generated
vendored
Normal file
99
e2e/vendor/k8s.io/apiserver/pkg/admission/initializer/interfaces.go
generated
vendored
Normal file
@ -0,0 +1,99 @@
|
||||
/*
|
||||
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/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/apiserver/pkg/cel/openapi/resolver"
|
||||
quota "k8s.io/apiserver/pkg/quota/v1"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/component-base/featuregate"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// WantsQuotaConfiguration defines a function which sets quota configuration for admission plugins that need it.
|
||||
type WantsQuotaConfiguration interface {
|
||||
SetQuotaConfiguration(quota.Configuration)
|
||||
admission.InitializationValidator
|
||||
}
|
||||
|
||||
// WantsDrainedNotification defines a function which sets the notification of where the apiserver
|
||||
// has already been drained for admission plugins that need it.
|
||||
// After receiving that notification, Admit/Validate calls won't be called anymore.
|
||||
type WantsDrainedNotification interface {
|
||||
SetDrainedNotification(<-chan struct{})
|
||||
admission.InitializationValidator
|
||||
}
|
||||
|
||||
// WantsFeatureGate defines a function which passes the featureGates for inspection by an admission plugin.
|
||||
// Admission plugins should not hold a reference to the featureGates. Instead, they should query a particular one
|
||||
// and assign it to a simple bool in the admission plugin struct.
|
||||
//
|
||||
// func (a *admissionPlugin) InspectFeatureGates(features featuregate.FeatureGate){
|
||||
// a.myFeatureIsOn = features.Enabled("my-feature")
|
||||
// }
|
||||
type WantsFeatures interface {
|
||||
InspectFeatureGates(featuregate.FeatureGate)
|
||||
admission.InitializationValidator
|
||||
}
|
||||
|
||||
type WantsDynamicClient interface {
|
||||
SetDynamicClient(dynamic.Interface)
|
||||
admission.InitializationValidator
|
||||
}
|
||||
|
||||
// WantsRESTMapper defines a function which sets RESTMapper for admission plugins that need it.
|
||||
type WantsRESTMapper interface {
|
||||
SetRESTMapper(meta.RESTMapper)
|
||||
admission.InitializationValidator
|
||||
}
|
||||
|
||||
// WantsSchemaResolver defines a function which sets the SchemaResolver for
|
||||
// an admission plugin that needs it.
|
||||
type WantsSchemaResolver interface {
|
||||
SetSchemaResolver(resolver resolver.SchemaResolver)
|
||||
admission.InitializationValidator
|
||||
}
|
||||
|
||||
// WantsExcludedAdmissionResources defines a function which sets the ExcludedAdmissionResources
|
||||
// for an admission plugin that needs it.
|
||||
type WantsExcludedAdmissionResources interface {
|
||||
SetExcludedAdmissionResources(excludedAdmissionResources []schema.GroupResource)
|
||||
admission.InitializationValidator
|
||||
}
|
172
e2e/vendor/k8s.io/apiserver/pkg/admission/interfaces.go
generated
vendored
Normal file
172
e2e/vendor/k8s.io/apiserver/pkg/admission/interfaces.go
generated
vendored
Normal file
@ -0,0 +1,172 @@
|
||||
/*
|
||||
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 (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||
"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
|
||||
// GetOperationOptions is the options for the operation being performed
|
||||
GetOperationOptions() runtime.Object
|
||||
// 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 and DELETE 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.
|
||||
// By default, an annotation gets logged into audit event if the request's audit level is greater or
|
||||
// equal to Metadata.
|
||||
AddAnnotation(key, value string) error
|
||||
|
||||
// AddAnnotationWithLevel sets annotation according to key-value pair with additional intended audit level.
|
||||
// An Annotation gets logged into audit event if the request's audit level is greater or equal to the
|
||||
// intended audit level.
|
||||
AddAnnotationWithLevel(key, value string, level auditinternal.Level) error
|
||||
|
||||
// GetReinvocationContext tracks the admission request information relevant to the re-invocation policy.
|
||||
GetReinvocationContext() ReinvocationContext
|
||||
}
|
||||
|
||||
// ObjectInterfaces is an interface used by AdmissionController to get object interfaces
|
||||
// such as Converter or Defaulter. These interfaces are normally coming from Request Scope
|
||||
// to handle special cases like CRDs.
|
||||
type ObjectInterfaces interface {
|
||||
// GetObjectCreater is the ObjectCreator appropriate for the requested object.
|
||||
GetObjectCreater() runtime.ObjectCreater
|
||||
// GetObjectTyper is the ObjectTyper appropriate for the requested object.
|
||||
GetObjectTyper() runtime.ObjectTyper
|
||||
// GetObjectDefaulter is the ObjectDefaulter appropriate for the requested object.
|
||||
GetObjectDefaulter() runtime.ObjectDefaulter
|
||||
// GetObjectConvertor is the ObjectConvertor appropriate for the requested object.
|
||||
GetObjectConvertor() runtime.ObjectConvertor
|
||||
// GetEquivalentResourceMapper is the EquivalentResourceMapper appropriate for finding equivalent resources and expected kind for the requested object.
|
||||
GetEquivalentResourceMapper() runtime.EquivalentResourceMapper
|
||||
}
|
||||
|
||||
// privateAnnotationsGetter is a private interface which allows users to get annotations from Attributes.
|
||||
type privateAnnotationsGetter interface {
|
||||
getAnnotations(maxLevel auditinternal.Level) map[string]string
|
||||
}
|
||||
|
||||
// AnnotationsGetter allows users to get annotations from Attributes. An alternate Attribute should implement
|
||||
// this interface.
|
||||
type AnnotationsGetter interface {
|
||||
GetAnnotations(maxLevel auditinternal.Level) map[string]string
|
||||
}
|
||||
|
||||
// ReinvocationContext provides access to the admission related state required to implement the re-invocation policy.
|
||||
type ReinvocationContext interface {
|
||||
// IsReinvoke returns true if the current admission check is a re-invocation.
|
||||
IsReinvoke() bool
|
||||
// SetIsReinvoke sets the current admission check as a re-invocation.
|
||||
SetIsReinvoke()
|
||||
// ShouldReinvoke returns true if any plugin has requested a re-invocation.
|
||||
ShouldReinvoke() bool
|
||||
// SetShouldReinvoke signals that a re-invocation is desired.
|
||||
SetShouldReinvoke()
|
||||
// AddValue set a value for a plugin name, possibly overriding a previous value.
|
||||
SetValue(plugin string, v interface{})
|
||||
// Value reads a value for a webhook.
|
||||
Value(plugin string) interface{}
|
||||
}
|
||||
|
||||
// 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.
|
||||
// Context is used only for timeout/deadline/cancellation and tracing information.
|
||||
Admit(ctx context.Context, a Attributes, o ObjectInterfaces) (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
|
||||
// Context is used only for timeout/deadline/cancellation and tracing information.
|
||||
Validate(ctx context.Context, a Attributes, o ObjectInterfaces) (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)
|
||||
}
|
356
e2e/vendor/k8s.io/apiserver/pkg/admission/metrics/metrics.go
generated
vendored
Normal file
356
e2e/vendor/k8s.io/apiserver/pkg/admission/metrics/metrics.go
generated
vendored
Normal file
@ -0,0 +1,356 @@
|
||||
/*
|
||||
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 (
|
||||
"context"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/component-base/metrics"
|
||||
"k8s.io/component-base/metrics/legacyregistry"
|
||||
)
|
||||
|
||||
// WebhookRejectionErrorType defines different error types that happen in a webhook rejection.
|
||||
type WebhookRejectionErrorType string
|
||||
|
||||
const (
|
||||
namespace = "apiserver"
|
||||
subsystem = "admission"
|
||||
|
||||
// WebhookRejectionCallingWebhookError identifies a calling webhook error which causes
|
||||
// a webhook admission to reject a request
|
||||
WebhookRejectionCallingWebhookError WebhookRejectionErrorType = "calling_webhook_error"
|
||||
// WebhookRejectionAPIServerInternalError identifies an apiserver internal error which
|
||||
// causes a webhook admission to reject a request
|
||||
WebhookRejectionAPIServerInternalError WebhookRejectionErrorType = "apiserver_internal_error"
|
||||
// WebhookRejectionNoError identifies a webhook properly rejected a request
|
||||
WebhookRejectionNoError WebhookRejectionErrorType = "no_error"
|
||||
)
|
||||
|
||||
var (
|
||||
latencySummaryMaxAge = 5 * time.Hour
|
||||
|
||||
// Metrics provides access to all admission metrics.
|
||||
Metrics = newAdmissionMetrics()
|
||||
)
|
||||
|
||||
// ObserverFunc is a func that emits metrics.
|
||||
type ObserverFunc func(ctx context.Context, elapsed time.Duration, rejected bool, attr admission.Attributes, stepType string, extraLabels ...string)
|
||||
|
||||
const (
|
||||
kindWebhook = "webhook"
|
||||
kindPolicy = "policy"
|
||||
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(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
|
||||
mutatingHandler, ok := p.Interface.(admission.MutationInterface)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
err := mutatingHandler.Admit(ctx, a, o)
|
||||
p.observer(ctx, 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(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
|
||||
validatingHandler, ok := p.Interface.(admission.ValidationInterface)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
err := validatingHandler.Validate(ctx, a, o)
|
||||
p.observer(ctx, 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
|
||||
webhookRejection *metrics.CounterVec
|
||||
webhookFailOpen *metrics.CounterVec
|
||||
webhookRequest *metrics.CounterVec
|
||||
matchConditionEvalErrors *metrics.CounterVec
|
||||
matchConditionExclusions *metrics.CounterVec
|
||||
matchConditionEvaluationSeconds *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.
|
||||
// Use buckets ranging from 5 ms to 2.5 seconds.
|
||||
step := &metricSet{
|
||||
latencies: metrics.NewHistogramVec(
|
||||
&metrics.HistogramOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "step_admission_duration_seconds",
|
||||
Help: "Admission sub-step latency histogram in seconds, broken out for each operation and API resource and step type (validate or admit).",
|
||||
Buckets: []float64{0.005, 0.025, 0.1, 0.5, 1.0, 2.5},
|
||||
StabilityLevel: metrics.STABLE,
|
||||
},
|
||||
[]string{"type", "operation", "rejected"},
|
||||
),
|
||||
|
||||
latenciesSummary: metrics.NewSummaryVec(
|
||||
&metrics.SummaryOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "step_admission_duration_seconds_summary",
|
||||
Help: "Admission sub-step latency summary in seconds, broken out for each operation and API resource and step type (validate or admit).",
|
||||
MaxAge: latencySummaryMaxAge,
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
[]string{"type", "operation", "rejected"},
|
||||
),
|
||||
}
|
||||
|
||||
// Built-in admission controller metrics. Each admission controller is identified by name.
|
||||
// Use buckets ranging from 5 ms to 2.5 seconds.
|
||||
controller := &metricSet{
|
||||
latencies: metrics.NewHistogramVec(
|
||||
&metrics.HistogramOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "controller_admission_duration_seconds",
|
||||
Help: "Admission controller latency histogram in seconds, identified by name and broken out for each operation and API resource and type (validate or admit).",
|
||||
Buckets: []float64{0.005, 0.025, 0.1, 0.5, 1.0, 2.5},
|
||||
StabilityLevel: metrics.STABLE,
|
||||
},
|
||||
[]string{"name", "type", "operation", "rejected"},
|
||||
),
|
||||
|
||||
latenciesSummary: nil,
|
||||
}
|
||||
|
||||
// Admission webhook metrics. Each webhook is identified by name.
|
||||
// Use buckets ranging from 5 ms to 2.5 seconds (admission webhooks timeout at 30 seconds by default).
|
||||
webhook := &metricSet{
|
||||
latencies: metrics.NewHistogramVec(
|
||||
&metrics.HistogramOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "webhook_admission_duration_seconds",
|
||||
Help: "Admission webhook latency histogram in seconds, identified by name and broken out for each operation and API resource and type (validate or admit).",
|
||||
Buckets: []float64{0.005, 0.025, 0.1, 0.5, 1.0, 2.5, 10, 25},
|
||||
StabilityLevel: metrics.STABLE,
|
||||
},
|
||||
[]string{"name", "type", "operation", "rejected"},
|
||||
),
|
||||
|
||||
latenciesSummary: nil,
|
||||
}
|
||||
|
||||
webhookRejection := metrics.NewCounterVec(
|
||||
&metrics.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "webhook_rejection_count",
|
||||
Help: "Admission webhook rejection count, identified by name and broken out for each admission type (validating or admit) and operation. Additional labels specify an error type (calling_webhook_error or apiserver_internal_error if an error occurred; no_error otherwise) and optionally a non-zero rejection code if the webhook rejects the request with an HTTP status code (honored by the apiserver when the code is greater or equal to 400). Codes greater than 600 are truncated to 600, to keep the metrics cardinality bounded.",
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
[]string{"name", "type", "operation", "error_type", "rejection_code"})
|
||||
|
||||
webhookFailOpen := metrics.NewCounterVec(
|
||||
&metrics.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "webhook_fail_open_count",
|
||||
Help: "Admission webhook fail open count, identified by name and broken out for each admission type (validating or admit).",
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
[]string{"name", "type"})
|
||||
|
||||
webhookRequest := metrics.NewCounterVec(
|
||||
&metrics.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "webhook_request_total",
|
||||
Help: "Admission webhook request total, identified by name and broken out for each admission type (validating or admit) and operation. Additional labels specify whether the request was rejected or not and an HTTP status code. Codes greater than 600 are truncated to 600, to keep the metrics cardinality bounded.",
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
[]string{"name", "type", "operation", "code", "rejected"})
|
||||
|
||||
matchConditionEvalError := metrics.NewCounterVec(
|
||||
&metrics.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "match_condition_evaluation_errors_total",
|
||||
Help: "Admission match condition evaluation errors count, identified by name of resource containing the match condition and broken out for each kind containing matchConditions (webhook or policy), operation and admission type (validate or admit).",
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
[]string{"name", "kind", "type", "operation"})
|
||||
|
||||
matchConditionExclusions := metrics.NewCounterVec(
|
||||
&metrics.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "match_condition_exclusions_total",
|
||||
Help: "Admission match condition evaluation exclusions count, identified by name of resource containing the match condition and broken out for each kind containing matchConditions (webhook or policy), operation and admission type (validate or admit).",
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
[]string{"name", "kind", "type", "operation"})
|
||||
|
||||
matchConditionEvaluationSeconds := &metricSet{
|
||||
latencies: metrics.NewHistogramVec(
|
||||
&metrics.HistogramOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "match_condition_evaluation_seconds",
|
||||
Help: "Admission match condition evaluation time in seconds, identified by name and broken out for each kind containing matchConditions (webhook or policy), operation and type (validate or admit).",
|
||||
Buckets: []float64{0.001, 0.005, 0.01, 0.025, 0.1, 0.2, 0.25},
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
[]string{"name", "kind", "type", "operation"},
|
||||
),
|
||||
latenciesSummary: nil,
|
||||
}
|
||||
|
||||
step.mustRegister()
|
||||
controller.mustRegister()
|
||||
webhook.mustRegister()
|
||||
matchConditionEvaluationSeconds.mustRegister()
|
||||
legacyregistry.MustRegister(webhookRejection)
|
||||
legacyregistry.MustRegister(webhookFailOpen)
|
||||
legacyregistry.MustRegister(webhookRequest)
|
||||
legacyregistry.MustRegister(matchConditionEvalError)
|
||||
legacyregistry.MustRegister(matchConditionExclusions)
|
||||
return &AdmissionMetrics{step: step, controller: controller, webhook: webhook, webhookRejection: webhookRejection, webhookFailOpen: webhookFailOpen, webhookRequest: webhookRequest, matchConditionEvalErrors: matchConditionEvalError, matchConditionExclusions: matchConditionExclusions, matchConditionEvaluationSeconds: matchConditionEvaluationSeconds}
|
||||
}
|
||||
|
||||
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(ctx context.Context, elapsed time.Duration, rejected bool, attr admission.Attributes, stepType string, extraLabels ...string) {
|
||||
m.step.observe(ctx, 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(ctx context.Context, elapsed time.Duration, rejected bool, attr admission.Attributes, stepType string, extraLabels ...string) {
|
||||
m.controller.observe(ctx, elapsed, append(extraLabels, stepType, string(attr.GetOperation()), strconv.FormatBool(rejected))...)
|
||||
}
|
||||
|
||||
// ObserveWebhook records admission related metrics for a admission webhook.
|
||||
func (m *AdmissionMetrics) ObserveWebhook(ctx context.Context, name string, elapsed time.Duration, rejected bool, attr admission.Attributes, stepType string, code int) {
|
||||
// We truncate codes greater than 600 to keep the cardinality bounded.
|
||||
if code > 600 {
|
||||
code = 600
|
||||
}
|
||||
m.webhookRequest.WithContext(ctx).WithLabelValues(name, stepType, string(attr.GetOperation()), strconv.Itoa(code), strconv.FormatBool(rejected)).Inc()
|
||||
m.webhook.observe(ctx, elapsed, name, stepType, string(attr.GetOperation()), strconv.FormatBool(rejected))
|
||||
}
|
||||
|
||||
// ObserveWebhookRejection records admission related metrics for an admission webhook rejection.
|
||||
func (m *AdmissionMetrics) ObserveWebhookRejection(ctx context.Context, name, stepType, operation string, errorType WebhookRejectionErrorType, rejectionCode int) {
|
||||
// We truncate codes greater than 600 to keep the cardinality bounded.
|
||||
// This should be rarely done by a malfunctioning webhook server.
|
||||
if rejectionCode > 600 {
|
||||
rejectionCode = 600
|
||||
}
|
||||
m.webhookRejection.WithContext(ctx).WithLabelValues(name, stepType, operation, string(errorType), strconv.Itoa(rejectionCode)).Inc()
|
||||
}
|
||||
|
||||
// ObserveWebhookFailOpen records validating or mutating webhook that fail open.
|
||||
func (m *AdmissionMetrics) ObserveWebhookFailOpen(ctx context.Context, name, stepType string) {
|
||||
m.webhookFailOpen.WithContext(ctx).WithLabelValues(name, stepType).Inc()
|
||||
}
|
||||
|
||||
// ObserveMatchConditionEvalError records validating or mutating webhook that are not called due to match conditions
|
||||
func (m *AdmissionMetrics) ObserveMatchConditionEvalError(ctx context.Context, name, kind, stepType, operation string) {
|
||||
m.matchConditionEvalErrors.WithContext(ctx).WithLabelValues(name, kind, stepType, operation).Inc()
|
||||
}
|
||||
|
||||
// ObserveMatchConditionExclusion records validating or mutating webhook that are not called due to match conditions
|
||||
func (m *AdmissionMetrics) ObserveMatchConditionExclusion(ctx context.Context, name, kind, stepType, operation string) {
|
||||
m.matchConditionExclusions.WithContext(ctx).WithLabelValues(name, kind, stepType, operation).Inc()
|
||||
}
|
||||
|
||||
// ObserveMatchConditionEvaluationTime records duration of match condition evaluation process.
|
||||
func (m *AdmissionMetrics) ObserveMatchConditionEvaluationTime(ctx context.Context, elapsed time.Duration, name, kind, stepType, operation string) {
|
||||
m.matchConditionEvaluationSeconds.observe(ctx, elapsed, name, kind, stepType, operation)
|
||||
}
|
||||
|
||||
type metricSet struct {
|
||||
latencies *metrics.HistogramVec
|
||||
latenciesSummary *metrics.SummaryVec
|
||||
}
|
||||
|
||||
// MustRegister registers all the prometheus metrics in the metricSet.
|
||||
func (m *metricSet) mustRegister() {
|
||||
legacyregistry.MustRegister(m.latencies)
|
||||
if m.latenciesSummary != nil {
|
||||
legacyregistry.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(ctx context.Context, elapsed time.Duration, labels ...string) {
|
||||
elapsedSeconds := elapsed.Seconds()
|
||||
m.latencies.WithContext(ctx).WithLabelValues(labels...).Observe(elapsedSeconds)
|
||||
if m.latenciesSummary != nil {
|
||||
m.latenciesSummary.WithContext(ctx).WithLabelValues(labels...).Observe(elapsedSeconds)
|
||||
}
|
||||
}
|
155
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/authorizer/caching_authorizer.go
generated
vendored
Normal file
155
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/authorizer/caching_authorizer.go
generated
vendored
Normal file
@ -0,0 +1,155 @@
|
||||
/*
|
||||
Copyright 2023 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 authorizer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
)
|
||||
|
||||
type authzResult struct {
|
||||
authorized authorizer.Decision
|
||||
reason string
|
||||
err error
|
||||
}
|
||||
|
||||
type cachingAuthorizer struct {
|
||||
authorizer authorizer.Authorizer
|
||||
decisions map[string]authzResult
|
||||
}
|
||||
|
||||
// NewCachingAuthorizer returns an authorizer that caches decisions for the duration
|
||||
// of the authorizers use. Intended to be used for short-lived operations such as
|
||||
// the handling of a request in the admission chain, and then discarded.
|
||||
func NewCachingAuthorizer(in authorizer.Authorizer) authorizer.Authorizer {
|
||||
return &cachingAuthorizer{
|
||||
authorizer: in,
|
||||
decisions: make(map[string]authzResult),
|
||||
}
|
||||
}
|
||||
|
||||
// The attribute accessors known to cache key construction. If this fails to compile, the cache
|
||||
// implementation may need to be updated.
|
||||
var _ authorizer.Attributes = (interface {
|
||||
GetUser() user.Info
|
||||
GetVerb() string
|
||||
IsReadOnly() bool
|
||||
GetNamespace() string
|
||||
GetResource() string
|
||||
GetSubresource() string
|
||||
GetName() string
|
||||
GetAPIGroup() string
|
||||
GetAPIVersion() string
|
||||
IsResourceRequest() bool
|
||||
GetPath() string
|
||||
GetFieldSelector() (fields.Requirements, error)
|
||||
GetLabelSelector() (labels.Requirements, error)
|
||||
})(nil)
|
||||
|
||||
// The user info accessors known to cache key construction. If this fails to compile, the cache
|
||||
// implementation may need to be updated.
|
||||
var _ user.Info = (interface {
|
||||
GetName() string
|
||||
GetUID() string
|
||||
GetGroups() []string
|
||||
GetExtra() map[string][]string
|
||||
})(nil)
|
||||
|
||||
// Authorize returns an authorization decision by delegating to another Authorizer. If an equivalent
|
||||
// check has already been performed, a cached result is returned. Not safe for concurrent use.
|
||||
func (ca *cachingAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
|
||||
type SerializableAttributes struct {
|
||||
authorizer.AttributesRecord
|
||||
LabelSelector string
|
||||
}
|
||||
|
||||
serializableAttributes := SerializableAttributes{
|
||||
AttributesRecord: authorizer.AttributesRecord{
|
||||
Verb: a.GetVerb(),
|
||||
Namespace: a.GetNamespace(),
|
||||
APIGroup: a.GetAPIGroup(),
|
||||
APIVersion: a.GetAPIVersion(),
|
||||
Resource: a.GetResource(),
|
||||
Subresource: a.GetSubresource(),
|
||||
Name: a.GetName(),
|
||||
ResourceRequest: a.IsResourceRequest(),
|
||||
Path: a.GetPath(),
|
||||
},
|
||||
}
|
||||
// in the error case, we won't honor this field selector, so the cache doesn't need it.
|
||||
if fieldSelector, err := a.GetFieldSelector(); len(fieldSelector) > 0 {
|
||||
serializableAttributes.FieldSelectorRequirements, serializableAttributes.FieldSelectorParsingErr = fieldSelector, err
|
||||
}
|
||||
if labelSelector, _ := a.GetLabelSelector(); len(labelSelector) > 0 {
|
||||
// the labels requirements have private elements so those don't help us serialize to a unique key
|
||||
serializableAttributes.LabelSelector = labelSelector.String()
|
||||
}
|
||||
|
||||
if u := a.GetUser(); u != nil {
|
||||
di := &user.DefaultInfo{
|
||||
Name: u.GetName(),
|
||||
UID: u.GetUID(),
|
||||
}
|
||||
|
||||
// Differently-ordered groups or extras could cause otherwise-equivalent checks to
|
||||
// have distinct cache keys.
|
||||
if groups := u.GetGroups(); len(groups) > 0 {
|
||||
di.Groups = make([]string, len(groups))
|
||||
copy(di.Groups, groups)
|
||||
sort.Strings(di.Groups)
|
||||
}
|
||||
|
||||
if extra := u.GetExtra(); len(extra) > 0 {
|
||||
di.Extra = make(map[string][]string, len(extra))
|
||||
for k, vs := range extra {
|
||||
vdupe := make([]string, len(vs))
|
||||
copy(vdupe, vs)
|
||||
sort.Strings(vdupe)
|
||||
di.Extra[k] = vdupe
|
||||
}
|
||||
}
|
||||
|
||||
serializableAttributes.User = di
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
if err := json.NewEncoder(&b).Encode(serializableAttributes); err != nil {
|
||||
return authorizer.DecisionNoOpinion, "", err
|
||||
}
|
||||
key := b.String()
|
||||
|
||||
if cached, ok := ca.decisions[key]; ok {
|
||||
return cached.authorized, cached.reason, cached.err
|
||||
}
|
||||
|
||||
authorized, reason, err := ca.authorizer.Authorize(ctx, a)
|
||||
|
||||
ca.decisions[key] = authzResult{
|
||||
authorized: authorized,
|
||||
reason: reason,
|
||||
err: err,
|
||||
}
|
||||
|
||||
return authorized, reason, err
|
||||
}
|
10
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/cel/OWNERS
generated
vendored
Normal file
10
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/cel/OWNERS
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
# See the OWNERS docs at https://go.k8s.io/owners
|
||||
|
||||
approvers:
|
||||
- jpbetz
|
||||
- cici37
|
||||
- alexzielenski
|
||||
reviewers:
|
||||
- jpbetz
|
||||
- cici37
|
||||
- alexzielenski
|
190
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/cel/activation.go
generated
vendored
Normal file
190
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/cel/activation.go
generated
vendored
Normal file
@ -0,0 +1,190 @@
|
||||
/*
|
||||
Copyright 2024 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 cel
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/google/cel-go/interpreter"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/cel"
|
||||
"k8s.io/apiserver/pkg/cel/library"
|
||||
)
|
||||
|
||||
// newActivation creates an activation for CEL admission plugins from the given request, admission chain and
|
||||
// variable binding information.
|
||||
func newActivation(compositionCtx CompositionContext, versionedAttr *admission.VersionedAttributes, request *admissionv1.AdmissionRequest, inputs OptionalVariableBindings, namespace *v1.Namespace) (*evaluationActivation, error) {
|
||||
oldObjectVal, err := objectToResolveVal(versionedAttr.VersionedOldObject)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to prepare oldObject variable for evaluation: %w", err)
|
||||
}
|
||||
objectVal, err := objectToResolveVal(versionedAttr.VersionedObject)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to prepare object variable for evaluation: %w", err)
|
||||
}
|
||||
var paramsVal, authorizerVal, requestResourceAuthorizerVal any
|
||||
if inputs.VersionedParams != nil {
|
||||
paramsVal, err = objectToResolveVal(inputs.VersionedParams)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to prepare params variable for evaluation: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if inputs.Authorizer != nil {
|
||||
authorizerVal = library.NewAuthorizerVal(versionedAttr.GetUserInfo(), inputs.Authorizer)
|
||||
requestResourceAuthorizerVal = library.NewResourceAuthorizerVal(versionedAttr.GetUserInfo(), inputs.Authorizer, versionedAttr)
|
||||
}
|
||||
|
||||
requestVal, err := convertObjectToUnstructured(request)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to prepare request variable for evaluation: %w", err)
|
||||
}
|
||||
namespaceVal, err := objectToResolveVal(namespace)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to prepare namespace variable for evaluation: %w", err)
|
||||
}
|
||||
va := &evaluationActivation{
|
||||
object: objectVal,
|
||||
oldObject: oldObjectVal,
|
||||
params: paramsVal,
|
||||
request: requestVal.Object,
|
||||
namespace: namespaceVal,
|
||||
authorizer: authorizerVal,
|
||||
requestResourceAuthorizer: requestResourceAuthorizerVal,
|
||||
}
|
||||
|
||||
// composition is an optional feature that only applies for ValidatingAdmissionPolicy and MutatingAdmissionPolicy.
|
||||
if compositionCtx != nil {
|
||||
va.variables = compositionCtx.Variables(va)
|
||||
}
|
||||
return va, nil
|
||||
}
|
||||
|
||||
type evaluationActivation struct {
|
||||
object, oldObject, params, request, namespace, authorizer, requestResourceAuthorizer, variables interface{}
|
||||
}
|
||||
|
||||
// ResolveName returns a value from the activation by qualified name, or false if the name
|
||||
// could not be found.
|
||||
func (a *evaluationActivation) ResolveName(name string) (interface{}, bool) {
|
||||
switch name {
|
||||
case ObjectVarName:
|
||||
return a.object, true
|
||||
case OldObjectVarName:
|
||||
return a.oldObject, true
|
||||
case ParamsVarName:
|
||||
return a.params, true // params may be null
|
||||
case RequestVarName:
|
||||
return a.request, true
|
||||
case NamespaceVarName:
|
||||
return a.namespace, true
|
||||
case AuthorizerVarName:
|
||||
return a.authorizer, a.authorizer != nil
|
||||
case RequestResourceAuthorizerVarName:
|
||||
return a.requestResourceAuthorizer, a.requestResourceAuthorizer != nil
|
||||
case VariableVarName: // variables always present
|
||||
return a.variables, true
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
// Parent returns the parent of the current activation, may be nil.
|
||||
// If non-nil, the parent will be searched during resolve calls.
|
||||
func (a *evaluationActivation) Parent() interpreter.Activation {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Evaluate runs a compiled CEL admission plugin expression using the provided activation and CEL
|
||||
// runtime cost budget.
|
||||
func (a *evaluationActivation) Evaluate(ctx context.Context, compositionCtx CompositionContext, compilationResult CompilationResult, remainingBudget int64) (EvaluationResult, int64, error) {
|
||||
var evaluation = EvaluationResult{}
|
||||
if compilationResult.ExpressionAccessor == nil { // in case of placeholder
|
||||
return evaluation, remainingBudget, nil
|
||||
}
|
||||
|
||||
evaluation.ExpressionAccessor = compilationResult.ExpressionAccessor
|
||||
if compilationResult.Error != nil {
|
||||
evaluation.Error = &cel.Error{
|
||||
Type: cel.ErrorTypeInvalid,
|
||||
Detail: fmt.Sprintf("compilation error: %v", compilationResult.Error),
|
||||
Cause: compilationResult.Error,
|
||||
}
|
||||
return evaluation, remainingBudget, nil
|
||||
}
|
||||
if compilationResult.Program == nil {
|
||||
evaluation.Error = &cel.Error{
|
||||
Type: cel.ErrorTypeInternal,
|
||||
Detail: "unexpected internal error compiling expression",
|
||||
}
|
||||
return evaluation, remainingBudget, nil
|
||||
}
|
||||
t1 := time.Now()
|
||||
evalResult, evalDetails, err := compilationResult.Program.ContextEval(ctx, a)
|
||||
// budget may be spent due to lazy evaluation of composited variables
|
||||
if compositionCtx != nil {
|
||||
compositionCost := compositionCtx.GetAndResetCost()
|
||||
if compositionCost > remainingBudget {
|
||||
return evaluation, -1, &cel.Error{
|
||||
Type: cel.ErrorTypeInvalid,
|
||||
Detail: "validation failed due to running out of cost budget, no further validation rules will be run",
|
||||
Cause: cel.ErrOutOfBudget,
|
||||
}
|
||||
}
|
||||
remainingBudget -= compositionCost
|
||||
}
|
||||
elapsed := time.Since(t1)
|
||||
evaluation.Elapsed = elapsed
|
||||
if evalDetails == nil {
|
||||
return evaluation, -1, &cel.Error{
|
||||
Type: cel.ErrorTypeInternal,
|
||||
Detail: fmt.Sprintf("runtime cost could not be calculated for expression: %v, no further expression will be run", compilationResult.ExpressionAccessor.GetExpression()),
|
||||
}
|
||||
} else {
|
||||
rtCost := evalDetails.ActualCost()
|
||||
if rtCost == nil {
|
||||
return evaluation, -1, &cel.Error{
|
||||
Type: cel.ErrorTypeInvalid,
|
||||
Detail: fmt.Sprintf("runtime cost could not be calculated for expression: %v, no further expression will be run", compilationResult.ExpressionAccessor.GetExpression()),
|
||||
Cause: cel.ErrOutOfBudget,
|
||||
}
|
||||
} else {
|
||||
if *rtCost > math.MaxInt64 || int64(*rtCost) > remainingBudget {
|
||||
return evaluation, -1, &cel.Error{
|
||||
Type: cel.ErrorTypeInvalid,
|
||||
Detail: "validation failed due to running out of cost budget, no further validation rules will be run",
|
||||
Cause: cel.ErrOutOfBudget,
|
||||
}
|
||||
}
|
||||
remainingBudget -= int64(*rtCost)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
evaluation.Error = &cel.Error{
|
||||
Type: cel.ErrorTypeInvalid,
|
||||
Detail: fmt.Sprintf("expression '%v' resulted in error: %v", compilationResult.ExpressionAccessor.GetExpression(), err),
|
||||
}
|
||||
} else {
|
||||
evaluation.EvalResult = evalResult
|
||||
}
|
||||
return evaluation, remainingBudget, nil
|
||||
}
|
305
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/cel/compile.go
generated
vendored
Normal file
305
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/cel/compile.go
generated
vendored
Normal file
@ -0,0 +1,305 @@
|
||||
/*
|
||||
Copyright 2022 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 cel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/version"
|
||||
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||
apiservercel "k8s.io/apiserver/pkg/cel"
|
||||
"k8s.io/apiserver/pkg/cel/common"
|
||||
"k8s.io/apiserver/pkg/cel/environment"
|
||||
"k8s.io/apiserver/pkg/cel/library"
|
||||
"k8s.io/apiserver/pkg/cel/mutation"
|
||||
)
|
||||
|
||||
const (
|
||||
ObjectVarName = "object"
|
||||
OldObjectVarName = "oldObject"
|
||||
ParamsVarName = "params"
|
||||
RequestVarName = "request"
|
||||
NamespaceVarName = "namespaceObject"
|
||||
AuthorizerVarName = "authorizer"
|
||||
RequestResourceAuthorizerVarName = "authorizer.requestResource"
|
||||
VariableVarName = "variables"
|
||||
)
|
||||
|
||||
// BuildRequestType generates a DeclType for AdmissionRequest. This may be replaced with a utility that
|
||||
// converts the native type definition to apiservercel.DeclType once such a utility becomes available.
|
||||
// The 'uid' field is omitted since it is not needed for in-process admission review.
|
||||
// The 'object' and 'oldObject' fields are omitted since they are exposed as root level CEL variables.
|
||||
func BuildRequestType() *apiservercel.DeclType {
|
||||
field := func(name string, declType *apiservercel.DeclType, required bool) *apiservercel.DeclField {
|
||||
return apiservercel.NewDeclField(name, declType, required, nil, nil)
|
||||
}
|
||||
fields := func(fields ...*apiservercel.DeclField) map[string]*apiservercel.DeclField {
|
||||
result := make(map[string]*apiservercel.DeclField, len(fields))
|
||||
for _, f := range fields {
|
||||
result[f.Name] = f
|
||||
}
|
||||
return result
|
||||
}
|
||||
gvkType := apiservercel.NewObjectType("kubernetes.GroupVersionKind", fields(
|
||||
field("group", apiservercel.StringType, true),
|
||||
field("version", apiservercel.StringType, true),
|
||||
field("kind", apiservercel.StringType, true),
|
||||
))
|
||||
gvrType := apiservercel.NewObjectType("kubernetes.GroupVersionResource", fields(
|
||||
field("group", apiservercel.StringType, true),
|
||||
field("version", apiservercel.StringType, true),
|
||||
field("resource", apiservercel.StringType, true),
|
||||
))
|
||||
userInfoType := apiservercel.NewObjectType("kubernetes.UserInfo", fields(
|
||||
field("username", apiservercel.StringType, false),
|
||||
field("uid", apiservercel.StringType, false),
|
||||
field("groups", apiservercel.NewListType(apiservercel.StringType, -1), false),
|
||||
field("extra", apiservercel.NewMapType(apiservercel.StringType, apiservercel.NewListType(apiservercel.StringType, -1), -1), false),
|
||||
))
|
||||
return apiservercel.NewObjectType("kubernetes.AdmissionRequest", fields(
|
||||
field("kind", gvkType, true),
|
||||
field("resource", gvrType, true),
|
||||
field("subResource", apiservercel.StringType, false),
|
||||
field("requestKind", gvkType, true),
|
||||
field("requestResource", gvrType, true),
|
||||
field("requestSubResource", apiservercel.StringType, false),
|
||||
field("name", apiservercel.StringType, true),
|
||||
field("namespace", apiservercel.StringType, false),
|
||||
field("operation", apiservercel.StringType, true),
|
||||
field("userInfo", userInfoType, true),
|
||||
field("dryRun", apiservercel.BoolType, false),
|
||||
field("options", apiservercel.DynType, false),
|
||||
))
|
||||
}
|
||||
|
||||
// BuildNamespaceType generates a DeclType for Namespace.
|
||||
// Certain nested fields in Namespace (e.g. managedFields, ownerReferences etc.) are omitted in the generated DeclType
|
||||
// by design.
|
||||
func BuildNamespaceType() *apiservercel.DeclType {
|
||||
field := func(name string, declType *apiservercel.DeclType, required bool) *apiservercel.DeclField {
|
||||
return apiservercel.NewDeclField(name, declType, required, nil, nil)
|
||||
}
|
||||
fields := func(fields ...*apiservercel.DeclField) map[string]*apiservercel.DeclField {
|
||||
result := make(map[string]*apiservercel.DeclField, len(fields))
|
||||
for _, f := range fields {
|
||||
result[f.Name] = f
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
specType := apiservercel.NewObjectType("kubernetes.NamespaceSpec", fields(
|
||||
field("finalizers", apiservercel.NewListType(apiservercel.StringType, -1), true),
|
||||
))
|
||||
conditionType := apiservercel.NewObjectType("kubernetes.NamespaceCondition", fields(
|
||||
field("status", apiservercel.StringType, true),
|
||||
field("type", apiservercel.StringType, true),
|
||||
field("lastTransitionTime", apiservercel.TimestampType, true),
|
||||
field("message", apiservercel.StringType, true),
|
||||
field("reason", apiservercel.StringType, true),
|
||||
))
|
||||
statusType := apiservercel.NewObjectType("kubernetes.NamespaceStatus", fields(
|
||||
field("conditions", apiservercel.NewListType(conditionType, -1), true),
|
||||
field("phase", apiservercel.StringType, true),
|
||||
))
|
||||
metadataType := apiservercel.NewObjectType("kubernetes.NamespaceMetadata", fields(
|
||||
field("name", apiservercel.StringType, true),
|
||||
field("generateName", apiservercel.StringType, true),
|
||||
field("namespace", apiservercel.StringType, true),
|
||||
field("labels", apiservercel.NewMapType(apiservercel.StringType, apiservercel.StringType, -1), true),
|
||||
field("annotations", apiservercel.NewMapType(apiservercel.StringType, apiservercel.StringType, -1), true),
|
||||
field("UID", apiservercel.StringType, true),
|
||||
field("creationTimestamp", apiservercel.TimestampType, true),
|
||||
field("deletionGracePeriodSeconds", apiservercel.IntType, true),
|
||||
field("deletionTimestamp", apiservercel.TimestampType, true),
|
||||
field("generation", apiservercel.IntType, true),
|
||||
field("resourceVersion", apiservercel.StringType, true),
|
||||
field("finalizers", apiservercel.NewListType(apiservercel.StringType, -1), true),
|
||||
))
|
||||
return apiservercel.NewObjectType("kubernetes.Namespace", fields(
|
||||
field("metadata", metadataType, true),
|
||||
field("spec", specType, true),
|
||||
field("status", statusType, true),
|
||||
))
|
||||
}
|
||||
|
||||
// CompilationResult represents a compiled validations expression.
|
||||
type CompilationResult struct {
|
||||
Program cel.Program
|
||||
Error *apiservercel.Error
|
||||
ExpressionAccessor ExpressionAccessor
|
||||
OutputType *cel.Type
|
||||
}
|
||||
|
||||
// Compiler provides a CEL expression compiler configured with the desired admission related CEL variables and
|
||||
// environment mode.
|
||||
type Compiler interface {
|
||||
CompileCELExpression(expressionAccessor ExpressionAccessor, options OptionalVariableDeclarations, mode environment.Type) CompilationResult
|
||||
}
|
||||
|
||||
type compiler struct {
|
||||
varEnvs variableDeclEnvs
|
||||
}
|
||||
|
||||
func NewCompiler(env *environment.EnvSet) Compiler {
|
||||
return &compiler{varEnvs: mustBuildEnvs(env)}
|
||||
}
|
||||
|
||||
type variableDeclEnvs map[OptionalVariableDeclarations]*environment.EnvSet
|
||||
|
||||
// CompileCELExpression returns a compiled CEL expression.
|
||||
// perCallLimit was added for testing purpose only. Callers should always use const PerCallLimit from k8s.io/apiserver/pkg/apis/cel/config.go as input.
|
||||
func (c compiler) CompileCELExpression(expressionAccessor ExpressionAccessor, options OptionalVariableDeclarations, envType environment.Type) CompilationResult {
|
||||
resultError := func(errorString string, errType apiservercel.ErrorType, cause error) CompilationResult {
|
||||
return CompilationResult{
|
||||
Error: &apiservercel.Error{
|
||||
Type: errType,
|
||||
Detail: errorString,
|
||||
Cause: cause,
|
||||
},
|
||||
ExpressionAccessor: expressionAccessor,
|
||||
}
|
||||
}
|
||||
|
||||
env, err := c.varEnvs[options].Env(envType)
|
||||
if err != nil {
|
||||
return resultError(fmt.Sprintf("unexpected error loading CEL environment: %v", err), apiservercel.ErrorTypeInternal, nil)
|
||||
}
|
||||
|
||||
ast, issues := env.Compile(expressionAccessor.GetExpression())
|
||||
if issues != nil {
|
||||
return resultError("compilation failed: "+issues.String(), apiservercel.ErrorTypeInvalid, apiservercel.NewCompilationError(issues))
|
||||
}
|
||||
found := false
|
||||
returnTypes := expressionAccessor.ReturnTypes()
|
||||
for _, returnType := range returnTypes {
|
||||
if ast.OutputType().IsExactType(returnType) || cel.AnyType.IsExactType(returnType) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
var reason string
|
||||
if len(returnTypes) == 1 {
|
||||
reason = fmt.Sprintf("must evaluate to %v but got %v", returnTypes[0].String(), ast.OutputType().String())
|
||||
} else {
|
||||
reason = fmt.Sprintf("must evaluate to one of %v but got %v", returnTypes, ast.OutputType().String())
|
||||
}
|
||||
|
||||
return resultError(reason, apiservercel.ErrorTypeInvalid, nil)
|
||||
}
|
||||
|
||||
_, err = cel.AstToCheckedExpr(ast)
|
||||
if err != nil {
|
||||
// should be impossible since env.Compile returned no issues
|
||||
return resultError("unexpected compilation error: "+err.Error(), apiservercel.ErrorTypeInternal, nil)
|
||||
}
|
||||
prog, err := env.Program(ast,
|
||||
cel.InterruptCheckFrequency(celconfig.CheckFrequency),
|
||||
)
|
||||
if err != nil {
|
||||
return resultError("program instantiation failed: "+err.Error(), apiservercel.ErrorTypeInternal, nil)
|
||||
}
|
||||
return CompilationResult{
|
||||
Program: prog,
|
||||
ExpressionAccessor: expressionAccessor,
|
||||
OutputType: ast.OutputType(),
|
||||
}
|
||||
}
|
||||
|
||||
func mustBuildEnvs(baseEnv *environment.EnvSet) variableDeclEnvs {
|
||||
requestType := BuildRequestType()
|
||||
namespaceType := BuildNamespaceType()
|
||||
envs := make(variableDeclEnvs, 8) // since the number of variable combinations is small, pre-build a environment for each
|
||||
for _, hasParams := range []bool{false, true} {
|
||||
for _, hasAuthorizer := range []bool{false, true} {
|
||||
var err error
|
||||
for _, strictCost := range []bool{false, true} {
|
||||
decl := OptionalVariableDeclarations{HasParams: hasParams, HasAuthorizer: hasAuthorizer, StrictCost: strictCost}
|
||||
envs[decl], err = createEnvForOpts(baseEnv, namespaceType, requestType, decl)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
// We only need this ObjectTypes where strict cost is true
|
||||
decl := OptionalVariableDeclarations{HasParams: hasParams, HasAuthorizer: hasAuthorizer, StrictCost: true, HasPatchTypes: true}
|
||||
envs[decl], err = createEnvForOpts(baseEnv, namespaceType, requestType, decl)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return envs
|
||||
}
|
||||
|
||||
func createEnvForOpts(baseEnv *environment.EnvSet, namespaceType *apiservercel.DeclType, requestType *apiservercel.DeclType, opts OptionalVariableDeclarations) (*environment.EnvSet, error) {
|
||||
var envOpts []cel.EnvOption
|
||||
envOpts = append(envOpts,
|
||||
cel.Variable(ObjectVarName, cel.DynType),
|
||||
cel.Variable(OldObjectVarName, cel.DynType),
|
||||
cel.Variable(NamespaceVarName, namespaceType.CelType()),
|
||||
cel.Variable(RequestVarName, requestType.CelType()))
|
||||
if opts.HasParams {
|
||||
envOpts = append(envOpts, cel.Variable(ParamsVarName, cel.DynType))
|
||||
}
|
||||
if opts.HasAuthorizer {
|
||||
envOpts = append(envOpts,
|
||||
cel.Variable(AuthorizerVarName, library.AuthorizerType),
|
||||
cel.Variable(RequestResourceAuthorizerVarName, library.ResourceCheckType))
|
||||
}
|
||||
|
||||
extended, err := baseEnv.Extend(
|
||||
environment.VersionedOptions{
|
||||
// Feature epoch was actually 1.26, but we artificially set it to 1.0 because these
|
||||
// options should always be present.
|
||||
IntroducedVersion: version.MajorMinor(1, 0),
|
||||
EnvOptions: envOpts,
|
||||
DeclTypes: []*apiservercel.DeclType{
|
||||
namespaceType,
|
||||
requestType,
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("environment misconfigured: %w", err)
|
||||
}
|
||||
if opts.StrictCost {
|
||||
extended, err = extended.Extend(environment.StrictCostOpt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("environment misconfigured: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if opts.HasPatchTypes {
|
||||
extended, err = extended.Extend(hasPatchTypes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("environment misconfigured: %w", err)
|
||||
}
|
||||
}
|
||||
return extended, nil
|
||||
}
|
||||
|
||||
var hasPatchTypes = environment.VersionedOptions{
|
||||
// Feature epoch was actually 1.32, but we artificially set it to 1.0 because these
|
||||
// options should always be present.
|
||||
IntroducedVersion: version.MajorMinor(1, 0),
|
||||
EnvOptions: []cel.EnvOption{
|
||||
common.ResolverEnvOption(&mutation.DynamicTypeResolver{}),
|
||||
environment.UnversionedLib(library.JSONPatch), // for jsonPatch.escape() function
|
||||
},
|
||||
}
|
276
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/cel/composition.go
generated
vendored
Normal file
276
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/cel/composition.go
generated
vendored
Normal file
@ -0,0 +1,276 @@
|
||||
/*
|
||||
Copyright 2023 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 cel
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
"github.com/google/cel-go/common/types/traits"
|
||||
|
||||
v1 "k8s.io/api/admission/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/version"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
apiservercel "k8s.io/apiserver/pkg/cel"
|
||||
"k8s.io/apiserver/pkg/cel/environment"
|
||||
"k8s.io/apiserver/pkg/cel/lazy"
|
||||
)
|
||||
|
||||
const VariablesTypeName = "kubernetes.variables"
|
||||
|
||||
// CompositedCompiler compiles expressions with variable composition.
|
||||
type CompositedCompiler struct {
|
||||
Compiler
|
||||
ConditionCompiler
|
||||
MutatingCompiler
|
||||
|
||||
CompositionEnv *CompositionEnv
|
||||
}
|
||||
|
||||
// CompositedConditionEvaluator provides evaluation of a condition expression with variable composition.
|
||||
// The expressions must return a boolean.
|
||||
type CompositedConditionEvaluator struct {
|
||||
ConditionEvaluator
|
||||
|
||||
compositionEnv *CompositionEnv
|
||||
}
|
||||
|
||||
// CompositedEvaluator provides evaluation of a single expression with variable composition.
|
||||
// The types that may returned by the expression is determined at compilation time.
|
||||
type CompositedEvaluator struct {
|
||||
MutatingEvaluator
|
||||
|
||||
compositionEnv *CompositionEnv
|
||||
}
|
||||
|
||||
func NewCompositedCompiler(envSet *environment.EnvSet) (*CompositedCompiler, error) {
|
||||
compositionContext, err := NewCompositionEnv(VariablesTypeName, envSet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewCompositedCompilerFromTemplate(compositionContext), nil
|
||||
}
|
||||
|
||||
func NewCompositedCompilerFromTemplate(context *CompositionEnv) *CompositedCompiler {
|
||||
context = &CompositionEnv{
|
||||
MapType: context.MapType,
|
||||
EnvSet: context.EnvSet,
|
||||
CompiledVariables: map[string]CompilationResult{},
|
||||
}
|
||||
compiler := NewCompiler(context.EnvSet)
|
||||
conditionCompiler := &conditionCompiler{compiler}
|
||||
mutation := &mutatingCompiler{compiler}
|
||||
return &CompositedCompiler{
|
||||
Compiler: compiler,
|
||||
ConditionCompiler: conditionCompiler,
|
||||
MutatingCompiler: mutation,
|
||||
CompositionEnv: context,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CompositedCompiler) CompileAndStoreVariables(variables []NamedExpressionAccessor, options OptionalVariableDeclarations, mode environment.Type) {
|
||||
for _, v := range variables {
|
||||
_ = c.CompileAndStoreVariable(v, options, mode)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CompositedCompiler) CompileAndStoreVariable(variable NamedExpressionAccessor, options OptionalVariableDeclarations, mode environment.Type) CompilationResult {
|
||||
result := c.Compiler.CompileCELExpression(variable, options, mode)
|
||||
c.CompositionEnv.AddField(variable.GetName(), result.OutputType)
|
||||
c.CompositionEnv.CompiledVariables[variable.GetName()] = result
|
||||
return result
|
||||
}
|
||||
|
||||
func (c *CompositedCompiler) CompileCondition(expressions []ExpressionAccessor, optionalDecls OptionalVariableDeclarations, envType environment.Type) ConditionEvaluator {
|
||||
condition := c.ConditionCompiler.CompileCondition(expressions, optionalDecls, envType)
|
||||
return &CompositedConditionEvaluator{
|
||||
ConditionEvaluator: condition,
|
||||
compositionEnv: c.CompositionEnv,
|
||||
}
|
||||
}
|
||||
|
||||
// CompileEvaluator compiles an mutatingEvaluator for the given expression, options and environment.
|
||||
func (c *CompositedCompiler) CompileMutatingEvaluator(expression ExpressionAccessor, optionalDecls OptionalVariableDeclarations, envType environment.Type) MutatingEvaluator {
|
||||
mutation := c.MutatingCompiler.CompileMutatingEvaluator(expression, optionalDecls, envType)
|
||||
return &CompositedEvaluator{
|
||||
MutatingEvaluator: mutation,
|
||||
compositionEnv: c.CompositionEnv,
|
||||
}
|
||||
}
|
||||
|
||||
type CompositionEnv struct {
|
||||
*environment.EnvSet
|
||||
|
||||
MapType *apiservercel.DeclType
|
||||
CompiledVariables map[string]CompilationResult
|
||||
}
|
||||
|
||||
func (c *CompositionEnv) AddField(name string, celType *cel.Type) {
|
||||
c.MapType.Fields[name] = apiservercel.NewDeclField(name, convertCelTypeToDeclType(celType), true, nil, nil)
|
||||
}
|
||||
|
||||
func NewCompositionEnv(typeName string, baseEnvSet *environment.EnvSet) (*CompositionEnv, error) {
|
||||
declType := apiservercel.NewObjectType(typeName, map[string]*apiservercel.DeclField{})
|
||||
envSet, err := baseEnvSet.Extend(environment.VersionedOptions{
|
||||
// set to 1.0 because composition is one of the fundamental components
|
||||
IntroducedVersion: version.MajorMinor(1, 0),
|
||||
EnvOptions: []cel.EnvOption{
|
||||
cel.Variable("variables", declType.CelType()),
|
||||
},
|
||||
DeclTypes: []*apiservercel.DeclType{
|
||||
declType,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &CompositionEnv{
|
||||
MapType: declType,
|
||||
EnvSet: envSet,
|
||||
CompiledVariables: map[string]CompilationResult{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *CompositionEnv) CreateContext(parent context.Context) CompositionContext {
|
||||
return &compositionContext{
|
||||
Context: parent,
|
||||
compositionEnv: c,
|
||||
}
|
||||
}
|
||||
|
||||
type CompositionContext interface {
|
||||
context.Context
|
||||
Variables(activation any) ref.Val
|
||||
GetAndResetCost() int64
|
||||
}
|
||||
|
||||
type compositionContext struct {
|
||||
context.Context
|
||||
|
||||
compositionEnv *CompositionEnv
|
||||
accumulatedCost int64
|
||||
}
|
||||
|
||||
func (c *compositionContext) Variables(activation any) ref.Val {
|
||||
lazyMap := lazy.NewMapValue(c.compositionEnv.MapType)
|
||||
for name, result := range c.compositionEnv.CompiledVariables {
|
||||
accessor := &variableAccessor{
|
||||
name: name,
|
||||
result: result,
|
||||
activation: activation,
|
||||
context: c,
|
||||
}
|
||||
lazyMap.Append(name, accessor.Callback)
|
||||
}
|
||||
return lazyMap
|
||||
}
|
||||
|
||||
func (f *CompositedConditionEvaluator) ForInput(ctx context.Context, versionedAttr *admission.VersionedAttributes, request *v1.AdmissionRequest, optionalVars OptionalVariableBindings, namespace *corev1.Namespace, runtimeCELCostBudget int64) ([]EvaluationResult, int64, error) {
|
||||
ctx = f.compositionEnv.CreateContext(ctx)
|
||||
return f.ConditionEvaluator.ForInput(ctx, versionedAttr, request, optionalVars, namespace, runtimeCELCostBudget)
|
||||
}
|
||||
|
||||
func (c *compositionContext) reportCost(cost int64) {
|
||||
c.accumulatedCost += cost
|
||||
}
|
||||
|
||||
func (c *compositionContext) GetAndResetCost() int64 {
|
||||
cost := c.accumulatedCost
|
||||
c.accumulatedCost = 0
|
||||
return cost
|
||||
}
|
||||
|
||||
type variableAccessor struct {
|
||||
name string
|
||||
result CompilationResult
|
||||
activation any
|
||||
context *compositionContext
|
||||
}
|
||||
|
||||
func (a *variableAccessor) Callback(_ *lazy.MapValue) ref.Val {
|
||||
if a.result.Error != nil {
|
||||
return types.NewErr("composited variable %q fails to compile: %v", a.name, a.result.Error)
|
||||
}
|
||||
|
||||
v, details, err := a.result.Program.ContextEval(a.context, a.activation)
|
||||
if details == nil {
|
||||
return types.NewErr("unable to get evaluation details of variable %q", a.name)
|
||||
}
|
||||
costPtr := details.ActualCost()
|
||||
if costPtr == nil {
|
||||
return types.NewErr("unable to calculate cost of variable %q", a.name)
|
||||
}
|
||||
cost := int64(*costPtr)
|
||||
if *costPtr > math.MaxInt64 {
|
||||
cost = math.MaxInt64
|
||||
}
|
||||
a.context.reportCost(cost)
|
||||
|
||||
if err != nil {
|
||||
return types.NewErr("composited variable %q fails to evaluate: %v", a.name, err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// convertCelTypeToDeclType converts a cel.Type to DeclType, for the use of
|
||||
// the TypeProvider and the cost estimator.
|
||||
// List and map types are created on-demand with their parameters converted recursively.
|
||||
func convertCelTypeToDeclType(celType *cel.Type) *apiservercel.DeclType {
|
||||
if celType == nil {
|
||||
return apiservercel.DynType
|
||||
}
|
||||
switch celType {
|
||||
case cel.AnyType:
|
||||
return apiservercel.AnyType
|
||||
case cel.BoolType:
|
||||
return apiservercel.BoolType
|
||||
case cel.BytesType:
|
||||
return apiservercel.BytesType
|
||||
case cel.DoubleType:
|
||||
return apiservercel.DoubleType
|
||||
case cel.DurationType:
|
||||
return apiservercel.DurationType
|
||||
case cel.IntType:
|
||||
return apiservercel.IntType
|
||||
case cel.NullType:
|
||||
return apiservercel.NullType
|
||||
case cel.StringType:
|
||||
return apiservercel.StringType
|
||||
case cel.TimestampType:
|
||||
return apiservercel.TimestampType
|
||||
case cel.UintType:
|
||||
return apiservercel.UintType
|
||||
default:
|
||||
if celType.HasTrait(traits.ContainerType) && celType.HasTrait(traits.IndexerType) {
|
||||
parameters := celType.Parameters()
|
||||
switch len(parameters) {
|
||||
case 1:
|
||||
elemType := convertCelTypeToDeclType(parameters[0])
|
||||
return apiservercel.NewListType(elemType, -1)
|
||||
case 2:
|
||||
keyType := convertCelTypeToDeclType(parameters[0])
|
||||
valueType := convertCelTypeToDeclType(parameters[1])
|
||||
return apiservercel.NewMapType(keyType, valueType, -1)
|
||||
}
|
||||
}
|
||||
return apiservercel.DynType
|
||||
}
|
||||
}
|
216
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/cel/condition.go
generated
vendored
Normal file
216
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/cel/condition.go
generated
vendored
Normal file
@ -0,0 +1,216 @@
|
||||
/*
|
||||
Copyright 2022 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 cel
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
authenticationv1 "k8s.io/api/authentication/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/cel/environment"
|
||||
)
|
||||
|
||||
// conditionCompiler implement the interface ConditionCompiler.
|
||||
type conditionCompiler struct {
|
||||
compiler Compiler
|
||||
}
|
||||
|
||||
func NewConditionCompiler(env *environment.EnvSet) ConditionCompiler {
|
||||
return &conditionCompiler{compiler: NewCompiler(env)}
|
||||
}
|
||||
|
||||
// CompileCondition compiles the cel expressions defined in the ExpressionAccessors into a ConditionEvaluator
|
||||
func (c *conditionCompiler) CompileCondition(expressionAccessors []ExpressionAccessor, options OptionalVariableDeclarations, mode environment.Type) ConditionEvaluator {
|
||||
compilationResults := make([]CompilationResult, len(expressionAccessors))
|
||||
for i, expressionAccessor := range expressionAccessors {
|
||||
if expressionAccessor == nil {
|
||||
continue
|
||||
}
|
||||
compilationResults[i] = c.compiler.CompileCELExpression(expressionAccessor, options, mode)
|
||||
}
|
||||
return NewCondition(compilationResults)
|
||||
}
|
||||
|
||||
// condition implements the ConditionEvaluator interface
|
||||
type condition struct {
|
||||
compilationResults []CompilationResult
|
||||
}
|
||||
|
||||
func NewCondition(compilationResults []CompilationResult) ConditionEvaluator {
|
||||
return &condition{
|
||||
compilationResults,
|
||||
}
|
||||
}
|
||||
|
||||
func convertObjectToUnstructured(obj interface{}) (*unstructured.Unstructured, error) {
|
||||
if obj == nil || reflect.ValueOf(obj).IsNil() {
|
||||
return &unstructured.Unstructured{Object: nil}, nil
|
||||
}
|
||||
ret, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &unstructured.Unstructured{Object: ret}, nil
|
||||
}
|
||||
|
||||
func objectToResolveVal(r runtime.Object) (interface{}, error) {
|
||||
if r == nil || reflect.ValueOf(r).IsNil() {
|
||||
return nil, nil
|
||||
}
|
||||
v, err := convertObjectToUnstructured(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return v.Object, nil
|
||||
}
|
||||
|
||||
// ForInput evaluates the compiled CEL expressions converting them into CELEvaluations
|
||||
// errors per evaluation are returned on the Evaluation object
|
||||
// runtimeCELCostBudget was added for testing purpose only. Callers should always use const RuntimeCELCostBudget from k8s.io/apiserver/pkg/apis/cel/config.go as input.
|
||||
func (c *condition) ForInput(ctx context.Context, versionedAttr *admission.VersionedAttributes, request *admissionv1.AdmissionRequest, inputs OptionalVariableBindings, namespace *v1.Namespace, runtimeCELCostBudget int64) ([]EvaluationResult, int64, error) {
|
||||
// TODO: replace unstructured with ref.Val for CEL variables when native type support is available
|
||||
evaluations := make([]EvaluationResult, len(c.compilationResults))
|
||||
var err error
|
||||
|
||||
// if this activation supports composition, we will need the compositionCtx. It may be nil.
|
||||
compositionCtx, _ := ctx.(CompositionContext)
|
||||
|
||||
activation, err := newActivation(compositionCtx, versionedAttr, request, inputs, namespace)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
|
||||
remainingBudget := runtimeCELCostBudget
|
||||
for i, compilationResult := range c.compilationResults {
|
||||
evaluations[i], remainingBudget, err = activation.Evaluate(ctx, compositionCtx, compilationResult, remainingBudget)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
}
|
||||
|
||||
return evaluations, remainingBudget, nil
|
||||
}
|
||||
|
||||
// TODO: to reuse https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/request/admissionreview.go#L154
|
||||
func CreateAdmissionRequest(attr admission.Attributes, equivalentGVR metav1.GroupVersionResource, equivalentKind metav1.GroupVersionKind) *admissionv1.AdmissionRequest {
|
||||
// Attempting to use same logic as webhook for constructing resource
|
||||
// GVK, GVR, subresource
|
||||
// Use the GVK, GVR that the matcher decided was equivalent to that of the request
|
||||
// https://github.com/kubernetes/kubernetes/blob/90c362b3430bcbbf8f245fadbcd521dab39f1d7c/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/webhook.go#L182-L210
|
||||
gvk := equivalentKind
|
||||
gvr := equivalentGVR
|
||||
subresource := attr.GetSubresource()
|
||||
|
||||
requestGVK := attr.GetKind()
|
||||
requestGVR := attr.GetResource()
|
||||
requestSubResource := attr.GetSubresource()
|
||||
|
||||
aUserInfo := attr.GetUserInfo()
|
||||
var userInfo authenticationv1.UserInfo
|
||||
if aUserInfo != nil {
|
||||
userInfo = authenticationv1.UserInfo{
|
||||
Extra: make(map[string]authenticationv1.ExtraValue),
|
||||
Groups: aUserInfo.GetGroups(),
|
||||
UID: aUserInfo.GetUID(),
|
||||
Username: aUserInfo.GetName(),
|
||||
}
|
||||
// Convert the extra information in the user object
|
||||
for key, val := range aUserInfo.GetExtra() {
|
||||
userInfo.Extra[key] = authenticationv1.ExtraValue(val)
|
||||
}
|
||||
}
|
||||
|
||||
dryRun := attr.IsDryRun()
|
||||
|
||||
return &admissionv1.AdmissionRequest{
|
||||
Kind: metav1.GroupVersionKind{
|
||||
Group: gvk.Group,
|
||||
Kind: gvk.Kind,
|
||||
Version: gvk.Version,
|
||||
},
|
||||
Resource: metav1.GroupVersionResource{
|
||||
Group: gvr.Group,
|
||||
Resource: gvr.Resource,
|
||||
Version: gvr.Version,
|
||||
},
|
||||
SubResource: subresource,
|
||||
RequestKind: &metav1.GroupVersionKind{
|
||||
Group: requestGVK.Group,
|
||||
Kind: requestGVK.Kind,
|
||||
Version: requestGVK.Version,
|
||||
},
|
||||
RequestResource: &metav1.GroupVersionResource{
|
||||
Group: requestGVR.Group,
|
||||
Resource: requestGVR.Resource,
|
||||
Version: requestGVR.Version,
|
||||
},
|
||||
RequestSubResource: requestSubResource,
|
||||
Name: attr.GetName(),
|
||||
Namespace: attr.GetNamespace(),
|
||||
Operation: admissionv1.Operation(attr.GetOperation()),
|
||||
UserInfo: userInfo,
|
||||
// Leave Object and OldObject unset since we don't provide access to them via request
|
||||
DryRun: &dryRun,
|
||||
Options: runtime.RawExtension{
|
||||
Object: attr.GetOperationOptions(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// CreateNamespaceObject creates a Namespace object that is suitable for the CEL evaluation.
|
||||
// If the namespace is nil, CreateNamespaceObject returns nil
|
||||
func CreateNamespaceObject(namespace *v1.Namespace) *v1.Namespace {
|
||||
if namespace == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &v1.Namespace{
|
||||
Status: namespace.Status,
|
||||
Spec: namespace.Spec,
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: namespace.Name,
|
||||
GenerateName: namespace.GenerateName,
|
||||
Namespace: namespace.Namespace,
|
||||
UID: namespace.UID,
|
||||
ResourceVersion: namespace.ResourceVersion,
|
||||
Generation: namespace.Generation,
|
||||
CreationTimestamp: namespace.CreationTimestamp,
|
||||
DeletionTimestamp: namespace.DeletionTimestamp,
|
||||
DeletionGracePeriodSeconds: namespace.DeletionGracePeriodSeconds,
|
||||
Labels: namespace.Labels,
|
||||
Annotations: namespace.Annotations,
|
||||
Finalizers: namespace.Finalizers,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// CompilationErrors returns a list of all the errors from the compilation of the mutatingEvaluator
|
||||
func (c *condition) CompilationErrors() []error {
|
||||
compilationErrors := []error{}
|
||||
for _, result := range c.compilationResults {
|
||||
if result.Error != nil {
|
||||
compilationErrors = append(compilationErrors, result.Error)
|
||||
}
|
||||
}
|
||||
return compilationErrors
|
||||
}
|
122
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/cel/interface.go
generated
vendored
Normal file
122
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/cel/interface.go
generated
vendored
Normal file
@ -0,0 +1,122 @@
|
||||
/*
|
||||
Copyright 2022 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 cel
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
|
||||
v1 "k8s.io/api/admission/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/apiserver/pkg/cel/environment"
|
||||
)
|
||||
|
||||
type ExpressionAccessor interface {
|
||||
GetExpression() string
|
||||
ReturnTypes() []*cel.Type
|
||||
}
|
||||
|
||||
// NamedExpressionAccessor extends NamedExpressionAccessor with a name.
|
||||
type NamedExpressionAccessor interface {
|
||||
ExpressionAccessor
|
||||
|
||||
GetName() string // follows the naming convention of ExpressionAccessor
|
||||
}
|
||||
|
||||
// EvaluationResult contains the minimal required fields and metadata of a cel evaluation
|
||||
type EvaluationResult struct {
|
||||
EvalResult ref.Val
|
||||
ExpressionAccessor ExpressionAccessor
|
||||
Elapsed time.Duration
|
||||
Error error
|
||||
}
|
||||
|
||||
// OptionalVariableDeclarations declares which optional CEL variables
|
||||
// are declared for an expression.
|
||||
type OptionalVariableDeclarations struct {
|
||||
// HasParams specifies if the "params" variable is declared.
|
||||
// The "params" variable may still be bound to "null" when declared.
|
||||
HasParams bool
|
||||
// HasAuthorizer specifies if the "authorizer" and "authorizer.requestResource"
|
||||
// variables are declared. When declared, the authorizer variables are
|
||||
// expected to be non-null.
|
||||
HasAuthorizer bool
|
||||
// StrictCost specifies if the CEL cost limitation is strict for extended libraries as well as native libraries.
|
||||
StrictCost bool
|
||||
// HasPatchTypes specifies if JSONPatch, Object, Object.metadata and similar types are available in CEL. These can be used
|
||||
// to initialize the typed objects in CEL required to create patches.
|
||||
HasPatchTypes bool
|
||||
}
|
||||
|
||||
// ConditionCompiler contains a function to assist with converting types and values to/from CEL-typed values.
|
||||
type ConditionCompiler interface {
|
||||
// CompileCondition is used for the cel expression compilation
|
||||
CompileCondition(expressions []ExpressionAccessor, optionalDecls OptionalVariableDeclarations, envType environment.Type) ConditionEvaluator
|
||||
}
|
||||
|
||||
// OptionalVariableBindings provides expression bindings for optional CEL variables.
|
||||
type OptionalVariableBindings struct {
|
||||
// VersionedParams provides the "params" variable binding. This variable binding may
|
||||
// be set to nil even when OptionalVariableDeclarations.HashParams is set to true.
|
||||
VersionedParams runtime.Object
|
||||
// Authorizer provides the authorizer used for the "authorizer" and
|
||||
// "authorizer.requestResource" variable bindings. If the expression was compiled with
|
||||
// OptionalVariableDeclarations.HasAuthorizer set to true this must be non-nil.
|
||||
Authorizer authorizer.Authorizer
|
||||
}
|
||||
|
||||
// ConditionEvaluator contains the result of compiling a CEL expression
|
||||
// that evaluates to a condition. This is used both for validation and pre-conditions.
|
||||
// It expects the inbound object to already have been converted to the version expected
|
||||
// by the underlying CEL code (which is indicated by the match criteria of a policy definition).
|
||||
// versionedParams may be nil.
|
||||
type ConditionEvaluator interface {
|
||||
// ForInput converts compiled CEL-typed values into evaluated CEL-typed value.
|
||||
// runtimeCELCostBudget was added for testing purpose only. Callers should always use const RuntimeCELCostBudget from k8s.io/apiserver/pkg/apis/cel/config.go as input.
|
||||
// If cost budget is calculated, the condition should return the remaining budget.
|
||||
ForInput(ctx context.Context, versionedAttr *admission.VersionedAttributes, request *v1.AdmissionRequest, optionalVars OptionalVariableBindings, namespace *corev1.Namespace, runtimeCELCostBudget int64) ([]EvaluationResult, int64, error)
|
||||
|
||||
// CompilationErrors returns a list of errors from the compilation of the mutatingEvaluator
|
||||
CompilationErrors() []error
|
||||
}
|
||||
|
||||
// MutatingCompiler contains a function to assist with converting types and values to/from CEL-typed values.
|
||||
type MutatingCompiler interface {
|
||||
// CompileMutatingEvaluator is used for the cel expression compilation
|
||||
CompileMutatingEvaluator(expression ExpressionAccessor, optionalDecls OptionalVariableDeclarations, envType environment.Type) MutatingEvaluator
|
||||
}
|
||||
|
||||
// MutatingEvaluator contains the result of compiling a CEL expression
|
||||
// that evaluates to a mutation.
|
||||
// It expects the inbound object to already have been converted to the version expected
|
||||
// by the underlying CEL code (which is indicated by the match criteria of a policy definition).
|
||||
// versionedParams may be nil.
|
||||
type MutatingEvaluator interface {
|
||||
// ForInput converts compiled CEL-typed values into a CEL-typed value representing a mutation.
|
||||
// runtimeCELCostBudget was added for testing purpose only. Callers should always use const RuntimeCELCostBudget from k8s.io/apiserver/pkg/apis/cel/config.go as input.
|
||||
// If cost budget is calculated, the condition should return the remaining budget.
|
||||
ForInput(ctx context.Context, versionedAttr *admission.VersionedAttributes, request *v1.AdmissionRequest, optionalVars OptionalVariableBindings, namespace *corev1.Namespace, runtimeCELCostBudget int64) (EvaluationResult, int64, error)
|
||||
|
||||
// CompilationErrors returns a list of errors from the compilation of the mutatingEvaluator
|
||||
CompilationErrors() []error
|
||||
}
|
73
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/cel/mutation.go
generated
vendored
Normal file
73
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/cel/mutation.go
generated
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
/*
|
||||
Copyright 2024 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 cel
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/cel/environment"
|
||||
)
|
||||
|
||||
// mutatingCompiler provides a MutatingCompiler implementation.
|
||||
type mutatingCompiler struct {
|
||||
compiler Compiler
|
||||
}
|
||||
|
||||
// CompileMutatingEvaluator compiles a CEL expression for admission plugins and returns an MutatingEvaluator for executing the
|
||||
// compiled CEL expression.
|
||||
func (p *mutatingCompiler) CompileMutatingEvaluator(expressionAccessor ExpressionAccessor, options OptionalVariableDeclarations, mode environment.Type) MutatingEvaluator {
|
||||
compilationResult := p.compiler.CompileCELExpression(expressionAccessor, options, mode)
|
||||
return NewMutatingEvaluator(compilationResult)
|
||||
}
|
||||
|
||||
type mutatingEvaluator struct {
|
||||
compilationResult CompilationResult
|
||||
}
|
||||
|
||||
func NewMutatingEvaluator(compilationResult CompilationResult) MutatingEvaluator {
|
||||
return &mutatingEvaluator{compilationResult}
|
||||
}
|
||||
|
||||
// ForInput evaluates the compiled CEL expression and returns an evaluation result
|
||||
// errors per evaluation are returned in the evaluation result
|
||||
// runtimeCELCostBudget was added for testing purpose only. Callers should always use const RuntimeCELCostBudget from k8s.io/apiserver/pkg/apis/cel/config.go as input.
|
||||
func (p *mutatingEvaluator) ForInput(ctx context.Context, versionedAttr *admission.VersionedAttributes, request *admissionv1.AdmissionRequest, inputs OptionalVariableBindings, namespace *v1.Namespace, runtimeCELCostBudget int64) (EvaluationResult, int64, error) {
|
||||
// if this activation supports composition, we will need the compositionCtx. It may be nil.
|
||||
compositionCtx, _ := ctx.(CompositionContext)
|
||||
|
||||
activation, err := newActivation(compositionCtx, versionedAttr, request, inputs, namespace)
|
||||
if err != nil {
|
||||
return EvaluationResult{}, -1, err
|
||||
}
|
||||
evaluation, remainingBudget, err := activation.Evaluate(ctx, compositionCtx, p.compilationResult, runtimeCELCostBudget)
|
||||
if err != nil {
|
||||
return evaluation, -1, err
|
||||
}
|
||||
return evaluation, remainingBudget, nil
|
||||
|
||||
}
|
||||
|
||||
// CompilationErrors returns a list of all the errors from the compilation of the mutatingEvaluator
|
||||
func (p *mutatingEvaluator) CompilationErrors() (compilationErrors []error) {
|
||||
if p.compilationResult.Error != nil {
|
||||
return []error{p.compilationResult.Error}
|
||||
}
|
||||
return nil
|
||||
}
|
233
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle/admission.go
generated
vendored
Normal file
233
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle/admission.go
generated
vendored
Normal file
@ -0,0 +1,233 @@
|
||||
/*
|
||||
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 (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilcache "k8s.io/apimachinery/pkg/util/cache"
|
||||
"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"
|
||||
"k8s.io/utils/clock"
|
||||
)
|
||||
|
||||
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(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
|
||||
// prevent deletion of immortal namespaces
|
||||
if a.GetOperation() == admission.Delete && a.GetKind().GroupKind() == v1.SchemeGroupVersion.WithKind("Namespace").GroupKind() && l.immortalNamespaces.Has(a.GetName()) {
|
||||
return errors.NewForbidden(a.GetResource().GroupResource(), a.GetName(), fmt.Errorf("this namespace may not be deleted"))
|
||||
}
|
||||
|
||||
// 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).InfoS("Namespace existed in cache after waiting", "namespace", klog.KRef("", 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(context.TODO(), a.GetNamespace(), metav1.GetOptions{})
|
||||
switch {
|
||||
case errors.IsNotFound(err):
|
||||
return err
|
||||
case err != nil:
|
||||
return errors.NewInternalError(err)
|
||||
}
|
||||
|
||||
klog.V(4).InfoS("Found namespace via storage lookup", "namespace", klog.KRef("", 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
|
||||
}
|
||||
|
||||
err := admission.NewForbidden(a, fmt.Errorf("unable to create new content in namespace %s because it is being terminated", a.GetNamespace()))
|
||||
if apierr, ok := err.(*errors.StatusError); ok {
|
||||
apierr.ErrStatus.Details.Causes = append(apierr.ErrStatus.Details.Causes, metav1.StatusCause{
|
||||
Type: v1.NamespaceTerminatingCause,
|
||||
Message: fmt.Sprintf("namespace %s is being terminated", a.GetNamespace()),
|
||||
Field: "metadata.namespace",
|
||||
})
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
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()]
|
||||
}
|
43
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/generic/accessor.go
generated
vendored
Normal file
43
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/generic/accessor.go
generated
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
Copyright 2024 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package generic
|
||||
|
||||
import (
|
||||
"k8s.io/api/admissionregistration/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
)
|
||||
|
||||
type PolicyAccessor interface {
|
||||
GetName() string
|
||||
GetNamespace() string
|
||||
GetParamKind() *v1.ParamKind
|
||||
GetMatchConstraints() *v1.MatchResources
|
||||
GetFailurePolicy() *v1.FailurePolicyType
|
||||
}
|
||||
|
||||
type BindingAccessor interface {
|
||||
GetName() string
|
||||
GetNamespace() string
|
||||
|
||||
// GetPolicyName returns the name of the (Validating/Mutating)AdmissionPolicy,
|
||||
// which is cluster-scoped, so namespace is usually left blank.
|
||||
// But we leave the door open to add a namespaced vesion in the future
|
||||
GetPolicyName() types.NamespacedName
|
||||
GetParamRef() *v1.ParamRef
|
||||
|
||||
GetMatchResources() *v1.MatchResources
|
||||
}
|
67
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/generic/interfaces.go
generated
vendored
Normal file
67
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/generic/interfaces.go
generated
vendored
Normal file
@ -0,0 +1,67 @@
|
||||
/*
|
||||
Copyright 2024 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/apiserver/pkg/admission"
|
||||
)
|
||||
|
||||
// Hook represents a dynamic admission hook. The hook may be a webhook or a
|
||||
// policy. For webhook, the Hook may describe how to contact the endpoint, expected
|
||||
// cert, etc. For policies, the hook may describe a compiled policy-binding pair.
|
||||
type Hook interface {
|
||||
// All hooks are expected to contain zero or more match conditions, object
|
||||
// selectors, namespace selectors to help the dispatcher decide when to apply
|
||||
// the hook.
|
||||
//
|
||||
// Methods of matching logic is applied are specific to the hook and left up
|
||||
// to the implementation.
|
||||
}
|
||||
|
||||
// Source can list dynamic admission plugins.
|
||||
type Source[H Hook] interface {
|
||||
// Hooks returns the list of currently known admission hooks.
|
||||
Hooks() []H
|
||||
|
||||
// Run the source. This method should be called only once at startup.
|
||||
Run(ctx context.Context) error
|
||||
|
||||
// HasSynced returns true if the source has completed its initial sync.
|
||||
HasSynced() bool
|
||||
}
|
||||
|
||||
// Dispatcher dispatches evaluates an admission request against the currently
|
||||
// active hooks returned by the source.
|
||||
type Dispatcher[H Hook] interface {
|
||||
// Start the dispatcher. This method should be called only once at startup.
|
||||
Start(ctx context.Context) error
|
||||
|
||||
// Dispatch a request to the policies. Dispatcher may choose not to
|
||||
// call a hook, either because the rules of the hook does not match, or
|
||||
// the namespaceSelector or the objectSelector of the hook does not
|
||||
// match. A non-nil error means the request is rejected.
|
||||
Dispatch(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces, hooks []H) error
|
||||
}
|
||||
|
||||
// An evaluator represents a compiled CEL expression that can be evaluated a
|
||||
// given a set of inputs used by the generic PolicyHook for Mutating and
|
||||
// ValidatingAdmissionPolicy.
|
||||
// Mutating and Validating may have different forms of evaluators
|
||||
type Evaluator interface {
|
||||
}
|
221
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/generic/plugin.go
generated
vendored
Normal file
221
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/generic/plugin.go
generated
vendored
Normal file
@ -0,0 +1,221 @@
|
||||
/*
|
||||
Copyright 2024 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"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/initializer"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/policy/matching"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
// H is the Hook type generated by the source and consumed by the dispatcher.
|
||||
// !TODO: Just pass in a Plugin[H] with accessors to all this information
|
||||
type sourceFactory[H any] func(informers.SharedInformerFactory, kubernetes.Interface, dynamic.Interface, meta.RESTMapper) Source[H]
|
||||
type dispatcherFactory[H any] func(authorizer.Authorizer, *matching.Matcher, kubernetes.Interface) Dispatcher[H]
|
||||
|
||||
// admissionResources is the list of resources related to CEL-based admission
|
||||
// features.
|
||||
var admissionResources = []schema.GroupResource{
|
||||
{Group: admissionregistrationv1.GroupName, Resource: "validatingadmissionpolicies"},
|
||||
{Group: admissionregistrationv1.GroupName, Resource: "validatingadmissionpolicybindings"},
|
||||
{Group: admissionregistrationv1.GroupName, Resource: "mutatingadmissionpolicies"},
|
||||
{Group: admissionregistrationv1.GroupName, Resource: "mutatingadmissionpolicybindings"},
|
||||
}
|
||||
|
||||
// AdmissionPolicyManager is an abstract admission plugin with all the
|
||||
// infrastructure to define Admit or Validate on-top.
|
||||
type Plugin[H any] struct {
|
||||
*admission.Handler
|
||||
|
||||
sourceFactory sourceFactory[H]
|
||||
dispatcherFactory dispatcherFactory[H]
|
||||
|
||||
source Source[H]
|
||||
dispatcher Dispatcher[H]
|
||||
matcher *matching.Matcher
|
||||
|
||||
informerFactory informers.SharedInformerFactory
|
||||
client kubernetes.Interface
|
||||
restMapper meta.RESTMapper
|
||||
dynamicClient dynamic.Interface
|
||||
excludedResources sets.Set[schema.GroupResource]
|
||||
stopCh <-chan struct{}
|
||||
authorizer authorizer.Authorizer
|
||||
enabled bool
|
||||
}
|
||||
|
||||
var (
|
||||
_ initializer.WantsExternalKubeInformerFactory = &Plugin[any]{}
|
||||
_ initializer.WantsExternalKubeClientSet = &Plugin[any]{}
|
||||
_ initializer.WantsRESTMapper = &Plugin[any]{}
|
||||
_ initializer.WantsDynamicClient = &Plugin[any]{}
|
||||
_ initializer.WantsDrainedNotification = &Plugin[any]{}
|
||||
_ initializer.WantsAuthorizer = &Plugin[any]{}
|
||||
_ initializer.WantsExcludedAdmissionResources = &Plugin[any]{}
|
||||
_ admission.InitializationValidator = &Plugin[any]{}
|
||||
)
|
||||
|
||||
func NewPlugin[H any](
|
||||
handler *admission.Handler,
|
||||
sourceFactory sourceFactory[H],
|
||||
dispatcherFactory dispatcherFactory[H],
|
||||
) *Plugin[H] {
|
||||
return &Plugin[H]{
|
||||
Handler: handler,
|
||||
sourceFactory: sourceFactory,
|
||||
dispatcherFactory: dispatcherFactory,
|
||||
|
||||
// always exclude admission/mutating policies and bindings
|
||||
excludedResources: sets.New(admissionResources...),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Plugin[H]) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) {
|
||||
c.informerFactory = f
|
||||
}
|
||||
|
||||
func (c *Plugin[H]) SetExternalKubeClientSet(client kubernetes.Interface) {
|
||||
c.client = client
|
||||
}
|
||||
|
||||
func (c *Plugin[H]) SetRESTMapper(mapper meta.RESTMapper) {
|
||||
c.restMapper = mapper
|
||||
}
|
||||
|
||||
func (c *Plugin[H]) SetDynamicClient(client dynamic.Interface) {
|
||||
c.dynamicClient = client
|
||||
}
|
||||
|
||||
func (c *Plugin[H]) SetDrainedNotification(stopCh <-chan struct{}) {
|
||||
c.stopCh = stopCh
|
||||
}
|
||||
|
||||
func (c *Plugin[H]) SetAuthorizer(authorizer authorizer.Authorizer) {
|
||||
c.authorizer = authorizer
|
||||
}
|
||||
|
||||
func (c *Plugin[H]) SetMatcher(matcher *matching.Matcher) {
|
||||
c.matcher = matcher
|
||||
}
|
||||
|
||||
func (c *Plugin[H]) SetEnabled(enabled bool) {
|
||||
c.enabled = enabled
|
||||
}
|
||||
|
||||
func (c *Plugin[H]) SetExcludedAdmissionResources(excludedResources []schema.GroupResource) {
|
||||
c.excludedResources.Insert(excludedResources...)
|
||||
}
|
||||
|
||||
// ValidateInitialization - once clientset and informer factory are provided, creates and starts the admission controller
|
||||
func (c *Plugin[H]) ValidateInitialization() error {
|
||||
// By default enabled is set to false. It is up to types which embed this
|
||||
// struct to set it to true (if feature gate is enabled, or other conditions)
|
||||
if !c.enabled {
|
||||
return nil
|
||||
}
|
||||
if c.Handler == nil {
|
||||
return errors.New("missing handler")
|
||||
}
|
||||
if c.informerFactory == nil {
|
||||
return errors.New("missing informer factory")
|
||||
}
|
||||
if c.client == nil {
|
||||
return errors.New("missing kubernetes client")
|
||||
}
|
||||
if c.restMapper == nil {
|
||||
return errors.New("missing rest mapper")
|
||||
}
|
||||
if c.dynamicClient == nil {
|
||||
return errors.New("missing dynamic client")
|
||||
}
|
||||
if c.stopCh == nil {
|
||||
return errors.New("missing stop channel")
|
||||
}
|
||||
if c.authorizer == nil {
|
||||
return errors.New("missing authorizer")
|
||||
}
|
||||
|
||||
// Use default matcher
|
||||
namespaceInformer := c.informerFactory.Core().V1().Namespaces()
|
||||
c.matcher = matching.NewMatcher(namespaceInformer.Lister(), c.client)
|
||||
|
||||
if err := c.matcher.ValidateInitialization(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.source = c.sourceFactory(c.informerFactory, c.client, c.dynamicClient, c.restMapper)
|
||||
c.dispatcher = c.dispatcherFactory(c.authorizer, c.matcher, c.client)
|
||||
|
||||
pluginContext, pluginContextCancel := context.WithCancel(context.Background())
|
||||
go func() {
|
||||
defer pluginContextCancel()
|
||||
<-c.stopCh
|
||||
}()
|
||||
|
||||
go func() {
|
||||
err := c.source.Run(pluginContext)
|
||||
if err != nil && !errors.Is(err, context.Canceled) {
|
||||
utilruntime.HandleError(fmt.Errorf("policy source context unexpectedly closed: %w", err))
|
||||
}
|
||||
}()
|
||||
|
||||
err := c.dispatcher.Start(pluginContext)
|
||||
if err != nil && !errors.Is(err, context.Canceled) {
|
||||
utilruntime.HandleError(fmt.Errorf("policy dispatcher context unexpectedly closed: %w", err))
|
||||
}
|
||||
|
||||
c.SetReadyFunc(func() bool {
|
||||
return namespaceInformer.Informer().HasSynced() && c.source.HasSynced()
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Plugin[H]) Dispatch(
|
||||
ctx context.Context,
|
||||
a admission.Attributes,
|
||||
o admission.ObjectInterfaces,
|
||||
) (err error) {
|
||||
if !c.enabled {
|
||||
return nil
|
||||
} else if c.shouldIgnoreResource(a) {
|
||||
return nil
|
||||
} else if !c.WaitForReady() {
|
||||
return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
|
||||
}
|
||||
|
||||
return c.dispatcher.Dispatch(ctx, a, o, c.source.Hooks())
|
||||
}
|
||||
|
||||
func (c *Plugin[H]) shouldIgnoreResource(attr admission.Attributes) bool {
|
||||
gvr := attr.GetResource()
|
||||
// exclusion decision ignores the version.
|
||||
gr := gvr.GroupResource()
|
||||
return c.excludedResources.Has(gr)
|
||||
}
|
417
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/generic/policy_dispatcher.go
generated
vendored
Normal file
417
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/generic/policy_dispatcher.go
generated
vendored
Normal file
@ -0,0 +1,417 @@
|
||||
/*
|
||||
Copyright 2024 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"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"k8s.io/api/admissionregistration/v1"
|
||||
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/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/policy/matching"
|
||||
webhookgeneric "k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
// PolicyInvocation is a single policy-binding-param tuple from a Policy Hook
|
||||
// in the context of a specific request. The params have already been resolved
|
||||
// and any error in configuration or setting up the invocation is stored in
|
||||
// the Error field.
|
||||
type PolicyInvocation[P runtime.Object, B runtime.Object, E Evaluator] struct {
|
||||
// Relevant policy for this hook.
|
||||
// This field is always populated
|
||||
Policy P
|
||||
|
||||
// Matched Kind for the request given the policy's matchconstraints
|
||||
// May be empty if there was an error matching the resource
|
||||
Kind schema.GroupVersionKind
|
||||
|
||||
// Matched Resource for the request given the policy's matchconstraints
|
||||
// May be empty if there was an error matching the resource
|
||||
Resource schema.GroupVersionResource
|
||||
|
||||
// Relevant binding for this hook.
|
||||
// May be empty if there was an error with the policy's configuration itself
|
||||
Binding B
|
||||
|
||||
// Compiled policy evaluator
|
||||
Evaluator E
|
||||
|
||||
// Params fetched by the binding to use to evaluate the policy
|
||||
Param runtime.Object
|
||||
}
|
||||
|
||||
// dispatcherDelegate is called during a request with a pre-filtered list
|
||||
// of (Policy, Binding, Param) tuples that are active and match the request.
|
||||
// The dispatcher delegate is responsible for updating the object on the
|
||||
// admission attributes in the case of mutation, or returning a status error in
|
||||
// the case of validation.
|
||||
//
|
||||
// The delegate provides the "validation" or "mutation" aspect of dispatcher functionality
|
||||
// (in contrast to generic.PolicyDispatcher which only selects active policies and params)
|
||||
type dispatcherDelegate[P, B runtime.Object, E Evaluator] func(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces, versionedAttributes webhookgeneric.VersionedAttributeAccessor, invocations []PolicyInvocation[P, B, E]) ([]PolicyError, *apierrors.StatusError)
|
||||
|
||||
type policyDispatcher[P runtime.Object, B runtime.Object, E Evaluator] struct {
|
||||
newPolicyAccessor func(P) PolicyAccessor
|
||||
newBindingAccessor func(B) BindingAccessor
|
||||
matcher PolicyMatcher
|
||||
delegate dispatcherDelegate[P, B, E]
|
||||
}
|
||||
|
||||
func NewPolicyDispatcher[P runtime.Object, B runtime.Object, E Evaluator](
|
||||
newPolicyAccessor func(P) PolicyAccessor,
|
||||
newBindingAccessor func(B) BindingAccessor,
|
||||
matcher *matching.Matcher,
|
||||
delegate dispatcherDelegate[P, B, E],
|
||||
) Dispatcher[PolicyHook[P, B, E]] {
|
||||
return &policyDispatcher[P, B, E]{
|
||||
newPolicyAccessor: newPolicyAccessor,
|
||||
newBindingAccessor: newBindingAccessor,
|
||||
matcher: NewPolicyMatcher(matcher),
|
||||
delegate: delegate,
|
||||
}
|
||||
}
|
||||
|
||||
// Dispatch implements generic.Dispatcher. It loops through all active hooks
|
||||
// (policy x binding pairs) and selects those which are active for the current
|
||||
// request. It then resolves all params and creates an Invocation for each
|
||||
// matching policy-binding-param tuple. The delegate is then called with the
|
||||
// list of tuples.
|
||||
func (d *policyDispatcher[P, B, E]) Start(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Note: MatchConditions expressions are not evaluated here. The dispatcher delegate
|
||||
// is expected to ignore the result of any policies whose match conditions dont pass.
|
||||
// This may be possible to refactor so matchconditions are checked here instead.
|
||||
func (d *policyDispatcher[P, B, E]) Dispatch(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces, hooks []PolicyHook[P, B, E]) error {
|
||||
var relevantHooks []PolicyInvocation[P, B, E]
|
||||
// Construct all the versions we need to call our webhooks
|
||||
versionedAttrAccessor := &versionedAttributeAccessor{
|
||||
versionedAttrs: map[schema.GroupVersionKind]*admission.VersionedAttributes{},
|
||||
attr: a,
|
||||
objectInterfaces: o,
|
||||
}
|
||||
|
||||
var policyErrors []PolicyError
|
||||
addConfigError := func(err error, definition PolicyAccessor, binding BindingAccessor) {
|
||||
var message error
|
||||
if binding == nil {
|
||||
message = fmt.Errorf("failed to configure policy: %w", err)
|
||||
} else {
|
||||
message = fmt.Errorf("failed to configure binding: %w", err)
|
||||
}
|
||||
|
||||
policyErrors = append(policyErrors, PolicyError{
|
||||
Policy: definition,
|
||||
Binding: binding,
|
||||
Message: message,
|
||||
})
|
||||
}
|
||||
|
||||
for _, hook := range hooks {
|
||||
policyAccessor := d.newPolicyAccessor(hook.Policy)
|
||||
matches, matchGVR, matchGVK, err := d.matcher.DefinitionMatches(a, o, policyAccessor)
|
||||
if err != nil {
|
||||
// There was an error evaluating if this policy matches anything.
|
||||
addConfigError(err, policyAccessor, nil)
|
||||
continue
|
||||
} else if !matches {
|
||||
continue
|
||||
} else if hook.ConfigurationError != nil {
|
||||
addConfigError(hook.ConfigurationError, policyAccessor, nil)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, binding := range hook.Bindings {
|
||||
bindingAccessor := d.newBindingAccessor(binding)
|
||||
matches, err = d.matcher.BindingMatches(a, o, bindingAccessor)
|
||||
if err != nil {
|
||||
// There was an error evaluating if this binding matches anything.
|
||||
addConfigError(err, policyAccessor, bindingAccessor)
|
||||
continue
|
||||
} else if !matches {
|
||||
continue
|
||||
}
|
||||
|
||||
// here the binding matches.
|
||||
// VersionedAttr result will be cached and reused later during parallel
|
||||
// hook calls.
|
||||
if _, err = versionedAttrAccessor.VersionedAttribute(matchGVK); err != nil {
|
||||
// VersionedAttr result will be cached and reused later during parallel
|
||||
// hook calls.
|
||||
addConfigError(err, policyAccessor, nil)
|
||||
continue
|
||||
}
|
||||
|
||||
// Collect params for this binding
|
||||
params, err := CollectParams(
|
||||
policyAccessor.GetParamKind(),
|
||||
hook.ParamInformer,
|
||||
hook.ParamScope,
|
||||
bindingAccessor.GetParamRef(),
|
||||
a.GetNamespace(),
|
||||
)
|
||||
if err != nil {
|
||||
// There was an error collecting params for this binding.
|
||||
addConfigError(err, policyAccessor, bindingAccessor)
|
||||
continue
|
||||
}
|
||||
|
||||
// If params is empty and there was no error, that means that
|
||||
// ParamNotFoundAction is ignore, so it shouldnt be added to list
|
||||
for _, param := range params {
|
||||
relevantHooks = append(relevantHooks, PolicyInvocation[P, B, E]{
|
||||
Policy: hook.Policy,
|
||||
Binding: binding,
|
||||
Kind: matchGVK,
|
||||
Resource: matchGVR,
|
||||
Param: param,
|
||||
Evaluator: hook.Evaluator,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(relevantHooks) > 0 {
|
||||
extraPolicyErrors, statusError := d.delegate(ctx, a, o, versionedAttrAccessor, relevantHooks)
|
||||
if statusError != nil {
|
||||
return statusError
|
||||
}
|
||||
policyErrors = append(policyErrors, extraPolicyErrors...)
|
||||
}
|
||||
|
||||
var filteredErrors []PolicyError
|
||||
for _, e := range policyErrors {
|
||||
// we always default the FailurePolicy if it is unset and validate it in API level
|
||||
var policy v1.FailurePolicyType
|
||||
if fp := e.Policy.GetFailurePolicy(); fp == nil {
|
||||
policy = v1.Fail
|
||||
} else {
|
||||
policy = *fp
|
||||
}
|
||||
|
||||
switch policy {
|
||||
case v1.Ignore:
|
||||
// TODO: add metrics for ignored error here
|
||||
continue
|
||||
case v1.Fail:
|
||||
filteredErrors = append(filteredErrors, e)
|
||||
default:
|
||||
filteredErrors = append(filteredErrors, e)
|
||||
}
|
||||
}
|
||||
|
||||
if len(filteredErrors) > 0 {
|
||||
|
||||
forbiddenErr := admission.NewForbidden(a, fmt.Errorf("admission request denied by policy"))
|
||||
|
||||
// The forbiddenErr is always a StatusError.
|
||||
var err *apierrors.StatusError
|
||||
if !errors.As(forbiddenErr, &err) {
|
||||
// Should never happen.
|
||||
return apierrors.NewInternalError(fmt.Errorf("failed to create status error"))
|
||||
}
|
||||
err.ErrStatus.Message = ""
|
||||
|
||||
for _, policyError := range filteredErrors {
|
||||
message := policyError.Error()
|
||||
|
||||
// If this is the first denied decision, use its message and reason
|
||||
// for the status error message.
|
||||
if err.ErrStatus.Message == "" {
|
||||
err.ErrStatus.Message = message
|
||||
if policyError.Reason != "" {
|
||||
err.ErrStatus.Reason = policyError.Reason
|
||||
}
|
||||
}
|
||||
|
||||
// Add the denied decision's message to the status error's details
|
||||
err.ErrStatus.Details.Causes = append(
|
||||
err.ErrStatus.Details.Causes,
|
||||
metav1.StatusCause{Message: message})
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Returns params to use to evaluate a policy-binding with given param
|
||||
// configuration. If the policy-binding has no param configuration, it
|
||||
// returns a single-element list with a nil param.
|
||||
func CollectParams(
|
||||
paramKind *v1.ParamKind,
|
||||
paramInformer informers.GenericInformer,
|
||||
paramScope meta.RESTScope,
|
||||
paramRef *v1.ParamRef,
|
||||
namespace string,
|
||||
) ([]runtime.Object, error) {
|
||||
// If definition has paramKind, paramRef is required in binding.
|
||||
// If definition has no paramKind, paramRef set in binding will be ignored.
|
||||
var params []runtime.Object
|
||||
var paramStore cache.GenericNamespaceLister
|
||||
|
||||
// Make sure the param kind is ready to use
|
||||
if paramKind != nil && paramRef != nil {
|
||||
if paramInformer == nil {
|
||||
return nil, fmt.Errorf("paramKind kind `%v` not known",
|
||||
paramKind.String())
|
||||
}
|
||||
|
||||
// Set up cluster-scoped, or namespaced access to the params
|
||||
// "default" if not provided, and paramKind is namespaced
|
||||
paramStore = paramInformer.Lister()
|
||||
if paramScope.Name() == meta.RESTScopeNameNamespace {
|
||||
paramsNamespace := namespace
|
||||
if len(paramRef.Namespace) > 0 {
|
||||
paramsNamespace = paramRef.Namespace
|
||||
} else if len(paramsNamespace) == 0 {
|
||||
// You must supply namespace if your matcher can possibly
|
||||
// match a cluster-scoped resource
|
||||
return nil, fmt.Errorf("cannot use namespaced paramRef in policy binding that matches cluster-scoped resources")
|
||||
}
|
||||
|
||||
paramStore = paramInformer.Lister().ByNamespace(paramsNamespace)
|
||||
}
|
||||
|
||||
// If the param informer for this admission policy has not yet
|
||||
// had time to perform an initial listing, don't attempt to use
|
||||
// it.
|
||||
timeoutCtx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if !cache.WaitForCacheSync(timeoutCtx.Done(), paramInformer.Informer().HasSynced) {
|
||||
return nil, fmt.Errorf("paramKind kind `%v` not yet synced to use for admission",
|
||||
paramKind.String())
|
||||
}
|
||||
}
|
||||
|
||||
// Find params to use with policy
|
||||
switch {
|
||||
case paramKind == nil:
|
||||
// ParamKind is unset. Ignore any globalParamRef or namespaceParamRef
|
||||
// setting.
|
||||
return []runtime.Object{nil}, nil
|
||||
case paramRef == nil:
|
||||
// Policy ParamKind is set, but binding does not use it.
|
||||
// Validate with nil params
|
||||
return []runtime.Object{nil}, nil
|
||||
case len(paramRef.Namespace) > 0 && paramScope.Name() == meta.RESTScopeRoot.Name():
|
||||
// Not allowed to set namespace for cluster-scoped param
|
||||
return nil, fmt.Errorf("paramRef.namespace must not be provided for a cluster-scoped `paramKind`")
|
||||
|
||||
case len(paramRef.Name) > 0:
|
||||
if paramRef.Selector != nil {
|
||||
// This should be validated, but just in case.
|
||||
return nil, fmt.Errorf("paramRef.name and paramRef.selector are mutually exclusive")
|
||||
}
|
||||
|
||||
switch param, err := paramStore.Get(paramRef.Name); {
|
||||
case err == nil:
|
||||
params = []runtime.Object{param}
|
||||
case apierrors.IsNotFound(err):
|
||||
// Param not yet available. User may need to wait a bit
|
||||
// before being able to use it for validation.
|
||||
//
|
||||
// Set params to nil to prepare for not found action
|
||||
params = nil
|
||||
case apierrors.IsInvalid(err):
|
||||
// Param mis-configured
|
||||
// require to set namespace for namespaced resource
|
||||
// and unset namespace for cluster scoped resource
|
||||
return nil, err
|
||||
default:
|
||||
// Internal error
|
||||
utilruntime.HandleError(err)
|
||||
return nil, err
|
||||
}
|
||||
case paramRef.Selector != nil:
|
||||
// Select everything by default if empty name and selector
|
||||
selector, err := metav1.LabelSelectorAsSelector(paramRef.Selector)
|
||||
if err != nil {
|
||||
// Cannot parse label selector: configuration error
|
||||
return nil, err
|
||||
|
||||
}
|
||||
|
||||
paramList, err := paramStore.List(selector)
|
||||
if err != nil {
|
||||
// There was a bad internal error
|
||||
utilruntime.HandleError(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Successfully grabbed params
|
||||
params = paramList
|
||||
default:
|
||||
// Should be unreachable due to validation
|
||||
return nil, fmt.Errorf("one of name or selector must be provided")
|
||||
}
|
||||
|
||||
// Apply fail action for params not found case
|
||||
if len(params) == 0 && paramRef.ParameterNotFoundAction != nil && *paramRef.ParameterNotFoundAction == v1.DenyAction {
|
||||
return nil, errors.New("no params found for policy binding with `Deny` parameterNotFoundAction")
|
||||
}
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
var _ webhookgeneric.VersionedAttributeAccessor = &versionedAttributeAccessor{}
|
||||
|
||||
type versionedAttributeAccessor struct {
|
||||
versionedAttrs map[schema.GroupVersionKind]*admission.VersionedAttributes
|
||||
attr admission.Attributes
|
||||
objectInterfaces admission.ObjectInterfaces
|
||||
}
|
||||
|
||||
func (v *versionedAttributeAccessor) VersionedAttribute(gvk schema.GroupVersionKind) (*admission.VersionedAttributes, error) {
|
||||
if val, ok := v.versionedAttrs[gvk]; ok {
|
||||
return val, nil
|
||||
}
|
||||
versionedAttr, err := admission.NewVersionedAttributes(v.attr, gvk, v.objectInterfaces)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v.versionedAttrs[gvk] = versionedAttr
|
||||
return versionedAttr, nil
|
||||
}
|
||||
|
||||
type PolicyError struct {
|
||||
Policy PolicyAccessor
|
||||
Binding BindingAccessor
|
||||
Message error
|
||||
Reason metav1.StatusReason
|
||||
}
|
||||
|
||||
func (c PolicyError) Error() string {
|
||||
if c.Binding != nil {
|
||||
return fmt.Sprintf("policy '%s' with binding '%s' denied request: %s", c.Policy.GetName(), c.Binding.GetName(), c.Message.Error())
|
||||
}
|
||||
|
||||
return fmt.Sprintf("policy %q denied request: %s", c.Policy.GetName(), c.Message.Error())
|
||||
}
|
108
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/generic/policy_matcher.go
generated
vendored
Normal file
108
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/generic/policy_matcher.go
generated
vendored
Normal file
@ -0,0 +1,108 @@
|
||||
/*
|
||||
Copyright 2022 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"
|
||||
|
||||
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/policy/matching"
|
||||
)
|
||||
|
||||
// Matcher is used for matching ValidatingAdmissionPolicy and ValidatingAdmissionPolicyBinding to attributes
|
||||
type PolicyMatcher interface {
|
||||
admission.InitializationValidator
|
||||
|
||||
// DefinitionMatches says whether this policy definition matches the provided admission
|
||||
// resource request
|
||||
DefinitionMatches(a admission.Attributes, o admission.ObjectInterfaces, definition PolicyAccessor) (bool, schema.GroupVersionResource, schema.GroupVersionKind, error)
|
||||
|
||||
// BindingMatches says whether this policy definition matches the provided admission
|
||||
// resource request
|
||||
BindingMatches(a admission.Attributes, o admission.ObjectInterfaces, binding BindingAccessor) (bool, error)
|
||||
|
||||
// GetNamespace retrieves the Namespace resource by the given name. The name may be empty, in which case
|
||||
// GetNamespace must return nil, nil
|
||||
GetNamespace(name string) (*corev1.Namespace, error)
|
||||
}
|
||||
|
||||
type matcher struct {
|
||||
Matcher *matching.Matcher
|
||||
}
|
||||
|
||||
func NewPolicyMatcher(m *matching.Matcher) PolicyMatcher {
|
||||
return &matcher{
|
||||
Matcher: m,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateInitialization checks if Matcher is initialized.
|
||||
func (c *matcher) ValidateInitialization() error {
|
||||
return c.Matcher.ValidateInitialization()
|
||||
}
|
||||
|
||||
// DefinitionMatches returns whether this ValidatingAdmissionPolicy matches the provided admission resource request
|
||||
func (c *matcher) DefinitionMatches(a admission.Attributes, o admission.ObjectInterfaces, definition PolicyAccessor) (bool, schema.GroupVersionResource, schema.GroupVersionKind, error) {
|
||||
constraints := definition.GetMatchConstraints()
|
||||
if constraints == nil {
|
||||
return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, fmt.Errorf("policy contained no match constraints, a required field")
|
||||
}
|
||||
criteria := matchCriteria{constraints: constraints}
|
||||
return c.Matcher.Matches(a, o, &criteria)
|
||||
}
|
||||
|
||||
// BindingMatches returns whether this ValidatingAdmissionPolicyBinding matches the provided admission resource request
|
||||
func (c *matcher) BindingMatches(a admission.Attributes, o admission.ObjectInterfaces, binding BindingAccessor) (bool, error) {
|
||||
matchResources := binding.GetMatchResources()
|
||||
if matchResources == nil {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
criteria := matchCriteria{constraints: matchResources}
|
||||
isMatch, _, _, err := c.Matcher.Matches(a, o, &criteria)
|
||||
return isMatch, err
|
||||
}
|
||||
|
||||
func (c *matcher) GetNamespace(name string) (*corev1.Namespace, error) {
|
||||
return c.Matcher.GetNamespace(name)
|
||||
}
|
||||
|
||||
var _ matching.MatchCriteria = &matchCriteria{}
|
||||
|
||||
type matchCriteria struct {
|
||||
constraints *admissionregistrationv1.MatchResources
|
||||
}
|
||||
|
||||
// GetParsedNamespaceSelector returns the converted LabelSelector which implements labels.Selector
|
||||
func (m *matchCriteria) GetParsedNamespaceSelector() (labels.Selector, error) {
|
||||
return metav1.LabelSelectorAsSelector(m.constraints.NamespaceSelector)
|
||||
}
|
||||
|
||||
// GetParsedObjectSelector returns the converted LabelSelector which implements labels.Selector
|
||||
func (m *matchCriteria) GetParsedObjectSelector() (labels.Selector, error) {
|
||||
return metav1.LabelSelectorAsSelector(m.constraints.ObjectSelector)
|
||||
}
|
||||
|
||||
// GetMatchResources returns the matchConstraints
|
||||
func (m *matchCriteria) GetMatchResources() admissionregistrationv1.MatchResources {
|
||||
return *m.constraints
|
||||
}
|
493
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/generic/policy_source.go
generated
vendored
Normal file
493
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/generic/policy_source.go
generated
vendored
Normal file
@ -0,0 +1,493 @@
|
||||
/*
|
||||
Copyright 2024 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"
|
||||
goerrors "errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/policy/internal/generic"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/dynamic/dynamicinformer"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
// Interval for refreshing policies.
|
||||
// TODO: Consider reducing this to a shorter duration or replacing this entirely
|
||||
// with checks that detect when a policy change took effect.
|
||||
const policyRefreshIntervalDefault = 1 * time.Second
|
||||
|
||||
var policyRefreshInterval = policyRefreshIntervalDefault
|
||||
|
||||
type policySource[P runtime.Object, B runtime.Object, E Evaluator] struct {
|
||||
ctx context.Context
|
||||
policyInformer generic.Informer[P]
|
||||
bindingInformer generic.Informer[B]
|
||||
restMapper meta.RESTMapper
|
||||
newPolicyAccessor func(P) PolicyAccessor
|
||||
newBindingAccessor func(B) BindingAccessor
|
||||
|
||||
informerFactory informers.SharedInformerFactory
|
||||
dynamicClient dynamic.Interface
|
||||
|
||||
compiler func(P) E
|
||||
|
||||
// Currently compiled list of valid/active policy-binding pairs
|
||||
policies atomic.Pointer[[]PolicyHook[P, B, E]]
|
||||
// Whether the cache of policies is dirty and needs to be recompiled
|
||||
policiesDirty atomic.Bool
|
||||
|
||||
lock sync.Mutex
|
||||
compiledPolicies map[types.NamespacedName]compiledPolicyEntry[E]
|
||||
|
||||
// Temporary until we use the dynamic informer factory
|
||||
paramsCRDControllers map[schema.GroupVersionKind]*paramInfo
|
||||
}
|
||||
|
||||
type paramInfo struct {
|
||||
mapping meta.RESTMapping
|
||||
|
||||
// When the param is changed, or the informer is done being used, the cancel
|
||||
// func should be called to stop/cleanup the original informer
|
||||
cancelFunc func()
|
||||
|
||||
// The lister for this param
|
||||
informer informers.GenericInformer
|
||||
}
|
||||
|
||||
type compiledPolicyEntry[E Evaluator] struct {
|
||||
policyVersion string
|
||||
evaluator E
|
||||
}
|
||||
|
||||
type PolicyHook[P runtime.Object, B runtime.Object, E Evaluator] struct {
|
||||
Policy P
|
||||
Bindings []B
|
||||
|
||||
// ParamInformer is the informer for the param CRD for this policy, or nil if
|
||||
// there is no param or if there was a configuration error
|
||||
ParamInformer informers.GenericInformer
|
||||
ParamScope meta.RESTScope
|
||||
|
||||
Evaluator E
|
||||
ConfigurationError error
|
||||
}
|
||||
|
||||
var _ Source[PolicyHook[runtime.Object, runtime.Object, Evaluator]] = &policySource[runtime.Object, runtime.Object, Evaluator]{}
|
||||
|
||||
func NewPolicySource[P runtime.Object, B runtime.Object, E Evaluator](
|
||||
policyInformer cache.SharedIndexInformer,
|
||||
bindingInformer cache.SharedIndexInformer,
|
||||
newPolicyAccessor func(P) PolicyAccessor,
|
||||
newBindingAccessor func(B) BindingAccessor,
|
||||
compiler func(P) E,
|
||||
paramInformerFactory informers.SharedInformerFactory,
|
||||
dynamicClient dynamic.Interface,
|
||||
restMapper meta.RESTMapper,
|
||||
) Source[PolicyHook[P, B, E]] {
|
||||
res := &policySource[P, B, E]{
|
||||
compiler: compiler,
|
||||
policyInformer: generic.NewInformer[P](policyInformer),
|
||||
bindingInformer: generic.NewInformer[B](bindingInformer),
|
||||
compiledPolicies: map[types.NamespacedName]compiledPolicyEntry[E]{},
|
||||
newPolicyAccessor: newPolicyAccessor,
|
||||
newBindingAccessor: newBindingAccessor,
|
||||
paramsCRDControllers: map[schema.GroupVersionKind]*paramInfo{},
|
||||
informerFactory: paramInformerFactory,
|
||||
dynamicClient: dynamicClient,
|
||||
restMapper: restMapper,
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// SetPolicyRefreshIntervalForTests allows the refresh interval to be overridden during tests.
|
||||
// This should only be called from tests.
|
||||
func SetPolicyRefreshIntervalForTests(interval time.Duration) func() {
|
||||
policyRefreshInterval = interval
|
||||
return func() {
|
||||
policyRefreshInterval = policyRefreshIntervalDefault
|
||||
}
|
||||
}
|
||||
|
||||
func (s *policySource[P, B, E]) Run(ctx context.Context) error {
|
||||
if s.ctx != nil {
|
||||
return fmt.Errorf("policy source already running")
|
||||
}
|
||||
|
||||
// Wait for initial cache sync of policies and informers before reconciling
|
||||
// any
|
||||
if !cache.WaitForNamedCacheSync(fmt.Sprintf("%T", s), ctx.Done(), s.UpstreamHasSynced) {
|
||||
err := ctx.Err()
|
||||
if err == nil {
|
||||
err = fmt.Errorf("initial cache sync for %T failed", s)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
s.ctx = ctx
|
||||
|
||||
// Perform initial policy compilation after initial list has finished
|
||||
s.notify()
|
||||
s.refreshPolicies()
|
||||
|
||||
notifyFuncs := cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: func(_ interface{}) {
|
||||
s.notify()
|
||||
},
|
||||
UpdateFunc: func(_, _ interface{}) {
|
||||
s.notify()
|
||||
},
|
||||
DeleteFunc: func(_ interface{}) {
|
||||
s.notify()
|
||||
},
|
||||
}
|
||||
handle, err := s.policyInformer.AddEventHandler(notifyFuncs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err := s.policyInformer.RemoveEventHandler(handle); err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("failed to remove policy event handler: %w", err))
|
||||
}
|
||||
}()
|
||||
|
||||
bindingHandle, err := s.bindingInformer.AddEventHandler(notifyFuncs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err := s.bindingInformer.RemoveEventHandler(bindingHandle); err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("failed to remove binding event handler: %w", err))
|
||||
}
|
||||
}()
|
||||
|
||||
// Start a worker that checks every second to see if policy data is dirty
|
||||
// and needs to be recompiled
|
||||
go func() {
|
||||
// Loop every 1 second until context is cancelled, refreshing policies
|
||||
wait.Until(s.refreshPolicies, policyRefreshInterval, ctx.Done())
|
||||
}()
|
||||
|
||||
<-ctx.Done()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *policySource[P, B, E]) UpstreamHasSynced() bool {
|
||||
return s.policyInformer.HasSynced() && s.bindingInformer.HasSynced()
|
||||
}
|
||||
|
||||
// HasSynced implements Source.
|
||||
func (s *policySource[P, B, E]) HasSynced() bool {
|
||||
// As an invariant we never store `nil` into the atomic list of processed
|
||||
// policy hooks. If it is nil, then we haven't compiled all the policies
|
||||
// and stored them yet.
|
||||
return s.Hooks() != nil
|
||||
}
|
||||
|
||||
// Hooks implements Source.
|
||||
func (s *policySource[P, B, E]) Hooks() []PolicyHook[P, B, E] {
|
||||
res := s.policies.Load()
|
||||
|
||||
// Error case should not happen since evaluation function never
|
||||
// returns error
|
||||
if res == nil {
|
||||
// Not yet synced
|
||||
return nil
|
||||
}
|
||||
|
||||
return *res
|
||||
}
|
||||
|
||||
func (s *policySource[P, B, E]) refreshPolicies() {
|
||||
if !s.UpstreamHasSynced() {
|
||||
return
|
||||
} else if !s.policiesDirty.Swap(false) {
|
||||
return
|
||||
}
|
||||
|
||||
// It is ok the cache gets marked dirty again between us clearing the
|
||||
// flag and us calculating the policies. The dirty flag would be marked again,
|
||||
// and we'd have a no-op after comparing resource versions on the next sync.
|
||||
klog.Infof("refreshing policies")
|
||||
policies, err := s.calculatePolicyData()
|
||||
|
||||
// Intentionally store policy list regardless of error. There may be
|
||||
// an error returned if there was a configuration error in one of the policies,
|
||||
// but we would still want those policies evaluated
|
||||
// (for instance to return error on failaction). Or if there was an error
|
||||
// listing all policies at all, we would want to wipe the list.
|
||||
s.policies.Store(&policies)
|
||||
|
||||
if err != nil {
|
||||
// An error was generated while syncing policies. Mark it as dirty again
|
||||
// so we can retry later
|
||||
utilruntime.HandleError(fmt.Errorf("encountered error syncing policies: %w. Rescheduling policy sync", err))
|
||||
s.notify()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *policySource[P, B, E]) notify() {
|
||||
s.policiesDirty.Store(true)
|
||||
}
|
||||
|
||||
// calculatePolicyData calculates the list of policies and bindings for each
|
||||
// policy. If there is an error in generation, it will return the error and
|
||||
// the partial list of policies that were able to be generated. Policies that
|
||||
// have an error will have a non-nil ConfigurationError field, but still be
|
||||
// included in the result.
|
||||
//
|
||||
// This function caches the result of the intermediate compilations
|
||||
func (s *policySource[P, B, E]) calculatePolicyData() ([]PolicyHook[P, B, E], error) {
|
||||
if !s.UpstreamHasSynced() {
|
||||
return nil, fmt.Errorf("cannot calculate policy data until upstream has synced")
|
||||
}
|
||||
|
||||
// Fat-fingered lock that can be made more fine-tuned if required
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
// Create a local copy of all policies and bindings
|
||||
policiesToBindings := map[types.NamespacedName][]B{}
|
||||
bindingList, err := s.bindingInformer.List(labels.Everything())
|
||||
if err != nil {
|
||||
// This should never happen unless types are misconfigured
|
||||
// (can't use meta.accessor on them)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Gather a list of all active policy bindings
|
||||
for _, bindingSpec := range bindingList {
|
||||
bindingAccessor := s.newBindingAccessor(bindingSpec)
|
||||
policyKey := bindingAccessor.GetPolicyName()
|
||||
|
||||
// Add this binding to the list of bindings for this policy
|
||||
policiesToBindings[policyKey] = append(policiesToBindings[policyKey], bindingSpec)
|
||||
}
|
||||
|
||||
result := make([]PolicyHook[P, B, E], 0, len(bindingList))
|
||||
usedParams := map[schema.GroupVersionKind]struct{}{}
|
||||
var errs []error
|
||||
for policyKey, bindingSpecs := range policiesToBindings {
|
||||
var inf generic.NamespacedLister[P] = s.policyInformer
|
||||
if len(policyKey.Namespace) > 0 {
|
||||
inf = s.policyInformer.Namespaced(policyKey.Namespace)
|
||||
}
|
||||
policySpec, err := inf.Get(policyKey.Name)
|
||||
if errors.IsNotFound(err) {
|
||||
// Policy for bindings doesn't exist. This can happen if the policy
|
||||
// was deleted before the binding, or the binding was created first.
|
||||
//
|
||||
// Just skip bindings that refer to non-existent policies
|
||||
// If the policy is recreated, the cache will be marked dirty and
|
||||
// this function will run again.
|
||||
continue
|
||||
} else if err != nil {
|
||||
// This should never happen since fetching from a cache should never
|
||||
// fail and this function checks that the cache was synced before
|
||||
// even getting to this point.
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
|
||||
var parsedParamKind *schema.GroupVersionKind
|
||||
policyAccessor := s.newPolicyAccessor(policySpec)
|
||||
|
||||
if paramKind := policyAccessor.GetParamKind(); paramKind != nil {
|
||||
groupVersion, err := schema.ParseGroupVersion(paramKind.APIVersion)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed to parse paramKind APIVersion: %w", err))
|
||||
continue
|
||||
}
|
||||
parsedParamKind = &schema.GroupVersionKind{
|
||||
Group: groupVersion.Group,
|
||||
Version: groupVersion.Version,
|
||||
Kind: paramKind.Kind,
|
||||
}
|
||||
|
||||
// TEMPORARY UNTIL WE HAVE SHARED PARAM INFORMERS
|
||||
usedParams[*parsedParamKind] = struct{}{}
|
||||
}
|
||||
|
||||
paramInformer, paramScope, configurationError := s.ensureParamsForPolicyLocked(parsedParamKind)
|
||||
result = append(result, PolicyHook[P, B, E]{
|
||||
Policy: policySpec,
|
||||
Bindings: bindingSpecs,
|
||||
Evaluator: s.compilePolicyLocked(policySpec),
|
||||
ParamInformer: paramInformer,
|
||||
ParamScope: paramScope,
|
||||
ConfigurationError: configurationError,
|
||||
})
|
||||
|
||||
// Should queue a re-sync for policy sync error. If our shared param
|
||||
// informer can notify us when CRD discovery changes we can remove this
|
||||
// and just rely on the informer to notify us when the CRDs change
|
||||
if configurationError != nil {
|
||||
errs = append(errs, configurationError)
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up orphaned policies by replacing the old cache of compiled policies
|
||||
// (the map of used policies is updated by `compilePolicy`)
|
||||
for policyKey := range s.compiledPolicies {
|
||||
if _, wasSeen := policiesToBindings[policyKey]; !wasSeen {
|
||||
delete(s.compiledPolicies, policyKey)
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up orphaned param informers
|
||||
for paramKind, info := range s.paramsCRDControllers {
|
||||
if _, wasSeen := usedParams[paramKind]; !wasSeen {
|
||||
info.cancelFunc()
|
||||
delete(s.paramsCRDControllers, paramKind)
|
||||
}
|
||||
}
|
||||
|
||||
err = nil
|
||||
if len(errs) > 0 {
|
||||
err = goerrors.Join(errs...)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
// ensureParamsForPolicyLocked ensures that the informer for the paramKind is
|
||||
// started and returns the informer and the scope of the paramKind.
|
||||
//
|
||||
// Must be called under write lock
|
||||
func (s *policySource[P, B, E]) ensureParamsForPolicyLocked(paramSource *schema.GroupVersionKind) (informers.GenericInformer, meta.RESTScope, error) {
|
||||
if paramSource == nil {
|
||||
return nil, nil, nil
|
||||
} else if info, ok := s.paramsCRDControllers[*paramSource]; ok {
|
||||
return info.informer, info.mapping.Scope, nil
|
||||
}
|
||||
|
||||
mapping, err := s.restMapper.RESTMapping(schema.GroupKind{
|
||||
Group: paramSource.Group,
|
||||
Kind: paramSource.Kind,
|
||||
}, paramSource.Version)
|
||||
|
||||
if err != nil {
|
||||
// Failed to resolve. Return error so we retry again (rate limited)
|
||||
// Save a record of this definition with an evaluator that unconditionally
|
||||
return nil, nil, fmt.Errorf("failed to find resource referenced by paramKind: '%v'", *paramSource)
|
||||
}
|
||||
|
||||
// We are not watching this param. Start an informer for it.
|
||||
instanceContext, instanceCancel := context.WithCancel(s.ctx)
|
||||
|
||||
var informer informers.GenericInformer
|
||||
|
||||
// Try to see if our provided informer factory has an informer for this type.
|
||||
// We assume the informer is already started, and starts all types associated
|
||||
// with it.
|
||||
if genericInformer, err := s.informerFactory.ForResource(mapping.Resource); err == nil {
|
||||
informer = genericInformer
|
||||
|
||||
// Start the informer
|
||||
s.informerFactory.Start(instanceContext.Done())
|
||||
|
||||
} else {
|
||||
// Dynamic JSON informer fallback.
|
||||
// Cannot use shared dynamic informer since it would be impossible
|
||||
// to clean CRD informers properly with multiple dependents
|
||||
// (cannot start ahead of time, and cannot track dependencies via stopCh)
|
||||
informer = dynamicinformer.NewFilteredDynamicInformer(
|
||||
s.dynamicClient,
|
||||
mapping.Resource,
|
||||
corev1.NamespaceAll,
|
||||
// Use same interval as is used for k8s typed sharedInformerFactory
|
||||
// https://github.com/kubernetes/kubernetes/blob/7e0923899fed622efbc8679cca6b000d43633e38/cmd/kube-apiserver/app/server.go#L430
|
||||
10*time.Minute,
|
||||
cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc},
|
||||
nil,
|
||||
)
|
||||
go informer.Informer().Run(instanceContext.Done())
|
||||
}
|
||||
|
||||
klog.Infof("informer started for %v", *paramSource)
|
||||
ret := ¶mInfo{
|
||||
mapping: *mapping,
|
||||
cancelFunc: instanceCancel,
|
||||
informer: informer,
|
||||
}
|
||||
s.paramsCRDControllers[*paramSource] = ret
|
||||
return ret.informer, mapping.Scope, nil
|
||||
}
|
||||
|
||||
// For testing
|
||||
func (s *policySource[P, B, E]) getParamInformer(param schema.GroupVersionKind) (informers.GenericInformer, meta.RESTScope) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
if info, ok := s.paramsCRDControllers[param]; ok {
|
||||
return info.informer, info.mapping.Scope
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// compilePolicyLocked compiles the policy and returns the evaluator for it.
|
||||
// If the policy has not changed since the last compilation, it will return
|
||||
// the cached evaluator.
|
||||
//
|
||||
// Must be called under write lock
|
||||
func (s *policySource[P, B, E]) compilePolicyLocked(policySpec P) E {
|
||||
policyMeta, err := meta.Accessor(policySpec)
|
||||
if err != nil {
|
||||
// This should not happen if P, and B have ObjectMeta, but
|
||||
// unfortunately there is no way to express "able to call
|
||||
// meta.Accessor" as a type constraint
|
||||
utilruntime.HandleError(err)
|
||||
var emptyEvaluator E
|
||||
return emptyEvaluator
|
||||
}
|
||||
key := types.NamespacedName{
|
||||
Namespace: policyMeta.GetNamespace(),
|
||||
Name: policyMeta.GetName(),
|
||||
}
|
||||
|
||||
compiledPolicy, wasCompiled := s.compiledPolicies[key]
|
||||
|
||||
// If the policy or binding has changed since it was last compiled,
|
||||
// and if there is no configuration error (like a missing param CRD)
|
||||
// then we recompile
|
||||
if !wasCompiled ||
|
||||
compiledPolicy.policyVersion != policyMeta.GetResourceVersion() {
|
||||
|
||||
compiledPolicy = compiledPolicyEntry[E]{
|
||||
policyVersion: policyMeta.GetResourceVersion(),
|
||||
evaluator: s.compiler(policySpec),
|
||||
}
|
||||
s.compiledPolicies[key] = compiledPolicy
|
||||
}
|
||||
|
||||
return compiledPolicy.evaluator
|
||||
}
|
626
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/generic/policy_test_context.go
generated
vendored
Normal file
626
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/generic/policy_test_context.go
generated
vendored
Normal file
@ -0,0 +1,626 @@
|
||||
/*
|
||||
Copyright 2024 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"
|
||||
"time"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/uuid"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/client-go/dynamic"
|
||||
dynamicfake "k8s.io/client-go/dynamic/fake"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
clienttesting "k8s.io/client-go/testing"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/component-base/featuregate"
|
||||
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/initializer"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
)
|
||||
|
||||
// PolicyTestContext is everything you need to unit test a policy plugin
|
||||
type PolicyTestContext[P runtime.Object, B runtime.Object, E Evaluator] struct {
|
||||
context.Context
|
||||
Plugin *Plugin[PolicyHook[P, B, E]]
|
||||
Source Source[PolicyHook[P, B, E]]
|
||||
Start func() error
|
||||
|
||||
scheme *runtime.Scheme
|
||||
restMapper *meta.DefaultRESTMapper
|
||||
policyGVR schema.GroupVersionResource
|
||||
bindingGVR schema.GroupVersionResource
|
||||
|
||||
policyGVK schema.GroupVersionKind
|
||||
bindingGVK schema.GroupVersionKind
|
||||
|
||||
nativeTracker clienttesting.ObjectTracker
|
||||
policyAndBindingTracker clienttesting.ObjectTracker
|
||||
unstructuredTracker clienttesting.ObjectTracker
|
||||
}
|
||||
|
||||
func NewPolicyTestContext[P, B runtime.Object, E Evaluator](
|
||||
newPolicyAccessor func(P) PolicyAccessor,
|
||||
newBindingAccessor func(B) BindingAccessor,
|
||||
compileFunc func(P) E,
|
||||
dispatcher dispatcherFactory[PolicyHook[P, B, E]],
|
||||
initialObjects []runtime.Object,
|
||||
paramMappings []meta.RESTMapping,
|
||||
) (*PolicyTestContext[P, B, E], func(), error) {
|
||||
var Pexample P
|
||||
var Bexample B
|
||||
|
||||
// Create a fake resource and kind for the provided policy and binding types
|
||||
fakePolicyGVR := schema.GroupVersionResource{
|
||||
Group: "policy.example.com",
|
||||
Version: "v1",
|
||||
Resource: "fakepolicies",
|
||||
}
|
||||
fakeBindingGVR := schema.GroupVersionResource{
|
||||
Group: "policy.example.com",
|
||||
Version: "v1",
|
||||
Resource: "fakebindings",
|
||||
}
|
||||
fakePolicyGVK := fakePolicyGVR.GroupVersion().WithKind("FakePolicy")
|
||||
fakeBindingGVK := fakeBindingGVR.GroupVersion().WithKind("FakeBinding")
|
||||
|
||||
policySourceTestScheme, err := func() (*runtime.Scheme, error) {
|
||||
scheme := runtime.NewScheme()
|
||||
|
||||
if err := fake.AddToScheme(scheme); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
scheme.AddKnownTypeWithName(fakePolicyGVK, Pexample)
|
||||
scheme.AddKnownTypeWithName(fakeBindingGVK, Bexample)
|
||||
scheme.AddKnownTypeWithName(fakePolicyGVK.GroupVersion().WithKind(fakePolicyGVK.Kind+"List"), &FakeList[P]{})
|
||||
scheme.AddKnownTypeWithName(fakeBindingGVK.GroupVersion().WithKind(fakeBindingGVK.Kind+"List"), &FakeList[B]{})
|
||||
|
||||
for _, mapping := range paramMappings {
|
||||
// Skip if it is in the scheme already
|
||||
if scheme.Recognizes(mapping.GroupVersionKind) {
|
||||
continue
|
||||
}
|
||||
scheme.AddKnownTypeWithName(mapping.GroupVersionKind, &unstructured.Unstructured{})
|
||||
scheme.AddKnownTypeWithName(mapping.GroupVersionKind.GroupVersion().WithKind(mapping.GroupVersionKind.Kind+"List"), &unstructured.UnstructuredList{})
|
||||
}
|
||||
|
||||
return scheme, nil
|
||||
}()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
fakeRestMapper := func() *meta.DefaultRESTMapper {
|
||||
res := meta.NewDefaultRESTMapper([]schema.GroupVersion{
|
||||
{
|
||||
Group: "",
|
||||
Version: "v1",
|
||||
},
|
||||
})
|
||||
|
||||
res.Add(fakePolicyGVK, meta.RESTScopeRoot)
|
||||
res.Add(fakeBindingGVK, meta.RESTScopeRoot)
|
||||
res.Add(corev1.SchemeGroupVersion.WithKind("ConfigMap"), meta.RESTScopeNamespace)
|
||||
|
||||
for _, mapping := range paramMappings {
|
||||
res.AddSpecific(mapping.GroupVersionKind, mapping.Resource, mapping.Resource, mapping.Scope)
|
||||
}
|
||||
|
||||
return res
|
||||
}()
|
||||
|
||||
nativeClient := fake.NewSimpleClientset()
|
||||
dynamicClient := dynamicfake.NewSimpleDynamicClient(policySourceTestScheme)
|
||||
fakeInformerFactory := informers.NewSharedInformerFactory(nativeClient, 30*time.Second)
|
||||
|
||||
// Make an object tracker specifically for our policies and bindings
|
||||
policiesAndBindingsTracker := clienttesting.NewObjectTracker(
|
||||
policySourceTestScheme,
|
||||
serializer.NewCodecFactory(policySourceTestScheme).UniversalDecoder())
|
||||
|
||||
// Make an informer for our policies and bindings
|
||||
|
||||
policyInformer := cache.NewSharedIndexInformer(
|
||||
&cache.ListWatch{
|
||||
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
|
||||
return policiesAndBindingsTracker.List(fakePolicyGVR, fakePolicyGVK, "")
|
||||
},
|
||||
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
|
||||
return policiesAndBindingsTracker.Watch(fakePolicyGVR, "")
|
||||
},
|
||||
},
|
||||
Pexample,
|
||||
30*time.Second,
|
||||
cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc},
|
||||
)
|
||||
bindingInformer := cache.NewSharedIndexInformer(
|
||||
&cache.ListWatch{
|
||||
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
|
||||
return policiesAndBindingsTracker.List(fakeBindingGVR, fakeBindingGVK, "")
|
||||
},
|
||||
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
|
||||
return policiesAndBindingsTracker.Watch(fakeBindingGVR, "")
|
||||
},
|
||||
},
|
||||
Bexample,
|
||||
30*time.Second,
|
||||
cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc},
|
||||
)
|
||||
|
||||
var source Source[PolicyHook[P, B, E]]
|
||||
plugin := NewPlugin[PolicyHook[P, B, E]](
|
||||
admission.NewHandler(admission.Connect, admission.Create, admission.Delete, admission.Update),
|
||||
func(sif informers.SharedInformerFactory, i1 kubernetes.Interface, i2 dynamic.Interface, r meta.RESTMapper) Source[PolicyHook[P, B, E]] {
|
||||
source = NewPolicySource[P, B, E](
|
||||
policyInformer,
|
||||
bindingInformer,
|
||||
newPolicyAccessor,
|
||||
newBindingAccessor,
|
||||
compileFunc,
|
||||
sif,
|
||||
i2,
|
||||
r,
|
||||
)
|
||||
return source
|
||||
}, dispatcher)
|
||||
plugin.SetEnabled(true)
|
||||
|
||||
featureGate := featuregate.NewFeatureGate()
|
||||
testContext, testCancel := context.WithCancel(context.Background())
|
||||
genericInitializer := initializer.New(
|
||||
nativeClient,
|
||||
dynamicClient,
|
||||
fakeInformerFactory,
|
||||
fakeAuthorizer{},
|
||||
featureGate,
|
||||
testContext.Done(),
|
||||
fakeRestMapper,
|
||||
)
|
||||
genericInitializer.Initialize(plugin)
|
||||
plugin.SetRESTMapper(fakeRestMapper)
|
||||
|
||||
if err := plugin.ValidateInitialization(); err != nil {
|
||||
testCancel()
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
res := &PolicyTestContext[P, B, E]{
|
||||
Context: testContext,
|
||||
Plugin: plugin,
|
||||
Source: source,
|
||||
|
||||
restMapper: fakeRestMapper,
|
||||
scheme: policySourceTestScheme,
|
||||
policyGVK: fakePolicyGVK,
|
||||
bindingGVK: fakeBindingGVK,
|
||||
policyGVR: fakePolicyGVR,
|
||||
bindingGVR: fakeBindingGVR,
|
||||
nativeTracker: nativeClient.Tracker(),
|
||||
policyAndBindingTracker: policiesAndBindingsTracker,
|
||||
unstructuredTracker: dynamicClient.Tracker(),
|
||||
}
|
||||
|
||||
for _, obj := range initialObjects {
|
||||
err := res.updateOne(obj)
|
||||
if err != nil {
|
||||
testCancel()
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
res.Start = func() error {
|
||||
fakeInformerFactory.Start(res.Done())
|
||||
go policyInformer.Run(res.Done())
|
||||
go bindingInformer.Run(res.Done())
|
||||
|
||||
if !cache.WaitForCacheSync(res.Done(), res.Source.HasSynced) {
|
||||
return fmt.Errorf("timed out waiting for initial cache sync")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return res, testCancel, nil
|
||||
}
|
||||
|
||||
// UpdateAndWait updates the given object in the test, or creates it if it doesn't exist
|
||||
// Depending upon object type, waits afterward until the object is synced
|
||||
// by the policy source
|
||||
//
|
||||
// Be aware the UpdateAndWait will modify the ResourceVersion of the
|
||||
// provided objects.
|
||||
func (p *PolicyTestContext[P, B, E]) UpdateAndWait(objects ...runtime.Object) error {
|
||||
return p.update(true, objects...)
|
||||
}
|
||||
|
||||
// Update updates the given object in the test, or creates it if it doesn't exist
|
||||
//
|
||||
// Be aware the Update will modify the ResourceVersion of the
|
||||
// provided objects.
|
||||
func (p *PolicyTestContext[P, B, E]) Update(objects ...runtime.Object) error {
|
||||
return p.update(false, objects...)
|
||||
}
|
||||
|
||||
// Objects the given object in the test, or creates it if it doesn't exist
|
||||
// Depending upon object type, waits afterward until the object is synced
|
||||
// by the policy source
|
||||
func (p *PolicyTestContext[P, B, E]) update(wait bool, objects ...runtime.Object) error {
|
||||
for _, object := range objects {
|
||||
if err := p.updateOne(object); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if wait {
|
||||
timeoutCtx, timeoutCancel := context.WithTimeout(p, 3*time.Second)
|
||||
defer timeoutCancel()
|
||||
|
||||
for _, object := range objects {
|
||||
if err := p.WaitForReconcile(timeoutCtx, object); err != nil {
|
||||
return fmt.Errorf("error waiting for reconcile of %v: %v", object, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Depending upon object type, waits afterward until the object is synced
|
||||
// by the policy source. Note that policies that are not bound are skipped,
|
||||
// so you should not try to wait for an unbound policy. Create both the binding
|
||||
// and policy, then wait.
|
||||
func (p *PolicyTestContext[P, B, E]) WaitForReconcile(timeoutCtx context.Context, object runtime.Object) error {
|
||||
if !p.Source.HasSynced() {
|
||||
return nil
|
||||
}
|
||||
|
||||
objectMeta, err := meta.Accessor(object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
objectGVK, _, err := p.inferGVK(object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch objectGVK {
|
||||
case p.policyGVK:
|
||||
return wait.PollUntilContextCancel(timeoutCtx, 100*time.Millisecond, true, func(ctx context.Context) (done bool, err error) {
|
||||
policies := p.Source.Hooks()
|
||||
for _, policy := range policies {
|
||||
policyMeta, err := meta.Accessor(policy.Policy)
|
||||
if err != nil {
|
||||
return true, err
|
||||
} else if policyMeta.GetName() == objectMeta.GetName() && policyMeta.GetResourceVersion() == objectMeta.GetResourceVersion() {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
case p.bindingGVK:
|
||||
return wait.PollUntilContextCancel(timeoutCtx, 100*time.Millisecond, true, func(ctx context.Context) (done bool, err error) {
|
||||
policies := p.Source.Hooks()
|
||||
for _, policy := range policies {
|
||||
for _, binding := range policy.Bindings {
|
||||
bindingMeta, err := meta.Accessor(binding)
|
||||
if err != nil {
|
||||
return true, err
|
||||
} else if bindingMeta.GetName() == objectMeta.GetName() && bindingMeta.GetResourceVersion() == objectMeta.GetResourceVersion() {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
|
||||
default:
|
||||
// Do nothing, params are visible immediately
|
||||
// Loop until one of the params is visible via get of the param informer
|
||||
return wait.PollUntilContextCancel(timeoutCtx, 100*time.Millisecond, true, func(ctx context.Context) (done bool, err error) {
|
||||
informer, scope := p.Source.(*policySource[P, B, E]).getParamInformer(objectGVK)
|
||||
if informer == nil {
|
||||
// Informer does not exist yet, keep waiting for sync
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if !cache.WaitForCacheSync(timeoutCtx.Done(), informer.Informer().HasSynced) {
|
||||
return false, fmt.Errorf("timed out waiting for cache sync of param informer")
|
||||
}
|
||||
|
||||
var lister cache.GenericNamespaceLister = informer.Lister()
|
||||
if scope == meta.RESTScopeNamespace {
|
||||
lister = informer.Lister().ByNamespace(objectMeta.GetNamespace())
|
||||
}
|
||||
|
||||
fetched, err := lister.Get(objectMeta.GetName())
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
return false, nil
|
||||
}
|
||||
return true, err
|
||||
}
|
||||
|
||||
// Ensure RV matches
|
||||
fetchedMeta, err := meta.Accessor(fetched)
|
||||
if err != nil {
|
||||
return true, err
|
||||
} else if fetchedMeta.GetResourceVersion() != objectMeta.GetResourceVersion() {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PolicyTestContext[P, B, E]) waitForDelete(ctx context.Context, objectGVK schema.GroupVersionKind, name types.NamespacedName) error {
|
||||
srce := p.Source.(*policySource[P, B, E])
|
||||
|
||||
return wait.PollUntilContextCancel(ctx, 100*time.Millisecond, true, func(ctx context.Context) (done bool, err error) {
|
||||
switch objectGVK {
|
||||
case p.policyGVK:
|
||||
for _, hook := range p.Source.Hooks() {
|
||||
accessor := srce.newPolicyAccessor(hook.Policy)
|
||||
if accessor.GetName() == name.Name && accessor.GetNamespace() == name.Namespace {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
case p.bindingGVK:
|
||||
for _, hook := range p.Source.Hooks() {
|
||||
for _, binding := range hook.Bindings {
|
||||
accessor := srce.newBindingAccessor(binding)
|
||||
if accessor.GetName() == name.Name && accessor.GetNamespace() == name.Namespace {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
default:
|
||||
// Do nothing, params are visible immediately
|
||||
// Loop until one of the params is visible via get of the param informer
|
||||
informer, scope := p.Source.(*policySource[P, B, E]).getParamInformer(objectGVK)
|
||||
if informer == nil {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
var lister cache.GenericNamespaceLister = informer.Lister()
|
||||
if scope == meta.RESTScopeNamespace {
|
||||
lister = informer.Lister().ByNamespace(name.Namespace)
|
||||
}
|
||||
|
||||
_, err = lister.Get(name.Name)
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
return true, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (p *PolicyTestContext[P, B, E]) updateOne(object runtime.Object) error {
|
||||
objectMeta, err := meta.Accessor(object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
objectMeta.SetResourceVersion(string(uuid.NewUUID()))
|
||||
objectGVK, gvr, err := p.inferGVK(object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch objectGVK {
|
||||
case p.policyGVK:
|
||||
err := p.policyAndBindingTracker.Update(p.policyGVR, object, objectMeta.GetNamespace())
|
||||
if errors.IsNotFound(err) {
|
||||
err = p.policyAndBindingTracker.Create(p.policyGVR, object, objectMeta.GetNamespace())
|
||||
}
|
||||
|
||||
return err
|
||||
case p.bindingGVK:
|
||||
err := p.policyAndBindingTracker.Update(p.bindingGVR, object, objectMeta.GetNamespace())
|
||||
if errors.IsNotFound(err) {
|
||||
err = p.policyAndBindingTracker.Create(p.bindingGVR, object, objectMeta.GetNamespace())
|
||||
}
|
||||
|
||||
return err
|
||||
default:
|
||||
if _, ok := object.(*unstructured.Unstructured); ok {
|
||||
if err := p.unstructuredTracker.Create(gvr, object, objectMeta.GetNamespace()); err != nil {
|
||||
if errors.IsAlreadyExists(err) {
|
||||
return p.unstructuredTracker.Update(gvr, object, objectMeta.GetNamespace())
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
} else if err := p.nativeTracker.Create(gvr, object, objectMeta.GetNamespace()); err != nil {
|
||||
if errors.IsAlreadyExists(err) {
|
||||
return p.nativeTracker.Update(gvr, object, objectMeta.GetNamespace())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Depending upon object type, waits afterward until the object is synced
|
||||
// by the policy source
|
||||
func (p *PolicyTestContext[P, B, E]) DeleteAndWait(object ...runtime.Object) error {
|
||||
for _, object := range object {
|
||||
if err := p.deleteOne(object); err != nil && !errors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
timeoutCtx, timeoutCancel := context.WithTimeout(p, 3*time.Second)
|
||||
defer timeoutCancel()
|
||||
|
||||
for _, object := range object {
|
||||
accessor, err := meta.Accessor(object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
objectGVK, _, err := p.inferGVK(object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := p.waitForDelete(
|
||||
timeoutCtx,
|
||||
objectGVK,
|
||||
types.NamespacedName{Name: accessor.GetName(), Namespace: accessor.GetNamespace()}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PolicyTestContext[P, B, E]) deleteOne(object runtime.Object) error {
|
||||
objectMeta, err := meta.Accessor(object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
objectMeta.SetResourceVersion(string(uuid.NewUUID()))
|
||||
objectGVK, gvr, err := p.inferGVK(object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch objectGVK {
|
||||
case p.policyGVK:
|
||||
return p.policyAndBindingTracker.Delete(p.policyGVR, objectMeta.GetNamespace(), objectMeta.GetName())
|
||||
case p.bindingGVK:
|
||||
return p.policyAndBindingTracker.Delete(p.bindingGVR, objectMeta.GetNamespace(), objectMeta.GetName())
|
||||
default:
|
||||
if _, ok := object.(*unstructured.Unstructured); ok {
|
||||
return p.unstructuredTracker.Delete(gvr, objectMeta.GetNamespace(), objectMeta.GetName())
|
||||
}
|
||||
return p.nativeTracker.Delete(gvr, objectMeta.GetNamespace(), objectMeta.GetName())
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PolicyTestContext[P, B, E]) Dispatch(
|
||||
new, old runtime.Object,
|
||||
operation admission.Operation,
|
||||
) error {
|
||||
if old == nil && new == nil {
|
||||
return fmt.Errorf("both old and new objects cannot be nil")
|
||||
}
|
||||
|
||||
nonNilObject := new
|
||||
if nonNilObject == nil {
|
||||
nonNilObject = old
|
||||
}
|
||||
|
||||
gvk, gvr, err := p.inferGVK(nonNilObject)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nonNilMeta, err := meta.Accessor(nonNilObject)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return p.Plugin.Dispatch(
|
||||
p,
|
||||
admission.NewAttributesRecord(
|
||||
new,
|
||||
old,
|
||||
gvk,
|
||||
nonNilMeta.GetName(),
|
||||
nonNilMeta.GetNamespace(),
|
||||
gvr,
|
||||
"",
|
||||
operation,
|
||||
nil,
|
||||
false,
|
||||
nil,
|
||||
), admission.NewObjectInterfacesFromScheme(p.scheme))
|
||||
}
|
||||
|
||||
func (p *PolicyTestContext[P, B, E]) inferGVK(object runtime.Object) (schema.GroupVersionKind, schema.GroupVersionResource, error) {
|
||||
objectGVK := object.GetObjectKind().GroupVersionKind()
|
||||
if objectGVK.Empty() {
|
||||
// If the object doesn't have a GVK, ask the schema for preferred GVK
|
||||
knownKinds, _, err := p.scheme.ObjectKinds(object)
|
||||
if err != nil {
|
||||
return schema.GroupVersionKind{}, schema.GroupVersionResource{}, err
|
||||
} else if len(knownKinds) == 0 {
|
||||
return schema.GroupVersionKind{}, schema.GroupVersionResource{}, fmt.Errorf("no known GVKs for object in schema: %T", object)
|
||||
}
|
||||
toTake := 0
|
||||
|
||||
// Prefer GVK if it is our fake policy or binding
|
||||
for i, knownKind := range knownKinds {
|
||||
if knownKind == p.policyGVK || knownKind == p.bindingGVK {
|
||||
toTake = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
objectGVK = knownKinds[toTake]
|
||||
}
|
||||
|
||||
// Make sure GVK is known to the fake rest mapper. To prevent cryptic error
|
||||
mapping, err := p.restMapper.RESTMapping(objectGVK.GroupKind(), objectGVK.Version)
|
||||
if err != nil {
|
||||
return schema.GroupVersionKind{}, schema.GroupVersionResource{}, err
|
||||
}
|
||||
return objectGVK, mapping.Resource, nil
|
||||
}
|
||||
|
||||
type FakeList[T runtime.Object] struct {
|
||||
metav1.TypeMeta
|
||||
metav1.ListMeta
|
||||
Items []T
|
||||
}
|
||||
|
||||
func (fl *FakeList[P]) DeepCopyObject() runtime.Object {
|
||||
copiedItems := make([]P, len(fl.Items))
|
||||
for i, item := range fl.Items {
|
||||
copiedItems[i] = item.DeepCopyObject().(P)
|
||||
}
|
||||
return &FakeList[P]{
|
||||
TypeMeta: fl.TypeMeta,
|
||||
ListMeta: fl.ListMeta,
|
||||
Items: copiedItems,
|
||||
}
|
||||
}
|
||||
|
||||
type fakeAuthorizer struct{}
|
||||
|
||||
func (f fakeAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
|
||||
return authorizer.DecisionAllow, "", nil
|
||||
}
|
283
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/internal/generic/controller.go
generated
vendored
Normal file
283
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/internal/generic/controller.go
generated
vendored
Normal file
@ -0,0 +1,283 @@
|
||||
/*
|
||||
Copyright 2022 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"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/client-go/tools/cache/synctrack"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
var _ Controller[runtime.Object] = &controller[runtime.Object]{}
|
||||
|
||||
type controller[T runtime.Object] struct {
|
||||
informer Informer[T]
|
||||
queue workqueue.TypedRateLimitingInterface[string]
|
||||
|
||||
// Returns an error if there was a transient error during reconciliation
|
||||
// and the object should be tried again later.
|
||||
reconciler func(namespace, name string, newObj T) error
|
||||
|
||||
options ControllerOptions
|
||||
|
||||
// must hold a func() bool or nil
|
||||
notificationsDelivered atomic.Value
|
||||
|
||||
hasProcessed synctrack.AsyncTracker[string]
|
||||
}
|
||||
|
||||
type ControllerOptions struct {
|
||||
Name string
|
||||
Workers uint
|
||||
}
|
||||
|
||||
func (c *controller[T]) Informer() Informer[T] {
|
||||
return c.informer
|
||||
}
|
||||
|
||||
func NewController[T runtime.Object](
|
||||
informer Informer[T],
|
||||
reconciler func(namepace, name string, newObj T) error,
|
||||
options ControllerOptions,
|
||||
) Controller[T] {
|
||||
if options.Workers == 0 {
|
||||
options.Workers = 2
|
||||
}
|
||||
|
||||
if len(options.Name) == 0 {
|
||||
options.Name = fmt.Sprintf("%T-controller", *new(T))
|
||||
}
|
||||
|
||||
c := &controller[T]{
|
||||
options: options,
|
||||
informer: informer,
|
||||
reconciler: reconciler,
|
||||
queue: nil,
|
||||
}
|
||||
c.hasProcessed.UpstreamHasSynced = func() bool {
|
||||
f := c.notificationsDelivered.Load()
|
||||
if f == nil {
|
||||
return false
|
||||
}
|
||||
return f.(func() bool)()
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// Runs the controller and returns an error explaining why running was stopped.
|
||||
// Reconciliation ends as soon as the context completes. If there are events
|
||||
// waiting to be processed at that itme, they will be dropped.
|
||||
func (c *controller[T]) Run(ctx context.Context) error {
|
||||
klog.Infof("starting %s", c.options.Name)
|
||||
defer klog.Infof("stopping %s", c.options.Name)
|
||||
|
||||
c.queue = workqueue.NewTypedRateLimitingQueueWithConfig(
|
||||
workqueue.DefaultTypedControllerRateLimiter[string](),
|
||||
workqueue.TypedRateLimitingQueueConfig[string]{Name: c.options.Name},
|
||||
)
|
||||
|
||||
// Forcefully shutdown workqueue. Drop any enqueued items.
|
||||
// Important to do this in a `defer` at the start of `Run`.
|
||||
// Otherwise, if there are any early returns without calling this, we
|
||||
// would never shut down the workqueue
|
||||
defer c.queue.ShutDown()
|
||||
|
||||
enqueue := func(obj interface{}, isInInitialList bool) {
|
||||
var key string
|
||||
var err error
|
||||
if key, err = cache.DeletionHandlingMetaNamespaceKeyFunc(obj); err != nil {
|
||||
utilruntime.HandleError(err)
|
||||
return
|
||||
}
|
||||
if isInInitialList {
|
||||
c.hasProcessed.Start(key)
|
||||
}
|
||||
|
||||
c.queue.Add(key)
|
||||
}
|
||||
|
||||
registration, err := c.informer.AddEventHandler(cache.ResourceEventHandlerDetailedFuncs{
|
||||
AddFunc: enqueue,
|
||||
UpdateFunc: func(oldObj, newObj interface{}) {
|
||||
oldMeta, err1 := meta.Accessor(oldObj)
|
||||
newMeta, err2 := meta.Accessor(newObj)
|
||||
|
||||
if err1 != nil || err2 != nil {
|
||||
if err1 != nil {
|
||||
utilruntime.HandleError(err1)
|
||||
}
|
||||
|
||||
if err2 != nil {
|
||||
utilruntime.HandleError(err2)
|
||||
}
|
||||
return
|
||||
} else if oldMeta.GetResourceVersion() == newMeta.GetResourceVersion() {
|
||||
if len(oldMeta.GetResourceVersion()) == 0 {
|
||||
klog.Warningf("%v throwing out update with empty RV. this is likely to happen if a test did not supply a resource version on an updated object", c.options.Name)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
enqueue(newObj, false)
|
||||
},
|
||||
DeleteFunc: func(obj interface{}) {
|
||||
// Enqueue
|
||||
enqueue(obj, false)
|
||||
},
|
||||
})
|
||||
|
||||
// Error might be raised if informer was started and stopped already
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.notificationsDelivered.Store(registration.HasSynced)
|
||||
|
||||
// Make sure event handler is removed from informer in case return early from
|
||||
// an error
|
||||
defer func() {
|
||||
c.notificationsDelivered.Store(func() bool { return false })
|
||||
// Remove event handler and Handle Error here. Error should only be raised
|
||||
// for improper usage of event handler API.
|
||||
if err := c.informer.RemoveEventHandler(registration); err != nil {
|
||||
utilruntime.HandleError(err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Wait for initial cache list to complete before beginning to reconcile
|
||||
// objects.
|
||||
if !cache.WaitForNamedCacheSync(c.options.Name, ctx.Done(), c.informer.HasSynced) {
|
||||
// ctx cancelled during cache sync. return early
|
||||
err := ctx.Err()
|
||||
if err == nil {
|
||||
// if context wasnt cancelled then the sync failed for another reason
|
||||
err = errors.New("cache sync failed")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
waitGroup := sync.WaitGroup{}
|
||||
|
||||
for i := uint(0); i < c.options.Workers; i++ {
|
||||
waitGroup.Add(1)
|
||||
go func() {
|
||||
defer waitGroup.Done()
|
||||
wait.Until(c.runWorker, time.Second, ctx.Done())
|
||||
}()
|
||||
}
|
||||
|
||||
klog.Infof("Started %v workers for %v", c.options.Workers, c.options.Name)
|
||||
|
||||
// Wait for context cancel.
|
||||
<-ctx.Done()
|
||||
|
||||
// Forcefully shutdown workqueue. Drop any enqueued items.
|
||||
c.queue.ShutDown()
|
||||
|
||||
// Workqueue shutdown signals for workers to stop. Wait for all workers to
|
||||
// clean up
|
||||
waitGroup.Wait()
|
||||
|
||||
// Only way for workers to ever stop is for caller to cancel the context
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
func (c *controller[T]) HasSynced() bool {
|
||||
return c.hasProcessed.HasSynced()
|
||||
}
|
||||
|
||||
func (c *controller[T]) runWorker() {
|
||||
for {
|
||||
key, shutdown := c.queue.Get()
|
||||
if shutdown {
|
||||
return
|
||||
}
|
||||
|
||||
// We wrap this block in a func so we can defer c.workqueue.Done.
|
||||
err := func(obj string) error {
|
||||
// We call Done here so the workqueue knows we have finished
|
||||
// processing this item. We also must remember to call Forget if we
|
||||
// do not want this work item being re-queued. For example, we do
|
||||
// not call Forget if a transient error occurs, instead the item is
|
||||
// put back on the workqueue and attempted again after a back-off
|
||||
// period.
|
||||
defer c.queue.Done(obj)
|
||||
defer c.hasProcessed.Finished(key)
|
||||
|
||||
if err := c.reconcile(key); err != nil {
|
||||
// Put the item back on the workqueue to handle any transient errors.
|
||||
c.queue.AddRateLimited(key)
|
||||
return fmt.Errorf("error syncing '%s': %s, requeuing", key, err.Error())
|
||||
}
|
||||
// Finally, if no error occurs we Forget this item so it is allowed
|
||||
// to be re-enqueued without a long rate limit
|
||||
c.queue.Forget(obj)
|
||||
klog.V(4).Infof("syncAdmissionPolicy(%q)", key)
|
||||
return nil
|
||||
}(key)
|
||||
|
||||
if err != nil {
|
||||
utilruntime.HandleError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *controller[T]) reconcile(key string) error {
|
||||
var newObj T
|
||||
var err error
|
||||
var namespace string
|
||||
var name string
|
||||
var lister NamespacedLister[T]
|
||||
|
||||
// Convert the namespace/name string into a distinct namespace and name
|
||||
namespace, name, err = cache.SplitMetaNamespaceKey(key)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("invalid resource key: %s", key))
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(namespace) > 0 {
|
||||
lister = c.informer.Namespaced(namespace)
|
||||
} else {
|
||||
lister = c.informer
|
||||
}
|
||||
|
||||
newObj, err = lister.Get(name)
|
||||
if err != nil {
|
||||
if !kerrors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
// Deleted object. Inform reconciler with empty
|
||||
}
|
||||
|
||||
return c.reconciler(namespace, name, newObj)
|
||||
}
|
29
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/internal/generic/doc.go
generated
vendored
Normal file
29
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/internal/generic/doc.go
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
/*
|
||||
Copyright 2022 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 contains a typed wrapper over cache SharedIndexInformer
|
||||
// and Lister (maybe eventually should have a home there?)
|
||||
//
|
||||
// This interface is being experimented with as an easier way to write controllers
|
||||
// with a bit less boilerplate.
|
||||
//
|
||||
// Informer/Lister classes are thin wrappers providing a type-safe interface
|
||||
// over regular interface{}-based Informers/Listers
|
||||
//
|
||||
// Controller[T] provides a reusable way to reconcile objects out of an informer
|
||||
// using the tried and true controller design pattern found all over k8s
|
||||
// codebase based upon syncFunc/reconcile
|
||||
package generic
|
40
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/internal/generic/informer.go
generated
vendored
Normal file
40
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/internal/generic/informer.go
generated
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
/*
|
||||
Copyright 2022 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package generic
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
var _ Informer[runtime.Object] = informer[runtime.Object]{}
|
||||
|
||||
type informer[T runtime.Object] struct {
|
||||
cache.SharedIndexInformer
|
||||
lister[T]
|
||||
}
|
||||
|
||||
// Creates a generic informer around a type-erased cache.SharedIndexInformer
|
||||
// It is incumbent on the caller to ensure that the generic type argument is
|
||||
// consistent with the type of the objects stored inside the SharedIndexInformer
|
||||
// as they will be casted.
|
||||
func NewInformer[T runtime.Object](informe cache.SharedIndexInformer) Informer[T] {
|
||||
return informer[T]{
|
||||
SharedIndexInformer: informe,
|
||||
lister: NewLister[T](informe.GetIndexer()),
|
||||
}
|
||||
}
|
62
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/internal/generic/interface.go
generated
vendored
Normal file
62
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/internal/generic/interface.go
generated
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
/*
|
||||
Copyright 2022 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package generic
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
type Controller[T runtime.Object] interface {
|
||||
// Meant to be run inside a goroutine
|
||||
// Waits for and reacts to changes in whatever type the controller
|
||||
// is concerned with.
|
||||
//
|
||||
// Returns an error always non-nil explaining why the worker stopped
|
||||
Run(ctx context.Context) error
|
||||
|
||||
// Retrieves the informer used to back this controller
|
||||
Informer() Informer[T]
|
||||
|
||||
// Returns true if the informer cache has synced, and all the objects from
|
||||
// the initial list have been reconciled at least once.
|
||||
HasSynced() bool
|
||||
}
|
||||
|
||||
type NamespacedLister[T any] interface {
|
||||
// List lists all ValidationRuleSets in the indexer for a given namespace.
|
||||
// Objects returned here must be treated as read-only.
|
||||
List(selector labels.Selector) (ret []T, err error)
|
||||
// Get retrieves the ValidationRuleSet from the indexer for a given namespace and name.
|
||||
// Objects returned here must be treated as read-only.
|
||||
Get(name string) (T, error)
|
||||
}
|
||||
|
||||
type Informer[T any] interface {
|
||||
cache.SharedIndexInformer
|
||||
Lister[T]
|
||||
}
|
||||
|
||||
// Lister[T] helps list Ts.
|
||||
// All objects returned here must be treated as read-only.
|
||||
type Lister[T any] interface {
|
||||
NamespacedLister[T]
|
||||
Namespaced(namespace string) NamespacedLister[T]
|
||||
}
|
100
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/internal/generic/lister.go
generated
vendored
Normal file
100
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/internal/generic/lister.go
generated
vendored
Normal file
@ -0,0 +1,100 @@
|
||||
/*
|
||||
Copyright 2022 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"
|
||||
"net/http"
|
||||
|
||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
var _ Lister[runtime.Object] = lister[runtime.Object]{}
|
||||
|
||||
type namespacedLister[T runtime.Object] struct {
|
||||
indexer cache.Indexer
|
||||
namespace string
|
||||
}
|
||||
|
||||
func (w namespacedLister[T]) List(selector labels.Selector) (ret []T, err error) {
|
||||
err = cache.ListAllByNamespace(w.indexer, w.namespace, selector, func(m interface{}) {
|
||||
ret = append(ret, m.(T))
|
||||
})
|
||||
return ret, err
|
||||
}
|
||||
|
||||
func (w namespacedLister[T]) Get(name string) (T, error) {
|
||||
var result T
|
||||
|
||||
obj, exists, err := w.indexer.GetByKey(w.namespace + "/" + name)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
if !exists {
|
||||
return result, &kerrors.StatusError{ErrStatus: metav1.Status{
|
||||
Status: metav1.StatusFailure,
|
||||
Code: http.StatusNotFound,
|
||||
Reason: metav1.StatusReasonNotFound,
|
||||
Message: fmt.Sprintf("%s not found", name),
|
||||
}}
|
||||
}
|
||||
result = obj.(T)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
type lister[T runtime.Object] struct {
|
||||
indexer cache.Indexer
|
||||
}
|
||||
|
||||
func (w lister[T]) List(selector labels.Selector) (ret []T, err error) {
|
||||
err = cache.ListAll(w.indexer, selector, func(m interface{}) {
|
||||
ret = append(ret, m.(T))
|
||||
})
|
||||
return ret, err
|
||||
}
|
||||
|
||||
func (w lister[T]) Get(name string) (T, error) {
|
||||
var result T
|
||||
|
||||
obj, exists, err := w.indexer.GetByKey(name)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
if !exists {
|
||||
// kerrors.StatusNotFound requires a GroupResource we cannot provide
|
||||
return result, &kerrors.StatusError{ErrStatus: metav1.Status{
|
||||
Status: metav1.StatusFailure,
|
||||
Code: http.StatusNotFound,
|
||||
Reason: metav1.StatusReasonNotFound,
|
||||
Message: fmt.Sprintf("%s not found", name),
|
||||
}}
|
||||
}
|
||||
result = obj.(T)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (w lister[T]) Namespaced(namespace string) NamespacedLister[T] {
|
||||
return namespacedLister[T]{namespace: namespace, indexer: w.indexer}
|
||||
}
|
||||
|
||||
func NewLister[T runtime.Object](indexer cache.Indexer) lister[T] {
|
||||
return lister[T]{indexer: indexer}
|
||||
}
|
200
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/matching/matching.go
generated
vendored
Normal file
200
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/matching/matching.go
generated
vendored
Normal file
@ -0,0 +1,200 @@
|
||||
/*
|
||||
Copyright 2022 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 matching
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
v1 "k8s.io/api/admissionregistration/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
listersv1 "k8s.io/client-go/listers/core/v1"
|
||||
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/namespace"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/object"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/rules"
|
||||
)
|
||||
|
||||
type MatchCriteria interface {
|
||||
namespace.NamespaceSelectorProvider
|
||||
object.ObjectSelectorProvider
|
||||
|
||||
GetMatchResources() v1.MatchResources
|
||||
}
|
||||
|
||||
// Matcher decides if a request matches against matchCriteria
|
||||
type Matcher struct {
|
||||
namespaceMatcher *namespace.Matcher
|
||||
objectMatcher *object.Matcher
|
||||
}
|
||||
|
||||
func (m *Matcher) GetNamespace(name string) (*corev1.Namespace, error) {
|
||||
return m.namespaceMatcher.GetNamespace(name)
|
||||
}
|
||||
|
||||
// NewMatcher initialize the matcher with dependencies requires
|
||||
func NewMatcher(
|
||||
namespaceLister listersv1.NamespaceLister,
|
||||
client kubernetes.Interface,
|
||||
) *Matcher {
|
||||
return &Matcher{
|
||||
namespaceMatcher: &namespace.Matcher{
|
||||
NamespaceLister: namespaceLister,
|
||||
Client: client,
|
||||
},
|
||||
objectMatcher: &object.Matcher{},
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateInitialization verify if the matcher is ready before use
|
||||
func (m *Matcher) ValidateInitialization() error {
|
||||
if err := m.namespaceMatcher.Validate(); err != nil {
|
||||
return fmt.Errorf("namespaceMatcher is not properly setup: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Matcher) Matches(attr admission.Attributes, o admission.ObjectInterfaces, criteria MatchCriteria) (bool, schema.GroupVersionResource, schema.GroupVersionKind, error) {
|
||||
matches, matchNsErr := m.namespaceMatcher.MatchNamespaceSelector(criteria, attr)
|
||||
// Should not return an error here for policy which do not apply to the request, even if err is an unexpected scenario.
|
||||
if !matches && matchNsErr == nil {
|
||||
return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, nil
|
||||
}
|
||||
|
||||
matches, matchObjErr := m.objectMatcher.MatchObjectSelector(criteria, attr)
|
||||
// Should not return an error here for policy which do not apply to the request, even if err is an unexpected scenario.
|
||||
if !matches && matchObjErr == nil {
|
||||
return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, nil
|
||||
}
|
||||
|
||||
matchResources := criteria.GetMatchResources()
|
||||
matchPolicy := matchResources.MatchPolicy
|
||||
if isExcluded, _, _, err := matchesResourceRules(matchResources.ExcludeResourceRules, matchPolicy, attr, o); isExcluded || err != nil {
|
||||
return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, err
|
||||
}
|
||||
|
||||
var (
|
||||
isMatch bool
|
||||
matchResource schema.GroupVersionResource
|
||||
matchKind schema.GroupVersionKind
|
||||
matchErr error
|
||||
)
|
||||
if len(matchResources.ResourceRules) == 0 {
|
||||
isMatch = true
|
||||
matchKind = attr.GetKind()
|
||||
matchResource = attr.GetResource()
|
||||
} else {
|
||||
isMatch, matchResource, matchKind, matchErr = matchesResourceRules(matchResources.ResourceRules, matchPolicy, attr, o)
|
||||
}
|
||||
if matchErr != nil {
|
||||
return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, matchErr
|
||||
}
|
||||
if !isMatch {
|
||||
return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, nil
|
||||
}
|
||||
|
||||
// now that we know this applies to this request otherwise, if there were selector errors, return them
|
||||
if matchNsErr != nil {
|
||||
return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, matchNsErr
|
||||
}
|
||||
if matchObjErr != nil {
|
||||
return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, matchObjErr
|
||||
}
|
||||
|
||||
return true, matchResource, matchKind, nil
|
||||
}
|
||||
|
||||
func matchesResourceRules(namedRules []v1.NamedRuleWithOperations, matchPolicy *v1.MatchPolicyType, attr admission.Attributes, o admission.ObjectInterfaces) (bool, schema.GroupVersionResource, schema.GroupVersionKind, error) {
|
||||
matchKind := attr.GetKind()
|
||||
matchResource := attr.GetResource()
|
||||
|
||||
for _, namedRule := range namedRules {
|
||||
rule := v1.RuleWithOperations(namedRule.RuleWithOperations)
|
||||
ruleMatcher := rules.Matcher{
|
||||
Rule: rule,
|
||||
Attr: attr,
|
||||
}
|
||||
if !ruleMatcher.Matches() {
|
||||
continue
|
||||
}
|
||||
// an empty name list always matches
|
||||
if len(namedRule.ResourceNames) == 0 {
|
||||
return true, matchResource, matchKind, nil
|
||||
}
|
||||
// TODO: GetName() can return an empty string if the user is relying on
|
||||
// the API server to generate the name... figure out what to do for this edge case
|
||||
name := attr.GetName()
|
||||
for _, matchedName := range namedRule.ResourceNames {
|
||||
if name == matchedName {
|
||||
return true, matchResource, matchKind, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if match policy is undefined or exact, don't perform fuzzy matching
|
||||
// note that defaulting to fuzzy matching is set by the API
|
||||
if matchPolicy == nil || *matchPolicy == v1.Exact {
|
||||
return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, nil
|
||||
}
|
||||
|
||||
attrWithOverride := &attrWithResourceOverride{Attributes: attr}
|
||||
equivalents := o.GetEquivalentResourceMapper().EquivalentResourcesFor(attr.GetResource(), attr.GetSubresource())
|
||||
for _, namedRule := range namedRules {
|
||||
for _, equivalent := range equivalents {
|
||||
if equivalent == attr.GetResource() {
|
||||
// we have already checked the original resource
|
||||
continue
|
||||
}
|
||||
attrWithOverride.resource = equivalent
|
||||
rule := v1.RuleWithOperations(namedRule.RuleWithOperations)
|
||||
m := rules.Matcher{
|
||||
Rule: rule,
|
||||
Attr: attrWithOverride,
|
||||
}
|
||||
if !m.Matches() {
|
||||
continue
|
||||
}
|
||||
matchKind = o.GetEquivalentResourceMapper().KindFor(equivalent, attr.GetSubresource())
|
||||
if matchKind.Empty() {
|
||||
return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, fmt.Errorf("unable to convert to %v: unknown kind", equivalent)
|
||||
}
|
||||
// an empty name list always matches
|
||||
if len(namedRule.ResourceNames) == 0 {
|
||||
return true, equivalent, matchKind, nil
|
||||
}
|
||||
|
||||
// TODO: GetName() can return an empty string if the user is relying on
|
||||
// the API server to generate the name... figure out what to do for this edge case
|
||||
name := attr.GetName()
|
||||
for _, matchedName := range namedRule.ResourceNames {
|
||||
if name == matchedName {
|
||||
return true, equivalent, matchKind, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, nil
|
||||
}
|
||||
|
||||
type attrWithResourceOverride struct {
|
||||
admission.Attributes
|
||||
resource schema.GroupVersionResource
|
||||
}
|
||||
|
||||
func (a *attrWithResourceOverride) GetResource() schema.GroupVersionResource { return a.resource }
|
144
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/accessor.go
generated
vendored
Normal file
144
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/accessor.go
generated
vendored
Normal file
@ -0,0 +1,144 @@
|
||||
/*
|
||||
Copyright 2024 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 (
|
||||
v1 "k8s.io/api/admissionregistration/v1"
|
||||
"k8s.io/api/admissionregistration/v1alpha1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/policy/generic"
|
||||
)
|
||||
|
||||
func NewMutatingAdmissionPolicyAccessor(obj *Policy) generic.PolicyAccessor {
|
||||
return &mutatingAdmissionPolicyAccessor{
|
||||
Policy: obj,
|
||||
}
|
||||
}
|
||||
|
||||
func NewMutatingAdmissionPolicyBindingAccessor(obj *PolicyBinding) generic.BindingAccessor {
|
||||
return &mutatingAdmissionPolicyBindingAccessor{
|
||||
PolicyBinding: obj,
|
||||
}
|
||||
}
|
||||
|
||||
type mutatingAdmissionPolicyAccessor struct {
|
||||
*Policy
|
||||
}
|
||||
|
||||
func (v *mutatingAdmissionPolicyAccessor) GetNamespace() string {
|
||||
return v.Namespace
|
||||
}
|
||||
|
||||
func (v *mutatingAdmissionPolicyAccessor) GetName() string {
|
||||
return v.Name
|
||||
}
|
||||
|
||||
func (v *mutatingAdmissionPolicyAccessor) GetParamKind() *v1.ParamKind {
|
||||
pk := v.Spec.ParamKind
|
||||
if pk == nil {
|
||||
return nil
|
||||
}
|
||||
return &v1.ParamKind{
|
||||
APIVersion: pk.APIVersion,
|
||||
Kind: pk.Kind,
|
||||
}
|
||||
}
|
||||
|
||||
func (v *mutatingAdmissionPolicyAccessor) GetMatchConstraints() *v1.MatchResources {
|
||||
return convertV1alpha1ResourceRulesToV1(v.Spec.MatchConstraints)
|
||||
}
|
||||
|
||||
func (v *mutatingAdmissionPolicyAccessor) GetFailurePolicy() *v1.FailurePolicyType {
|
||||
return toV1FailurePolicy(v.Spec.FailurePolicy)
|
||||
}
|
||||
|
||||
func toV1FailurePolicy(failurePolicy *v1alpha1.FailurePolicyType) *v1.FailurePolicyType {
|
||||
if failurePolicy == nil {
|
||||
return nil
|
||||
}
|
||||
fp := v1.FailurePolicyType(*failurePolicy)
|
||||
return &fp
|
||||
}
|
||||
|
||||
type mutatingAdmissionPolicyBindingAccessor struct {
|
||||
*PolicyBinding
|
||||
}
|
||||
|
||||
func (v *mutatingAdmissionPolicyBindingAccessor) GetNamespace() string {
|
||||
return v.Namespace
|
||||
}
|
||||
|
||||
func (v *mutatingAdmissionPolicyBindingAccessor) GetName() string {
|
||||
return v.Name
|
||||
}
|
||||
|
||||
func (v *mutatingAdmissionPolicyBindingAccessor) GetPolicyName() types.NamespacedName {
|
||||
return types.NamespacedName{
|
||||
Namespace: "",
|
||||
Name: v.Spec.PolicyName,
|
||||
}
|
||||
}
|
||||
|
||||
func (v *mutatingAdmissionPolicyBindingAccessor) GetMatchResources() *v1.MatchResources {
|
||||
return convertV1alpha1ResourceRulesToV1(v.Spec.MatchResources)
|
||||
}
|
||||
|
||||
func (v *mutatingAdmissionPolicyBindingAccessor) GetParamRef() *v1.ParamRef {
|
||||
if v.Spec.ParamRef == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var nfa *v1.ParameterNotFoundActionType
|
||||
if v.Spec.ParamRef.ParameterNotFoundAction != nil {
|
||||
nfa = new(v1.ParameterNotFoundActionType)
|
||||
*nfa = v1.ParameterNotFoundActionType(*v.Spec.ParamRef.ParameterNotFoundAction)
|
||||
}
|
||||
|
||||
return &v1.ParamRef{
|
||||
Name: v.Spec.ParamRef.Name,
|
||||
Namespace: v.Spec.ParamRef.Namespace,
|
||||
Selector: v.Spec.ParamRef.Selector,
|
||||
ParameterNotFoundAction: nfa,
|
||||
}
|
||||
}
|
||||
|
||||
func convertV1alpha1ResourceRulesToV1(mc *v1alpha1.MatchResources) *v1.MatchResources {
|
||||
if mc == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var res v1.MatchResources
|
||||
res.NamespaceSelector = mc.NamespaceSelector
|
||||
res.ObjectSelector = mc.ObjectSelector
|
||||
for _, ex := range mc.ExcludeResourceRules {
|
||||
res.ExcludeResourceRules = append(res.ExcludeResourceRules, v1.NamedRuleWithOperations{
|
||||
ResourceNames: ex.ResourceNames,
|
||||
RuleWithOperations: ex.RuleWithOperations,
|
||||
})
|
||||
}
|
||||
for _, ex := range mc.ResourceRules {
|
||||
res.ResourceRules = append(res.ResourceRules, v1.NamedRuleWithOperations{
|
||||
ResourceNames: ex.ResourceNames,
|
||||
RuleWithOperations: ex.RuleWithOperations,
|
||||
})
|
||||
}
|
||||
if mc.MatchPolicy != nil {
|
||||
mp := v1.MatchPolicyType(*mc.MatchPolicy)
|
||||
res.MatchPolicy = &mp
|
||||
}
|
||||
return &res
|
||||
}
|
81
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/compilation.go
generated
vendored
Normal file
81
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/compilation.go
generated
vendored
Normal file
@ -0,0 +1,81 @@
|
||||
/*
|
||||
Copyright 2024 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"
|
||||
|
||||
"k8s.io/api/admissionregistration/v1alpha1"
|
||||
plugincel "k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/policy/mutating/patch"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions"
|
||||
apiservercel "k8s.io/apiserver/pkg/cel"
|
||||
"k8s.io/apiserver/pkg/cel/environment"
|
||||
)
|
||||
|
||||
// compilePolicy compiles the policy into a PolicyEvaluator
|
||||
// any error is stored and delayed until invocation.
|
||||
//
|
||||
// Each individual mutation is compiled into MutationEvaluationFunc and
|
||||
// returned is a PolicyEvaluator in the same order as the mutations appeared in the policy.
|
||||
func compilePolicy(policy *Policy) PolicyEvaluator {
|
||||
opts := plugincel.OptionalVariableDeclarations{HasParams: policy.Spec.ParamKind != nil, StrictCost: true, HasAuthorizer: true}
|
||||
compiler, err := plugincel.NewCompositedCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true))
|
||||
if err != nil {
|
||||
return PolicyEvaluator{Error: &apiservercel.Error{
|
||||
Type: apiservercel.ErrorTypeInternal,
|
||||
Detail: fmt.Sprintf("failed to initialize CEL compiler: %v", err),
|
||||
}}
|
||||
}
|
||||
|
||||
// Compile and store variables
|
||||
compiler.CompileAndStoreVariables(convertv1alpha1Variables(policy.Spec.Variables), opts, environment.StoredExpressions)
|
||||
|
||||
// Compile matchers
|
||||
var matcher matchconditions.Matcher = nil
|
||||
matchConditions := policy.Spec.MatchConditions
|
||||
if len(matchConditions) > 0 {
|
||||
matchExpressionAccessors := make([]plugincel.ExpressionAccessor, len(matchConditions))
|
||||
for i := range matchConditions {
|
||||
matchExpressionAccessors[i] = (*matchconditions.MatchCondition)(&matchConditions[i])
|
||||
}
|
||||
matcher = matchconditions.NewMatcher(compiler.CompileCondition(matchExpressionAccessors, opts, environment.StoredExpressions), toV1FailurePolicy(policy.Spec.FailurePolicy), "policy", "validate", policy.Name)
|
||||
}
|
||||
|
||||
// Compiler patchers
|
||||
var patchers []patch.Patcher
|
||||
patchOptions := opts
|
||||
patchOptions.HasPatchTypes = true
|
||||
for _, m := range policy.Spec.Mutations {
|
||||
switch m.PatchType {
|
||||
case v1alpha1.PatchTypeJSONPatch:
|
||||
if m.JSONPatch != nil {
|
||||
accessor := &patch.JSONPatchCondition{Expression: m.JSONPatch.Expression}
|
||||
compileResult := compiler.CompileMutatingEvaluator(accessor, patchOptions, environment.StoredExpressions)
|
||||
patchers = append(patchers, patch.NewJSONPatcher(compileResult))
|
||||
}
|
||||
case v1alpha1.PatchTypeApplyConfiguration:
|
||||
if m.ApplyConfiguration != nil {
|
||||
accessor := &patch.ApplyConfigurationCondition{Expression: m.ApplyConfiguration.Expression}
|
||||
compileResult := compiler.CompileMutatingEvaluator(accessor, patchOptions, environment.StoredExpressions)
|
||||
patchers = append(patchers, patch.NewApplyConfigurationPatcher(compileResult))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return PolicyEvaluator{Matcher: matcher, Mutators: patchers, CompositionEnv: compiler.CompositionEnv}
|
||||
}
|
295
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/dispatcher.go
generated
vendored
Normal file
295
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/dispatcher.go
generated
vendored
Normal file
@ -0,0 +1,295 @@
|
||||
/*
|
||||
Copyright 2024 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package mutating
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/api/admissionregistration/v1alpha1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
admissionauthorizer "k8s.io/apiserver/pkg/admission/plugin/authorizer"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/policy/generic"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/policy/matching"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/policy/mutating/patch"
|
||||
webhookgeneric "k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
|
||||
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
)
|
||||
|
||||
func NewDispatcher(a authorizer.Authorizer, m *matching.Matcher, tcm patch.TypeConverterManager) generic.Dispatcher[PolicyHook] {
|
||||
res := &dispatcher{
|
||||
matcher: m,
|
||||
authz: a,
|
||||
typeConverterManager: tcm,
|
||||
}
|
||||
res.Dispatcher = generic.NewPolicyDispatcher[*Policy, *PolicyBinding, PolicyEvaluator](
|
||||
NewMutatingAdmissionPolicyAccessor,
|
||||
NewMutatingAdmissionPolicyBindingAccessor,
|
||||
m,
|
||||
res.dispatchInvocations,
|
||||
)
|
||||
return res
|
||||
}
|
||||
|
||||
type dispatcher struct {
|
||||
matcher *matching.Matcher
|
||||
authz authorizer.Authorizer
|
||||
typeConverterManager patch.TypeConverterManager
|
||||
generic.Dispatcher[PolicyHook]
|
||||
}
|
||||
|
||||
func (d *dispatcher) Start(ctx context.Context) error {
|
||||
go d.typeConverterManager.Run(ctx)
|
||||
return d.Dispatcher.Start(ctx)
|
||||
}
|
||||
|
||||
func (d *dispatcher) dispatchInvocations(
|
||||
ctx context.Context,
|
||||
a admission.Attributes,
|
||||
o admission.ObjectInterfaces,
|
||||
versionedAttributes webhookgeneric.VersionedAttributeAccessor,
|
||||
invocations []generic.PolicyInvocation[*Policy, *PolicyBinding, PolicyEvaluator],
|
||||
) ([]generic.PolicyError, *k8serrors.StatusError) {
|
||||
var lastVersionedAttr *admission.VersionedAttributes
|
||||
|
||||
reinvokeCtx := a.GetReinvocationContext()
|
||||
var policyReinvokeCtx *policyReinvokeContext
|
||||
if v := reinvokeCtx.Value(PluginName); v != nil {
|
||||
policyReinvokeCtx = v.(*policyReinvokeContext)
|
||||
} else {
|
||||
policyReinvokeCtx = &policyReinvokeContext{}
|
||||
reinvokeCtx.SetValue(PluginName, policyReinvokeCtx)
|
||||
}
|
||||
|
||||
if reinvokeCtx.IsReinvoke() && policyReinvokeCtx.IsOutputChangedSinceLastPolicyInvocation(a.GetObject()) {
|
||||
// If the object has changed, we know the in-tree plugin re-invocations have mutated the object,
|
||||
// and we need to reinvoke all eligible policies.
|
||||
policyReinvokeCtx.RequireReinvokingPreviouslyInvokedPlugins()
|
||||
}
|
||||
defer func() {
|
||||
policyReinvokeCtx.SetLastPolicyInvocationOutput(a.GetObject())
|
||||
}()
|
||||
|
||||
var policyErrors []generic.PolicyError
|
||||
addConfigError := func(err error, invocation generic.PolicyInvocation[*Policy, *PolicyBinding, PolicyEvaluator], reason metav1.StatusReason) {
|
||||
policyErrors = append(policyErrors, generic.PolicyError{
|
||||
Message: err,
|
||||
Policy: NewMutatingAdmissionPolicyAccessor(invocation.Policy),
|
||||
Binding: NewMutatingAdmissionPolicyBindingAccessor(invocation.Binding),
|
||||
Reason: reason,
|
||||
})
|
||||
}
|
||||
|
||||
// There is at least one invocation to invoke. Make sure we have a namespace
|
||||
// object if the incoming object is not cluster scoped to pass into the evaluator.
|
||||
var namespace *v1.Namespace
|
||||
var err error
|
||||
namespaceName := a.GetNamespace()
|
||||
|
||||
// Special case, the namespace object has the namespace of itself (maybe a bug).
|
||||
// unset it if the incoming object is a namespace
|
||||
if gvk := a.GetKind(); gvk.Kind == "Namespace" && gvk.Version == "v1" && gvk.Group == "" {
|
||||
namespaceName = ""
|
||||
}
|
||||
|
||||
// if it is cluster scoped, namespaceName will be empty
|
||||
// Otherwise, get the Namespace resource.
|
||||
if namespaceName != "" {
|
||||
namespace, err = d.matcher.GetNamespace(namespaceName)
|
||||
if err != nil {
|
||||
return nil, k8serrors.NewNotFound(schema.GroupResource{Group: "", Resource: "namespaces"}, namespaceName)
|
||||
}
|
||||
}
|
||||
|
||||
authz := admissionauthorizer.NewCachingAuthorizer(d.authz)
|
||||
|
||||
// Should loop through invocations, handling possible error and invoking
|
||||
// evaluator to apply patch, also should handle re-invocations
|
||||
for _, invocation := range invocations {
|
||||
if invocation.Evaluator.CompositionEnv != nil {
|
||||
ctx = invocation.Evaluator.CompositionEnv.CreateContext(ctx)
|
||||
}
|
||||
if len(invocation.Evaluator.Mutators) != len(invocation.Policy.Spec.Mutations) {
|
||||
// This would be a bug. The compiler should always return exactly as
|
||||
// many evaluators as there are mutations
|
||||
return nil, k8serrors.NewInternalError(fmt.Errorf("expected %v compiled evaluators for policy %v, got %v",
|
||||
len(invocation.Policy.Spec.Mutations), invocation.Policy.Name, len(invocation.Evaluator.Mutators)))
|
||||
}
|
||||
|
||||
versionedAttr, err := versionedAttributes.VersionedAttribute(invocation.Kind)
|
||||
if err != nil {
|
||||
// This should never happen, we pre-warm versoined attribute
|
||||
// accessors before starting the dispatcher
|
||||
return nil, k8serrors.NewInternalError(err)
|
||||
}
|
||||
|
||||
if invocation.Evaluator.Matcher != nil {
|
||||
matchResults := invocation.Evaluator.Matcher.Match(ctx, versionedAttr, invocation.Param, authz)
|
||||
if matchResults.Error != nil {
|
||||
addConfigError(matchResults.Error, invocation, metav1.StatusReasonInvalid)
|
||||
continue
|
||||
}
|
||||
|
||||
// if preconditions are not met, then skip mutations
|
||||
if !matchResults.Matches {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
invocationKey, invocationKeyErr := keyFor(invocation)
|
||||
if invocationKeyErr != nil {
|
||||
// This should never happen. It occurs if there is a programming
|
||||
// error causing the Param not to be a valid object.
|
||||
return nil, k8serrors.NewInternalError(invocationKeyErr)
|
||||
}
|
||||
if reinvokeCtx.IsReinvoke() && !policyReinvokeCtx.ShouldReinvoke(invocationKey) {
|
||||
continue
|
||||
}
|
||||
|
||||
objectBeforeMutations := versionedAttr.VersionedObject
|
||||
// Mutations for a single invocation of a MutatingAdmissionPolicy are evaluated
|
||||
// in order.
|
||||
for mutationIndex := range invocation.Policy.Spec.Mutations {
|
||||
lastVersionedAttr = versionedAttr
|
||||
if versionedAttr.VersionedObject == nil { // Do not call patchers if there is no object to patch.
|
||||
continue
|
||||
}
|
||||
|
||||
patcher := invocation.Evaluator.Mutators[mutationIndex]
|
||||
optionalVariables := cel.OptionalVariableBindings{VersionedParams: invocation.Param, Authorizer: authz}
|
||||
err = d.dispatchOne(ctx, patcher, o, versionedAttr, namespace, invocation.Resource, optionalVariables)
|
||||
if err != nil {
|
||||
var statusError *k8serrors.StatusError
|
||||
if errors.As(err, &statusError) {
|
||||
return nil, statusError
|
||||
}
|
||||
|
||||
addConfigError(err, invocation, metav1.StatusReasonInvalid)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if !apiequality.Semantic.DeepEqual(objectBeforeMutations, versionedAttr.VersionedObject) {
|
||||
// The mutation has changed the object. Prepare to reinvoke all previous mutations that are eligible for re-invocation.
|
||||
policyReinvokeCtx.RequireReinvokingPreviouslyInvokedPlugins()
|
||||
reinvokeCtx.SetShouldReinvoke()
|
||||
}
|
||||
if invocation.Policy.Spec.ReinvocationPolicy == v1alpha1.IfNeededReinvocationPolicy {
|
||||
policyReinvokeCtx.AddReinvocablePolicyToPreviouslyInvoked(invocationKey)
|
||||
}
|
||||
}
|
||||
|
||||
if lastVersionedAttr != nil && lastVersionedAttr.VersionedObject != nil && lastVersionedAttr.Dirty {
|
||||
policyReinvokeCtx.RequireReinvokingPreviouslyInvokedPlugins()
|
||||
reinvokeCtx.SetShouldReinvoke()
|
||||
if err := o.GetObjectConvertor().Convert(lastVersionedAttr.VersionedObject, lastVersionedAttr.Attributes.GetObject(), nil); err != nil {
|
||||
return nil, k8serrors.NewInternalError(fmt.Errorf("failed to convert object: %w", err))
|
||||
}
|
||||
}
|
||||
|
||||
return policyErrors, nil
|
||||
}
|
||||
|
||||
func (d *dispatcher) dispatchOne(
|
||||
ctx context.Context,
|
||||
patcher patch.Patcher,
|
||||
o admission.ObjectInterfaces,
|
||||
versionedAttributes *admission.VersionedAttributes,
|
||||
namespace *v1.Namespace,
|
||||
resource schema.GroupVersionResource,
|
||||
optionalVariables cel.OptionalVariableBindings,
|
||||
) (err error) {
|
||||
if patcher == nil {
|
||||
// internal error. this should not happen
|
||||
return k8serrors.NewInternalError(fmt.Errorf("policy evaluator is nil"))
|
||||
}
|
||||
|
||||
// Find type converter for the invoked Group-Version.
|
||||
typeConverter := d.typeConverterManager.GetTypeConverter(versionedAttributes.VersionedKind)
|
||||
if typeConverter == nil {
|
||||
// This can happen if the request is for a resource whose schema
|
||||
// has not been registered with the type converter manager.
|
||||
return k8serrors.NewServiceUnavailable(fmt.Sprintf("Resource kind %s not found. There can be a delay between when CustomResourceDefinitions are created and when they are available.", versionedAttributes.VersionedKind))
|
||||
}
|
||||
|
||||
patchRequest := patch.Request{
|
||||
MatchedResource: resource,
|
||||
VersionedAttributes: versionedAttributes,
|
||||
ObjectInterfaces: o,
|
||||
OptionalVariables: optionalVariables,
|
||||
Namespace: namespace,
|
||||
TypeConverter: typeConverter,
|
||||
}
|
||||
newVersionedObject, err := patcher.Patch(ctx, patchRequest, celconfig.RuntimeCELCostBudget)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch versionedAttributes.VersionedObject.(type) {
|
||||
case *unstructured.Unstructured:
|
||||
// No conversion needed before defaulting for the patch object if the admitted object is unstructured.
|
||||
default:
|
||||
// Before defaulting, if the admitted object is a typed object, convert unstructured patch result back to a typed object.
|
||||
newVersionedObject, err = o.GetObjectConvertor().ConvertToVersion(newVersionedObject, versionedAttributes.GetKind().GroupVersion())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
o.GetObjectDefaulter().Default(newVersionedObject)
|
||||
|
||||
versionedAttributes.Dirty = true
|
||||
versionedAttributes.VersionedObject = newVersionedObject
|
||||
return nil
|
||||
}
|
||||
|
||||
func keyFor(invocation generic.PolicyInvocation[*Policy, *PolicyBinding, PolicyEvaluator]) (key, error) {
|
||||
var paramUID types.NamespacedName
|
||||
if invocation.Param != nil {
|
||||
paramAccessor, err := meta.Accessor(invocation.Param)
|
||||
if err != nil {
|
||||
// This should never happen, as the param should have been validated
|
||||
// before being passed to the plugin.
|
||||
return key{}, err
|
||||
}
|
||||
paramUID = types.NamespacedName{
|
||||
Name: paramAccessor.GetName(),
|
||||
Namespace: paramAccessor.GetNamespace(),
|
||||
}
|
||||
}
|
||||
|
||||
return key{
|
||||
PolicyUID: types.NamespacedName{
|
||||
Name: invocation.Policy.GetName(),
|
||||
Namespace: invocation.Policy.GetNamespace(),
|
||||
},
|
||||
BindingUID: types.NamespacedName{
|
||||
Name: invocation.Binding.GetName(),
|
||||
Namespace: invocation.Binding.GetNamespace(),
|
||||
},
|
||||
ParamUID: paramUID,
|
||||
}, nil
|
||||
}
|
45
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/patch/interface.go
generated
vendored
Normal file
45
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/patch/interface.go
generated
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
Copyright 2024 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 patch
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/managedfields"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||
)
|
||||
|
||||
// Patcher provides a patch function to perform a mutation to an object in the admission chain.
|
||||
type Patcher interface {
|
||||
// Patch returns a copy of the object in the request, modified to change specified by the patch.
|
||||
// The original object in the request MUST NOT be modified in-place.
|
||||
Patch(ctx context.Context, request Request, runtimeCELCostBudget int64) (runtime.Object, error)
|
||||
}
|
||||
|
||||
// Request defines the arguments required by a patcher.
|
||||
type Request struct {
|
||||
MatchedResource schema.GroupVersionResource
|
||||
VersionedAttributes *admission.VersionedAttributes
|
||||
ObjectInterfaces admission.ObjectInterfaces
|
||||
OptionalVariables cel.OptionalVariableBindings
|
||||
Namespace *v1.Namespace
|
||||
TypeConverter managedfields.TypeConverter
|
||||
}
|
192
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/patch/json_patch.go
generated
vendored
Normal file
192
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/patch/json_patch.go
generated
vendored
Normal file
@ -0,0 +1,192 @@
|
||||
/*
|
||||
Copyright 2024 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 patch
|
||||
|
||||
import (
|
||||
"context"
|
||||
gojson "encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
celgo "github.com/google/cel-go/cel"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/traits"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
jsonpatch "gopkg.in/evanphx/json-patch.v4"
|
||||
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer/json"
|
||||
plugincel "k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||
"k8s.io/apiserver/pkg/cel/mutation"
|
||||
"k8s.io/apiserver/pkg/cel/mutation/dynamic"
|
||||
pointer "k8s.io/utils/ptr"
|
||||
)
|
||||
|
||||
// JSONPatchCondition contains the inputs needed to compile and evaluate a cel expression
|
||||
// that returns a JSON patch value.
|
||||
type JSONPatchCondition struct {
|
||||
Expression string
|
||||
}
|
||||
|
||||
var _ plugincel.ExpressionAccessor = &JSONPatchCondition{}
|
||||
|
||||
func (v *JSONPatchCondition) GetExpression() string {
|
||||
return v.Expression
|
||||
}
|
||||
|
||||
func (v *JSONPatchCondition) ReturnTypes() []*celgo.Type {
|
||||
return []*celgo.Type{celgo.ListType(jsonPatchType)}
|
||||
}
|
||||
|
||||
var jsonPatchType = types.NewObjectType("JSONPatch")
|
||||
|
||||
// NewJSONPatcher creates a patcher that performs a JSON Patch mutation.
|
||||
func NewJSONPatcher(patchEvaluator plugincel.MutatingEvaluator) Patcher {
|
||||
return &jsonPatcher{patchEvaluator}
|
||||
}
|
||||
|
||||
type jsonPatcher struct {
|
||||
PatchEvaluator plugincel.MutatingEvaluator
|
||||
}
|
||||
|
||||
func (e *jsonPatcher) Patch(ctx context.Context, r Request, runtimeCELCostBudget int64) (runtime.Object, error) {
|
||||
admissionRequest := plugincel.CreateAdmissionRequest(
|
||||
r.VersionedAttributes.Attributes,
|
||||
metav1.GroupVersionResource(r.MatchedResource),
|
||||
metav1.GroupVersionKind(r.VersionedAttributes.VersionedKind))
|
||||
|
||||
compileErrors := e.PatchEvaluator.CompilationErrors()
|
||||
if len(compileErrors) > 0 {
|
||||
return nil, errors.Join(compileErrors...)
|
||||
}
|
||||
patchObj, _, err := e.evaluatePatchExpression(ctx, e.PatchEvaluator, runtimeCELCostBudget, r, admissionRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
o := r.ObjectInterfaces
|
||||
jsonSerializer := json.NewSerializerWithOptions(json.DefaultMetaFactory, o.GetObjectCreater(), o.GetObjectTyper(), json.SerializerOptions{Pretty: false, Strict: true})
|
||||
objJS, err := runtime.Encode(jsonSerializer, r.VersionedAttributes.VersionedObject)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create JSON patch: %w", err)
|
||||
}
|
||||
patchedJS, err := patchObj.Apply(objJS)
|
||||
if err != nil {
|
||||
if errors.Is(err, jsonpatch.ErrTestFailed) {
|
||||
// If a json patch fails a test operation, the patch must not be applied
|
||||
return r.VersionedAttributes.VersionedObject, nil
|
||||
}
|
||||
return nil, fmt.Errorf("JSON Patch: %w", err)
|
||||
}
|
||||
|
||||
var newVersionedObject runtime.Object
|
||||
if _, ok := r.VersionedAttributes.VersionedObject.(*unstructured.Unstructured); ok {
|
||||
newVersionedObject = &unstructured.Unstructured{}
|
||||
} else {
|
||||
newVersionedObject, err = o.GetObjectCreater().New(r.VersionedAttributes.VersionedKind)
|
||||
if err != nil {
|
||||
return nil, apierrors.NewInternalError(err)
|
||||
}
|
||||
}
|
||||
|
||||
if newVersionedObject, _, err = jsonSerializer.Decode(patchedJS, nil, newVersionedObject); err != nil {
|
||||
return nil, apierrors.NewInternalError(err)
|
||||
}
|
||||
|
||||
return newVersionedObject, nil
|
||||
}
|
||||
|
||||
func (e *jsonPatcher) evaluatePatchExpression(ctx context.Context, patchEvaluator plugincel.MutatingEvaluator, remainingBudget int64, r Request, admissionRequest *admissionv1.AdmissionRequest) (jsonpatch.Patch, int64, error) {
|
||||
var err error
|
||||
var eval plugincel.EvaluationResult
|
||||
eval, remainingBudget, err = patchEvaluator.ForInput(ctx, r.VersionedAttributes, admissionRequest, r.OptionalVariables, r.Namespace, remainingBudget)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
if eval.Error != nil {
|
||||
return nil, -1, eval.Error
|
||||
}
|
||||
refVal := eval.EvalResult
|
||||
|
||||
// the return type can be any valid CEL value.
|
||||
// Scalars, maps and lists are used to set the value when the path points to a field of that type.
|
||||
// ObjectVal is used when the path points to a struct. A map like "{"field1": 1, "fieldX": bool}" is not
|
||||
// possible in Kubernetes CEL because maps and lists may not have mixed types.
|
||||
|
||||
iter, ok := refVal.(traits.Lister)
|
||||
if !ok {
|
||||
// Should never happen since compiler checks return type.
|
||||
return nil, -1, fmt.Errorf("type mismatch: JSONPatchType.expression should evaluate to array")
|
||||
}
|
||||
result := jsonpatch.Patch{}
|
||||
for it := iter.Iterator(); it.HasNext() == types.True; {
|
||||
v := it.Next()
|
||||
patchObj, err := v.ConvertToNative(reflect.TypeOf(&mutation.JSONPatchVal{}))
|
||||
if err != nil {
|
||||
// Should never happen since return type is checked by compiler.
|
||||
return nil, -1, fmt.Errorf("type mismatch: JSONPatchType.expression should evaluate to array of JSONPatch: %w", err)
|
||||
}
|
||||
op, ok := patchObj.(*mutation.JSONPatchVal)
|
||||
if !ok {
|
||||
// Should never happen since return type is checked by compiler.
|
||||
return nil, -1, fmt.Errorf("type mismatch: JSONPatchType.expression should evaluate to array of JSONPatch, got element of %T", patchObj)
|
||||
}
|
||||
|
||||
// Construct a JSON Patch from the evaluated CEL expression
|
||||
resultOp := jsonpatch.Operation{}
|
||||
resultOp["op"] = pointer.To(gojson.RawMessage(strconv.Quote(op.Op)))
|
||||
resultOp["path"] = pointer.To(gojson.RawMessage(strconv.Quote(op.Path)))
|
||||
if len(op.From) > 0 {
|
||||
resultOp["from"] = pointer.To(gojson.RawMessage(strconv.Quote(op.From)))
|
||||
}
|
||||
if op.Val != nil {
|
||||
if objVal, ok := op.Val.(*dynamic.ObjectVal); ok {
|
||||
// TODO: Object initializers are insufficiently type checked.
|
||||
// In the interim, we use this sanity check to detect type mismatches
|
||||
// between field names and Object initializers. For example,
|
||||
// "Object.spec{ selector: Object.spec.wrong{}}" is detected as a mismatch.
|
||||
// Before beta, attaching full type information both to Object initializers and
|
||||
// the "object" and "oldObject" variables is needed. This will allow CEL to
|
||||
// perform comprehensive runtime type checking.
|
||||
err := objVal.CheckTypeNamesMatchFieldPathNames()
|
||||
if err != nil {
|
||||
return nil, -1, fmt.Errorf("type mismatch: %w", err)
|
||||
}
|
||||
}
|
||||
// CEL data literals representing arbitrary JSON values can be serialized to JSON for use in
|
||||
// JSON Patch if first converted to pb.Value.
|
||||
v, err := op.Val.ConvertToNative(reflect.TypeOf(&structpb.Value{}))
|
||||
if err != nil {
|
||||
return nil, -1, fmt.Errorf("JSONPath valueExpression evaluated to a type that could not marshal to JSON: %w", err)
|
||||
}
|
||||
b, err := gojson.Marshal(v)
|
||||
if err != nil {
|
||||
return nil, -1, fmt.Errorf("JSONPath valueExpression evaluated to a type that could not marshal to JSON: %w", err)
|
||||
}
|
||||
resultOp["value"] = pointer.To[gojson.RawMessage](b)
|
||||
}
|
||||
|
||||
result = append(result, resultOp)
|
||||
}
|
||||
|
||||
return result, remainingBudget, nil
|
||||
}
|
217
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/patch/smd.go
generated
vendored
Normal file
217
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/patch/smd.go
generated
vendored
Normal file
@ -0,0 +1,217 @@
|
||||
/*
|
||||
Copyright 2024 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 patch
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
celgo "github.com/google/cel-go/cel"
|
||||
celtypes "github.com/google/cel-go/common/types"
|
||||
"strings"
|
||||
|
||||
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
||||
"sigs.k8s.io/structured-merge-diff/v4/schema"
|
||||
"sigs.k8s.io/structured-merge-diff/v4/typed"
|
||||
"sigs.k8s.io/structured-merge-diff/v4/value"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/managedfields"
|
||||
plugincel "k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||
"k8s.io/apiserver/pkg/cel/mutation/dynamic"
|
||||
)
|
||||
|
||||
// ApplyConfigurationCondition contains the inputs needed to compile and evaluate a cel expression
|
||||
// that returns an apply configuration
|
||||
type ApplyConfigurationCondition struct {
|
||||
Expression string
|
||||
}
|
||||
|
||||
var _ plugincel.ExpressionAccessor = &ApplyConfigurationCondition{}
|
||||
|
||||
func (v *ApplyConfigurationCondition) GetExpression() string {
|
||||
return v.Expression
|
||||
}
|
||||
|
||||
func (v *ApplyConfigurationCondition) ReturnTypes() []*celgo.Type {
|
||||
return []*celgo.Type{applyConfigObjectType}
|
||||
}
|
||||
|
||||
var applyConfigObjectType = celtypes.NewObjectType("Object")
|
||||
|
||||
// NewApplyConfigurationPatcher creates a patcher that performs an applyConfiguration mutation.
|
||||
func NewApplyConfigurationPatcher(expressionEvaluator plugincel.MutatingEvaluator) Patcher {
|
||||
return &applyConfigPatcher{expressionEvaluator: expressionEvaluator}
|
||||
}
|
||||
|
||||
type applyConfigPatcher struct {
|
||||
expressionEvaluator plugincel.MutatingEvaluator
|
||||
}
|
||||
|
||||
func (e *applyConfigPatcher) Patch(ctx context.Context, r Request, runtimeCELCostBudget int64) (runtime.Object, error) {
|
||||
admissionRequest := plugincel.CreateAdmissionRequest(
|
||||
r.VersionedAttributes.Attributes,
|
||||
metav1.GroupVersionResource(r.MatchedResource),
|
||||
metav1.GroupVersionKind(r.VersionedAttributes.VersionedKind))
|
||||
|
||||
compileErrors := e.expressionEvaluator.CompilationErrors()
|
||||
if len(compileErrors) > 0 {
|
||||
return nil, errors.Join(compileErrors...)
|
||||
}
|
||||
eval, _, err := e.expressionEvaluator.ForInput(ctx, r.VersionedAttributes, admissionRequest, r.OptionalVariables, r.Namespace, runtimeCELCostBudget)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if eval.Error != nil {
|
||||
return nil, eval.Error
|
||||
}
|
||||
v := eval.EvalResult
|
||||
|
||||
// The compiler ensures that the return type is an ObjectVal with type name of "Object".
|
||||
objVal, ok := v.(*dynamic.ObjectVal)
|
||||
if !ok {
|
||||
// Should not happen since the compiler type checks the return type.
|
||||
return nil, fmt.Errorf("unsupported return type from ApplyConfiguration expression: %v", v.Type())
|
||||
}
|
||||
// TODO: Object initializers are insufficiently type checked.
|
||||
// In the interim, we use this sanity check to detect type mismatches
|
||||
// between field names and Object initializers. For example,
|
||||
// "Object.spec{ selector: Object.spec.wrong{}}" is detected as a mismatch.
|
||||
// Before beta, attaching full type information both to Object initializers and
|
||||
// the "object" and "oldObject" variables is needed. This will allow CEL to
|
||||
// perform comprehensive runtime type checking.
|
||||
err = objVal.CheckTypeNamesMatchFieldPathNames()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("type mismatch: %w", err)
|
||||
}
|
||||
|
||||
value, ok := objVal.Value().(map[string]any)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid return type: %T", v)
|
||||
}
|
||||
|
||||
patchObject := unstructured.Unstructured{Object: value}
|
||||
patchObject.SetGroupVersionKind(r.VersionedAttributes.VersionedObject.GetObjectKind().GroupVersionKind())
|
||||
patched, err := ApplyStructuredMergeDiff(r.TypeConverter, r.VersionedAttributes.VersionedObject, &patchObject)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error applying patch: %w", err)
|
||||
}
|
||||
return patched, nil
|
||||
}
|
||||
|
||||
// ApplyStructuredMergeDiff applies a structured merge diff to an object and returns a copy of the object
|
||||
// with the patch applied.
|
||||
func ApplyStructuredMergeDiff(
|
||||
typeConverter managedfields.TypeConverter,
|
||||
originalObject runtime.Object,
|
||||
patch *unstructured.Unstructured,
|
||||
) (runtime.Object, error) {
|
||||
if patch.GroupVersionKind() != originalObject.GetObjectKind().GroupVersionKind() {
|
||||
return nil, fmt.Errorf("patch (%v) and original object (%v) are not of the same gvk", patch.GroupVersionKind().String(), originalObject.GetObjectKind().GroupVersionKind().String())
|
||||
} else if typeConverter == nil {
|
||||
return nil, fmt.Errorf("type converter must not be nil")
|
||||
}
|
||||
|
||||
patchObjTyped, err := typeConverter.ObjectToTyped(patch)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert patch object to typed object: %w", err)
|
||||
}
|
||||
|
||||
err = validatePatch(patchObjTyped)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid ApplyConfiguration: %w", err)
|
||||
}
|
||||
|
||||
liveObjTyped, err := typeConverter.ObjectToTyped(originalObject)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert original object to typed object: %w", err)
|
||||
}
|
||||
|
||||
newObjTyped, err := liveObjTyped.Merge(patchObjTyped)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to merge patch: %w", err)
|
||||
}
|
||||
|
||||
// Our mutating admission policy sets the fields but does not track ownership.
|
||||
// Newly introduced fields in the patch won't be tracked by a field manager
|
||||
// (so if the original object is updated again but the mutating policy is
|
||||
// not active, the fields will be dropped).
|
||||
//
|
||||
// This necessarily means that changes to an object by a mutating policy
|
||||
// are only preserved if the policy was active at the time of the change.
|
||||
// (If the policy is not active, the changes may be dropped.)
|
||||
|
||||
newObj, err := typeConverter.TypedToObject(newObjTyped)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert typed object to object: %w", err)
|
||||
}
|
||||
|
||||
return newObj, nil
|
||||
}
|
||||
|
||||
// validatePatch searches an apply configuration for any arrays, maps or structs elements that are atomic and returns
|
||||
// an error if any are found.
|
||||
// This prevents accidental removal of fields that can occur when the user intends to modify some
|
||||
// fields in an atomic type, not realizing that all fields not explicitly set in the new value
|
||||
// of the atomic will be removed.
|
||||
func validatePatch(v *typed.TypedValue) error {
|
||||
atomics := findAtomics(nil, v.Schema(), v.TypeRef(), v.AsValue())
|
||||
if len(atomics) > 0 {
|
||||
return fmt.Errorf("may not mutate atomic arrays, maps or structs: %v", strings.Join(atomics, ", "))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// findAtomics returns field paths for any atomic arrays, maps or structs found when traversing the given value.
|
||||
func findAtomics(path []fieldpath.PathElement, s *schema.Schema, tr schema.TypeRef, v value.Value) (atomics []string) {
|
||||
if a, ok := s.Resolve(tr); ok { // Validation pass happens before this and checks that all schemas can be resolved
|
||||
if v.IsMap() && a.Map != nil {
|
||||
if a.Map.ElementRelationship == schema.Atomic {
|
||||
atomics = append(atomics, pathString(path))
|
||||
}
|
||||
v.AsMap().Iterate(func(key string, val value.Value) bool {
|
||||
pe := fieldpath.PathElement{FieldName: &key}
|
||||
if sf, ok := a.Map.FindField(key); ok {
|
||||
tr = sf.Type
|
||||
atomics = append(atomics, findAtomics(append(path, pe), s, tr, val)...)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
if v.IsList() && a.List != nil {
|
||||
if a.List.ElementRelationship == schema.Atomic {
|
||||
atomics = append(atomics, pathString(path))
|
||||
}
|
||||
list := v.AsList()
|
||||
for i := 0; i < list.Length(); i++ {
|
||||
pe := fieldpath.PathElement{Index: &i}
|
||||
atomics = append(atomics, findAtomics(append(path, pe), s, a.List.ElementType, list.At(i))...)
|
||||
}
|
||||
}
|
||||
}
|
||||
return atomics
|
||||
}
|
||||
|
||||
func pathString(path []fieldpath.PathElement) string {
|
||||
sb := strings.Builder{}
|
||||
for _, p := range path {
|
||||
sb.WriteString(p.String())
|
||||
}
|
||||
return sb.String()
|
||||
}
|
187
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/patch/typeconverter.go
generated
vendored
Normal file
187
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/patch/typeconverter.go
generated
vendored
Normal file
@ -0,0 +1,187 @@
|
||||
/*
|
||||
Copyright 2024 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 patch
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/managedfields"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/openapi"
|
||||
"k8s.io/kube-openapi/pkg/spec3"
|
||||
)
|
||||
|
||||
type TypeConverterManager interface {
|
||||
// GetTypeConverter returns a type converter for the given GVK
|
||||
GetTypeConverter(gvk schema.GroupVersionKind) managedfields.TypeConverter
|
||||
Run(ctx context.Context)
|
||||
}
|
||||
|
||||
func NewTypeConverterManager(
|
||||
staticTypeConverter managedfields.TypeConverter,
|
||||
openapiClient openapi.Client,
|
||||
) TypeConverterManager {
|
||||
return &typeConverterManager{
|
||||
staticTypeConverter: staticTypeConverter,
|
||||
openapiClient: openapiClient,
|
||||
typeConverterMap: make(map[schema.GroupVersion]typeConverterCacheEntry),
|
||||
lastFetchedPaths: make(map[schema.GroupVersion]openapi.GroupVersion),
|
||||
}
|
||||
}
|
||||
|
||||
type typeConverterCacheEntry struct {
|
||||
typeConverter managedfields.TypeConverter
|
||||
entry openapi.GroupVersion
|
||||
}
|
||||
|
||||
// typeConverterManager helps us make sure we have an up to date schema and
|
||||
// type converter for our openapi models. It should be connfigured to use a
|
||||
// static type converter for natively typed schemas, and fetches the schema
|
||||
// for CRDs/other over the network on demand (trying to reduce network calls where necessary)
|
||||
type typeConverterManager struct {
|
||||
// schemaCache is used to cache the schema for a given GVK
|
||||
staticTypeConverter managedfields.TypeConverter
|
||||
|
||||
// discoveryClient is used to fetch the schema for a given GVK
|
||||
openapiClient openapi.Client
|
||||
|
||||
lock sync.RWMutex
|
||||
|
||||
typeConverterMap map[schema.GroupVersion]typeConverterCacheEntry
|
||||
lastFetchedPaths map[schema.GroupVersion]openapi.GroupVersion
|
||||
}
|
||||
|
||||
func (t *typeConverterManager) Run(ctx context.Context) {
|
||||
// Loop every 5s refershing the OpenAPI schema list to know which
|
||||
// schemas have been invalidated. This should use e-tags under the hood
|
||||
_ = wait.PollUntilContextCancel(ctx, 5*time.Second, true, func(_ context.Context) (done bool, err error) {
|
||||
paths, err := t.openapiClient.Paths()
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("failed to fetch openapi paths: %w", err))
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// The /openapi/v3 endpoint contains a list of paths whose ServerRelativeURL
|
||||
// value changes every time the schema is updated. So we poll /openapi/v3
|
||||
// to get the "version number" for each schema, and invalidate our cache
|
||||
// if the version number has changed since we pulled it.
|
||||
parsedPaths := make(map[schema.GroupVersion]openapi.GroupVersion, len(paths))
|
||||
for path, entry := range paths {
|
||||
if !strings.HasPrefix(path, "apis/") && !strings.HasPrefix(path, "api/") {
|
||||
continue
|
||||
}
|
||||
path = strings.TrimPrefix(path, "apis/")
|
||||
path = strings.TrimPrefix(path, "api/")
|
||||
|
||||
gv, err := schema.ParseGroupVersion(path)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("failed to parse group version %q: %w", path, err))
|
||||
return false, nil
|
||||
}
|
||||
|
||||
parsedPaths[gv] = entry
|
||||
}
|
||||
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
t.lastFetchedPaths = parsedPaths
|
||||
return false, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (t *typeConverterManager) GetTypeConverter(gvk schema.GroupVersionKind) managedfields.TypeConverter {
|
||||
// Check to see if the static type converter handles this GVK
|
||||
if t.staticTypeConverter != nil {
|
||||
//!TODO: Add ability to check existence to type converter
|
||||
// working around for now but seeing if getting a typed version of an
|
||||
// empty object returns error
|
||||
stub := &unstructured.Unstructured{}
|
||||
stub.SetGroupVersionKind(gvk)
|
||||
|
||||
if _, err := t.staticTypeConverter.ObjectToTyped(stub); err == nil {
|
||||
return t.staticTypeConverter
|
||||
}
|
||||
}
|
||||
|
||||
gv := gvk.GroupVersion()
|
||||
|
||||
existing, entry, err := func() (managedfields.TypeConverter, openapi.GroupVersion, error) {
|
||||
t.lock.RLock()
|
||||
defer t.lock.RUnlock()
|
||||
|
||||
// If schema is not supported by static type converter, ask discovery
|
||||
// for the schema
|
||||
entry, ok := t.lastFetchedPaths[gv]
|
||||
if !ok {
|
||||
// If we can't get the schema, we can't do anything
|
||||
return nil, nil, fmt.Errorf("no schema for %v", gvk)
|
||||
}
|
||||
|
||||
// If the entry schema has not changed, used the same type converter
|
||||
if existing, ok := t.typeConverterMap[gv]; ok && existing.entry.ServerRelativeURL() == entry.ServerRelativeURL() {
|
||||
// If we have a type converter for this GVK, return it
|
||||
return existing.typeConverter, existing.entry, nil
|
||||
}
|
||||
|
||||
return nil, entry, nil
|
||||
}()
|
||||
if err != nil {
|
||||
utilruntime.HandleError(err)
|
||||
return nil
|
||||
} else if existing != nil {
|
||||
return existing
|
||||
}
|
||||
|
||||
schBytes, err := entry.Schema(runtime.ContentTypeJSON)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("failed to get schema for %v: %w", gvk, err))
|
||||
return nil
|
||||
}
|
||||
|
||||
var sch spec3.OpenAPI
|
||||
if err := json.Unmarshal(schBytes, &sch); err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("failed to unmarshal schema for %v: %w", gvk, err))
|
||||
return nil
|
||||
}
|
||||
|
||||
// The schema has changed, or there is no entry for it, generate
|
||||
// a new type converter for this GV
|
||||
tc, err := managedfields.NewTypeConverter(sch.Components.Schemas, false)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("failed to create type converter for %v: %w", gvk, err))
|
||||
return nil
|
||||
}
|
||||
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
||||
t.typeConverterMap[gv] = typeConverterCacheEntry{
|
||||
typeConverter: tc,
|
||||
entry: entry,
|
||||
}
|
||||
|
||||
return tc
|
||||
}
|
151
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/plugin.go
generated
vendored
Normal file
151
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/plugin.go
generated
vendored
Normal file
@ -0,0 +1,151 @@
|
||||
/*
|
||||
Copyright 2024 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package mutating
|
||||
|
||||
import (
|
||||
"context"
|
||||
celgo "github.com/google/cel-go/cel"
|
||||
"io"
|
||||
|
||||
"k8s.io/api/admissionregistration/v1alpha1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/managedfields"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/policy/generic"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/policy/matching"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/policy/mutating/patch"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/component-base/featuregate"
|
||||
)
|
||||
|
||||
const (
|
||||
// PluginName indicates the name of admission plug-in
|
||||
PluginName = "MutatingAdmissionPolicy"
|
||||
)
|
||||
|
||||
// Register registers a plugin
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register(PluginName, func(configFile io.Reader) (admission.Interface, error) {
|
||||
return NewPlugin(configFile), nil
|
||||
})
|
||||
}
|
||||
|
||||
type Policy = v1alpha1.MutatingAdmissionPolicy
|
||||
type PolicyBinding = v1alpha1.MutatingAdmissionPolicyBinding
|
||||
type PolicyMutation = v1alpha1.Mutation
|
||||
type PolicyHook = generic.PolicyHook[*Policy, *PolicyBinding, PolicyEvaluator]
|
||||
|
||||
type Mutator struct {
|
||||
}
|
||||
type MutationEvaluationFunc func(
|
||||
ctx context.Context,
|
||||
matchedResource schema.GroupVersionResource,
|
||||
versionedAttr *admission.VersionedAttributes,
|
||||
o admission.ObjectInterfaces,
|
||||
versionedParams runtime.Object,
|
||||
namespace *corev1.Namespace,
|
||||
typeConverter managedfields.TypeConverter,
|
||||
runtimeCELCostBudget int64,
|
||||
authorizer authorizer.Authorizer,
|
||||
) (runtime.Object, error)
|
||||
|
||||
type PolicyEvaluator struct {
|
||||
Matcher matchconditions.Matcher
|
||||
Mutators []patch.Patcher
|
||||
CompositionEnv *cel.CompositionEnv
|
||||
Error error
|
||||
}
|
||||
|
||||
// Plugin is an implementation of admission.Interface.
|
||||
type Plugin struct {
|
||||
*generic.Plugin[PolicyHook]
|
||||
}
|
||||
|
||||
var _ admission.Interface = &Plugin{}
|
||||
var _ admission.MutationInterface = &Plugin{}
|
||||
|
||||
// NewPlugin returns a generic admission webhook plugin.
|
||||
func NewPlugin(_ io.Reader) *Plugin {
|
||||
// There is no request body to mutate for DELETE, so this plugin never handles that operation.
|
||||
handler := admission.NewHandler(admission.Create, admission.Update, admission.Connect)
|
||||
res := &Plugin{}
|
||||
res.Plugin = generic.NewPlugin(
|
||||
handler,
|
||||
func(f informers.SharedInformerFactory, client kubernetes.Interface, dynamicClient dynamic.Interface, restMapper meta.RESTMapper) generic.Source[PolicyHook] {
|
||||
return generic.NewPolicySource(
|
||||
f.Admissionregistration().V1alpha1().MutatingAdmissionPolicies().Informer(),
|
||||
f.Admissionregistration().V1alpha1().MutatingAdmissionPolicyBindings().Informer(),
|
||||
NewMutatingAdmissionPolicyAccessor,
|
||||
NewMutatingAdmissionPolicyBindingAccessor,
|
||||
compilePolicy,
|
||||
//!TODO: Create a way to share param informers between
|
||||
// mutating/validating plugins
|
||||
f,
|
||||
dynamicClient,
|
||||
restMapper,
|
||||
)
|
||||
},
|
||||
func(a authorizer.Authorizer, m *matching.Matcher, client kubernetes.Interface) generic.Dispatcher[PolicyHook] {
|
||||
return NewDispatcher(a, m, patch.NewTypeConverterManager(nil, client.Discovery().OpenAPIV3()))
|
||||
},
|
||||
)
|
||||
return res
|
||||
}
|
||||
|
||||
// Admit makes an admission decision based on the request attributes.
|
||||
func (a *Plugin) Admit(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces) error {
|
||||
return a.Plugin.Dispatch(ctx, attr, o)
|
||||
}
|
||||
|
||||
func (a *Plugin) InspectFeatureGates(featureGates featuregate.FeatureGate) {
|
||||
a.Plugin.SetEnabled(featureGates.Enabled(features.MutatingAdmissionPolicy))
|
||||
}
|
||||
|
||||
// Variable is a named expression for composition.
|
||||
type Variable struct {
|
||||
Name string
|
||||
Expression string
|
||||
}
|
||||
|
||||
func (v *Variable) GetExpression() string {
|
||||
return v.Expression
|
||||
}
|
||||
|
||||
func (v *Variable) ReturnTypes() []*celgo.Type {
|
||||
return []*celgo.Type{celgo.AnyType, celgo.DynType}
|
||||
}
|
||||
|
||||
func (v *Variable) GetName() string {
|
||||
return v.Name
|
||||
}
|
||||
|
||||
func convertv1alpha1Variables(variables []v1alpha1.Variable) []cel.NamedExpressionAccessor {
|
||||
namedExpressions := make([]cel.NamedExpressionAccessor, len(variables))
|
||||
for i, variable := range variables {
|
||||
namedExpressions[i] = &Variable{Name: variable.Name, Expression: variable.Expression}
|
||||
}
|
||||
return namedExpressions
|
||||
}
|
76
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/reinvocationcontext.go
generated
vendored
Normal file
76
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/reinvocationcontext.go
generated
vendored
Normal file
@ -0,0 +1,76 @@
|
||||
/*
|
||||
Copyright 2024 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package mutating
|
||||
|
||||
import (
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
type key struct {
|
||||
PolicyUID types.NamespacedName
|
||||
BindingUID types.NamespacedName
|
||||
ParamUID types.NamespacedName
|
||||
MutationIndex int
|
||||
}
|
||||
|
||||
type policyReinvokeContext struct {
|
||||
// lastPolicyOutput holds the result of the last Policy admission plugin call
|
||||
lastPolicyOutput runtime.Object
|
||||
// previouslyInvokedReinvocablePolicys holds the set of policies that have been invoked and
|
||||
// should be reinvoked if a later mutation occurs
|
||||
previouslyInvokedReinvocablePolicies sets.Set[key]
|
||||
// reinvokePolicies holds the set of Policies that should be reinvoked
|
||||
reinvokePolicies sets.Set[key]
|
||||
}
|
||||
|
||||
func (rc *policyReinvokeContext) ShouldReinvoke(policy key) bool {
|
||||
return rc.reinvokePolicies.Has(policy)
|
||||
}
|
||||
|
||||
func (rc *policyReinvokeContext) IsOutputChangedSinceLastPolicyInvocation(object runtime.Object) bool {
|
||||
return !apiequality.Semantic.DeepEqual(rc.lastPolicyOutput, object)
|
||||
}
|
||||
|
||||
func (rc *policyReinvokeContext) SetLastPolicyInvocationOutput(object runtime.Object) {
|
||||
if object == nil {
|
||||
rc.lastPolicyOutput = nil
|
||||
return
|
||||
}
|
||||
rc.lastPolicyOutput = object.DeepCopyObject()
|
||||
}
|
||||
|
||||
func (rc *policyReinvokeContext) AddReinvocablePolicyToPreviouslyInvoked(policy key) {
|
||||
if rc.previouslyInvokedReinvocablePolicies == nil {
|
||||
rc.previouslyInvokedReinvocablePolicies = sets.New[key]()
|
||||
}
|
||||
rc.previouslyInvokedReinvocablePolicies.Insert(policy)
|
||||
}
|
||||
|
||||
func (rc *policyReinvokeContext) RequireReinvokingPreviouslyInvokedPlugins() {
|
||||
if len(rc.previouslyInvokedReinvocablePolicies) > 0 {
|
||||
if rc.reinvokePolicies == nil {
|
||||
rc.reinvokePolicies = sets.New[key]()
|
||||
}
|
||||
for s := range rc.previouslyInvokedReinvocablePolicies {
|
||||
rc.reinvokePolicies.Insert(s)
|
||||
}
|
||||
rc.previouslyInvokedReinvocablePolicies = sets.New[key]()
|
||||
}
|
||||
}
|
86
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/validating/accessor.go
generated
vendored
Normal file
86
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/validating/accessor.go
generated
vendored
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
Copyright 2024 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 (
|
||||
"k8s.io/api/admissionregistration/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/policy/generic"
|
||||
)
|
||||
|
||||
func NewValidatingAdmissionPolicyAccessor(obj *v1.ValidatingAdmissionPolicy) generic.PolicyAccessor {
|
||||
return &validatingAdmissionPolicyAccessor{
|
||||
ValidatingAdmissionPolicy: obj,
|
||||
}
|
||||
}
|
||||
|
||||
func NewValidatingAdmissionPolicyBindingAccessor(obj *v1.ValidatingAdmissionPolicyBinding) generic.BindingAccessor {
|
||||
return &validatingAdmissionPolicyBindingAccessor{
|
||||
ValidatingAdmissionPolicyBinding: obj,
|
||||
}
|
||||
}
|
||||
|
||||
type validatingAdmissionPolicyAccessor struct {
|
||||
*v1.ValidatingAdmissionPolicy
|
||||
}
|
||||
|
||||
func (v *validatingAdmissionPolicyAccessor) GetNamespace() string {
|
||||
return v.Namespace
|
||||
}
|
||||
|
||||
func (v *validatingAdmissionPolicyAccessor) GetName() string {
|
||||
return v.Name
|
||||
}
|
||||
|
||||
func (v *validatingAdmissionPolicyAccessor) GetParamKind() *v1.ParamKind {
|
||||
return v.Spec.ParamKind
|
||||
}
|
||||
|
||||
func (v *validatingAdmissionPolicyAccessor) GetMatchConstraints() *v1.MatchResources {
|
||||
return v.Spec.MatchConstraints
|
||||
}
|
||||
|
||||
func (v *validatingAdmissionPolicyAccessor) GetFailurePolicy() *v1.FailurePolicyType {
|
||||
return v.Spec.FailurePolicy
|
||||
}
|
||||
|
||||
type validatingAdmissionPolicyBindingAccessor struct {
|
||||
*v1.ValidatingAdmissionPolicyBinding
|
||||
}
|
||||
|
||||
func (v *validatingAdmissionPolicyBindingAccessor) GetNamespace() string {
|
||||
return v.Namespace
|
||||
}
|
||||
|
||||
func (v *validatingAdmissionPolicyBindingAccessor) GetName() string {
|
||||
return v.Name
|
||||
}
|
||||
|
||||
func (v *validatingAdmissionPolicyBindingAccessor) GetPolicyName() types.NamespacedName {
|
||||
return types.NamespacedName{
|
||||
Namespace: "",
|
||||
Name: v.Spec.PolicyName,
|
||||
}
|
||||
}
|
||||
|
||||
func (v *validatingAdmissionPolicyBindingAccessor) GetMatchResources() *v1.MatchResources {
|
||||
return v.Spec.MatchResources
|
||||
}
|
||||
|
||||
func (v *validatingAdmissionPolicyBindingAccessor) GetParamRef() *v1.ParamRef {
|
||||
return v.Spec.ParamRef
|
||||
}
|
420
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/validating/dispatcher.go
generated
vendored
Normal file
420
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/validating/dispatcher.go
generated
vendored
Normal file
@ -0,0 +1,420 @@
|
||||
/*
|
||||
Copyright 2022 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"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utiljson "k8s.io/apimachinery/pkg/util/json"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
admissionauthorizer "k8s.io/apiserver/pkg/admission/plugin/authorizer"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/policy/generic"
|
||||
celmetrics "k8s.io/apiserver/pkg/admission/plugin/policy/validating/metrics"
|
||||
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/apiserver/pkg/warning"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
type dispatcher struct {
|
||||
matcher generic.PolicyMatcher
|
||||
authz authorizer.Authorizer
|
||||
}
|
||||
|
||||
var _ generic.Dispatcher[PolicyHook] = &dispatcher{}
|
||||
|
||||
func NewDispatcher(
|
||||
authorizer authorizer.Authorizer,
|
||||
matcher generic.PolicyMatcher,
|
||||
) generic.Dispatcher[PolicyHook] {
|
||||
return &dispatcher{
|
||||
matcher: matcher,
|
||||
authz: authorizer,
|
||||
}
|
||||
}
|
||||
|
||||
// contains the cel PolicyDecisions along with the ValidatingAdmissionPolicy and ValidatingAdmissionPolicyBinding
|
||||
// that determined the decision
|
||||
type policyDecisionWithMetadata struct {
|
||||
PolicyDecision
|
||||
Definition *admissionregistrationv1.ValidatingAdmissionPolicy
|
||||
Binding *admissionregistrationv1.ValidatingAdmissionPolicyBinding
|
||||
}
|
||||
|
||||
func (c *dispatcher) Start(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Dispatch implements generic.Dispatcher.
|
||||
func (c *dispatcher) Dispatch(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces, hooks []PolicyHook) error {
|
||||
|
||||
var deniedDecisions []policyDecisionWithMetadata
|
||||
|
||||
addConfigError := func(err error, definition *admissionregistrationv1.ValidatingAdmissionPolicy, binding *admissionregistrationv1.ValidatingAdmissionPolicyBinding) {
|
||||
// we always default the FailurePolicy if it is unset and validate it in API level
|
||||
var policy admissionregistrationv1.FailurePolicyType
|
||||
if definition.Spec.FailurePolicy == nil {
|
||||
policy = admissionregistrationv1.Fail
|
||||
} else {
|
||||
policy = *definition.Spec.FailurePolicy
|
||||
}
|
||||
|
||||
// apply FailurePolicy specified in ValidatingAdmissionPolicy, the default would be Fail
|
||||
switch policy {
|
||||
case admissionregistrationv1.Ignore:
|
||||
// TODO: add metrics for ignored error here
|
||||
return
|
||||
case admissionregistrationv1.Fail:
|
||||
var message string
|
||||
if binding == nil {
|
||||
message = fmt.Errorf("failed to configure policy: %w", err).Error()
|
||||
} else {
|
||||
message = fmt.Errorf("failed to configure binding: %w", err).Error()
|
||||
}
|
||||
deniedDecisions = append(deniedDecisions, policyDecisionWithMetadata{
|
||||
PolicyDecision: PolicyDecision{
|
||||
Action: ActionDeny,
|
||||
Message: message,
|
||||
},
|
||||
Definition: definition,
|
||||
Binding: binding,
|
||||
})
|
||||
default:
|
||||
deniedDecisions = append(deniedDecisions, policyDecisionWithMetadata{
|
||||
PolicyDecision: PolicyDecision{
|
||||
Action: ActionDeny,
|
||||
Message: fmt.Errorf("unrecognized failure policy: '%v'", policy).Error(),
|
||||
},
|
||||
Definition: definition,
|
||||
Binding: binding,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
authz := admissionauthorizer.NewCachingAuthorizer(c.authz)
|
||||
|
||||
for _, hook := range hooks {
|
||||
// versionedAttributes will be set to non-nil inside of the loop, but
|
||||
// is scoped outside of the param loop so we only convert once. We defer
|
||||
// conversion so that it is only performed when we know a policy matches,
|
||||
// saving the cost of converting non-matching requests.
|
||||
var versionedAttr *admission.VersionedAttributes
|
||||
|
||||
definition := hook.Policy
|
||||
matches, matchResource, matchKind, err := c.matcher.DefinitionMatches(a, o, NewValidatingAdmissionPolicyAccessor(definition))
|
||||
if err != nil {
|
||||
// Configuration error.
|
||||
addConfigError(err, definition, nil)
|
||||
continue
|
||||
}
|
||||
if !matches {
|
||||
// Policy definition does not match request
|
||||
continue
|
||||
} else if hook.ConfigurationError != nil {
|
||||
// Configuration error.
|
||||
addConfigError(hook.ConfigurationError, definition, nil)
|
||||
continue
|
||||
}
|
||||
|
||||
auditAnnotationCollector := newAuditAnnotationCollector()
|
||||
for _, binding := range hook.Bindings {
|
||||
// If the key is inside dependentBindings, there is guaranteed to
|
||||
// be a bindingInfo for it
|
||||
matches, err := c.matcher.BindingMatches(a, o, NewValidatingAdmissionPolicyBindingAccessor(binding))
|
||||
if err != nil {
|
||||
// Configuration error.
|
||||
addConfigError(err, definition, binding)
|
||||
continue
|
||||
}
|
||||
if !matches {
|
||||
continue
|
||||
}
|
||||
|
||||
params, err := generic.CollectParams(
|
||||
hook.Policy.Spec.ParamKind,
|
||||
hook.ParamInformer,
|
||||
hook.ParamScope,
|
||||
binding.Spec.ParamRef,
|
||||
a.GetNamespace(),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
addConfigError(err, definition, binding)
|
||||
continue
|
||||
} else if versionedAttr == nil && len(params) > 0 {
|
||||
// As optimization versionedAttr creation is deferred until
|
||||
// first use. Since > 0 params, we will validate
|
||||
va, err := admission.NewVersionedAttributes(a, matchKind, o)
|
||||
if err != nil {
|
||||
wrappedErr := fmt.Errorf("failed to convert object version: %w", err)
|
||||
addConfigError(wrappedErr, definition, binding)
|
||||
continue
|
||||
}
|
||||
versionedAttr = va
|
||||
}
|
||||
|
||||
var validationResults []ValidateResult
|
||||
var namespace *v1.Namespace
|
||||
namespaceName := a.GetNamespace()
|
||||
|
||||
// Special case, the namespace object has the namespace of itself (maybe a bug).
|
||||
// unset it if the incoming object is a namespace
|
||||
if gvk := a.GetKind(); gvk.Kind == "Namespace" && gvk.Version == "v1" && gvk.Group == "" {
|
||||
namespaceName = ""
|
||||
}
|
||||
|
||||
// if it is cluster scoped, namespaceName will be empty
|
||||
// Otherwise, get the Namespace resource.
|
||||
if namespaceName != "" {
|
||||
namespace, err = c.matcher.GetNamespace(namespaceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, param := range params {
|
||||
var p runtime.Object = param
|
||||
if p != nil && p.GetObjectKind().GroupVersionKind().Empty() {
|
||||
// Make sure param has TypeMeta populated
|
||||
// This is a simple hack to make sure typeMeta is
|
||||
// available to CEL without making copies of objects, etc.
|
||||
p = &wrappedParam{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: definition.Spec.ParamKind.APIVersion,
|
||||
Kind: definition.Spec.ParamKind.Kind,
|
||||
},
|
||||
nested: param,
|
||||
}
|
||||
}
|
||||
|
||||
validationResults = append(validationResults,
|
||||
hook.Evaluator.Validate(
|
||||
ctx,
|
||||
matchResource,
|
||||
versionedAttr,
|
||||
p,
|
||||
namespace,
|
||||
celconfig.RuntimeCELCostBudget,
|
||||
authz,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
for _, validationResult := range validationResults {
|
||||
for i, decision := range validationResult.Decisions {
|
||||
switch decision.Action {
|
||||
case ActionAdmit:
|
||||
if decision.Evaluation == EvalError {
|
||||
celmetrics.Metrics.ObserveAdmission(ctx, decision.Elapsed, definition.Name, binding.Name, ErrorType(&decision))
|
||||
}
|
||||
case ActionDeny:
|
||||
for _, action := range binding.Spec.ValidationActions {
|
||||
switch action {
|
||||
case admissionregistrationv1.Deny:
|
||||
deniedDecisions = append(deniedDecisions, policyDecisionWithMetadata{
|
||||
Definition: definition,
|
||||
Binding: binding,
|
||||
PolicyDecision: decision,
|
||||
})
|
||||
celmetrics.Metrics.ObserveRejection(ctx, decision.Elapsed, definition.Name, binding.Name, ErrorType(&decision))
|
||||
case admissionregistrationv1.Audit:
|
||||
publishValidationFailureAnnotation(binding, i, decision, versionedAttr)
|
||||
celmetrics.Metrics.ObserveAudit(ctx, decision.Elapsed, definition.Name, binding.Name, ErrorType(&decision))
|
||||
case admissionregistrationv1.Warn:
|
||||
warning.AddWarning(ctx, "", fmt.Sprintf("Validation failed for ValidatingAdmissionPolicy '%s' with binding '%s': %s", definition.Name, binding.Name, decision.Message))
|
||||
celmetrics.Metrics.ObserveWarn(ctx, decision.Elapsed, definition.Name, binding.Name, ErrorType(&decision))
|
||||
}
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unrecognized evaluation decision '%s' for ValidatingAdmissionPolicyBinding '%s' with ValidatingAdmissionPolicy '%s'",
|
||||
decision.Action, binding.Name, definition.Name)
|
||||
}
|
||||
}
|
||||
|
||||
for _, auditAnnotation := range validationResult.AuditAnnotations {
|
||||
switch auditAnnotation.Action {
|
||||
case AuditAnnotationActionPublish:
|
||||
value := auditAnnotation.Value
|
||||
if len(auditAnnotation.Value) > maxAuditAnnotationValueLength {
|
||||
value = value[:maxAuditAnnotationValueLength]
|
||||
}
|
||||
auditAnnotationCollector.add(auditAnnotation.Key, value)
|
||||
case AuditAnnotationActionError:
|
||||
// When failurePolicy=fail, audit annotation errors result in deny
|
||||
d := policyDecisionWithMetadata{
|
||||
Definition: definition,
|
||||
Binding: binding,
|
||||
PolicyDecision: PolicyDecision{
|
||||
Action: ActionDeny,
|
||||
Evaluation: EvalError,
|
||||
Message: auditAnnotation.Error,
|
||||
Elapsed: auditAnnotation.Elapsed,
|
||||
},
|
||||
}
|
||||
deniedDecisions = append(deniedDecisions, d)
|
||||
celmetrics.Metrics.ObserveRejection(ctx, auditAnnotation.Elapsed, definition.Name, binding.Name, ErrorType(&d.PolicyDecision))
|
||||
case AuditAnnotationActionExclude: // skip it
|
||||
default:
|
||||
return fmt.Errorf("unsupported AuditAnnotation Action: %s", auditAnnotation.Action)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
auditAnnotationCollector.publish(definition.Name, a)
|
||||
}
|
||||
|
||||
if len(deniedDecisions) > 0 {
|
||||
// TODO: refactor admission.NewForbidden so the name extraction is reusable but the code/reason is customizable
|
||||
var message string
|
||||
deniedDecision := deniedDecisions[0]
|
||||
if deniedDecision.Binding != nil {
|
||||
message = fmt.Sprintf("ValidatingAdmissionPolicy '%s' with binding '%s' denied request: %s", deniedDecision.Definition.Name, deniedDecision.Binding.Name, deniedDecision.Message)
|
||||
} else {
|
||||
message = fmt.Sprintf("ValidatingAdmissionPolicy '%s' denied request: %s", deniedDecision.Definition.Name, deniedDecision.Message)
|
||||
}
|
||||
err := admission.NewForbidden(a, errors.New(message)).(*k8serrors.StatusError)
|
||||
reason := deniedDecision.Reason
|
||||
if len(reason) == 0 {
|
||||
reason = metav1.StatusReasonInvalid
|
||||
}
|
||||
err.ErrStatus.Reason = reason
|
||||
err.ErrStatus.Code = reasonToCode(reason)
|
||||
err.ErrStatus.Details.Causes = append(err.ErrStatus.Details.Causes, metav1.StatusCause{Message: message})
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func publishValidationFailureAnnotation(binding *admissionregistrationv1.ValidatingAdmissionPolicyBinding, expressionIndex int, decision PolicyDecision, attributes admission.Attributes) {
|
||||
key := "validation.policy.admission.k8s.io/validation_failure"
|
||||
// Marshal to a list of failures since, in the future, we may need to support multiple failures
|
||||
valueJSON, err := utiljson.Marshal([]ValidationFailureValue{{
|
||||
ExpressionIndex: expressionIndex,
|
||||
Message: decision.Message,
|
||||
ValidationActions: binding.Spec.ValidationActions,
|
||||
Binding: binding.Name,
|
||||
Policy: binding.Spec.PolicyName,
|
||||
}})
|
||||
if err != nil {
|
||||
klog.Warningf("Failed to set admission audit annotation %s for ValidatingAdmissionPolicy %s and ValidatingAdmissionPolicyBinding %s: %v", key, binding.Spec.PolicyName, binding.Name, err)
|
||||
}
|
||||
value := string(valueJSON)
|
||||
if err := attributes.AddAnnotation(key, value); err != nil {
|
||||
klog.Warningf("Failed to set admission audit annotation %s to %s for ValidatingAdmissionPolicy %s and ValidatingAdmissionPolicyBinding %s: %v", key, value, binding.Spec.PolicyName, binding.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
const maxAuditAnnotationValueLength = 10 * 1024
|
||||
|
||||
// validationFailureValue defines the JSON format of a "validation.policy.admission.k8s.io/validation_failure" audit
|
||||
// annotation value.
|
||||
type ValidationFailureValue struct {
|
||||
Message string `json:"message"`
|
||||
Policy string `json:"policy"`
|
||||
Binding string `json:"binding"`
|
||||
ExpressionIndex int `json:"expressionIndex"`
|
||||
ValidationActions []admissionregistrationv1.ValidationAction `json:"validationActions"`
|
||||
}
|
||||
|
||||
type auditAnnotationCollector struct {
|
||||
annotations map[string][]string
|
||||
}
|
||||
|
||||
func newAuditAnnotationCollector() auditAnnotationCollector {
|
||||
return auditAnnotationCollector{annotations: map[string][]string{}}
|
||||
}
|
||||
|
||||
func (a auditAnnotationCollector) add(key, value string) {
|
||||
// If multiple bindings produces the exact same key and value for an audit annotation,
|
||||
// ignore the duplicates.
|
||||
for _, v := range a.annotations[key] {
|
||||
if v == value {
|
||||
return
|
||||
}
|
||||
}
|
||||
a.annotations[key] = append(a.annotations[key], value)
|
||||
}
|
||||
|
||||
func (a auditAnnotationCollector) publish(policyName string, attributes admission.Attributes) {
|
||||
for key, bindingAnnotations := range a.annotations {
|
||||
var value string
|
||||
if len(bindingAnnotations) == 1 {
|
||||
value = bindingAnnotations[0]
|
||||
} else {
|
||||
// Multiple distinct values can exist when binding params are used in the valueExpression of an auditAnnotation.
|
||||
// When this happens, the values are concatenated into a comma-separated list.
|
||||
value = strings.Join(bindingAnnotations, ", ")
|
||||
}
|
||||
if err := attributes.AddAnnotation(policyName+"/"+key, value); err != nil {
|
||||
klog.Warningf("Failed to set admission audit annotation %s to %s for ValidatingAdmissionPolicy %s: %v", key, value, policyName, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A workaround to fact that native types do not have TypeMeta populated, which
|
||||
// is needed for CEL expressions to be able to access the value.
|
||||
type wrappedParam struct {
|
||||
metav1.TypeMeta
|
||||
nested runtime.Object
|
||||
}
|
||||
|
||||
func (w *wrappedParam) MarshalJSON() ([]byte, error) {
|
||||
return nil, errors.New("MarshalJSON unimplemented for wrappedParam")
|
||||
}
|
||||
|
||||
func (w *wrappedParam) UnmarshalJSON(data []byte) error {
|
||||
return errors.New("UnmarshalJSON unimplemented for wrappedParam")
|
||||
}
|
||||
|
||||
func (w *wrappedParam) ToUnstructured() interface{} {
|
||||
res, err := runtime.DefaultUnstructuredConverter.ToUnstructured(w.nested)
|
||||
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
metaRes, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&w.TypeMeta)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for k, v := range metaRes {
|
||||
res[k] = v
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func (w *wrappedParam) DeepCopyObject() runtime.Object {
|
||||
return &wrappedParam{
|
||||
TypeMeta: w.TypeMeta,
|
||||
nested: w.nested.DeepCopyObject(),
|
||||
}
|
||||
}
|
||||
|
||||
func (w *wrappedParam) GetObjectKind() schema.ObjectKind {
|
||||
return w
|
||||
}
|
38
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/validating/errors.go
generated
vendored
Normal file
38
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/validating/errors.go
generated
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
/*
|
||||
Copyright 2024 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 (
|
||||
"strings"
|
||||
|
||||
celmetrics "k8s.io/apiserver/pkg/admission/plugin/policy/validating/metrics"
|
||||
)
|
||||
|
||||
// ErrorType decodes the error to determine the error type
|
||||
// that the metrics understand.
|
||||
func ErrorType(decision *PolicyDecision) celmetrics.ValidationErrorType {
|
||||
if decision.Evaluation == EvalAdmit {
|
||||
return celmetrics.ValidationNoError
|
||||
}
|
||||
if strings.HasPrefix(decision.Message, "compilation") {
|
||||
return celmetrics.ValidationCompileError
|
||||
}
|
||||
if strings.HasPrefix(decision.Message, "validation failed due to running out of cost budget") {
|
||||
return celmetrics.ValidatingOutOfBudget
|
||||
}
|
||||
return celmetrics.ValidatingInvalidError
|
||||
}
|
31
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/validating/initializer.go
generated
vendored
Normal file
31
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/validating/initializer.go
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
Copyright 2022 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"
|
||||
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
)
|
||||
|
||||
type CELPolicyEvaluator interface {
|
||||
admission.InitializationValidator
|
||||
|
||||
Validate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error
|
||||
HasSynced() bool
|
||||
Run(stopCh <-chan struct{})
|
||||
}
|
95
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/validating/interface.go
generated
vendored
Normal file
95
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/validating/interface.go
generated
vendored
Normal file
@ -0,0 +1,95 @@
|
||||
/*
|
||||
Copyright 2022 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"
|
||||
|
||||
celgo "github.com/google/cel-go/cel"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
)
|
||||
|
||||
var _ cel.ExpressionAccessor = &ValidationCondition{}
|
||||
|
||||
// ValidationCondition contains the inputs needed to compile, evaluate and validate a cel expression
|
||||
type ValidationCondition struct {
|
||||
Expression string
|
||||
Message string
|
||||
Reason *metav1.StatusReason
|
||||
}
|
||||
|
||||
func (v *ValidationCondition) GetExpression() string {
|
||||
return v.Expression
|
||||
}
|
||||
|
||||
func (v *ValidationCondition) ReturnTypes() []*celgo.Type {
|
||||
return []*celgo.Type{celgo.BoolType}
|
||||
}
|
||||
|
||||
// AuditAnnotationCondition contains the inputs needed to compile, evaluate and publish a cel audit annotation
|
||||
type AuditAnnotationCondition struct {
|
||||
Key string
|
||||
ValueExpression string
|
||||
}
|
||||
|
||||
func (v *AuditAnnotationCondition) GetExpression() string {
|
||||
return v.ValueExpression
|
||||
}
|
||||
|
||||
func (v *AuditAnnotationCondition) ReturnTypes() []*celgo.Type {
|
||||
return []*celgo.Type{celgo.StringType, celgo.NullType}
|
||||
}
|
||||
|
||||
// Variable is a named expression for composition.
|
||||
type Variable struct {
|
||||
Name string
|
||||
Expression string
|
||||
}
|
||||
|
||||
func (v *Variable) GetExpression() string {
|
||||
return v.Expression
|
||||
}
|
||||
|
||||
func (v *Variable) ReturnTypes() []*celgo.Type {
|
||||
return []*celgo.Type{celgo.AnyType, celgo.DynType}
|
||||
}
|
||||
|
||||
func (v *Variable) GetName() string {
|
||||
return v.Name
|
||||
}
|
||||
|
||||
// ValidateResult defines the result of a Validator.Validate operation.
|
||||
type ValidateResult struct {
|
||||
// Decisions specifies the outcome of the validation as well as the details about the decision.
|
||||
Decisions []PolicyDecision
|
||||
// AuditAnnotations specifies the audit annotations that should be recorded for the validation.
|
||||
AuditAnnotations []PolicyAuditAnnotation
|
||||
}
|
||||
|
||||
// Validator is contains logic for converting ValidationEvaluation to PolicyDecisions
|
||||
type Validator interface {
|
||||
// Validate is used to take cel evaluations and convert into decisions
|
||||
// runtimeCELCostBudget was added for testing purpose only. Callers should always use const RuntimeCELCostBudget from k8s.io/apiserver/pkg/apis/cel/config.go as input.
|
||||
Validate(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *corev1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult
|
||||
}
|
36
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/validating/message.go
generated
vendored
Normal file
36
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/validating/message.go
generated
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
Copyright 2023 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 (
|
||||
celgo "github.com/google/cel-go/cel"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||
)
|
||||
|
||||
var _ cel.ExpressionAccessor = (*MessageExpressionCondition)(nil)
|
||||
|
||||
type MessageExpressionCondition struct {
|
||||
MessageExpression string
|
||||
}
|
||||
|
||||
func (m *MessageExpressionCondition) GetExpression() string {
|
||||
return m.MessageExpression
|
||||
}
|
||||
|
||||
func (m *MessageExpressionCondition) ReturnTypes() []*celgo.Type {
|
||||
return []*celgo.Type{celgo.StringType}
|
||||
}
|
38
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/validating/metrics/errors.go
generated
vendored
Normal file
38
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/validating/metrics/errors.go
generated
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
/*
|
||||
Copyright 2024 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 (
|
||||
"errors"
|
||||
|
||||
apiservercel "k8s.io/apiserver/pkg/cel"
|
||||
)
|
||||
|
||||
// ErrorType decodes the error to determine the error type
|
||||
// that the metrics understand.
|
||||
func ErrorType(err error) ValidationErrorType {
|
||||
if err == nil {
|
||||
return ValidationNoError
|
||||
}
|
||||
if errors.Is(err, apiservercel.ErrCompilation) {
|
||||
return ValidationCompileError
|
||||
}
|
||||
if errors.Is(err, apiservercel.ErrOutOfBudget) {
|
||||
return ValidatingOutOfBudget
|
||||
}
|
||||
return ValidatingInvalidError
|
||||
}
|
122
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/validating/metrics/metrics.go
generated
vendored
Normal file
122
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/validating/metrics/metrics.go
generated
vendored
Normal file
@ -0,0 +1,122 @@
|
||||
/*
|
||||
Copyright 2022 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 (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"k8s.io/component-base/metrics"
|
||||
"k8s.io/component-base/metrics/legacyregistry"
|
||||
)
|
||||
|
||||
const (
|
||||
metricsNamespace = "apiserver"
|
||||
metricsSubsystem = "validating_admission_policy"
|
||||
)
|
||||
|
||||
// ValidationErrorType defines different error types that happen to a validation expression
|
||||
type ValidationErrorType string
|
||||
|
||||
const (
|
||||
// ValidationCompileError indicates that the expression fails to compile.
|
||||
ValidationCompileError ValidationErrorType = "compile_error"
|
||||
// ValidatingInvalidError indicates that the expression fails due to internal
|
||||
// errors that are out of the control of the user.
|
||||
ValidatingInvalidError ValidationErrorType = "invalid_error"
|
||||
// ValidatingOutOfBudget indicates that the expression fails due to running
|
||||
// out of cost budget, or the budget cannot be obtained.
|
||||
ValidatingOutOfBudget ValidationErrorType = "out_of_budget"
|
||||
// ValidationNoError indicates that the expression returns without an error.
|
||||
ValidationNoError ValidationErrorType = "no_error"
|
||||
)
|
||||
|
||||
var (
|
||||
// Metrics provides access to validation admission metrics.
|
||||
Metrics = newValidationAdmissionMetrics()
|
||||
)
|
||||
|
||||
// ValidatingAdmissionPolicyMetrics aggregates Prometheus metrics related to validation admission control.
|
||||
type ValidatingAdmissionPolicyMetrics struct {
|
||||
policyCheck *metrics.CounterVec
|
||||
policyLatency *metrics.HistogramVec
|
||||
}
|
||||
|
||||
func newValidationAdmissionMetrics() *ValidatingAdmissionPolicyMetrics {
|
||||
check := metrics.NewCounterVec(
|
||||
&metrics.CounterOpts{
|
||||
Namespace: metricsNamespace,
|
||||
Subsystem: metricsSubsystem,
|
||||
Name: "check_total",
|
||||
Help: "Validation admission policy check total, labeled by policy and further identified by binding and enforcement action taken.",
|
||||
StabilityLevel: metrics.BETA,
|
||||
},
|
||||
[]string{"policy", "policy_binding", "error_type", "enforcement_action"},
|
||||
)
|
||||
latency := metrics.NewHistogramVec(&metrics.HistogramOpts{
|
||||
Namespace: metricsNamespace,
|
||||
Subsystem: metricsSubsystem,
|
||||
Name: "check_duration_seconds",
|
||||
Help: "Validation admission latency for individual validation expressions in seconds, labeled by policy and further including binding and enforcement action taken.",
|
||||
// the bucket distribution here is based oo the benchmark suite at
|
||||
// github.com/DangerOnTheRanger/cel-benchmark performed on 16-core Intel Xeon
|
||||
// the lowest bucket was based around the 180ns/op figure for BenchmarkAccess,
|
||||
// plus some additional leeway to account for the apiserver doing other things
|
||||
// the largest bucket was chosen based on the fact that benchmarks indicate the
|
||||
// same Xeon running a CEL expression close to the estimated cost limit takes
|
||||
// around 760ms, so that bucket should only ever have the slowest CEL expressions
|
||||
// in it
|
||||
Buckets: []float64{0.0000005, 0.001, 0.01, 0.1, 1.0},
|
||||
StabilityLevel: metrics.BETA,
|
||||
},
|
||||
[]string{"policy", "policy_binding", "error_type", "enforcement_action"},
|
||||
)
|
||||
|
||||
legacyregistry.MustRegister(check)
|
||||
legacyregistry.MustRegister(latency)
|
||||
return &ValidatingAdmissionPolicyMetrics{policyCheck: check, policyLatency: latency}
|
||||
}
|
||||
|
||||
// Reset resets all validation admission-related Prometheus metrics.
|
||||
func (m *ValidatingAdmissionPolicyMetrics) Reset() {
|
||||
m.policyCheck.Reset()
|
||||
m.policyLatency.Reset()
|
||||
}
|
||||
|
||||
// ObserveAdmission observes a policy validation, with an optional error to indicate the error that may occur but ignored.
|
||||
func (m *ValidatingAdmissionPolicyMetrics) ObserveAdmission(ctx context.Context, elapsed time.Duration, policy, binding string, errorType ValidationErrorType) {
|
||||
m.policyCheck.WithContext(ctx).WithLabelValues(policy, binding, string(errorType), "allow").Inc()
|
||||
m.policyLatency.WithContext(ctx).WithLabelValues(policy, binding, string(errorType), "allow").Observe(elapsed.Seconds())
|
||||
}
|
||||
|
||||
// ObserveRejection observes a policy validation error that was at least one of the reasons for a deny.
|
||||
func (m *ValidatingAdmissionPolicyMetrics) ObserveRejection(ctx context.Context, elapsed time.Duration, policy, binding string, errorType ValidationErrorType) {
|
||||
m.policyCheck.WithContext(ctx).WithLabelValues(policy, binding, string(errorType), "deny").Inc()
|
||||
m.policyLatency.WithContext(ctx).WithLabelValues(policy, binding, string(errorType), "deny").Observe(elapsed.Seconds())
|
||||
}
|
||||
|
||||
// ObserveAudit observes a policy validation audit annotation was published for a validation failure.
|
||||
func (m *ValidatingAdmissionPolicyMetrics) ObserveAudit(ctx context.Context, elapsed time.Duration, policy, binding string, errorType ValidationErrorType) {
|
||||
m.policyCheck.WithContext(ctx).WithLabelValues(policy, binding, string(errorType), "audit").Inc()
|
||||
m.policyLatency.WithContext(ctx).WithLabelValues(policy, binding, string(errorType), "audit").Observe(elapsed.Seconds())
|
||||
}
|
||||
|
||||
// ObserveWarn observes a policy validation warning was published for a validation failure.
|
||||
func (m *ValidatingAdmissionPolicyMetrics) ObserveWarn(ctx context.Context, elapsed time.Duration, policy, binding string, errorType ValidationErrorType) {
|
||||
m.policyCheck.WithContext(ctx).WithLabelValues(policy, binding, string(errorType), "warn").Inc()
|
||||
m.policyLatency.WithContext(ctx).WithLabelValues(policy, binding, string(errorType), "warn").Observe(elapsed.Seconds())
|
||||
}
|
211
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/validating/plugin.go
generated
vendored
Normal file
211
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/validating/plugin.go
generated
vendored
Normal file
@ -0,0 +1,211 @@
|
||||
/*
|
||||
Copyright 2024 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"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
v1 "k8s.io/api/admissionregistration/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/initializer"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/policy/generic"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/policy/matching"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/apiserver/pkg/cel/environment"
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
const (
|
||||
// PluginName indicates the name of admission plug-in
|
||||
PluginName = "ValidatingAdmissionPolicy"
|
||||
)
|
||||
|
||||
var (
|
||||
lazyCompositionEnvTemplateWithStrictCostInit sync.Once
|
||||
lazyCompositionEnvTemplateWithStrictCost *cel.CompositionEnv
|
||||
|
||||
lazyCompositionEnvTemplateWithoutStrictCostInit sync.Once
|
||||
lazyCompositionEnvTemplateWithoutStrictCost *cel.CompositionEnv
|
||||
)
|
||||
|
||||
func getCompositionEnvTemplateWithStrictCost() *cel.CompositionEnv {
|
||||
lazyCompositionEnvTemplateWithStrictCostInit.Do(func() {
|
||||
env, err := cel.NewCompositionEnv(cel.VariablesTypeName, environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
lazyCompositionEnvTemplateWithStrictCost = env
|
||||
})
|
||||
return lazyCompositionEnvTemplateWithStrictCost
|
||||
}
|
||||
|
||||
func getCompositionEnvTemplateWithoutStrictCost() *cel.CompositionEnv {
|
||||
lazyCompositionEnvTemplateWithoutStrictCostInit.Do(func() {
|
||||
env, err := cel.NewCompositionEnv(cel.VariablesTypeName, environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), false))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
lazyCompositionEnvTemplateWithoutStrictCost = env
|
||||
})
|
||||
return lazyCompositionEnvTemplateWithoutStrictCost
|
||||
}
|
||||
|
||||
// Register registers a plugin
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register(PluginName, func(configFile io.Reader) (admission.Interface, error) {
|
||||
return NewPlugin(configFile), nil
|
||||
})
|
||||
}
|
||||
|
||||
// Plugin is an implementation of admission.Interface.
|
||||
type Policy = v1.ValidatingAdmissionPolicy
|
||||
type PolicyBinding = v1.ValidatingAdmissionPolicyBinding
|
||||
type PolicyEvaluator = Validator
|
||||
type PolicyHook = generic.PolicyHook[*Policy, *PolicyBinding, PolicyEvaluator]
|
||||
|
||||
type Plugin struct {
|
||||
*generic.Plugin[PolicyHook]
|
||||
}
|
||||
|
||||
var _ admission.Interface = &Plugin{}
|
||||
var _ admission.ValidationInterface = &Plugin{}
|
||||
var _ initializer.WantsExcludedAdmissionResources = &Plugin{}
|
||||
|
||||
func NewPlugin(_ io.Reader) *Plugin {
|
||||
handler := admission.NewHandler(admission.Connect, admission.Create, admission.Delete, admission.Update)
|
||||
|
||||
p := &Plugin{
|
||||
Plugin: generic.NewPlugin(
|
||||
handler,
|
||||
func(f informers.SharedInformerFactory, client kubernetes.Interface, dynamicClient dynamic.Interface, restMapper meta.RESTMapper) generic.Source[PolicyHook] {
|
||||
return generic.NewPolicySource(
|
||||
f.Admissionregistration().V1().ValidatingAdmissionPolicies().Informer(),
|
||||
f.Admissionregistration().V1().ValidatingAdmissionPolicyBindings().Informer(),
|
||||
NewValidatingAdmissionPolicyAccessor,
|
||||
NewValidatingAdmissionPolicyBindingAccessor,
|
||||
compilePolicy,
|
||||
f,
|
||||
dynamicClient,
|
||||
restMapper,
|
||||
)
|
||||
},
|
||||
func(a authorizer.Authorizer, m *matching.Matcher, client kubernetes.Interface) generic.Dispatcher[PolicyHook] {
|
||||
return NewDispatcher(a, generic.NewPolicyMatcher(m))
|
||||
},
|
||||
),
|
||||
}
|
||||
p.SetEnabled(true)
|
||||
return p
|
||||
}
|
||||
|
||||
// Validate makes an admission decision based on the request attributes.
|
||||
func (a *Plugin) Validate(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces) error {
|
||||
return a.Plugin.Dispatch(ctx, attr, o)
|
||||
}
|
||||
|
||||
func compilePolicy(policy *Policy) Validator {
|
||||
hasParam := false
|
||||
if policy.Spec.ParamKind != nil {
|
||||
hasParam = true
|
||||
}
|
||||
strictCost := utilfeature.DefaultFeatureGate.Enabled(features.StrictCostEnforcementForVAP)
|
||||
optionalVars := cel.OptionalVariableDeclarations{HasParams: hasParam, HasAuthorizer: true, StrictCost: strictCost}
|
||||
expressionOptionalVars := cel.OptionalVariableDeclarations{HasParams: hasParam, HasAuthorizer: false, StrictCost: strictCost}
|
||||
failurePolicy := policy.Spec.FailurePolicy
|
||||
var matcher matchconditions.Matcher = nil
|
||||
matchConditions := policy.Spec.MatchConditions
|
||||
var compositionEnvTemplate *cel.CompositionEnv
|
||||
if strictCost {
|
||||
compositionEnvTemplate = getCompositionEnvTemplateWithStrictCost()
|
||||
} else {
|
||||
compositionEnvTemplate = getCompositionEnvTemplateWithoutStrictCost()
|
||||
}
|
||||
filterCompiler := cel.NewCompositedCompilerFromTemplate(compositionEnvTemplate)
|
||||
filterCompiler.CompileAndStoreVariables(convertv1beta1Variables(policy.Spec.Variables), optionalVars, environment.StoredExpressions)
|
||||
|
||||
if len(matchConditions) > 0 {
|
||||
matchExpressionAccessors := make([]cel.ExpressionAccessor, len(matchConditions))
|
||||
for i := range matchConditions {
|
||||
matchExpressionAccessors[i] = (*matchconditions.MatchCondition)(&matchConditions[i])
|
||||
}
|
||||
matcher = matchconditions.NewMatcher(filterCompiler.CompileCondition(matchExpressionAccessors, optionalVars, environment.StoredExpressions), failurePolicy, "policy", "validate", policy.Name)
|
||||
}
|
||||
res := NewValidator(
|
||||
filterCompiler.CompileCondition(convertv1Validations(policy.Spec.Validations), optionalVars, environment.StoredExpressions),
|
||||
matcher,
|
||||
filterCompiler.CompileCondition(convertv1AuditAnnotations(policy.Spec.AuditAnnotations), optionalVars, environment.StoredExpressions),
|
||||
filterCompiler.CompileCondition(convertv1MessageExpressions(policy.Spec.Validations), expressionOptionalVars, environment.StoredExpressions),
|
||||
failurePolicy,
|
||||
)
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func convertv1Validations(inputValidations []v1.Validation) []cel.ExpressionAccessor {
|
||||
celExpressionAccessor := make([]cel.ExpressionAccessor, len(inputValidations))
|
||||
for i, validation := range inputValidations {
|
||||
validation := ValidationCondition{
|
||||
Expression: validation.Expression,
|
||||
Message: validation.Message,
|
||||
Reason: validation.Reason,
|
||||
}
|
||||
celExpressionAccessor[i] = &validation
|
||||
}
|
||||
return celExpressionAccessor
|
||||
}
|
||||
|
||||
func convertv1MessageExpressions(inputValidations []v1.Validation) []cel.ExpressionAccessor {
|
||||
celExpressionAccessor := make([]cel.ExpressionAccessor, len(inputValidations))
|
||||
for i, validation := range inputValidations {
|
||||
if validation.MessageExpression != "" {
|
||||
condition := MessageExpressionCondition{
|
||||
MessageExpression: validation.MessageExpression,
|
||||
}
|
||||
celExpressionAccessor[i] = &condition
|
||||
}
|
||||
}
|
||||
return celExpressionAccessor
|
||||
}
|
||||
|
||||
func convertv1AuditAnnotations(inputValidations []v1.AuditAnnotation) []cel.ExpressionAccessor {
|
||||
celExpressionAccessor := make([]cel.ExpressionAccessor, len(inputValidations))
|
||||
for i, validation := range inputValidations {
|
||||
validation := AuditAnnotationCondition{
|
||||
Key: validation.Key,
|
||||
ValueExpression: validation.ValueExpression,
|
||||
}
|
||||
celExpressionAccessor[i] = &validation
|
||||
}
|
||||
return celExpressionAccessor
|
||||
}
|
||||
|
||||
func convertv1beta1Variables(variables []v1.Variable) []cel.NamedExpressionAccessor {
|
||||
namedExpressions := make([]cel.NamedExpressionAccessor, len(variables))
|
||||
for i, variable := range variables {
|
||||
namedExpressions[i] = &Variable{Name: variable.Name, Expression: variable.Expression}
|
||||
}
|
||||
return namedExpressions
|
||||
}
|
87
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/validating/policy_decision.go
generated
vendored
Normal file
87
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/validating/policy_decision.go
generated
vendored
Normal file
@ -0,0 +1,87 @@
|
||||
/*
|
||||
Copyright 2022 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 (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type PolicyDecisionAction string
|
||||
|
||||
const (
|
||||
ActionAdmit PolicyDecisionAction = "admit"
|
||||
ActionDeny PolicyDecisionAction = "deny"
|
||||
)
|
||||
|
||||
type PolicyDecisionEvaluation string
|
||||
|
||||
const (
|
||||
EvalAdmit PolicyDecisionEvaluation = "admit"
|
||||
EvalError PolicyDecisionEvaluation = "error"
|
||||
EvalDeny PolicyDecisionEvaluation = "deny"
|
||||
)
|
||||
|
||||
// PolicyDecision contains the action determined from a cel evaluation along with metadata such as message, reason and duration
|
||||
type PolicyDecision struct {
|
||||
Action PolicyDecisionAction
|
||||
Evaluation PolicyDecisionEvaluation
|
||||
Message string
|
||||
Reason metav1.StatusReason
|
||||
Elapsed time.Duration
|
||||
}
|
||||
|
||||
type PolicyAuditAnnotationAction string
|
||||
|
||||
const (
|
||||
// AuditAnnotationActionPublish indicates that the audit annotation should be
|
||||
// published with the audit event.
|
||||
AuditAnnotationActionPublish PolicyAuditAnnotationAction = "publish"
|
||||
// AuditAnnotationActionError indicates that the valueExpression resulted
|
||||
// in an error.
|
||||
AuditAnnotationActionError PolicyAuditAnnotationAction = "error"
|
||||
// AuditAnnotationActionExclude indicates that the audit annotation should be excluded
|
||||
// because the valueExpression evaluated to null, or because FailurePolicy is Ignore
|
||||
// and the expression failed with a parse error, type check error, or runtime error.
|
||||
AuditAnnotationActionExclude PolicyAuditAnnotationAction = "exclude"
|
||||
)
|
||||
|
||||
type PolicyAuditAnnotation struct {
|
||||
Key string
|
||||
Value string
|
||||
Elapsed time.Duration
|
||||
Action PolicyAuditAnnotationAction
|
||||
Error string
|
||||
}
|
||||
|
||||
func reasonToCode(r metav1.StatusReason) int32 {
|
||||
switch r {
|
||||
case metav1.StatusReasonForbidden:
|
||||
return http.StatusForbidden
|
||||
case metav1.StatusReasonUnauthorized:
|
||||
return http.StatusUnauthorized
|
||||
case metav1.StatusReasonRequestEntityTooLarge:
|
||||
return http.StatusRequestEntityTooLarge
|
||||
case metav1.StatusReasonInvalid:
|
||||
return http.StatusUnprocessableEntity
|
||||
default:
|
||||
// It should not reach here since we only allow above reason to be set from API level
|
||||
return http.StatusUnprocessableEntity
|
||||
}
|
||||
}
|
489
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/validating/typechecking.go
generated
vendored
Normal file
489
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/validating/typechecking.go
generated
vendored
Normal file
@ -0,0 +1,489 @@
|
||||
/*
|
||||
Copyright 2023 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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
|
||||
"k8s.io/api/admissionregistration/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
"k8s.io/apimachinery/pkg/util/version"
|
||||
plugincel "k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||
apiservercel "k8s.io/apiserver/pkg/cel"
|
||||
"k8s.io/apiserver/pkg/cel/common"
|
||||
"k8s.io/apiserver/pkg/cel/environment"
|
||||
"k8s.io/apiserver/pkg/cel/library"
|
||||
"k8s.io/apiserver/pkg/cel/openapi"
|
||||
"k8s.io/apiserver/pkg/cel/openapi/resolver"
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
const maxTypesToCheck = 10
|
||||
|
||||
type TypeChecker struct {
|
||||
SchemaResolver resolver.SchemaResolver
|
||||
RestMapper meta.RESTMapper
|
||||
}
|
||||
|
||||
// TypeCheckingContext holds information about the policy being type-checked.
|
||||
// The struct is opaque to the caller.
|
||||
type TypeCheckingContext struct {
|
||||
gvks []schema.GroupVersionKind
|
||||
declTypes []*apiservercel.DeclType
|
||||
paramGVK schema.GroupVersionKind
|
||||
paramDeclType *apiservercel.DeclType
|
||||
|
||||
variables []v1.Variable
|
||||
}
|
||||
|
||||
type typeOverwrite struct {
|
||||
object *apiservercel.DeclType
|
||||
params *apiservercel.DeclType
|
||||
}
|
||||
|
||||
// TypeCheckingResult holds the issues found during type checking, any returned
|
||||
// error, and the gvk that the type checking is performed against.
|
||||
type TypeCheckingResult struct {
|
||||
// GVK is the associated GVK
|
||||
GVK schema.GroupVersionKind
|
||||
// Issues contain machine-readable information about the typechecking result.
|
||||
Issues error
|
||||
// Err is the possible error that was encounter during type checking.
|
||||
Err error
|
||||
}
|
||||
|
||||
// TypeCheckingResults is a collection of TypeCheckingResult
|
||||
type TypeCheckingResults []*TypeCheckingResult
|
||||
|
||||
func (rs TypeCheckingResults) String() string {
|
||||
var messages []string
|
||||
for _, r := range rs {
|
||||
message := r.String()
|
||||
if message != "" {
|
||||
messages = append(messages, message)
|
||||
}
|
||||
}
|
||||
return strings.Join(messages, "\n")
|
||||
}
|
||||
|
||||
// String converts the result to human-readable form as a string.
|
||||
func (r *TypeCheckingResult) String() string {
|
||||
if r.Issues == nil && r.Err == nil {
|
||||
return ""
|
||||
}
|
||||
if r.Err != nil {
|
||||
return fmt.Sprintf("%v: type checking error: %v\n", r.GVK, r.Err)
|
||||
}
|
||||
return fmt.Sprintf("%v: %s\n", r.GVK, r.Issues)
|
||||
}
|
||||
|
||||
// Check preforms the type check against the given policy, and format the result
|
||||
// as []ExpressionWarning that is ready to be set in policy.Status
|
||||
// The result is nil if type checking returns no warning.
|
||||
// The policy object is NOT mutated. The caller should update Status accordingly
|
||||
func (c *TypeChecker) Check(policy *v1.ValidatingAdmissionPolicy) []v1.ExpressionWarning {
|
||||
ctx := c.CreateContext(policy)
|
||||
|
||||
// warnings to return, note that the capacity is optimistically set to zero
|
||||
var warnings []v1.ExpressionWarning // intentionally not setting capacity
|
||||
|
||||
// check main validation expressions and their message expressions, located in spec.validations[*]
|
||||
fieldRef := field.NewPath("spec", "validations")
|
||||
for i, v := range policy.Spec.Validations {
|
||||
results := c.CheckExpression(ctx, v.Expression)
|
||||
if len(results) != 0 {
|
||||
warnings = append(warnings, v1.ExpressionWarning{
|
||||
FieldRef: fieldRef.Index(i).Child("expression").String(),
|
||||
Warning: results.String(),
|
||||
})
|
||||
}
|
||||
// Note that MessageExpression is optional
|
||||
if v.MessageExpression == "" {
|
||||
continue
|
||||
}
|
||||
results = c.CheckExpression(ctx, v.MessageExpression)
|
||||
if len(results) != 0 {
|
||||
warnings = append(warnings, v1.ExpressionWarning{
|
||||
FieldRef: fieldRef.Index(i).Child("messageExpression").String(),
|
||||
Warning: results.String(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return warnings
|
||||
}
|
||||
|
||||
// CreateContext resolves all types and their schemas from a policy definition and creates the context.
|
||||
func (c *TypeChecker) CreateContext(policy *v1.ValidatingAdmissionPolicy) *TypeCheckingContext {
|
||||
ctx := new(TypeCheckingContext)
|
||||
allGvks := c.typesToCheck(policy)
|
||||
gvks := make([]schema.GroupVersionKind, 0, len(allGvks))
|
||||
declTypes := make([]*apiservercel.DeclType, 0, len(allGvks))
|
||||
for _, gvk := range allGvks {
|
||||
declType, err := c.declType(gvk)
|
||||
if err != nil {
|
||||
// type checking errors MUST NOT alter the behavior of the policy
|
||||
// even if an error occurs.
|
||||
if !errors.Is(err, resolver.ErrSchemaNotFound) {
|
||||
// Anything except ErrSchemaNotFound is an internal error
|
||||
klog.V(2).ErrorS(err, "internal error: schema resolution failure", "gvk", gvk)
|
||||
}
|
||||
// skip for not found or internal error
|
||||
continue
|
||||
}
|
||||
gvks = append(gvks, gvk)
|
||||
declTypes = append(declTypes, declType)
|
||||
}
|
||||
ctx.gvks = gvks
|
||||
ctx.declTypes = declTypes
|
||||
|
||||
paramsGVK := c.paramsGVK(policy) // maybe empty, correctly handled
|
||||
paramsDeclType, err := c.declType(paramsGVK)
|
||||
if err != nil {
|
||||
if !errors.Is(err, resolver.ErrSchemaNotFound) {
|
||||
klog.V(2).ErrorS(err, "internal error: cannot resolve schema for params", "gvk", paramsGVK)
|
||||
}
|
||||
paramsDeclType = nil
|
||||
}
|
||||
ctx.paramGVK = paramsGVK
|
||||
ctx.paramDeclType = paramsDeclType
|
||||
ctx.variables = policy.Spec.Variables
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (c *TypeChecker) compiler(ctx *TypeCheckingContext, typeOverwrite typeOverwrite) (*plugincel.CompositedCompiler, error) {
|
||||
envSet, err := buildEnvSet(
|
||||
/* hasParams */ ctx.paramDeclType != nil,
|
||||
/* hasAuthorizer */ true,
|
||||
typeOverwrite)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
env, err := plugincel.NewCompositionEnv(plugincel.VariablesTypeName, envSet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
compiler := &plugincel.CompositedCompiler{
|
||||
Compiler: &typeCheckingCompiler{typeOverwrite: typeOverwrite, compositionEnv: env},
|
||||
CompositionEnv: env,
|
||||
}
|
||||
return compiler, nil
|
||||
}
|
||||
|
||||
// CheckExpression type checks a single expression, given the context
|
||||
func (c *TypeChecker) CheckExpression(ctx *TypeCheckingContext, expression string) TypeCheckingResults {
|
||||
var results TypeCheckingResults
|
||||
for i, gvk := range ctx.gvks {
|
||||
declType := ctx.declTypes[i]
|
||||
compiler, err := c.compiler(ctx, typeOverwrite{
|
||||
object: declType,
|
||||
params: ctx.paramDeclType,
|
||||
})
|
||||
if err != nil {
|
||||
utilruntime.HandleError(err)
|
||||
continue
|
||||
}
|
||||
options := plugincel.OptionalVariableDeclarations{
|
||||
HasParams: ctx.paramDeclType != nil,
|
||||
HasAuthorizer: true,
|
||||
StrictCost: utilfeature.DefaultFeatureGate.Enabled(features.StrictCostEnforcementForVAP),
|
||||
}
|
||||
compiler.CompileAndStoreVariables(convertv1beta1Variables(ctx.variables), options, environment.StoredExpressions)
|
||||
result := compiler.CompileCELExpression(celExpression(expression), options, environment.StoredExpressions)
|
||||
if err := result.Error; err != nil {
|
||||
typeCheckingResult := &TypeCheckingResult{GVK: gvk}
|
||||
if err.Type == apiservercel.ErrorTypeInvalid {
|
||||
typeCheckingResult.Issues = err
|
||||
} else {
|
||||
typeCheckingResult.Err = err
|
||||
}
|
||||
results = append(results, typeCheckingResult)
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
type celExpression string
|
||||
|
||||
func (c celExpression) GetExpression() string {
|
||||
return string(c)
|
||||
}
|
||||
|
||||
func (c celExpression) ReturnTypes() []*cel.Type {
|
||||
return []*cel.Type{cel.AnyType}
|
||||
}
|
||||
func generateUniqueTypeName(kind string) string {
|
||||
return fmt.Sprintf("%s%d", kind, time.Now().Nanosecond())
|
||||
}
|
||||
|
||||
func (c *TypeChecker) declType(gvk schema.GroupVersionKind) (*apiservercel.DeclType, error) {
|
||||
if gvk.Empty() {
|
||||
return nil, nil
|
||||
}
|
||||
s, err := c.SchemaResolver.ResolveSchema(gvk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return common.SchemaDeclType(&openapi.Schema{Schema: s}, true).MaybeAssignTypeName(generateUniqueTypeName(gvk.Kind)), nil
|
||||
}
|
||||
|
||||
func (c *TypeChecker) paramsGVK(policy *v1.ValidatingAdmissionPolicy) schema.GroupVersionKind {
|
||||
if policy.Spec.ParamKind == nil {
|
||||
return schema.GroupVersionKind{}
|
||||
}
|
||||
gv, err := schema.ParseGroupVersion(policy.Spec.ParamKind.APIVersion)
|
||||
if err != nil {
|
||||
return schema.GroupVersionKind{}
|
||||
}
|
||||
return gv.WithKind(policy.Spec.ParamKind.Kind)
|
||||
}
|
||||
|
||||
// typesToCheck extracts a list of GVKs that needs type checking from the policy
|
||||
// the result is sorted in the order of Group, Version, and Kind
|
||||
func (c *TypeChecker) typesToCheck(p *v1.ValidatingAdmissionPolicy) []schema.GroupVersionKind {
|
||||
gvks := sets.New[schema.GroupVersionKind]()
|
||||
if p.Spec.MatchConstraints == nil || len(p.Spec.MatchConstraints.ResourceRules) == 0 {
|
||||
return nil
|
||||
}
|
||||
restMapperRefreshAttempted := false // at most once per policy, refresh RESTMapper and retry resolution.
|
||||
for _, rule := range p.Spec.MatchConstraints.ResourceRules {
|
||||
groups := extractGroups(&rule.Rule)
|
||||
if len(groups) == 0 {
|
||||
continue
|
||||
}
|
||||
versions := extractVersions(&rule.Rule)
|
||||
if len(versions) == 0 {
|
||||
continue
|
||||
}
|
||||
resources := extractResources(&rule.Rule)
|
||||
if len(resources) == 0 {
|
||||
continue
|
||||
}
|
||||
// sort GVRs so that the loop below provides
|
||||
// consistent results.
|
||||
sort.Strings(groups)
|
||||
sort.Strings(versions)
|
||||
sort.Strings(resources)
|
||||
count := 0
|
||||
for _, group := range groups {
|
||||
for _, version := range versions {
|
||||
for _, resource := range resources {
|
||||
gvr := schema.GroupVersionResource{
|
||||
Group: group,
|
||||
Version: version,
|
||||
Resource: resource,
|
||||
}
|
||||
resolved, err := c.RestMapper.KindsFor(gvr)
|
||||
if err != nil {
|
||||
if restMapperRefreshAttempted {
|
||||
// RESTMapper refresh happens at most once per policy
|
||||
continue
|
||||
}
|
||||
c.tryRefreshRESTMapper()
|
||||
restMapperRefreshAttempted = true
|
||||
resolved, err = c.RestMapper.KindsFor(gvr)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
for _, r := range resolved {
|
||||
if !r.Empty() {
|
||||
gvks.Insert(r)
|
||||
count++
|
||||
// early return if maximum number of types are already
|
||||
// collected
|
||||
if count == maxTypesToCheck {
|
||||
if gvks.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
return sortGVKList(gvks.UnsortedList())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if gvks.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
return sortGVKList(gvks.UnsortedList())
|
||||
}
|
||||
|
||||
func extractGroups(rule *v1.Rule) []string {
|
||||
groups := make([]string, 0, len(rule.APIGroups))
|
||||
for _, group := range rule.APIGroups {
|
||||
// give up if wildcard
|
||||
if strings.ContainsAny(group, "*") {
|
||||
return nil
|
||||
}
|
||||
groups = append(groups, group)
|
||||
}
|
||||
return groups
|
||||
}
|
||||
|
||||
func extractVersions(rule *v1.Rule) []string {
|
||||
versions := make([]string, 0, len(rule.APIVersions))
|
||||
for _, version := range rule.APIVersions {
|
||||
if strings.ContainsAny(version, "*") {
|
||||
return nil
|
||||
}
|
||||
versions = append(versions, version)
|
||||
}
|
||||
return versions
|
||||
}
|
||||
|
||||
func extractResources(rule *v1.Rule) []string {
|
||||
resources := make([]string, 0, len(rule.Resources))
|
||||
for _, resource := range rule.Resources {
|
||||
// skip wildcard and subresources
|
||||
if strings.ContainsAny(resource, "*/") {
|
||||
continue
|
||||
}
|
||||
resources = append(resources, resource)
|
||||
}
|
||||
return resources
|
||||
}
|
||||
|
||||
// sortGVKList sorts the list by Group, Version, and Kind
|
||||
// returns the list itself.
|
||||
func sortGVKList(list []schema.GroupVersionKind) []schema.GroupVersionKind {
|
||||
sort.Slice(list, func(i, j int) bool {
|
||||
if g := strings.Compare(list[i].Group, list[j].Group); g != 0 {
|
||||
return g < 0
|
||||
}
|
||||
if v := strings.Compare(list[i].Version, list[j].Version); v != 0 {
|
||||
return v < 0
|
||||
}
|
||||
return strings.Compare(list[i].Kind, list[j].Kind) < 0
|
||||
})
|
||||
return list
|
||||
}
|
||||
|
||||
// tryRefreshRESTMapper refreshes the RESTMapper if it supports refreshing.
|
||||
func (c *TypeChecker) tryRefreshRESTMapper() {
|
||||
if r, ok := c.RestMapper.(meta.ResettableRESTMapper); ok {
|
||||
r.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
func buildEnvSet(hasParams bool, hasAuthorizer bool, types typeOverwrite) (*environment.EnvSet, error) {
|
||||
baseEnv := environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), utilfeature.DefaultFeatureGate.Enabled(features.StrictCostEnforcementForVAP))
|
||||
requestType := plugincel.BuildRequestType()
|
||||
namespaceType := plugincel.BuildNamespaceType()
|
||||
|
||||
var varOpts []cel.EnvOption
|
||||
var declTypes []*apiservercel.DeclType
|
||||
|
||||
// namespace, hand-crafted type
|
||||
declTypes = append(declTypes, namespaceType)
|
||||
varOpts = append(varOpts, createVariableOpts(namespaceType, plugincel.NamespaceVarName)...)
|
||||
|
||||
// request, hand-crafted type
|
||||
declTypes = append(declTypes, requestType)
|
||||
varOpts = append(varOpts, createVariableOpts(requestType, plugincel.RequestVarName)...)
|
||||
|
||||
// object and oldObject, same type, type(s) resolved from constraints
|
||||
declTypes = append(declTypes, types.object)
|
||||
varOpts = append(varOpts, createVariableOpts(types.object, plugincel.ObjectVarName, plugincel.OldObjectVarName)...)
|
||||
|
||||
// params, defined by ParamKind
|
||||
if hasParams && types.params != nil {
|
||||
declTypes = append(declTypes, types.params)
|
||||
varOpts = append(varOpts, createVariableOpts(types.params, plugincel.ParamsVarName)...)
|
||||
}
|
||||
|
||||
// authorizer, implicitly available to all expressions of a policy
|
||||
if hasAuthorizer {
|
||||
// we only need its structure but not the variable itself
|
||||
varOpts = append(varOpts, cel.Variable("authorizer", library.AuthorizerType))
|
||||
}
|
||||
|
||||
return baseEnv.Extend(
|
||||
environment.VersionedOptions{
|
||||
// Feature epoch was actually 1.26, but we artificially set it to 1.0 because these
|
||||
// options should always be present.
|
||||
IntroducedVersion: version.MajorMinor(1, 0),
|
||||
EnvOptions: varOpts,
|
||||
DeclTypes: declTypes,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// createVariableOpts creates a slice of EnvOption
|
||||
// that can be used for creating a CEL env containing variables of declType.
|
||||
// declType can be nil, in which case the variables will be of DynType.
|
||||
func createVariableOpts(declType *apiservercel.DeclType, variables ...string) []cel.EnvOption {
|
||||
opts := make([]cel.EnvOption, 0, len(variables))
|
||||
t := cel.DynType
|
||||
if declType != nil {
|
||||
t = declType.CelType()
|
||||
}
|
||||
for _, v := range variables {
|
||||
opts = append(opts, cel.Variable(v, t))
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
type typeCheckingCompiler struct {
|
||||
compositionEnv *plugincel.CompositionEnv
|
||||
typeOverwrite typeOverwrite
|
||||
}
|
||||
|
||||
// CompileCELExpression compiles the given expression.
|
||||
// The implementation is the same as that of staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/compile.go
|
||||
// except that:
|
||||
// - object, oldObject, and params are typed instead of Dyn
|
||||
// - compiler does not enforce the output type
|
||||
// - the compiler does not initialize the program
|
||||
func (c *typeCheckingCompiler) CompileCELExpression(expressionAccessor plugincel.ExpressionAccessor, options plugincel.OptionalVariableDeclarations, mode environment.Type) plugincel.CompilationResult {
|
||||
resultError := func(errorString string, errType apiservercel.ErrorType) plugincel.CompilationResult {
|
||||
return plugincel.CompilationResult{
|
||||
Error: &apiservercel.Error{
|
||||
Type: errType,
|
||||
Detail: errorString,
|
||||
},
|
||||
ExpressionAccessor: expressionAccessor,
|
||||
}
|
||||
}
|
||||
env, err := c.compositionEnv.Env(mode)
|
||||
if err != nil {
|
||||
return resultError(fmt.Sprintf("fail to build env: %v", err), apiservercel.ErrorTypeInternal)
|
||||
}
|
||||
ast, issues := env.Compile(expressionAccessor.GetExpression())
|
||||
if issues != nil {
|
||||
return resultError(issues.String(), apiservercel.ErrorTypeInvalid)
|
||||
}
|
||||
// type checker does not require the program, however the type must still be set.
|
||||
return plugincel.CompilationResult{
|
||||
OutputType: ast.OutputType(),
|
||||
}
|
||||
}
|
||||
|
||||
var _ plugincel.Compiler = (*typeCheckingCompiler)(nil)
|
249
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/validating/validator.go
generated
vendored
Normal file
249
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/policy/validating/validator.go
generated
vendored
Normal file
@ -0,0 +1,249 @@
|
||||
/*
|
||||
Copyright 2022 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"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
celtypes "github.com/google/cel-go/common/types"
|
||||
|
||||
v1 "k8s.io/api/admissionregistration/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions"
|
||||
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
apiservercel "k8s.io/apiserver/pkg/cel"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
// validator implements the Validator interface
|
||||
type validator struct {
|
||||
celMatcher matchconditions.Matcher
|
||||
validationFilter cel.ConditionEvaluator
|
||||
auditAnnotationFilter cel.ConditionEvaluator
|
||||
messageFilter cel.ConditionEvaluator
|
||||
failPolicy *v1.FailurePolicyType
|
||||
}
|
||||
|
||||
func NewValidator(validationFilter cel.ConditionEvaluator, celMatcher matchconditions.Matcher, auditAnnotationFilter, messageFilter cel.ConditionEvaluator, failPolicy *v1.FailurePolicyType) Validator {
|
||||
return &validator{
|
||||
celMatcher: celMatcher,
|
||||
validationFilter: validationFilter,
|
||||
auditAnnotationFilter: auditAnnotationFilter,
|
||||
messageFilter: messageFilter,
|
||||
failPolicy: failPolicy,
|
||||
}
|
||||
}
|
||||
|
||||
func policyDecisionActionForError(f v1.FailurePolicyType) PolicyDecisionAction {
|
||||
if f == v1.Ignore {
|
||||
return ActionAdmit
|
||||
}
|
||||
return ActionDeny
|
||||
}
|
||||
|
||||
func auditAnnotationEvaluationForError(f v1.FailurePolicyType) PolicyAuditAnnotationAction {
|
||||
if f == v1.Ignore {
|
||||
return AuditAnnotationActionExclude
|
||||
}
|
||||
return AuditAnnotationActionError
|
||||
}
|
||||
|
||||
// Validate takes a list of Evaluation and a failure policy and converts them into actionable PolicyDecisions
|
||||
// runtimeCELCostBudget was added for testing purpose only. Callers should always use const RuntimeCELCostBudget from k8s.io/apiserver/pkg/apis/cel/config.go as input.
|
||||
|
||||
func (v *validator) Validate(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *corev1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||
var f v1.FailurePolicyType
|
||||
if v.failPolicy == nil {
|
||||
f = v1.Fail
|
||||
} else {
|
||||
f = *v.failPolicy
|
||||
}
|
||||
if v.celMatcher != nil {
|
||||
matchResults := v.celMatcher.Match(ctx, versionedAttr, versionedParams, authz)
|
||||
if matchResults.Error != nil {
|
||||
return ValidateResult{
|
||||
Decisions: []PolicyDecision{
|
||||
{
|
||||
Action: policyDecisionActionForError(f),
|
||||
Evaluation: EvalError,
|
||||
Message: matchResults.Error.Error(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// if preconditions are not met, then do not return any validations
|
||||
if !matchResults.Matches {
|
||||
return ValidateResult{}
|
||||
}
|
||||
}
|
||||
|
||||
optionalVars := cel.OptionalVariableBindings{VersionedParams: versionedParams, Authorizer: authz}
|
||||
expressionOptionalVars := cel.OptionalVariableBindings{VersionedParams: versionedParams}
|
||||
admissionRequest := cel.CreateAdmissionRequest(versionedAttr.Attributes, metav1.GroupVersionResource(matchedResource), metav1.GroupVersionKind(versionedAttr.VersionedKind))
|
||||
// Decide which fields are exposed
|
||||
ns := cel.CreateNamespaceObject(namespace)
|
||||
evalResults, remainingBudget, err := v.validationFilter.ForInput(ctx, versionedAttr, admissionRequest, optionalVars, ns, runtimeCELCostBudget)
|
||||
if err != nil {
|
||||
return ValidateResult{
|
||||
Decisions: []PolicyDecision{
|
||||
{
|
||||
Action: policyDecisionActionForError(f),
|
||||
Evaluation: EvalError,
|
||||
Message: err.Error(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
decisions := make([]PolicyDecision, len(evalResults))
|
||||
messageResults, _, err := v.messageFilter.ForInput(ctx, versionedAttr, admissionRequest, expressionOptionalVars, ns, remainingBudget)
|
||||
for i, evalResult := range evalResults {
|
||||
var decision = &decisions[i]
|
||||
decision.Elapsed = evalResult.Elapsed
|
||||
// TODO: move this to generics
|
||||
validation, ok := evalResult.ExpressionAccessor.(*ValidationCondition)
|
||||
if !ok {
|
||||
klog.Error("Invalid type conversion to ValidationCondition")
|
||||
decision.Action = policyDecisionActionForError(f)
|
||||
decision.Evaluation = EvalError
|
||||
decision.Message = "Invalid type sent to validator, expected ValidationCondition"
|
||||
continue
|
||||
}
|
||||
|
||||
var messageResult *cel.EvaluationResult
|
||||
if len(messageResults) > i {
|
||||
messageResult = &messageResults[i]
|
||||
}
|
||||
if evalResult.Error != nil {
|
||||
decision.Action = policyDecisionActionForError(f)
|
||||
decision.Evaluation = EvalError
|
||||
decision.Message = evalResult.Error.Error()
|
||||
} else if errors.Is(err, apiservercel.ErrInternal) || errors.Is(err, apiservercel.ErrOutOfBudget) {
|
||||
decision.Action = policyDecisionActionForError(f)
|
||||
decision.Evaluation = EvalError
|
||||
decision.Message = fmt.Sprintf("failed messageExpression: %s", err)
|
||||
} else if evalResult.EvalResult != celtypes.True {
|
||||
decision.Action = ActionDeny
|
||||
decision.Evaluation = EvalDeny
|
||||
if validation.Reason == nil {
|
||||
decision.Reason = metav1.StatusReasonInvalid
|
||||
} else {
|
||||
decision.Reason = *validation.Reason
|
||||
}
|
||||
// decide the failure message
|
||||
var message string
|
||||
// attempt to set message with messageExpression result
|
||||
if messageResult != nil && messageResult.Error == nil && messageResult.EvalResult != nil {
|
||||
// also fallback if the eval result is non-string (including null) or
|
||||
// whitespaces.
|
||||
if message, ok = messageResult.EvalResult.Value().(string); ok {
|
||||
message = strings.TrimSpace(message)
|
||||
// deny excessively long message from EvalResult
|
||||
if len(message) > celconfig.MaxEvaluatedMessageExpressionSizeBytes {
|
||||
klog.V(2).InfoS("excessively long message denied", "message", message)
|
||||
message = ""
|
||||
}
|
||||
// deny message that contains newlines
|
||||
if strings.ContainsAny(message, "\n") {
|
||||
klog.V(2).InfoS("multi-line message denied", "message", message)
|
||||
message = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
if messageResult != nil && messageResult.Error != nil {
|
||||
// log any error with messageExpression
|
||||
klog.V(2).ErrorS(messageResult.Error, "error while evaluating messageExpression")
|
||||
}
|
||||
// fallback to set message to the custom message
|
||||
if message == "" && len(validation.Message) > 0 {
|
||||
message = strings.TrimSpace(validation.Message)
|
||||
}
|
||||
// fallback to use the expression to compose a message
|
||||
if message == "" {
|
||||
message = fmt.Sprintf("failed expression: %v", strings.TrimSpace(validation.Expression))
|
||||
}
|
||||
decision.Message = message
|
||||
} else {
|
||||
decision.Action = ActionAdmit
|
||||
decision.Evaluation = EvalAdmit
|
||||
}
|
||||
}
|
||||
|
||||
options := cel.OptionalVariableBindings{VersionedParams: versionedParams}
|
||||
auditAnnotationEvalResults, _, err := v.auditAnnotationFilter.ForInput(ctx, versionedAttr, admissionRequest, options, namespace, runtimeCELCostBudget)
|
||||
if err != nil {
|
||||
return ValidateResult{
|
||||
Decisions: []PolicyDecision{
|
||||
{
|
||||
Action: policyDecisionActionForError(f),
|
||||
Evaluation: EvalError,
|
||||
Message: err.Error(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
auditAnnotationResults := make([]PolicyAuditAnnotation, len(auditAnnotationEvalResults))
|
||||
for i, evalResult := range auditAnnotationEvalResults {
|
||||
if evalResult.ExpressionAccessor == nil {
|
||||
continue
|
||||
}
|
||||
var auditAnnotationResult = &auditAnnotationResults[i]
|
||||
auditAnnotationResult.Elapsed = evalResult.Elapsed
|
||||
// TODO: move this to generics
|
||||
validation, ok := evalResult.ExpressionAccessor.(*AuditAnnotationCondition)
|
||||
if !ok {
|
||||
klog.Error("Invalid type conversion to AuditAnnotationCondition")
|
||||
auditAnnotationResult.Action = auditAnnotationEvaluationForError(f)
|
||||
auditAnnotationResult.Error = fmt.Sprintf("Invalid type sent to validator, expected AuditAnnotationCondition but got %T", evalResult.ExpressionAccessor)
|
||||
continue
|
||||
}
|
||||
auditAnnotationResult.Key = validation.Key
|
||||
|
||||
if evalResult.Error != nil {
|
||||
auditAnnotationResult.Action = auditAnnotationEvaluationForError(f)
|
||||
auditAnnotationResult.Error = evalResult.Error.Error()
|
||||
} else {
|
||||
switch evalResult.EvalResult.Type() {
|
||||
case celtypes.StringType:
|
||||
value := strings.TrimSpace(evalResult.EvalResult.Value().(string))
|
||||
if len(value) == 0 {
|
||||
auditAnnotationResult.Action = AuditAnnotationActionExclude
|
||||
} else {
|
||||
auditAnnotationResult.Action = AuditAnnotationActionPublish
|
||||
auditAnnotationResult.Value = value
|
||||
}
|
||||
case celtypes.NullType:
|
||||
auditAnnotationResult.Action = AuditAnnotationActionExclude
|
||||
default:
|
||||
auditAnnotationResult.Action = AuditAnnotationActionError
|
||||
auditAnnotationResult.Error = fmt.Sprintf("valueExpression '%v' resulted in unsupported return type: %v. "+
|
||||
"Return type must be either string or null.", validation.ValueExpression, evalResult.EvalResult.Type())
|
||||
}
|
||||
}
|
||||
}
|
||||
return ValidateResult{Decisions: decisions, AuditAnnotations: auditAnnotationResults}
|
||||
}
|
388
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/accessors.go
generated
vendored
Normal file
388
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/accessors.go
generated
vendored
Normal file
@ -0,0 +1,388 @@
|
||||
/*
|
||||
Copyright 2019 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
v1 "k8s.io/api/admissionregistration/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/namespace"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/object"
|
||||
"k8s.io/apiserver/pkg/cel/environment"
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
webhookutil "k8s.io/apiserver/pkg/util/webhook"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
// WebhookAccessor provides a common interface to both mutating and validating webhook types.
|
||||
type WebhookAccessor interface {
|
||||
// This accessor provides the methods needed to support matching against webhook
|
||||
// predicates
|
||||
namespace.NamespaceSelectorProvider
|
||||
object.ObjectSelectorProvider
|
||||
|
||||
// GetUID gets a string that uniquely identifies the webhook.
|
||||
GetUID() string
|
||||
|
||||
// GetConfigurationName gets the name of the webhook configuration that owns this webhook.
|
||||
GetConfigurationName() string
|
||||
|
||||
// GetRESTClient gets the webhook client
|
||||
GetRESTClient(clientManager *webhookutil.ClientManager) (*rest.RESTClient, error)
|
||||
|
||||
// GetCompiledMatcher gets the compiled matcher object
|
||||
GetCompiledMatcher(compiler cel.ConditionCompiler) matchconditions.Matcher
|
||||
|
||||
// GetName gets the webhook Name field. Note that the name is scoped to the webhook
|
||||
// configuration and does not provide a globally unique identity, if a unique identity is
|
||||
// needed, use GetUID.
|
||||
GetName() string
|
||||
// GetClientConfig gets the webhook ClientConfig field.
|
||||
GetClientConfig() v1.WebhookClientConfig
|
||||
// GetRules gets the webhook Rules field.
|
||||
GetRules() []v1.RuleWithOperations
|
||||
// GetFailurePolicy gets the webhook FailurePolicy field.
|
||||
GetFailurePolicy() *v1.FailurePolicyType
|
||||
// GetMatchPolicy gets the webhook MatchPolicy field.
|
||||
GetMatchPolicy() *v1.MatchPolicyType
|
||||
// GetNamespaceSelector gets the webhook NamespaceSelector field.
|
||||
GetNamespaceSelector() *metav1.LabelSelector
|
||||
// GetObjectSelector gets the webhook ObjectSelector field.
|
||||
GetObjectSelector() *metav1.LabelSelector
|
||||
// GetSideEffects gets the webhook SideEffects field.
|
||||
GetSideEffects() *v1.SideEffectClass
|
||||
// GetTimeoutSeconds gets the webhook TimeoutSeconds field.
|
||||
GetTimeoutSeconds() *int32
|
||||
// GetAdmissionReviewVersions gets the webhook AdmissionReviewVersions field.
|
||||
GetAdmissionReviewVersions() []string
|
||||
|
||||
// GetMatchConditions gets the webhook match conditions field.
|
||||
GetMatchConditions() []v1.MatchCondition
|
||||
|
||||
// GetMutatingWebhook if the accessor contains a MutatingWebhook, returns it and true, else returns false.
|
||||
GetMutatingWebhook() (*v1.MutatingWebhook, bool)
|
||||
// GetValidatingWebhook if the accessor contains a ValidatingWebhook, returns it and true, else returns false.
|
||||
GetValidatingWebhook() (*v1.ValidatingWebhook, bool)
|
||||
|
||||
// GetType returns the type of the accessor (validate or admit)
|
||||
GetType() string
|
||||
}
|
||||
|
||||
// NewMutatingWebhookAccessor creates an accessor for a MutatingWebhook.
|
||||
func NewMutatingWebhookAccessor(uid, configurationName string, h *v1.MutatingWebhook) WebhookAccessor {
|
||||
return &mutatingWebhookAccessor{uid: uid, configurationName: configurationName, MutatingWebhook: h}
|
||||
}
|
||||
|
||||
type mutatingWebhookAccessor struct {
|
||||
*v1.MutatingWebhook
|
||||
uid string
|
||||
configurationName string
|
||||
|
||||
initObjectSelector sync.Once
|
||||
objectSelector labels.Selector
|
||||
objectSelectorErr error
|
||||
|
||||
initNamespaceSelector sync.Once
|
||||
namespaceSelector labels.Selector
|
||||
namespaceSelectorErr error
|
||||
|
||||
initClient sync.Once
|
||||
client *rest.RESTClient
|
||||
clientErr error
|
||||
|
||||
compileMatcher sync.Once
|
||||
compiledMatcher matchconditions.Matcher
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetUID() string {
|
||||
return m.uid
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetConfigurationName() string {
|
||||
return m.configurationName
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetRESTClient(clientManager *webhookutil.ClientManager) (*rest.RESTClient, error) {
|
||||
m.initClient.Do(func() {
|
||||
m.client, m.clientErr = clientManager.HookClient(hookClientConfigForWebhook(m))
|
||||
})
|
||||
return m.client, m.clientErr
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetType() string {
|
||||
return "admit"
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetCompiledMatcher(compiler cel.ConditionCompiler) matchconditions.Matcher {
|
||||
m.compileMatcher.Do(func() {
|
||||
expressions := make([]cel.ExpressionAccessor, len(m.MutatingWebhook.MatchConditions))
|
||||
for i, matchCondition := range m.MutatingWebhook.MatchConditions {
|
||||
expressions[i] = &matchconditions.MatchCondition{
|
||||
Name: matchCondition.Name,
|
||||
Expression: matchCondition.Expression,
|
||||
}
|
||||
}
|
||||
strictCost := false
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.StrictCostEnforcementForWebhooks) {
|
||||
strictCost = true
|
||||
}
|
||||
m.compiledMatcher = matchconditions.NewMatcher(compiler.CompileCondition(
|
||||
expressions,
|
||||
cel.OptionalVariableDeclarations{
|
||||
HasParams: false,
|
||||
HasAuthorizer: true,
|
||||
StrictCost: strictCost,
|
||||
},
|
||||
environment.StoredExpressions,
|
||||
), m.FailurePolicy, "webhook", "admit", m.Name)
|
||||
})
|
||||
return m.compiledMatcher
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetParsedNamespaceSelector() (labels.Selector, error) {
|
||||
m.initNamespaceSelector.Do(func() {
|
||||
m.namespaceSelector, m.namespaceSelectorErr = metav1.LabelSelectorAsSelector(m.NamespaceSelector)
|
||||
})
|
||||
return m.namespaceSelector, m.namespaceSelectorErr
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetParsedObjectSelector() (labels.Selector, error) {
|
||||
m.initObjectSelector.Do(func() {
|
||||
m.objectSelector, m.objectSelectorErr = metav1.LabelSelectorAsSelector(m.ObjectSelector)
|
||||
})
|
||||
return m.objectSelector, m.objectSelectorErr
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetName() string {
|
||||
return m.Name
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetClientConfig() v1.WebhookClientConfig {
|
||||
return m.ClientConfig
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetRules() []v1.RuleWithOperations {
|
||||
return m.Rules
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetFailurePolicy() *v1.FailurePolicyType {
|
||||
return m.FailurePolicy
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetMatchPolicy() *v1.MatchPolicyType {
|
||||
return m.MatchPolicy
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetNamespaceSelector() *metav1.LabelSelector {
|
||||
return m.NamespaceSelector
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetObjectSelector() *metav1.LabelSelector {
|
||||
return m.ObjectSelector
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetSideEffects() *v1.SideEffectClass {
|
||||
return m.SideEffects
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetTimeoutSeconds() *int32 {
|
||||
return m.TimeoutSeconds
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetAdmissionReviewVersions() []string {
|
||||
return m.AdmissionReviewVersions
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetMatchConditions() []v1.MatchCondition {
|
||||
return m.MatchConditions
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetMutatingWebhook() (*v1.MutatingWebhook, bool) {
|
||||
return m.MutatingWebhook, true
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetValidatingWebhook() (*v1.ValidatingWebhook, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// NewValidatingWebhookAccessor creates an accessor for a ValidatingWebhook.
|
||||
func NewValidatingWebhookAccessor(uid, configurationName string, h *v1.ValidatingWebhook) WebhookAccessor {
|
||||
return &validatingWebhookAccessor{uid: uid, configurationName: configurationName, ValidatingWebhook: h}
|
||||
}
|
||||
|
||||
type validatingWebhookAccessor struct {
|
||||
*v1.ValidatingWebhook
|
||||
uid string
|
||||
configurationName string
|
||||
|
||||
initObjectSelector sync.Once
|
||||
objectSelector labels.Selector
|
||||
objectSelectorErr error
|
||||
|
||||
initNamespaceSelector sync.Once
|
||||
namespaceSelector labels.Selector
|
||||
namespaceSelectorErr error
|
||||
|
||||
initClient sync.Once
|
||||
client *rest.RESTClient
|
||||
clientErr error
|
||||
|
||||
compileMatcher sync.Once
|
||||
compiledMatcher matchconditions.Matcher
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetUID() string {
|
||||
return v.uid
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetConfigurationName() string {
|
||||
return v.configurationName
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetRESTClient(clientManager *webhookutil.ClientManager) (*rest.RESTClient, error) {
|
||||
v.initClient.Do(func() {
|
||||
v.client, v.clientErr = clientManager.HookClient(hookClientConfigForWebhook(v))
|
||||
})
|
||||
return v.client, v.clientErr
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetCompiledMatcher(compiler cel.ConditionCompiler) matchconditions.Matcher {
|
||||
v.compileMatcher.Do(func() {
|
||||
expressions := make([]cel.ExpressionAccessor, len(v.ValidatingWebhook.MatchConditions))
|
||||
for i, matchCondition := range v.ValidatingWebhook.MatchConditions {
|
||||
expressions[i] = &matchconditions.MatchCondition{
|
||||
Name: matchCondition.Name,
|
||||
Expression: matchCondition.Expression,
|
||||
}
|
||||
}
|
||||
strictCost := false
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.StrictCostEnforcementForWebhooks) {
|
||||
strictCost = true
|
||||
}
|
||||
v.compiledMatcher = matchconditions.NewMatcher(compiler.CompileCondition(
|
||||
expressions,
|
||||
cel.OptionalVariableDeclarations{
|
||||
HasParams: false,
|
||||
HasAuthorizer: true,
|
||||
StrictCost: strictCost,
|
||||
},
|
||||
environment.StoredExpressions,
|
||||
), v.FailurePolicy, "webhook", "validating", v.Name)
|
||||
})
|
||||
return v.compiledMatcher
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetParsedNamespaceSelector() (labels.Selector, error) {
|
||||
v.initNamespaceSelector.Do(func() {
|
||||
v.namespaceSelector, v.namespaceSelectorErr = metav1.LabelSelectorAsSelector(v.NamespaceSelector)
|
||||
})
|
||||
return v.namespaceSelector, v.namespaceSelectorErr
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetParsedObjectSelector() (labels.Selector, error) {
|
||||
v.initObjectSelector.Do(func() {
|
||||
v.objectSelector, v.objectSelectorErr = metav1.LabelSelectorAsSelector(v.ObjectSelector)
|
||||
})
|
||||
return v.objectSelector, v.objectSelectorErr
|
||||
}
|
||||
|
||||
func (m *validatingWebhookAccessor) GetType() string {
|
||||
return "validate"
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetName() string {
|
||||
return v.Name
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetClientConfig() v1.WebhookClientConfig {
|
||||
return v.ClientConfig
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetRules() []v1.RuleWithOperations {
|
||||
return v.Rules
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetFailurePolicy() *v1.FailurePolicyType {
|
||||
return v.FailurePolicy
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetMatchPolicy() *v1.MatchPolicyType {
|
||||
return v.MatchPolicy
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetNamespaceSelector() *metav1.LabelSelector {
|
||||
return v.NamespaceSelector
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetObjectSelector() *metav1.LabelSelector {
|
||||
return v.ObjectSelector
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetSideEffects() *v1.SideEffectClass {
|
||||
return v.SideEffects
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetTimeoutSeconds() *int32 {
|
||||
return v.TimeoutSeconds
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetAdmissionReviewVersions() []string {
|
||||
return v.AdmissionReviewVersions
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetMatchConditions() []v1.MatchCondition {
|
||||
return v.MatchConditions
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetMutatingWebhook() (*v1.MutatingWebhook, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetValidatingWebhook() (*v1.ValidatingWebhook, bool) {
|
||||
return v.ValidatingWebhook, true
|
||||
}
|
||||
|
||||
// hookClientConfigForWebhook construct a webhookutil.ClientConfig using a WebhookAccessor to access
|
||||
// v1beta1.MutatingWebhook and v1beta1.ValidatingWebhook API objects. webhookutil.ClientConfig is used
|
||||
// to create a HookClient and the purpose of the config struct is to share that with other packages
|
||||
// that need to create a HookClient.
|
||||
func hookClientConfigForWebhook(w WebhookAccessor) webhookutil.ClientConfig {
|
||||
ret := webhookutil.ClientConfig{Name: w.GetName(), CABundle: w.GetClientConfig().CABundle}
|
||||
if w.GetClientConfig().URL != nil {
|
||||
ret.URL = *w.GetClientConfig().URL
|
||||
}
|
||||
if w.GetClientConfig().Service != nil {
|
||||
ret.Service = &webhookutil.ClientConfigService{
|
||||
Name: w.GetClientConfig().Service.Name,
|
||||
Namespace: w.GetClientConfig().Service.Namespace,
|
||||
}
|
||||
if w.GetClientConfig().Service.Port != nil {
|
||||
ret.Service.Port = *w.GetClientConfig().Service.Port
|
||||
} else {
|
||||
ret.Service.Port = 443
|
||||
}
|
||||
if w.GetClientConfig().Service.Path != nil {
|
||||
ret.Service.Path = *w.GetClientConfig().Service.Path
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
19
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/doc.go
generated
vendored
Normal file
19
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/doc.go
generated
vendored
Normal 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 // import "k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission"
|
53
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/register.go
generated
vendored
Normal file
53
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/register.go
generated
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package webhookadmission
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
var (
|
||||
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
|
||||
AddToScheme = SchemeBuilder.AddToScheme
|
||||
)
|
||||
|
||||
// GroupName is the group name use in this package
|
||||
const GroupName = "apiserver.config.k8s.io"
|
||||
|
||||
// SchemeGroupVersion is group version used to register these objects
|
||||
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal}
|
||||
|
||||
// Kind takes an unqualified kind and returns a Group qualified GroupKind
|
||||
func Kind(kind string) schema.GroupKind {
|
||||
return SchemeGroupVersion.WithKind(kind).GroupKind()
|
||||
}
|
||||
|
||||
// Resource takes an unqualified resource and returns a Group qualified GroupResource
|
||||
func Resource(resource string) schema.GroupResource {
|
||||
return SchemeGroupVersion.WithResource(resource).GroupResource()
|
||||
}
|
||||
|
||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&WebhookAdmission{},
|
||||
)
|
||||
scheme.AddKnownTypeWithName(SchemeGroupVersion.WithKind("WebhookAdmissionConfiguration"),
|
||||
&WebhookAdmission{},
|
||||
)
|
||||
return nil
|
||||
}
|
29
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/types.go
generated
vendored
Normal file
29
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/types.go
generated
vendored
Normal 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
|
||||
}
|
23
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1/doc.go
generated
vendored
Normal file
23
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1/doc.go
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
/*
|
||||
Copyright 2019 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// +k8s:deepcopy-gen=package
|
||||
// +k8s:conversion-gen=k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission
|
||||
// +k8s:defaulter-gen=TypeMeta
|
||||
// +groupName=apiserver.config.k8s.io
|
||||
|
||||
// Package v1 is the v1 version of the API.
|
||||
package v1 // import "k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1"
|
50
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1/register.go
generated
vendored
Normal file
50
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1/register.go
generated
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
Copyright 2019 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// GroupName is the group name use in this package
|
||||
const GroupName = "apiserver.config.k8s.io"
|
||||
|
||||
// SchemeGroupVersion is group version used to register these objects
|
||||
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1"}
|
||||
|
||||
var (
|
||||
// TODO: move SchemeBuilder with zz_generated.deepcopy.go to k8s.io/api.
|
||||
// localSchemeBuilder and AddToScheme will stay in k8s.io/kubernetes.
|
||||
SchemeBuilder runtime.SchemeBuilder
|
||||
localSchemeBuilder = &SchemeBuilder
|
||||
AddToScheme = localSchemeBuilder.AddToScheme
|
||||
)
|
||||
|
||||
func init() {
|
||||
// We only register manually written functions here. The registration of the
|
||||
// generated functions takes place in the generated files. The separation
|
||||
// makes the code compile even when the generated files are missing.
|
||||
localSchemeBuilder.Register(addKnownTypes)
|
||||
}
|
||||
|
||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypeWithName(SchemeGroupVersion.WithKind("WebhookAdmissionConfiguration"),
|
||||
&WebhookAdmission{},
|
||||
)
|
||||
return nil
|
||||
}
|
29
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1/types.go
generated
vendored
Normal file
29
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1/types.go
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
/*
|
||||
Copyright 2019 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package v1
|
||||
|
||||
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// WebhookAdmission provides configuration for the webhook admission controller.
|
||||
type WebhookAdmission struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
|
||||
// KubeConfigFile is the path to the kubeconfig file.
|
||||
KubeConfigFile string `json:"kubeConfigFile"`
|
||||
}
|
68
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1/zz_generated.conversion.go
generated
vendored
Normal file
68
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1/zz_generated.conversion.go
generated
vendored
Normal file
@ -0,0 +1,68 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by conversion-gen. DO NOT EDIT.
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
conversion "k8s.io/apimachinery/pkg/conversion"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
webhookadmission "k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission"
|
||||
)
|
||||
|
||||
func init() {
|
||||
localSchemeBuilder.Register(RegisterConversions)
|
||||
}
|
||||
|
||||
// RegisterConversions adds conversion functions to the given scheme.
|
||||
// Public to allow building arbitrary schemes.
|
||||
func RegisterConversions(s *runtime.Scheme) error {
|
||||
if err := s.AddGeneratedConversionFunc((*WebhookAdmission)(nil), (*webhookadmission.WebhookAdmission)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1_WebhookAdmission_To_webhookadmission_WebhookAdmission(a.(*WebhookAdmission), b.(*webhookadmission.WebhookAdmission), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*webhookadmission.WebhookAdmission)(nil), (*WebhookAdmission)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_webhookadmission_WebhookAdmission_To_v1_WebhookAdmission(a.(*webhookadmission.WebhookAdmission), b.(*WebhookAdmission), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func autoConvert_v1_WebhookAdmission_To_webhookadmission_WebhookAdmission(in *WebhookAdmission, out *webhookadmission.WebhookAdmission, s conversion.Scope) error {
|
||||
out.KubeConfigFile = in.KubeConfigFile
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1_WebhookAdmission_To_webhookadmission_WebhookAdmission is an autogenerated conversion function.
|
||||
func Convert_v1_WebhookAdmission_To_webhookadmission_WebhookAdmission(in *WebhookAdmission, out *webhookadmission.WebhookAdmission, s conversion.Scope) error {
|
||||
return autoConvert_v1_WebhookAdmission_To_webhookadmission_WebhookAdmission(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_webhookadmission_WebhookAdmission_To_v1_WebhookAdmission(in *webhookadmission.WebhookAdmission, out *WebhookAdmission, s conversion.Scope) error {
|
||||
out.KubeConfigFile = in.KubeConfigFile
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_webhookadmission_WebhookAdmission_To_v1_WebhookAdmission is an autogenerated conversion function.
|
||||
func Convert_webhookadmission_WebhookAdmission_To_v1_WebhookAdmission(in *webhookadmission.WebhookAdmission, out *WebhookAdmission, s conversion.Scope) error {
|
||||
return autoConvert_webhookadmission_WebhookAdmission_To_v1_WebhookAdmission(in, out, s)
|
||||
}
|
51
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1/zz_generated.deepcopy.go
generated
vendored
Normal file
51
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1/zz_generated.deepcopy.go
generated
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by deepcopy-gen. DO NOT EDIT.
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *WebhookAdmission) DeepCopyInto(out *WebhookAdmission) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebhookAdmission.
|
||||
func (in *WebhookAdmission) DeepCopy() *WebhookAdmission {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(WebhookAdmission)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *WebhookAdmission) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
33
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1/zz_generated.defaults.go
generated
vendored
Normal file
33
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1/zz_generated.defaults.go
generated
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by defaulter-gen. DO NOT EDIT.
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// RegisterDefaults adds defaulters functions to the given scheme.
|
||||
// Public to allow building arbitrary schemes.
|
||||
// All generated defaulters are covering - they call all nested defaulters.
|
||||
func RegisterDefaults(scheme *runtime.Scheme) error {
|
||||
return nil
|
||||
}
|
23
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1/doc.go
generated
vendored
Normal file
23
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1/doc.go
generated
vendored
Normal 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 // import "k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1"
|
50
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1/register.go
generated
vendored
Normal file
50
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1/register.go
generated
vendored
Normal 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
|
||||
}
|
29
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1/types.go
generated
vendored
Normal file
29
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1/types.go
generated
vendored
Normal 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"`
|
||||
}
|
68
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1/zz_generated.conversion.go
generated
vendored
Normal file
68
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1/zz_generated.conversion.go
generated
vendored
Normal file
@ -0,0 +1,68 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +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)
|
||||
}
|
51
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1/zz_generated.deepcopy.go
generated
vendored
Normal file
51
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1/zz_generated.deepcopy.go
generated
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +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
|
||||
}
|
33
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1/zz_generated.defaults.go
generated
vendored
Normal file
33
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1/zz_generated.defaults.go
generated
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +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
|
||||
}
|
51
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/zz_generated.deepcopy.go
generated
vendored
Normal file
51
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/zz_generated.deepcopy.go
generated
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +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
|
||||
}
|
70
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/kubeconfig.go
generated
vendored
Normal file
70
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/kubeconfig.go
generated
vendored
Normal 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 config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1"
|
||||
)
|
||||
|
||||
var (
|
||||
scheme = runtime.NewScheme()
|
||||
codecs = serializer.NewCodecFactory(scheme)
|
||||
)
|
||||
|
||||
func init() {
|
||||
utilruntime.Must(webhookadmission.AddToScheme(scheme))
|
||||
utilruntime.Must(v1.AddToScheme(scheme))
|
||||
utilruntime.Must(v1alpha1.AddToScheme(scheme))
|
||||
}
|
||||
|
||||
// LoadConfig extract the KubeConfigFile from configFile
|
||||
func LoadConfig(configFile io.Reader) (string, error) {
|
||||
var kubeconfigFile string
|
||||
if configFile != nil {
|
||||
// we have a config so parse it.
|
||||
data, err := io.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
|
||||
}
|
18
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/errors/doc.go
generated
vendored
Normal file
18
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/errors/doc.go
generated
vendored
Normal 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"
|
63
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/errors/statuserror.go
generated
vendored
Normal file
63
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/errors/statuserror.go
generated
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// ToStatusErr returns a StatusError with information about the webhook plugin
|
||||
func ToStatusErr(webhookName string, result *metav1.Status) *apierrors.StatusError {
|
||||
deniedBy := fmt.Sprintf("admission webhook %q denied the request", webhookName)
|
||||
const noExp = "without explanation"
|
||||
|
||||
if result == nil {
|
||||
result = &metav1.Status{Status: metav1.StatusFailure}
|
||||
}
|
||||
|
||||
// Make sure we don't return < 400 status codes along with a rejection
|
||||
if result.Code < http.StatusBadRequest {
|
||||
result.Code = http.StatusBadRequest
|
||||
}
|
||||
// Make sure we don't return "" or "Success" status along with a rejection
|
||||
if result.Status == "" || result.Status == metav1.StatusSuccess {
|
||||
result.Status = metav1.StatusFailure
|
||||
}
|
||||
|
||||
switch {
|
||||
case len(result.Message) > 0:
|
||||
result.Message = fmt.Sprintf("%s: %s", deniedBy, result.Message)
|
||||
case len(result.Reason) > 0:
|
||||
result.Message = fmt.Sprintf("%s: %s", deniedBy, result.Reason)
|
||||
default:
|
||||
result.Message = fmt.Sprintf("%s %s", deniedBy, noExp)
|
||||
}
|
||||
|
||||
return &apierrors.StatusError{
|
||||
ErrStatus: *result,
|
||||
}
|
||||
}
|
||||
|
||||
// NewDryRunUnsupportedErr returns a StatusError with information about the webhook plugin
|
||||
func NewDryRunUnsupportedErr(webhookName string) *apierrors.StatusError {
|
||||
reason := fmt.Sprintf("admission webhook %q does not support dry run", webhookName)
|
||||
return apierrors.NewBadRequest(reason)
|
||||
}
|
53
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/interfaces.go
generated
vendored
Normal file
53
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/interfaces.go
generated
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package generic
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook"
|
||||
)
|
||||
|
||||
type VersionedAttributeAccessor interface {
|
||||
VersionedAttribute(gvk schema.GroupVersionKind) (*admission.VersionedAttributes, error)
|
||||
}
|
||||
|
||||
// Source can list dynamic webhook plugins.
|
||||
type Source interface {
|
||||
Webhooks() []webhook.WebhookAccessor
|
||||
HasSynced() bool
|
||||
}
|
||||
|
||||
// WebhookInvocation describes how to call a webhook, including the resource and subresource the webhook registered for,
|
||||
// and the kind that should be sent to the webhook.
|
||||
type WebhookInvocation struct {
|
||||
Webhook webhook.WebhookAccessor
|
||||
Resource schema.GroupVersionResource
|
||||
Subresource string
|
||||
Kind schema.GroupVersionKind
|
||||
}
|
||||
|
||||
// Dispatcher dispatches webhook call to a list of webhooks with admission attributes as argument.
|
||||
type Dispatcher interface {
|
||||
// Dispatch a request to the webhooks. Dispatcher may choose not to
|
||||
// call a hook, either because the rules of the hook does not match, or
|
||||
// the namespaceSelector or the objectSelector of the hook does not
|
||||
// match. A non-nil error means the request is rejected.
|
||||
Dispatch(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces, hooks []webhook.WebhookAccessor) error
|
||||
}
|
264
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/webhook.go
generated
vendored
Normal file
264
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/webhook.go
generated
vendored
Normal file
@ -0,0 +1,264 @@
|
||||
/*
|
||||
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"
|
||||
|
||||
"k8s.io/apiserver/pkg/cel/environment"
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
admissionv1beta1 "k8s.io/api/admission/v1beta1"
|
||||
v1 "k8s.io/api/admissionregistration/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
genericadmissioninit "k8s.io/apiserver/pkg/admission/initializer"
|
||||
admissionmetrics "k8s.io/apiserver/pkg/admission/metrics"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/config"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/namespace"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/object"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/rules"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
webhookutil "k8s.io/apiserver/pkg/util/webhook"
|
||||
"k8s.io/client-go/informers"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
// Webhook is an abstract admission plugin with all the infrastructure to define Admit or Validate on-top.
|
||||
type Webhook struct {
|
||||
*admission.Handler
|
||||
|
||||
sourceFactory sourceFactory
|
||||
|
||||
hookSource Source
|
||||
clientManager *webhookutil.ClientManager
|
||||
namespaceMatcher *namespace.Matcher
|
||||
objectMatcher *object.Matcher
|
||||
dispatcher Dispatcher
|
||||
filterCompiler cel.ConditionCompiler
|
||||
authorizer authorizer.Authorizer
|
||||
}
|
||||
|
||||
var (
|
||||
_ genericadmissioninit.WantsExternalKubeClientSet = &Webhook{}
|
||||
_ admission.Interface = &Webhook{}
|
||||
)
|
||||
|
||||
type sourceFactory func(f informers.SharedInformerFactory) Source
|
||||
type dispatcherFactory func(cm *webhookutil.ClientManager) Dispatcher
|
||||
|
||||
// NewWebhook creates a new generic admission webhook.
|
||||
func NewWebhook(handler *admission.Handler, configFile io.Reader, sourceFactory sourceFactory, dispatcherFactory dispatcherFactory) (*Webhook, error) {
|
||||
kubeconfigFile, err := config.LoadConfig(configFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cm, err := webhookutil.NewClientManager(
|
||||
[]schema.GroupVersion{
|
||||
admissionv1beta1.SchemeGroupVersion,
|
||||
admissionv1.SchemeGroupVersion,
|
||||
},
|
||||
admissionv1beta1.AddToScheme,
|
||||
admissionv1.AddToScheme,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authInfoResolver, err := webhookutil.NewDefaultAuthenticationInfoResolver(kubeconfigFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Set defaults which may be overridden later.
|
||||
cm.SetAuthenticationInfoResolver(authInfoResolver)
|
||||
cm.SetServiceResolver(webhookutil.NewDefaultServiceResolver())
|
||||
|
||||
return &Webhook{
|
||||
Handler: handler,
|
||||
sourceFactory: sourceFactory,
|
||||
clientManager: &cm,
|
||||
namespaceMatcher: &namespace.Matcher{},
|
||||
objectMatcher: &object.Matcher{},
|
||||
dispatcher: dispatcherFactory(&cm),
|
||||
filterCompiler: cel.NewConditionCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), utilfeature.DefaultFeatureGate.Enabled(features.StrictCostEnforcementForWebhooks))),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SetAuthenticationInfoResolverWrapper sets the
|
||||
// AuthenticationInfoResolverWrapper.
|
||||
// TODO find a better way wire this, but keep this pull small for now.
|
||||
func (a *Webhook) SetAuthenticationInfoResolverWrapper(wrapper webhookutil.AuthenticationInfoResolverWrapper) {
|
||||
a.clientManager.SetAuthenticationInfoResolverWrapper(wrapper)
|
||||
}
|
||||
|
||||
// SetServiceResolver sets a service resolver for the webhook admission plugin.
|
||||
// Passing a nil resolver does not have an effect, instead a default one will be used.
|
||||
func (a *Webhook) SetServiceResolver(sr webhookutil.ServiceResolver) {
|
||||
a.clientManager.SetServiceResolver(sr)
|
||||
}
|
||||
|
||||
// SetExternalKubeClientSet implements the WantsExternalKubeInformerFactory interface.
|
||||
// It sets external ClientSet for admission plugins that need it
|
||||
func (a *Webhook) SetExternalKubeClientSet(client clientset.Interface) {
|
||||
a.namespaceMatcher.Client = client
|
||||
}
|
||||
|
||||
// SetExternalKubeInformerFactory implements the WantsExternalKubeInformerFactory interface.
|
||||
func (a *Webhook) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) {
|
||||
namespaceInformer := f.Core().V1().Namespaces()
|
||||
a.namespaceMatcher.NamespaceLister = namespaceInformer.Lister()
|
||||
a.hookSource = a.sourceFactory(f)
|
||||
a.SetReadyFunc(func() bool {
|
||||
return namespaceInformer.Informer().HasSynced() && a.hookSource.HasSynced()
|
||||
})
|
||||
}
|
||||
|
||||
func (a *Webhook) SetAuthorizer(authorizer authorizer.Authorizer) {
|
||||
a.authorizer = authorizer
|
||||
}
|
||||
|
||||
// ValidateInitialization implements the InitializationValidator interface.
|
||||
func (a *Webhook) ValidateInitialization() error {
|
||||
if a.hookSource == nil {
|
||||
return fmt.Errorf("kubernetes client is not properly setup")
|
||||
}
|
||||
if err := a.namespaceMatcher.Validate(); err != nil {
|
||||
return fmt.Errorf("namespaceMatcher is not properly setup: %v", err)
|
||||
}
|
||||
if err := a.clientManager.Validate(); err != nil {
|
||||
return fmt.Errorf("clientManager is not properly setup: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ShouldCallHook returns invocation details if the webhook should be called, nil if the webhook should not be called,
|
||||
// or an error if an error was encountered during evaluation.
|
||||
func (a *Webhook) ShouldCallHook(ctx context.Context, h webhook.WebhookAccessor, attr admission.Attributes, o admission.ObjectInterfaces, v VersionedAttributeAccessor) (*WebhookInvocation, *apierrors.StatusError) {
|
||||
matches, matchNsErr := a.namespaceMatcher.MatchNamespaceSelector(h, attr)
|
||||
// Should not return an error here for webhooks which do not apply to the request, even if err is an unexpected scenario.
|
||||
if !matches && matchNsErr == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Should not return an error here for webhooks which do not apply to the request, even if err is an unexpected scenario.
|
||||
matches, matchObjErr := a.objectMatcher.MatchObjectSelector(h, attr)
|
||||
if !matches && matchObjErr == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var invocation *WebhookInvocation
|
||||
for _, r := range h.GetRules() {
|
||||
m := rules.Matcher{Rule: r, Attr: attr}
|
||||
if m.Matches() {
|
||||
invocation = &WebhookInvocation{
|
||||
Webhook: h,
|
||||
Resource: attr.GetResource(),
|
||||
Subresource: attr.GetSubresource(),
|
||||
Kind: attr.GetKind(),
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if invocation == nil && h.GetMatchPolicy() != nil && *h.GetMatchPolicy() == v1.Equivalent {
|
||||
attrWithOverride := &attrWithResourceOverride{Attributes: attr}
|
||||
equivalents := o.GetEquivalentResourceMapper().EquivalentResourcesFor(attr.GetResource(), attr.GetSubresource())
|
||||
// honor earlier rules first
|
||||
OuterLoop:
|
||||
for _, r := range h.GetRules() {
|
||||
// see if the rule matches any of the equivalent resources
|
||||
for _, equivalent := range equivalents {
|
||||
if equivalent == attr.GetResource() {
|
||||
// exclude attr.GetResource(), which we already checked
|
||||
continue
|
||||
}
|
||||
attrWithOverride.resource = equivalent
|
||||
m := rules.Matcher{Rule: r, Attr: attrWithOverride}
|
||||
if m.Matches() {
|
||||
kind := o.GetEquivalentResourceMapper().KindFor(equivalent, attr.GetSubresource())
|
||||
if kind.Empty() {
|
||||
return nil, apierrors.NewInternalError(fmt.Errorf("unable to convert to %v: unknown kind", equivalent))
|
||||
}
|
||||
invocation = &WebhookInvocation{
|
||||
Webhook: h,
|
||||
Resource: equivalent,
|
||||
Subresource: attr.GetSubresource(),
|
||||
Kind: kind,
|
||||
}
|
||||
break OuterLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if invocation == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if matchNsErr != nil {
|
||||
return nil, matchNsErr
|
||||
}
|
||||
if matchObjErr != nil {
|
||||
return nil, matchObjErr
|
||||
}
|
||||
matchConditions := h.GetMatchConditions()
|
||||
if len(matchConditions) > 0 {
|
||||
versionedAttr, err := v.VersionedAttribute(invocation.Kind)
|
||||
if err != nil {
|
||||
return nil, apierrors.NewInternalError(err)
|
||||
}
|
||||
|
||||
matcher := h.GetCompiledMatcher(a.filterCompiler)
|
||||
matchResult := matcher.Match(ctx, versionedAttr, nil, a.authorizer)
|
||||
|
||||
if matchResult.Error != nil {
|
||||
klog.Warningf("Failed evaluating match conditions, failing closed %v: %v", h.GetName(), matchResult.Error)
|
||||
return nil, apierrors.NewForbidden(attr.GetResource().GroupResource(), attr.GetName(), matchResult.Error)
|
||||
} else if !matchResult.Matches {
|
||||
admissionmetrics.Metrics.ObserveMatchConditionExclusion(ctx, h.GetName(), "webhook", h.GetType(), string(attr.GetOperation()))
|
||||
// if no match, always skip webhook
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
return invocation, nil
|
||||
}
|
||||
|
||||
type attrWithResourceOverride struct {
|
||||
admission.Attributes
|
||||
resource schema.GroupVersionResource
|
||||
}
|
||||
|
||||
func (a *attrWithResourceOverride) GetResource() schema.GroupVersionResource { return a.resource }
|
||||
|
||||
// Dispatch is called by the downstream Validate or Admit methods.
|
||||
func (a *Webhook) Dispatch(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces) error {
|
||||
if rules.IsExemptAdmissionConfigurationResource(attr) {
|
||||
return nil
|
||||
}
|
||||
if !a.WaitForReady() {
|
||||
return admission.NewForbidden(attr, fmt.Errorf("not yet ready to handle request"))
|
||||
}
|
||||
hooks := a.hookSource.Webhooks()
|
||||
return a.dispatcher.Dispatch(ctx, attr, o, hooks)
|
||||
}
|
37
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions/interface.go
generated
vendored
Normal file
37
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions/interface.go
generated
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
/*
|
||||
Copyright 2023 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 matchconditions
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
)
|
||||
|
||||
type MatchResult struct {
|
||||
Matches bool
|
||||
Error error
|
||||
FailedConditionName string
|
||||
}
|
||||
|
||||
// Matcher contains logic for converting Evaluations to bool of matches or does not match
|
||||
type Matcher interface {
|
||||
// Match is used to take cel evaluations and convert into decisions
|
||||
Match(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, authz authorizer.Authorizer) MatchResult
|
||||
}
|
144
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions/matcher.go
generated
vendored
Normal file
144
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions/matcher.go
generated
vendored
Normal file
@ -0,0 +1,144 @@
|
||||
/*
|
||||
Copyright 2023 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 matchconditions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
celtypes "github.com/google/cel-go/common/types"
|
||||
|
||||
v1 "k8s.io/api/admissionregistration/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
admissionmetrics "k8s.io/apiserver/pkg/admission/metrics"
|
||||
celplugin "k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
var _ celplugin.ExpressionAccessor = &MatchCondition{}
|
||||
|
||||
// MatchCondition contains the inputs needed to compile, evaluate and match a cel expression
|
||||
type MatchCondition v1.MatchCondition
|
||||
|
||||
func (v *MatchCondition) GetExpression() string {
|
||||
return v.Expression
|
||||
}
|
||||
|
||||
func (v *MatchCondition) ReturnTypes() []*cel.Type {
|
||||
return []*cel.Type{cel.BoolType}
|
||||
}
|
||||
|
||||
var _ Matcher = &matcher{}
|
||||
|
||||
// matcher evaluates compiled cel expressions and determines if they match the given request or not
|
||||
type matcher struct {
|
||||
filter celplugin.ConditionEvaluator
|
||||
failPolicy v1.FailurePolicyType
|
||||
matcherType string
|
||||
matcherKind string
|
||||
objectName string
|
||||
}
|
||||
|
||||
func NewMatcher(filter celplugin.ConditionEvaluator, failPolicy *v1.FailurePolicyType, matcherKind, matcherType, objectName string) Matcher {
|
||||
var f v1.FailurePolicyType
|
||||
if failPolicy == nil {
|
||||
f = v1.Fail
|
||||
} else {
|
||||
f = *failPolicy
|
||||
}
|
||||
return &matcher{
|
||||
filter: filter,
|
||||
failPolicy: f,
|
||||
matcherKind: matcherKind,
|
||||
matcherType: matcherType,
|
||||
objectName: objectName,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *matcher) Match(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, authz authorizer.Authorizer) MatchResult {
|
||||
t := time.Now()
|
||||
evalResults, _, err := m.filter.ForInput(ctx, versionedAttr, celplugin.CreateAdmissionRequest(versionedAttr.Attributes, metav1.GroupVersionResource(versionedAttr.GetResource()), metav1.GroupVersionKind(versionedAttr.VersionedKind)), celplugin.OptionalVariableBindings{
|
||||
VersionedParams: versionedParams,
|
||||
Authorizer: authz,
|
||||
}, nil, celconfig.RuntimeCELCostBudgetMatchConditions)
|
||||
|
||||
if err != nil {
|
||||
admissionmetrics.Metrics.ObserveMatchConditionEvaluationTime(ctx, time.Since(t), m.objectName, m.matcherKind, m.matcherType, string(versionedAttr.GetOperation()))
|
||||
// filter returning error is unexpected and not an evaluation error so not incrementing metric here
|
||||
if m.failPolicy == v1.Fail {
|
||||
return MatchResult{
|
||||
Error: err,
|
||||
}
|
||||
} else if m.failPolicy == v1.Ignore {
|
||||
return MatchResult{
|
||||
Matches: false,
|
||||
}
|
||||
}
|
||||
//TODO: add default so that if in future we add different failure types it doesn't fall through
|
||||
}
|
||||
|
||||
errorList := []error{}
|
||||
for _, evalResult := range evalResults {
|
||||
matchCondition, ok := evalResult.ExpressionAccessor.(*MatchCondition)
|
||||
if !ok {
|
||||
// This shouldnt happen, but if it does treat same as eval error
|
||||
klog.Error("Invalid type conversion to MatchCondition")
|
||||
errorList = append(errorList, errors.New(fmt.Sprintf("internal error converting ExpressionAccessor to MatchCondition")))
|
||||
continue
|
||||
}
|
||||
if evalResult.Error != nil {
|
||||
errorList = append(errorList, evalResult.Error)
|
||||
admissionmetrics.Metrics.ObserveMatchConditionEvalError(ctx, m.objectName, m.matcherKind, m.matcherType, string(versionedAttr.GetOperation()))
|
||||
}
|
||||
if evalResult.EvalResult == celtypes.False {
|
||||
admissionmetrics.Metrics.ObserveMatchConditionEvaluationTime(ctx, time.Since(t), m.objectName, m.matcherKind, m.matcherType, string(versionedAttr.GetOperation()))
|
||||
// If any condition false, skip calling webhook always
|
||||
return MatchResult{
|
||||
Matches: false,
|
||||
FailedConditionName: matchCondition.Name,
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(errorList) > 0 {
|
||||
admissionmetrics.Metrics.ObserveMatchConditionEvaluationTime(ctx, time.Since(t), m.objectName, m.matcherKind, m.matcherType, string(versionedAttr.GetOperation()))
|
||||
// If mix of true and eval errors then resort to fail policy
|
||||
if m.failPolicy == v1.Fail {
|
||||
// mix of true and errors with fail policy fail should fail request without calling webhook
|
||||
err = utilerrors.NewAggregate(errorList)
|
||||
return MatchResult{
|
||||
Error: err,
|
||||
}
|
||||
} else if m.failPolicy == v1.Ignore {
|
||||
// if fail policy ignore then skip call to webhook
|
||||
return MatchResult{
|
||||
Matches: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
// if no results eval to false, return matches true with list of any errors encountered
|
||||
return MatchResult{
|
||||
Matches: true,
|
||||
}
|
||||
}
|
496
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/dispatcher.go
generated
vendored
Normal file
496
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/dispatcher.go
generated
vendored
Normal file
@ -0,0 +1,496 @@
|
||||
/*
|
||||
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"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
jsonpatch "gopkg.in/evanphx/json-patch.v4"
|
||||
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer/json"
|
||||
utiljson "k8s.io/apimachinery/pkg/util/json"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
admissionmetrics "k8s.io/apiserver/pkg/admission/metrics"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook"
|
||||
webhookerrors "k8s.io/apiserver/pkg/admission/plugin/webhook/errors"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
|
||||
webhookrequest "k8s.io/apiserver/pkg/admission/plugin/webhook/request"
|
||||
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||
endpointsrequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
webhookutil "k8s.io/apiserver/pkg/util/webhook"
|
||||
"k8s.io/apiserver/pkg/warning"
|
||||
"k8s.io/component-base/tracing"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
// PatchAuditAnnotationPrefix is a prefix for persisting webhook patch in audit annotation.
|
||||
// Audit handler decides whether annotation with this prefix should be logged based on audit level.
|
||||
// Since mutating webhook patches the request body, audit level must be greater or equal to Request
|
||||
// for the annotation to be logged
|
||||
PatchAuditAnnotationPrefix = "patch.webhook.admission.k8s.io/"
|
||||
// MutationAuditAnnotationPrefix is a prefix for presisting webhook mutation existence in audit annotation.
|
||||
MutationAuditAnnotationPrefix = "mutation.webhook.admission.k8s.io/"
|
||||
// MutationAnnotationFailedOpenKeyPrefix in an annotation indicates
|
||||
// the mutating webhook failed open when the webhook backend connection
|
||||
// failed or returned an internal server error.
|
||||
MutationAuditAnnotationFailedOpenKeyPrefix string = "failed-open." + MutationAuditAnnotationPrefix
|
||||
)
|
||||
|
||||
type mutatingDispatcher struct {
|
||||
cm *webhookutil.ClientManager
|
||||
plugin *Plugin
|
||||
}
|
||||
|
||||
func newMutatingDispatcher(p *Plugin) func(cm *webhookutil.ClientManager) generic.Dispatcher {
|
||||
return func(cm *webhookutil.ClientManager) generic.Dispatcher {
|
||||
return &mutatingDispatcher{cm, p}
|
||||
}
|
||||
}
|
||||
|
||||
var _ generic.VersionedAttributeAccessor = &versionedAttributeAccessor{}
|
||||
|
||||
type versionedAttributeAccessor struct {
|
||||
versionedAttr *admission.VersionedAttributes
|
||||
attr admission.Attributes
|
||||
objectInterfaces admission.ObjectInterfaces
|
||||
}
|
||||
|
||||
func (v *versionedAttributeAccessor) VersionedAttribute(gvk schema.GroupVersionKind) (*admission.VersionedAttributes, error) {
|
||||
if v.versionedAttr == nil {
|
||||
// First call, create versioned attributes
|
||||
var err error
|
||||
if v.versionedAttr, err = admission.NewVersionedAttributes(v.attr, gvk, v.objectInterfaces); err != nil {
|
||||
return nil, apierrors.NewInternalError(err)
|
||||
}
|
||||
} else {
|
||||
// Subsequent call, convert existing versioned attributes to the requested version
|
||||
if err := admission.ConvertVersionedAttributes(v.versionedAttr, gvk, v.objectInterfaces); err != nil {
|
||||
return nil, apierrors.NewInternalError(err)
|
||||
}
|
||||
}
|
||||
return v.versionedAttr, nil
|
||||
}
|
||||
|
||||
var _ generic.Dispatcher = &mutatingDispatcher{}
|
||||
|
||||
func (a *mutatingDispatcher) Dispatch(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces, hooks []webhook.WebhookAccessor) error {
|
||||
reinvokeCtx := attr.GetReinvocationContext()
|
||||
var webhookReinvokeCtx *webhookReinvokeContext
|
||||
if v := reinvokeCtx.Value(PluginName); v != nil {
|
||||
webhookReinvokeCtx = v.(*webhookReinvokeContext)
|
||||
} else {
|
||||
webhookReinvokeCtx = &webhookReinvokeContext{}
|
||||
reinvokeCtx.SetValue(PluginName, webhookReinvokeCtx)
|
||||
}
|
||||
|
||||
if reinvokeCtx.IsReinvoke() && webhookReinvokeCtx.IsOutputChangedSinceLastWebhookInvocation(attr.GetObject()) {
|
||||
// If the object has changed, we know the in-tree plugin re-invocations have mutated the object,
|
||||
// and we need to reinvoke all eligible webhooks.
|
||||
webhookReinvokeCtx.RequireReinvokingPreviouslyInvokedPlugins()
|
||||
}
|
||||
defer func() {
|
||||
webhookReinvokeCtx.SetLastWebhookInvocationOutput(attr.GetObject())
|
||||
}()
|
||||
v := &versionedAttributeAccessor{
|
||||
attr: attr,
|
||||
objectInterfaces: o,
|
||||
}
|
||||
for i, hook := range hooks {
|
||||
attrForCheck := attr
|
||||
if v.versionedAttr != nil {
|
||||
attrForCheck = v.versionedAttr
|
||||
}
|
||||
|
||||
invocation, statusErr := a.plugin.ShouldCallHook(ctx, hook, attrForCheck, o, v)
|
||||
if statusErr != nil {
|
||||
return statusErr
|
||||
}
|
||||
if invocation == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
hook, ok := invocation.Webhook.GetMutatingWebhook()
|
||||
if !ok {
|
||||
return fmt.Errorf("mutating webhook dispatch requires v1.MutatingWebhook, but got %T", hook)
|
||||
}
|
||||
// This means that during reinvocation, a webhook will not be
|
||||
// called for the first time. For example, if the webhook is
|
||||
// skipped in the first round because of mismatching labels,
|
||||
// even if the labels become matching, the webhook does not
|
||||
// get called during reinvocation.
|
||||
if reinvokeCtx.IsReinvoke() && !webhookReinvokeCtx.ShouldReinvokeWebhook(invocation.Webhook.GetUID()) {
|
||||
continue
|
||||
}
|
||||
|
||||
versionedAttr, err := v.VersionedAttribute(invocation.Kind)
|
||||
if err != nil {
|
||||
return apierrors.NewInternalError(err)
|
||||
}
|
||||
|
||||
t := time.Now()
|
||||
round := 0
|
||||
if reinvokeCtx.IsReinvoke() {
|
||||
round = 1
|
||||
}
|
||||
|
||||
annotator := newWebhookAnnotator(versionedAttr, round, i, hook.Name, invocation.Webhook.GetConfigurationName())
|
||||
changed, err := a.callAttrMutatingHook(ctx, hook, invocation, versionedAttr, annotator, o, round, i)
|
||||
ignoreClientCallFailures := hook.FailurePolicy != nil && *hook.FailurePolicy == admissionregistrationv1.Ignore
|
||||
rejected := false
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case *webhookutil.ErrCallingWebhook:
|
||||
if !ignoreClientCallFailures {
|
||||
rejected = true
|
||||
// Ignore context cancelled from webhook metrics
|
||||
if !errors.Is(err.Reason, context.Canceled) {
|
||||
admissionmetrics.Metrics.ObserveWebhookRejection(ctx, hook.Name, "admit", string(versionedAttr.Attributes.GetOperation()), admissionmetrics.WebhookRejectionCallingWebhookError, int(err.Status.ErrStatus.Code))
|
||||
}
|
||||
}
|
||||
admissionmetrics.Metrics.ObserveWebhook(ctx, hook.Name, time.Since(t), rejected, versionedAttr.Attributes, "admit", int(err.Status.ErrStatus.Code))
|
||||
case *webhookutil.ErrWebhookRejection:
|
||||
rejected = true
|
||||
admissionmetrics.Metrics.ObserveWebhookRejection(ctx, hook.Name, "admit", string(versionedAttr.Attributes.GetOperation()), admissionmetrics.WebhookRejectionNoError, int(err.Status.ErrStatus.Code))
|
||||
admissionmetrics.Metrics.ObserveWebhook(ctx, hook.Name, time.Since(t), rejected, versionedAttr.Attributes, "admit", int(err.Status.ErrStatus.Code))
|
||||
default:
|
||||
rejected = true
|
||||
admissionmetrics.Metrics.ObserveWebhookRejection(ctx, hook.Name, "admit", string(versionedAttr.Attributes.GetOperation()), admissionmetrics.WebhookRejectionAPIServerInternalError, 0)
|
||||
admissionmetrics.Metrics.ObserveWebhook(ctx, hook.Name, time.Since(t), rejected, versionedAttr.Attributes, "admit", 0)
|
||||
}
|
||||
} else {
|
||||
admissionmetrics.Metrics.ObserveWebhook(ctx, hook.Name, time.Since(t), rejected, versionedAttr.Attributes, "admit", 200)
|
||||
}
|
||||
if changed {
|
||||
// Patch had changed the object. Prepare to reinvoke all previous mutations that are eligible for re-invocation.
|
||||
webhookReinvokeCtx.RequireReinvokingPreviouslyInvokedPlugins()
|
||||
reinvokeCtx.SetShouldReinvoke()
|
||||
}
|
||||
if hook.ReinvocationPolicy != nil && *hook.ReinvocationPolicy == admissionregistrationv1.IfNeededReinvocationPolicy {
|
||||
webhookReinvokeCtx.AddReinvocableWebhookToPreviouslyInvoked(invocation.Webhook.GetUID())
|
||||
}
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if callErr, ok := err.(*webhookutil.ErrCallingWebhook); ok {
|
||||
if ignoreClientCallFailures {
|
||||
// Ignore context cancelled from webhook metrics
|
||||
if errors.Is(callErr.Reason, context.Canceled) {
|
||||
klog.Warningf("Context canceled when calling webhook %v", hook.Name)
|
||||
} else {
|
||||
klog.Warningf("Failed calling webhook, failing open %v: %v", hook.Name, callErr)
|
||||
admissionmetrics.Metrics.ObserveWebhookFailOpen(ctx, hook.Name, "admit")
|
||||
annotator.addFailedOpenAnnotation()
|
||||
}
|
||||
utilruntime.HandleError(callErr)
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// parent context is canceled or timed out, no point in continuing
|
||||
return apierrors.NewTimeoutError("request did not complete within requested timeout", 0)
|
||||
default:
|
||||
// individual webhook timed out, but parent context did not, continue
|
||||
continue
|
||||
}
|
||||
}
|
||||
klog.Warningf("Failed calling webhook, failing closed %v: %v", hook.Name, err)
|
||||
return apierrors.NewInternalError(err)
|
||||
}
|
||||
if rejectionErr, ok := err.(*webhookutil.ErrWebhookRejection); ok {
|
||||
return rejectionErr.Status
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// convert versionedAttr.VersionedObject to the internal version in the underlying admission.Attributes
|
||||
if v.versionedAttr != nil && v.versionedAttr.VersionedObject != nil && v.versionedAttr.Dirty {
|
||||
return o.GetObjectConvertor().Convert(v.versionedAttr.VersionedObject, v.versionedAttr.Attributes.GetObject(), nil)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// note that callAttrMutatingHook updates attr
|
||||
|
||||
func (a *mutatingDispatcher) callAttrMutatingHook(ctx context.Context, h *admissionregistrationv1.MutatingWebhook, invocation *generic.WebhookInvocation, attr *admission.VersionedAttributes, annotator *webhookAnnotator, o admission.ObjectInterfaces, round, idx int) (bool, error) {
|
||||
configurationName := invocation.Webhook.GetConfigurationName()
|
||||
changed := false
|
||||
defer func() { annotator.addMutationAnnotation(changed) }()
|
||||
if attr.Attributes.IsDryRun() {
|
||||
if h.SideEffects == nil {
|
||||
return false, &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook SideEffects is nil"), Status: apierrors.NewBadRequest("Webhook SideEffects is nil")}
|
||||
}
|
||||
if !(*h.SideEffects == admissionregistrationv1.SideEffectClassNone || *h.SideEffects == admissionregistrationv1.SideEffectClassNoneOnDryRun) {
|
||||
return false, webhookerrors.NewDryRunUnsupportedErr(h.Name)
|
||||
}
|
||||
}
|
||||
|
||||
uid, request, response, err := webhookrequest.CreateAdmissionObjects(attr, invocation)
|
||||
if err != nil {
|
||||
return false, &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("could not create admission objects: %w", err), Status: apierrors.NewBadRequest("error creating admission objects")}
|
||||
}
|
||||
// Make the webhook request
|
||||
client, err := invocation.Webhook.GetRESTClient(a.cm)
|
||||
if err != nil {
|
||||
return false, &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("could not get REST client: %w", err), Status: apierrors.NewBadRequest("error getting REST client")}
|
||||
}
|
||||
ctx, span := tracing.Start(ctx, "Call mutating webhook",
|
||||
attribute.String("configuration", configurationName),
|
||||
attribute.String("webhook", h.Name),
|
||||
attribute.Stringer("resource", attr.GetResource()),
|
||||
attribute.String("subresource", attr.GetSubresource()),
|
||||
attribute.String("operation", string(attr.GetOperation())),
|
||||
attribute.String("UID", string(uid)))
|
||||
defer span.End(500 * time.Millisecond)
|
||||
|
||||
// if the webhook has a specific timeout, wrap the context to apply it
|
||||
if h.TimeoutSeconds != nil {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ctx, time.Duration(*h.TimeoutSeconds)*time.Second)
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
r := client.Post().Body(request)
|
||||
|
||||
// if the context has a deadline, set it as a parameter to inform the backend
|
||||
if deadline, hasDeadline := ctx.Deadline(); hasDeadline {
|
||||
// compute the timeout
|
||||
if timeout := time.Until(deadline); timeout > 0 {
|
||||
// if it's not an even number of seconds, round up to the nearest second
|
||||
if truncated := timeout.Truncate(time.Second); truncated != timeout {
|
||||
timeout = truncated + time.Second
|
||||
}
|
||||
// set the timeout
|
||||
r.Timeout(timeout)
|
||||
}
|
||||
}
|
||||
|
||||
do := func() { err = r.Do(ctx).Into(response) }
|
||||
if wd, ok := endpointsrequest.LatencyTrackersFrom(ctx); ok {
|
||||
tmp := do
|
||||
do = func() { wd.MutatingWebhookTracker.Track(tmp) }
|
||||
}
|
||||
do()
|
||||
if err != nil {
|
||||
var status *apierrors.StatusError
|
||||
if se, ok := err.(*apierrors.StatusError); ok {
|
||||
status = se
|
||||
} else {
|
||||
status = apierrors.NewBadRequest("error calling webhook")
|
||||
}
|
||||
return false, &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("failed to call webhook: %w", err), Status: status}
|
||||
}
|
||||
span.AddEvent("Request completed")
|
||||
|
||||
result, err := webhookrequest.VerifyAdmissionResponse(uid, true, response)
|
||||
if err != nil {
|
||||
return false, &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("received invalid webhook response: %w", err), Status: apierrors.NewServiceUnavailable("error validating webhook response")}
|
||||
}
|
||||
|
||||
for k, v := range result.AuditAnnotations {
|
||||
key := h.Name + "/" + k
|
||||
if err := attr.Attributes.AddAnnotation(key, v); err != nil {
|
||||
klog.Warningf("Failed to set admission audit annotation %s to %s for mutating webhook %s: %v", key, v, h.Name, err)
|
||||
}
|
||||
}
|
||||
for _, w := range result.Warnings {
|
||||
warning.AddWarning(ctx, "", w)
|
||||
}
|
||||
|
||||
if !result.Allowed {
|
||||
return false, &webhookutil.ErrWebhookRejection{Status: webhookerrors.ToStatusErr(h.Name, result.Result)}
|
||||
}
|
||||
|
||||
if len(result.Patch) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
patchObj, err := jsonpatch.DecodePatch(result.Patch)
|
||||
if err != nil {
|
||||
return false, apierrors.NewInternalError(err)
|
||||
}
|
||||
|
||||
if len(patchObj) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// if a non-empty patch was provided, and we have no object we can apply it to (e.g. a DELETE admission operation), error
|
||||
if attr.VersionedObject == nil {
|
||||
return false, apierrors.NewInternalError(fmt.Errorf("admission webhook %q attempted to modify the object, which is not supported for this operation", h.Name))
|
||||
}
|
||||
|
||||
var patchedJS []byte
|
||||
jsonSerializer := json.NewSerializerWithOptions(json.DefaultMetaFactory, o.GetObjectCreater(), o.GetObjectTyper(), json.SerializerOptions{})
|
||||
switch result.PatchType {
|
||||
// VerifyAdmissionResponse normalizes to v1 patch types, regardless of the AdmissionReview version used
|
||||
case admissionv1.PatchTypeJSONPatch:
|
||||
objJS, err := runtime.Encode(jsonSerializer, attr.VersionedObject)
|
||||
if err != nil {
|
||||
return false, apierrors.NewInternalError(err)
|
||||
}
|
||||
patchedJS, err = patchObj.Apply(objJS)
|
||||
if err != nil {
|
||||
return false, apierrors.NewInternalError(err)
|
||||
}
|
||||
default:
|
||||
return false, &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("unsupported patch type %q", result.PatchType), Status: webhookerrors.ToStatusErr(h.Name, result.Result)}
|
||||
}
|
||||
|
||||
var newVersionedObject runtime.Object
|
||||
if _, ok := attr.VersionedObject.(*unstructured.Unstructured); ok {
|
||||
// Custom Resources don't have corresponding Go struct's.
|
||||
// They are represented as Unstructured.
|
||||
newVersionedObject = &unstructured.Unstructured{}
|
||||
} else {
|
||||
newVersionedObject, err = o.GetObjectCreater().New(attr.VersionedKind)
|
||||
if err != nil {
|
||||
return false, apierrors.NewInternalError(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: if we have multiple mutating webhooks, we can remember the json
|
||||
// instead of encoding and decoding for each one.
|
||||
if newVersionedObject, _, err = jsonSerializer.Decode(patchedJS, nil, newVersionedObject); err != nil {
|
||||
return false, apierrors.NewInternalError(err)
|
||||
}
|
||||
|
||||
changed = !apiequality.Semantic.DeepEqual(attr.VersionedObject, newVersionedObject)
|
||||
span.AddEvent("Patch applied")
|
||||
annotator.addPatchAnnotation(patchObj, result.PatchType)
|
||||
attr.Dirty = true
|
||||
attr.VersionedObject = newVersionedObject
|
||||
o.GetObjectDefaulter().Default(attr.VersionedObject)
|
||||
return changed, nil
|
||||
}
|
||||
|
||||
type webhookAnnotator struct {
|
||||
attr *admission.VersionedAttributes
|
||||
failedOpenAnnotationKey string
|
||||
patchAnnotationKey string
|
||||
mutationAnnotationKey string
|
||||
webhook string
|
||||
configuration string
|
||||
}
|
||||
|
||||
func newWebhookAnnotator(attr *admission.VersionedAttributes, round, idx int, webhook, configuration string) *webhookAnnotator {
|
||||
return &webhookAnnotator{
|
||||
attr: attr,
|
||||
failedOpenAnnotationKey: fmt.Sprintf("%sround_%d_index_%d", MutationAuditAnnotationFailedOpenKeyPrefix, round, idx),
|
||||
patchAnnotationKey: fmt.Sprintf("%sround_%d_index_%d", PatchAuditAnnotationPrefix, round, idx),
|
||||
mutationAnnotationKey: fmt.Sprintf("%sround_%d_index_%d", MutationAuditAnnotationPrefix, round, idx),
|
||||
webhook: webhook,
|
||||
configuration: configuration,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *webhookAnnotator) addFailedOpenAnnotation() {
|
||||
if w.attr == nil || w.attr.Attributes == nil {
|
||||
return
|
||||
}
|
||||
value := w.webhook
|
||||
if err := w.attr.Attributes.AddAnnotation(w.failedOpenAnnotationKey, value); err != nil {
|
||||
klog.Warningf("failed to set failed open annotation for mutating webhook key %s to %s: %v", w.failedOpenAnnotationKey, value, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *webhookAnnotator) addMutationAnnotation(mutated bool) {
|
||||
if w.attr == nil || w.attr.Attributes == nil {
|
||||
return
|
||||
}
|
||||
value, err := mutationAnnotationValue(w.configuration, w.webhook, mutated)
|
||||
if err != nil {
|
||||
klog.Warningf("unexpected error composing mutating webhook annotation: %v", err)
|
||||
return
|
||||
}
|
||||
if err := w.attr.Attributes.AddAnnotation(w.mutationAnnotationKey, value); err != nil {
|
||||
klog.Warningf("failed to set mutation annotation for mutating webhook key %s to %s: %v", w.mutationAnnotationKey, value, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *webhookAnnotator) addPatchAnnotation(patch interface{}, patchType admissionv1.PatchType) {
|
||||
if w.attr == nil || w.attr.Attributes == nil {
|
||||
return
|
||||
}
|
||||
var value string
|
||||
var err error
|
||||
switch patchType {
|
||||
case admissionv1.PatchTypeJSONPatch:
|
||||
value, err = jsonPatchAnnotationValue(w.configuration, w.webhook, patch)
|
||||
if err != nil {
|
||||
klog.Warningf("unexpected error composing mutating webhook JSON patch annotation: %v", err)
|
||||
return
|
||||
}
|
||||
default:
|
||||
klog.Warningf("unsupported patch type for mutating webhook annotation: %v", patchType)
|
||||
return
|
||||
}
|
||||
if err := w.attr.Attributes.AddAnnotationWithLevel(w.patchAnnotationKey, value, auditinternal.LevelRequest); err != nil {
|
||||
// NOTE: we don't log actual patch in kube-apiserver log to avoid potentially
|
||||
// leaking information
|
||||
klog.Warningf("failed to set patch annotation for mutating webhook key %s; confugiration name: %s, webhook name: %s", w.patchAnnotationKey, w.configuration, w.webhook)
|
||||
}
|
||||
}
|
||||
|
||||
// MutationAuditAnnotation logs if a webhook invocation mutated the request object
|
||||
type MutationAuditAnnotation struct {
|
||||
Configuration string `json:"configuration"`
|
||||
Webhook string `json:"webhook"`
|
||||
Mutated bool `json:"mutated"`
|
||||
}
|
||||
|
||||
// PatchAuditAnnotation logs a patch from a mutating webhook
|
||||
type PatchAuditAnnotation struct {
|
||||
Configuration string `json:"configuration"`
|
||||
Webhook string `json:"webhook"`
|
||||
Patch interface{} `json:"patch,omitempty"`
|
||||
PatchType string `json:"patchType,omitempty"`
|
||||
}
|
||||
|
||||
func mutationAnnotationValue(configuration, webhook string, mutated bool) (string, error) {
|
||||
m := MutationAuditAnnotation{
|
||||
Configuration: configuration,
|
||||
Webhook: webhook,
|
||||
Mutated: mutated,
|
||||
}
|
||||
bytes, err := utiljson.Marshal(m)
|
||||
return string(bytes), err
|
||||
}
|
||||
|
||||
func jsonPatchAnnotationValue(configuration, webhook string, patch interface{}) (string, error) {
|
||||
p := PatchAuditAnnotation{
|
||||
Configuration: configuration,
|
||||
Webhook: webhook,
|
||||
Patch: patch,
|
||||
PatchType: string(admissionv1.PatchTypeJSONPatch),
|
||||
}
|
||||
bytes, err := utiljson.Marshal(p)
|
||||
return string(bytes), err
|
||||
}
|
19
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/doc.go
generated
vendored
Normal file
19
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/doc.go
generated
vendored
Normal 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"
|
76
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/plugin.go
generated
vendored
Normal file
76
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/plugin.go
generated
vendored
Normal file
@ -0,0 +1,76 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package mutating
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/configuration"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
|
||||
)
|
||||
|
||||
const (
|
||||
// PluginName indicates the name of admission plug-in
|
||||
PluginName = "MutatingAdmissionWebhook"
|
||||
)
|
||||
|
||||
// Register registers a plugin
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register(PluginName, func(configFile io.Reader) (admission.Interface, error) {
|
||||
plugin, err := NewMutatingWebhook(configFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return plugin, nil
|
||||
})
|
||||
}
|
||||
|
||||
// Plugin is an implementation of admission.Interface.
|
||||
type Plugin struct {
|
||||
*generic.Webhook
|
||||
}
|
||||
|
||||
var _ admission.MutationInterface = &Plugin{}
|
||||
|
||||
// NewMutatingWebhook returns a generic admission webhook plugin.
|
||||
func NewMutatingWebhook(configFile io.Reader) (*Plugin, error) {
|
||||
handler := admission.NewHandler(admission.Connect, admission.Create, admission.Delete, admission.Update)
|
||||
p := &Plugin{}
|
||||
var err error
|
||||
p.Webhook, err = generic.NewWebhook(handler, configFile, configuration.NewMutatingWebhookConfigurationManager, newMutatingDispatcher(p))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// ValidateInitialization implements the InitializationValidator interface.
|
||||
func (a *Plugin) ValidateInitialization() error {
|
||||
if err := a.Webhook.ValidateInitialization(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Admit makes an admission decision based on the request attributes.
|
||||
func (a *Plugin) Admit(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces) error {
|
||||
return a.Webhook.Dispatch(ctx, attr, o)
|
||||
}
|
68
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/reinvocationcontext.go
generated
vendored
Normal file
68
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/reinvocationcontext.go
generated
vendored
Normal file
@ -0,0 +1,68 @@
|
||||
/*
|
||||
Copyright 2019 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package mutating
|
||||
|
||||
import (
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
type webhookReinvokeContext struct {
|
||||
// lastWebhookOutput holds the result of the last webhook admission plugin call
|
||||
lastWebhookOutput runtime.Object
|
||||
// previouslyInvokedReinvocableWebhooks holds the set of webhooks that have been invoked and
|
||||
// should be reinvoked if a later mutation occurs
|
||||
previouslyInvokedReinvocableWebhooks sets.String
|
||||
// reinvokeWebhooks holds the set of webhooks that should be reinvoked
|
||||
reinvokeWebhooks sets.String
|
||||
}
|
||||
|
||||
func (rc *webhookReinvokeContext) ShouldReinvokeWebhook(webhook string) bool {
|
||||
return rc.reinvokeWebhooks.Has(webhook)
|
||||
}
|
||||
|
||||
func (rc *webhookReinvokeContext) IsOutputChangedSinceLastWebhookInvocation(object runtime.Object) bool {
|
||||
return !apiequality.Semantic.DeepEqual(rc.lastWebhookOutput, object)
|
||||
}
|
||||
|
||||
func (rc *webhookReinvokeContext) SetLastWebhookInvocationOutput(object runtime.Object) {
|
||||
if object == nil {
|
||||
rc.lastWebhookOutput = nil
|
||||
return
|
||||
}
|
||||
rc.lastWebhookOutput = object.DeepCopyObject()
|
||||
}
|
||||
|
||||
func (rc *webhookReinvokeContext) AddReinvocableWebhookToPreviouslyInvoked(webhook string) {
|
||||
if rc.previouslyInvokedReinvocableWebhooks == nil {
|
||||
rc.previouslyInvokedReinvocableWebhooks = sets.NewString()
|
||||
}
|
||||
rc.previouslyInvokedReinvocableWebhooks.Insert(webhook)
|
||||
}
|
||||
|
||||
func (rc *webhookReinvokeContext) RequireReinvokingPreviouslyInvokedPlugins() {
|
||||
if len(rc.previouslyInvokedReinvocableWebhooks) > 0 {
|
||||
if rc.reinvokeWebhooks == nil {
|
||||
rc.reinvokeWebhooks = sets.NewString()
|
||||
}
|
||||
for s := range rc.previouslyInvokedReinvocableWebhooks {
|
||||
rc.reinvokeWebhooks.Insert(s)
|
||||
}
|
||||
rc.previouslyInvokedReinvocableWebhooks = sets.NewString()
|
||||
}
|
||||
}
|
20
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/namespace/doc.go
generated
vendored
Normal file
20
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/namespace/doc.go
generated
vendored
Normal 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/predicates/namespace"
|
131
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/namespace/matcher.go
generated
vendored
Normal file
131
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/namespace/matcher.go
generated
vendored
Normal file
@ -0,0 +1,131 @@
|
||||
/*
|
||||
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 (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
type NamespaceSelectorProvider interface {
|
||||
// GetNamespaceSelector gets the webhook NamespaceSelector field.
|
||||
GetParsedNamespaceSelector() (labels.Selector, error)
|
||||
}
|
||||
|
||||
// Matcher decides if a request is exempted by the NamespaceSelector of a
|
||||
// webhook configuration.
|
||||
type Matcher struct {
|
||||
NamespaceLister corelisters.NamespaceLister
|
||||
Client clientset.Interface
|
||||
}
|
||||
|
||||
func (m *Matcher) GetNamespace(name string) (*v1.Namespace, error) {
|
||||
return m.NamespaceLister.Get(name)
|
||||
}
|
||||
|
||||
// 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 client"))
|
||||
}
|
||||
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(context.TODO(), 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(p NamespaceSelectorProvider, attr admission.Attributes) (bool, *apierrors.StatusError) {
|
||||
namespaceName := attr.GetNamespace()
|
||||
if len(namespaceName) == 0 && attr.GetResource().Resource != "namespaces" {
|
||||
// If the request is about a cluster scoped resource, and it is not a
|
||||
// namespace, it is never exempted.
|
||||
// TODO: figure out a way selective exempt cluster scoped resources.
|
||||
// Also update the comment in types.go
|
||||
return true, nil
|
||||
}
|
||||
selector, err := p.GetParsedNamespaceSelector()
|
||||
if err != nil {
|
||||
return false, apierrors.NewInternalError(err)
|
||||
}
|
||||
if selector.Empty() {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
namespaceLabels, err := m.GetNamespaceLabels(attr)
|
||||
// this means the namespace is not found, for backwards compatibility,
|
||||
// return a 404
|
||||
if apierrors.IsNotFound(err) {
|
||||
status, ok := err.(apierrors.APIStatus)
|
||||
if !ok {
|
||||
return false, apierrors.NewInternalError(err)
|
||||
}
|
||||
return false, &apierrors.StatusError{ErrStatus: status.Status()}
|
||||
}
|
||||
if err != nil {
|
||||
return false, apierrors.NewInternalError(err)
|
||||
}
|
||||
return selector.Matches(labels.Set(namespaceLabels)), nil
|
||||
}
|
20
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/object/doc.go
generated
vendored
Normal file
20
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/object/doc.go
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
Copyright 2019 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package object defines the utilities that are used by the webhook plugin to
|
||||
// decide if a webhook should run, as long as either the old object or the new
|
||||
// object has labels matching the webhook config's objectSelector.
|
||||
package object // import "k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/object"
|
61
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/object/matcher.go
generated
vendored
Normal file
61
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/object/matcher.go
generated
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
/*
|
||||
Copyright 2019 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package object
|
||||
|
||||
import (
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
type ObjectSelectorProvider interface {
|
||||
// GetObjectSelector gets the webhook ObjectSelector field.
|
||||
GetParsedObjectSelector() (labels.Selector, error)
|
||||
}
|
||||
|
||||
// Matcher decides if a request selected by the ObjectSelector.
|
||||
type Matcher struct {
|
||||
}
|
||||
|
||||
func matchObject(obj runtime.Object, selector labels.Selector) bool {
|
||||
if obj == nil {
|
||||
return false
|
||||
}
|
||||
accessor, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
klog.V(5).InfoS("Accessing metadata failed", "object", obj, "err", err)
|
||||
return false
|
||||
}
|
||||
return selector.Matches(labels.Set(accessor.GetLabels()))
|
||||
|
||||
}
|
||||
|
||||
// MatchObjectSelector decideds whether the request matches the ObjectSelector
|
||||
// of the webhook. Only when they match, the webhook is called.
|
||||
func (m *Matcher) MatchObjectSelector(p ObjectSelectorProvider, attr admission.Attributes) (bool, *apierrors.StatusError) {
|
||||
selector, err := p.GetParsedObjectSelector()
|
||||
if err != nil {
|
||||
return false, apierrors.NewInternalError(err)
|
||||
}
|
||||
if selector.Empty() {
|
||||
return true, nil
|
||||
}
|
||||
return matchObject(attr.GetObject(), selector) || matchObject(attr.GetOldObject(), selector), nil
|
||||
}
|
129
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/rules/rules.go
generated
vendored
Normal file
129
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/rules/rules.go
generated
vendored
Normal file
@ -0,0 +1,129 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package rules
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"k8s.io/api/admissionregistration/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
)
|
||||
|
||||
// Matcher determines if the Attr matches the Rule.
|
||||
type Matcher struct {
|
||||
Rule v1.RuleWithOperations
|
||||
Attr admission.Attributes
|
||||
}
|
||||
|
||||
// Matches returns if the Attr matches the Rule.
|
||||
func (r *Matcher) Matches() bool {
|
||||
return r.scope() &&
|
||||
r.operation() &&
|
||||
r.group() &&
|
||||
r.version() &&
|
||||
r.resource()
|
||||
}
|
||||
|
||||
func exactOrWildcard(items []string, requested string) bool {
|
||||
for _, item := range items {
|
||||
if item == "*" {
|
||||
return true
|
||||
}
|
||||
if item == requested {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
var namespaceResource = schema.GroupVersionResource{Group: "", Version: "v1", Resource: "namespaces"}
|
||||
|
||||
func (r *Matcher) scope() bool {
|
||||
if r.Rule.Scope == nil || *r.Rule.Scope == v1.AllScopes {
|
||||
return true
|
||||
}
|
||||
// attr.GetNamespace() is set to the name of the namespace for requests of the namespace object itself.
|
||||
switch *r.Rule.Scope {
|
||||
case v1.NamespacedScope:
|
||||
// first make sure that we are not requesting a namespace object (namespace objects are cluster-scoped)
|
||||
return r.Attr.GetResource() != namespaceResource && r.Attr.GetNamespace() != metav1.NamespaceNone
|
||||
case v1.ClusterScope:
|
||||
// also return true if the request is for a namespace object (namespace objects are cluster-scoped)
|
||||
return r.Attr.GetResource() == namespaceResource || r.Attr.GetNamespace() == metav1.NamespaceNone
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Matcher) group() bool {
|
||||
return exactOrWildcard(r.Rule.APIGroups, r.Attr.GetResource().Group)
|
||||
}
|
||||
|
||||
func (r *Matcher) version() bool {
|
||||
return exactOrWildcard(r.Rule.APIVersions, r.Attr.GetResource().Version)
|
||||
}
|
||||
|
||||
func (r *Matcher) operation() bool {
|
||||
attrOp := r.Attr.GetOperation()
|
||||
for _, op := range r.Rule.Operations {
|
||||
if op == v1.OperationAll {
|
||||
return true
|
||||
}
|
||||
// The constants are the same such that this is a valid cast (and this
|
||||
// is tested).
|
||||
if op == v1.OperationType(attrOp) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func splitResource(resSub string) (res, sub string) {
|
||||
parts := strings.SplitN(resSub, "/", 2)
|
||||
if len(parts) == 2 {
|
||||
return parts[0], parts[1]
|
||||
}
|
||||
return parts[0], ""
|
||||
}
|
||||
|
||||
func (r *Matcher) resource() bool {
|
||||
opRes, opSub := r.Attr.GetResource().Resource, r.Attr.GetSubresource()
|
||||
for _, res := range r.Rule.Resources {
|
||||
res, sub := splitResource(res)
|
||||
resMatch := res == "*" || res == opRes
|
||||
subMatch := sub == "*" || sub == opSub
|
||||
if resMatch && subMatch {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsExemptAdmissionConfigurationResource determines if an admission.Attributes object is describing
|
||||
// the admission of a ValidatingWebhookConfiguration or a MutatingWebhookConfiguration or a ValidatingAdmissionPolicy or a ValidatingAdmissionPolicyBinding
|
||||
func IsExemptAdmissionConfigurationResource(attr admission.Attributes) bool {
|
||||
gvk := attr.GetKind()
|
||||
if gvk.Group == "admissionregistration.k8s.io" {
|
||||
if gvk.Kind == "ValidatingWebhookConfiguration" || gvk.Kind == "MutatingWebhookConfiguration" || gvk.Kind == "ValidatingAdmissionPolicy" || gvk.Kind == "ValidatingAdmissionPolicyBinding" || gvk.Kind == "MutatingAdmissionPolicy" || gvk.Kind == "MutatingAdmissionPolicyBinding" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
284
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/request/admissionreview.go
generated
vendored
Normal file
284
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/request/admissionreview.go
generated
vendored
Normal file
@ -0,0 +1,284 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package request
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
admissionv1beta1 "k8s.io/api/admission/v1beta1"
|
||||
authenticationv1 "k8s.io/api/authentication/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/uuid"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
|
||||
)
|
||||
|
||||
// AdmissionResponse contains the fields extracted from an AdmissionReview response
|
||||
type AdmissionResponse struct {
|
||||
AuditAnnotations map[string]string
|
||||
Allowed bool
|
||||
Patch []byte
|
||||
PatchType admissionv1.PatchType
|
||||
Result *metav1.Status
|
||||
Warnings []string
|
||||
}
|
||||
|
||||
// VerifyAdmissionResponse checks the validity of the provided admission review object, and returns the
|
||||
// audit annotations, whether the response allowed the request, any provided patch/patchType/status,
|
||||
// or an error if the provided admission review was not valid.
|
||||
func VerifyAdmissionResponse(uid types.UID, mutating bool, review runtime.Object) (*AdmissionResponse, error) {
|
||||
switch r := review.(type) {
|
||||
case *admissionv1.AdmissionReview:
|
||||
if r.Response == nil {
|
||||
return nil, fmt.Errorf("webhook response was absent")
|
||||
}
|
||||
|
||||
// Verify UID matches
|
||||
if r.Response.UID != uid {
|
||||
return nil, fmt.Errorf("expected response.uid=%q, got %q", uid, r.Response.UID)
|
||||
}
|
||||
|
||||
// Verify GVK
|
||||
v1GVK := admissionv1.SchemeGroupVersion.WithKind("AdmissionReview")
|
||||
if r.GroupVersionKind() != v1GVK {
|
||||
return nil, fmt.Errorf("expected webhook response of %v, got %v", v1GVK.String(), r.GroupVersionKind().String())
|
||||
}
|
||||
|
||||
patch := []byte(nil)
|
||||
patchType := admissionv1.PatchType("")
|
||||
|
||||
if mutating {
|
||||
// Ensure a mutating webhook provides both patch and patchType together
|
||||
if len(r.Response.Patch) > 0 && r.Response.PatchType == nil {
|
||||
return nil, fmt.Errorf("webhook returned response.patch but not response.patchType")
|
||||
}
|
||||
if len(r.Response.Patch) == 0 && r.Response.PatchType != nil {
|
||||
return nil, fmt.Errorf("webhook returned response.patchType but not response.patch")
|
||||
}
|
||||
patch = r.Response.Patch
|
||||
if r.Response.PatchType != nil {
|
||||
patchType = *r.Response.PatchType
|
||||
if len(patchType) == 0 {
|
||||
return nil, fmt.Errorf("webhook returned invalid response.patchType of %q", patchType)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Ensure a validating webhook doesn't return patch or patchType
|
||||
if len(r.Response.Patch) > 0 {
|
||||
return nil, fmt.Errorf("validating webhook may not return response.patch")
|
||||
}
|
||||
if r.Response.PatchType != nil {
|
||||
return nil, fmt.Errorf("validating webhook may not return response.patchType")
|
||||
}
|
||||
}
|
||||
|
||||
return &AdmissionResponse{
|
||||
AuditAnnotations: r.Response.AuditAnnotations,
|
||||
Allowed: r.Response.Allowed,
|
||||
Patch: patch,
|
||||
PatchType: patchType,
|
||||
Result: r.Response.Result,
|
||||
Warnings: r.Response.Warnings,
|
||||
}, nil
|
||||
|
||||
case *admissionv1beta1.AdmissionReview:
|
||||
if r.Response == nil {
|
||||
return nil, fmt.Errorf("webhook response was absent")
|
||||
}
|
||||
|
||||
// Response GVK and response.uid were not verified in v1beta1 handling, allow any
|
||||
|
||||
patch := []byte(nil)
|
||||
patchType := admissionv1.PatchType("")
|
||||
if mutating {
|
||||
patch = r.Response.Patch
|
||||
if len(r.Response.Patch) > 0 {
|
||||
// patch type was not verified in v1beta1 admissionreview handling. pin to only supported version if a patch is provided.
|
||||
patchType = admissionv1.PatchTypeJSONPatch
|
||||
}
|
||||
}
|
||||
|
||||
return &AdmissionResponse{
|
||||
AuditAnnotations: r.Response.AuditAnnotations,
|
||||
Allowed: r.Response.Allowed,
|
||||
Patch: patch,
|
||||
PatchType: patchType,
|
||||
Result: r.Response.Result,
|
||||
Warnings: r.Response.Warnings,
|
||||
}, nil
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected response type %T", review)
|
||||
}
|
||||
}
|
||||
|
||||
// CreateAdmissionObjects returns the unique request uid, the AdmissionReview object to send the webhook and to decode the response into,
|
||||
// or an error if the webhook does not support receiving any of the admission review versions we know to send
|
||||
func CreateAdmissionObjects(versionedAttributes *admission.VersionedAttributes, invocation *generic.WebhookInvocation) (uid types.UID, request, response runtime.Object, err error) {
|
||||
for _, version := range invocation.Webhook.GetAdmissionReviewVersions() {
|
||||
switch version {
|
||||
case admissionv1.SchemeGroupVersion.Version:
|
||||
uid := types.UID(uuid.NewUUID())
|
||||
request := CreateV1AdmissionReview(uid, versionedAttributes, invocation)
|
||||
response := &admissionv1.AdmissionReview{}
|
||||
return uid, request, response, nil
|
||||
|
||||
case admissionv1beta1.SchemeGroupVersion.Version:
|
||||
uid := types.UID(uuid.NewUUID())
|
||||
request := CreateV1beta1AdmissionReview(uid, versionedAttributes, invocation)
|
||||
response := &admissionv1beta1.AdmissionReview{}
|
||||
return uid, request, response, nil
|
||||
|
||||
}
|
||||
}
|
||||
return "", nil, nil, fmt.Errorf("webhook does not accept known AdmissionReview versions (v1, v1beta1)")
|
||||
}
|
||||
|
||||
// CreateV1AdmissionReview creates an AdmissionReview for the provided admission.Attributes
|
||||
func CreateV1AdmissionReview(uid types.UID, versionedAttributes *admission.VersionedAttributes, invocation *generic.WebhookInvocation) *admissionv1.AdmissionReview {
|
||||
attr := versionedAttributes.Attributes
|
||||
gvk := invocation.Kind
|
||||
gvr := invocation.Resource
|
||||
subresource := invocation.Subresource
|
||||
requestGVK := attr.GetKind()
|
||||
requestGVR := attr.GetResource()
|
||||
requestSubResource := attr.GetSubresource()
|
||||
aUserInfo := attr.GetUserInfo()
|
||||
userInfo := authenticationv1.UserInfo{
|
||||
Extra: make(map[string]authenticationv1.ExtraValue),
|
||||
Groups: aUserInfo.GetGroups(),
|
||||
UID: aUserInfo.GetUID(),
|
||||
Username: aUserInfo.GetName(),
|
||||
}
|
||||
dryRun := attr.IsDryRun()
|
||||
|
||||
// Convert the extra information in the user object
|
||||
for key, val := range aUserInfo.GetExtra() {
|
||||
userInfo.Extra[key] = authenticationv1.ExtraValue(val)
|
||||
}
|
||||
|
||||
return &admissionv1.AdmissionReview{
|
||||
Request: &admissionv1.AdmissionRequest{
|
||||
UID: uid,
|
||||
Kind: metav1.GroupVersionKind{
|
||||
Group: gvk.Group,
|
||||
Kind: gvk.Kind,
|
||||
Version: gvk.Version,
|
||||
},
|
||||
Resource: metav1.GroupVersionResource{
|
||||
Group: gvr.Group,
|
||||
Resource: gvr.Resource,
|
||||
Version: gvr.Version,
|
||||
},
|
||||
SubResource: subresource,
|
||||
RequestKind: &metav1.GroupVersionKind{
|
||||
Group: requestGVK.Group,
|
||||
Kind: requestGVK.Kind,
|
||||
Version: requestGVK.Version,
|
||||
},
|
||||
RequestResource: &metav1.GroupVersionResource{
|
||||
Group: requestGVR.Group,
|
||||
Resource: requestGVR.Resource,
|
||||
Version: requestGVR.Version,
|
||||
},
|
||||
RequestSubResource: requestSubResource,
|
||||
Name: attr.GetName(),
|
||||
Namespace: attr.GetNamespace(),
|
||||
Operation: admissionv1.Operation(attr.GetOperation()),
|
||||
UserInfo: userInfo,
|
||||
Object: runtime.RawExtension{
|
||||
Object: versionedAttributes.VersionedObject,
|
||||
},
|
||||
OldObject: runtime.RawExtension{
|
||||
Object: versionedAttributes.VersionedOldObject,
|
||||
},
|
||||
DryRun: &dryRun,
|
||||
Options: runtime.RawExtension{
|
||||
Object: attr.GetOperationOptions(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// CreateV1beta1AdmissionReview creates an AdmissionReview for the provided admission.Attributes
|
||||
func CreateV1beta1AdmissionReview(uid types.UID, versionedAttributes *admission.VersionedAttributes, invocation *generic.WebhookInvocation) *admissionv1beta1.AdmissionReview {
|
||||
attr := versionedAttributes.Attributes
|
||||
gvk := invocation.Kind
|
||||
gvr := invocation.Resource
|
||||
subresource := invocation.Subresource
|
||||
requestGVK := attr.GetKind()
|
||||
requestGVR := attr.GetResource()
|
||||
requestSubResource := attr.GetSubresource()
|
||||
aUserInfo := attr.GetUserInfo()
|
||||
userInfo := authenticationv1.UserInfo{
|
||||
Extra: make(map[string]authenticationv1.ExtraValue),
|
||||
Groups: aUserInfo.GetGroups(),
|
||||
UID: aUserInfo.GetUID(),
|
||||
Username: aUserInfo.GetName(),
|
||||
}
|
||||
dryRun := attr.IsDryRun()
|
||||
|
||||
// Convert the extra information in the user object
|
||||
for key, val := range aUserInfo.GetExtra() {
|
||||
userInfo.Extra[key] = authenticationv1.ExtraValue(val)
|
||||
}
|
||||
|
||||
return &admissionv1beta1.AdmissionReview{
|
||||
Request: &admissionv1beta1.AdmissionRequest{
|
||||
UID: uid,
|
||||
Kind: metav1.GroupVersionKind{
|
||||
Group: gvk.Group,
|
||||
Kind: gvk.Kind,
|
||||
Version: gvk.Version,
|
||||
},
|
||||
Resource: metav1.GroupVersionResource{
|
||||
Group: gvr.Group,
|
||||
Resource: gvr.Resource,
|
||||
Version: gvr.Version,
|
||||
},
|
||||
SubResource: subresource,
|
||||
RequestKind: &metav1.GroupVersionKind{
|
||||
Group: requestGVK.Group,
|
||||
Kind: requestGVK.Kind,
|
||||
Version: requestGVK.Version,
|
||||
},
|
||||
RequestResource: &metav1.GroupVersionResource{
|
||||
Group: requestGVR.Group,
|
||||
Resource: requestGVR.Resource,
|
||||
Version: requestGVR.Version,
|
||||
},
|
||||
RequestSubResource: requestSubResource,
|
||||
Name: attr.GetName(),
|
||||
Namespace: attr.GetNamespace(),
|
||||
Operation: admissionv1beta1.Operation(attr.GetOperation()),
|
||||
UserInfo: userInfo,
|
||||
Object: runtime.RawExtension{
|
||||
Object: versionedAttributes.VersionedObject,
|
||||
},
|
||||
OldObject: runtime.RawExtension{
|
||||
Object: versionedAttributes.VersionedOldObject,
|
||||
},
|
||||
DryRun: &dryRun,
|
||||
Options: runtime.RawExtension{
|
||||
Object: attr.GetOperationOptions(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
18
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/request/doc.go
generated
vendored
Normal file
18
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/request/doc.go
generated
vendored
Normal 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"
|
333
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/dispatcher.go
generated
vendored
Normal file
333
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/dispatcher.go
generated
vendored
Normal file
@ -0,0 +1,333 @@
|
||||
/*
|
||||
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"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
|
||||
v1 "k8s.io/api/admissionregistration/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
admissionmetrics "k8s.io/apiserver/pkg/admission/metrics"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook"
|
||||
webhookerrors "k8s.io/apiserver/pkg/admission/plugin/webhook/errors"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
|
||||
webhookrequest "k8s.io/apiserver/pkg/admission/plugin/webhook/request"
|
||||
endpointsrequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
webhookutil "k8s.io/apiserver/pkg/util/webhook"
|
||||
"k8s.io/apiserver/pkg/warning"
|
||||
"k8s.io/component-base/tracing"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
// ValidatingAuditAnnotationPrefix is a prefix for keeping noteworthy
|
||||
// validating audit annotations.
|
||||
ValidatingAuditAnnotationPrefix = "validating.webhook.admission.k8s.io/"
|
||||
// ValidatingAuditAnnotationFailedOpenKeyPrefix in an annotation indicates
|
||||
// the validating webhook failed open when the webhook backend connection
|
||||
// failed or returned an internal server error.
|
||||
ValidatingAuditAnnotationFailedOpenKeyPrefix = "failed-open." + ValidatingAuditAnnotationPrefix
|
||||
)
|
||||
|
||||
type validatingDispatcher struct {
|
||||
cm *webhookutil.ClientManager
|
||||
plugin *Plugin
|
||||
}
|
||||
|
||||
func newValidatingDispatcher(p *Plugin) func(cm *webhookutil.ClientManager) generic.Dispatcher {
|
||||
return func(cm *webhookutil.ClientManager) generic.Dispatcher {
|
||||
return &validatingDispatcher{cm, p}
|
||||
}
|
||||
}
|
||||
|
||||
var _ generic.VersionedAttributeAccessor = &versionedAttributeAccessor{}
|
||||
|
||||
type versionedAttributeAccessor struct {
|
||||
versionedAttrs map[schema.GroupVersionKind]*admission.VersionedAttributes
|
||||
attr admission.Attributes
|
||||
objectInterfaces admission.ObjectInterfaces
|
||||
}
|
||||
|
||||
func (v *versionedAttributeAccessor) VersionedAttribute(gvk schema.GroupVersionKind) (*admission.VersionedAttributes, error) {
|
||||
if val, ok := v.versionedAttrs[gvk]; ok {
|
||||
return val, nil
|
||||
}
|
||||
versionedAttr, err := admission.NewVersionedAttributes(v.attr, gvk, v.objectInterfaces)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v.versionedAttrs[gvk] = versionedAttr
|
||||
return versionedAttr, nil
|
||||
}
|
||||
|
||||
var _ generic.Dispatcher = &validatingDispatcher{}
|
||||
|
||||
func (d *validatingDispatcher) Dispatch(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces, hooks []webhook.WebhookAccessor) error {
|
||||
var relevantHooks []*generic.WebhookInvocation
|
||||
// Construct all the versions we need to call our webhooks
|
||||
versionedAttrAccessor := &versionedAttributeAccessor{
|
||||
versionedAttrs: map[schema.GroupVersionKind]*admission.VersionedAttributes{},
|
||||
attr: attr,
|
||||
objectInterfaces: o,
|
||||
}
|
||||
for _, hook := range hooks {
|
||||
invocation, statusError := d.plugin.ShouldCallHook(ctx, hook, attr, o, versionedAttrAccessor)
|
||||
if statusError != nil {
|
||||
return statusError
|
||||
}
|
||||
if invocation == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
relevantHooks = append(relevantHooks, invocation)
|
||||
// VersionedAttr result will be cached and reused later during parallel webhook calls
|
||||
_, err := versionedAttrAccessor.VersionedAttribute(invocation.Kind)
|
||||
if err != nil {
|
||||
return apierrors.NewInternalError(err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(relevantHooks) == 0 {
|
||||
// no matching hooks
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if the request has already timed out before spawning remote calls
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// parent context is canceled or timed out, no point in continuing
|
||||
return apierrors.NewTimeoutError("request did not complete within requested timeout", 0)
|
||||
default:
|
||||
}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
errCh := make(chan error, 2*len(relevantHooks)) // double the length to handle extra errors for panics in the gofunc
|
||||
wg.Add(len(relevantHooks))
|
||||
for i := range relevantHooks {
|
||||
go func(invocation *generic.WebhookInvocation, idx int) {
|
||||
ignoreClientCallFailures := false
|
||||
hookName := "unknown"
|
||||
versionedAttr := versionedAttrAccessor.versionedAttrs[invocation.Kind]
|
||||
// The ordering of these two defers is critical. The wg.Done will release the parent go func to close the errCh
|
||||
// that is used by the second defer to report errors. The recovery and error reporting must be done first.
|
||||
defer wg.Done()
|
||||
defer func() {
|
||||
// HandleCrash has already called the crash handlers and it has been configured to utilruntime.ReallyCrash
|
||||
// This block prevents the second panic from failing our process.
|
||||
// This failure mode for the handler functions properly using the channel below.
|
||||
recover()
|
||||
}()
|
||||
defer utilruntime.HandleCrash(
|
||||
func(r interface{}) {
|
||||
if r == nil {
|
||||
return
|
||||
}
|
||||
if ignoreClientCallFailures {
|
||||
// if failures are supposed to ignored, ignore it
|
||||
klog.Warningf("Panic calling webhook, failing open %v: %v", hookName, r)
|
||||
admissionmetrics.Metrics.ObserveWebhookFailOpen(ctx, hookName, "validating")
|
||||
key := fmt.Sprintf("%sround_0_index_%d", ValidatingAuditAnnotationFailedOpenKeyPrefix, idx)
|
||||
value := hookName
|
||||
if err := versionedAttr.Attributes.AddAnnotation(key, value); err != nil {
|
||||
klog.Warningf("Failed to set admission audit annotation %s to %s for validating webhook %s: %v", key, value, hookName, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
// this ensures that the admission request fails and a message is provided.
|
||||
errCh <- apierrors.NewInternalError(fmt.Errorf("ValidatingAdmissionWebhook/%v has panicked: %v", hookName, r))
|
||||
},
|
||||
)
|
||||
|
||||
hook, ok := invocation.Webhook.GetValidatingWebhook()
|
||||
if !ok {
|
||||
utilruntime.HandleError(fmt.Errorf("validating webhook dispatch requires v1.ValidatingWebhook, but got %T", hook))
|
||||
return
|
||||
}
|
||||
hookName = hook.Name
|
||||
ignoreClientCallFailures = hook.FailurePolicy != nil && *hook.FailurePolicy == v1.Ignore
|
||||
t := time.Now()
|
||||
err := d.callHook(ctx, hook, invocation, versionedAttr)
|
||||
rejected := false
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case *webhookutil.ErrCallingWebhook:
|
||||
if !ignoreClientCallFailures {
|
||||
rejected = true
|
||||
// Ignore context cancelled from webhook metrics
|
||||
if !errors.Is(err.Reason, context.Canceled) {
|
||||
admissionmetrics.Metrics.ObserveWebhookRejection(ctx, hook.Name, "validating", string(versionedAttr.Attributes.GetOperation()), admissionmetrics.WebhookRejectionCallingWebhookError, int(err.Status.ErrStatus.Code))
|
||||
}
|
||||
}
|
||||
admissionmetrics.Metrics.ObserveWebhook(ctx, hook.Name, time.Since(t), rejected, versionedAttr.Attributes, "validating", int(err.Status.ErrStatus.Code))
|
||||
case *webhookutil.ErrWebhookRejection:
|
||||
rejected = true
|
||||
admissionmetrics.Metrics.ObserveWebhookRejection(ctx, hook.Name, "validating", string(versionedAttr.Attributes.GetOperation()), admissionmetrics.WebhookRejectionNoError, int(err.Status.ErrStatus.Code))
|
||||
admissionmetrics.Metrics.ObserveWebhook(ctx, hook.Name, time.Since(t), rejected, versionedAttr.Attributes, "validating", int(err.Status.ErrStatus.Code))
|
||||
default:
|
||||
rejected = true
|
||||
admissionmetrics.Metrics.ObserveWebhookRejection(ctx, hook.Name, "validating", string(versionedAttr.Attributes.GetOperation()), admissionmetrics.WebhookRejectionAPIServerInternalError, 0)
|
||||
admissionmetrics.Metrics.ObserveWebhook(ctx, hook.Name, time.Since(t), rejected, versionedAttr.Attributes, "validating", 0)
|
||||
}
|
||||
} else {
|
||||
admissionmetrics.Metrics.ObserveWebhook(ctx, hook.Name, time.Since(t), rejected, versionedAttr.Attributes, "validating", 200)
|
||||
return
|
||||
}
|
||||
|
||||
if callErr, ok := err.(*webhookutil.ErrCallingWebhook); ok {
|
||||
if ignoreClientCallFailures {
|
||||
// Ignore context cancelled from webhook metrics
|
||||
if errors.Is(callErr.Reason, context.Canceled) {
|
||||
klog.Warningf("Context canceled when calling webhook %v", hook.Name)
|
||||
} else {
|
||||
klog.Warningf("Failed calling webhook, failing open %v: %v", hook.Name, callErr)
|
||||
admissionmetrics.Metrics.ObserveWebhookFailOpen(ctx, hook.Name, "validating")
|
||||
key := fmt.Sprintf("%sround_0_index_%d", ValidatingAuditAnnotationFailedOpenKeyPrefix, idx)
|
||||
value := hook.Name
|
||||
if err := versionedAttr.Attributes.AddAnnotation(key, value); err != nil {
|
||||
klog.Warningf("Failed to set admission audit annotation %s to %s for validating webhook %s: %v", key, value, hook.Name, err)
|
||||
}
|
||||
}
|
||||
utilruntime.HandleError(callErr)
|
||||
return
|
||||
}
|
||||
|
||||
klog.Warningf("Failed calling webhook, failing closed %v: %v", hook.Name, err)
|
||||
errCh <- apierrors.NewInternalError(err)
|
||||
return
|
||||
}
|
||||
|
||||
if rejectionErr, ok := err.(*webhookutil.ErrWebhookRejection); ok {
|
||||
err = rejectionErr.Status
|
||||
}
|
||||
klog.Warningf("rejected by webhook %q: %#v", hook.Name, err)
|
||||
errCh <- err
|
||||
}(relevantHooks[i], 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 *v1.ValidatingWebhook, invocation *generic.WebhookInvocation, attr *admission.VersionedAttributes) error {
|
||||
if attr.Attributes.IsDryRun() {
|
||||
if h.SideEffects == nil {
|
||||
return &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook SideEffects is nil"), Status: apierrors.NewBadRequest("Webhook SideEffects is nil")}
|
||||
}
|
||||
if !(*h.SideEffects == v1.SideEffectClassNone || *h.SideEffects == v1.SideEffectClassNoneOnDryRun) {
|
||||
return webhookerrors.NewDryRunUnsupportedErr(h.Name)
|
||||
}
|
||||
}
|
||||
|
||||
uid, request, response, err := webhookrequest.CreateAdmissionObjects(attr, invocation)
|
||||
if err != nil {
|
||||
return &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("could not create admission objects: %w", err), Status: apierrors.NewBadRequest("error creating admission objects")}
|
||||
}
|
||||
// Make the webhook request
|
||||
client, err := invocation.Webhook.GetRESTClient(d.cm)
|
||||
if err != nil {
|
||||
return &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("could not get REST client: %w", err), Status: apierrors.NewBadRequest("error getting REST client")}
|
||||
}
|
||||
ctx, span := tracing.Start(ctx, "Call validating webhook",
|
||||
attribute.String("configuration", invocation.Webhook.GetConfigurationName()),
|
||||
attribute.String("webhook", h.Name),
|
||||
attribute.Stringer("resource", attr.GetResource()),
|
||||
attribute.String("subresource", attr.GetSubresource()),
|
||||
attribute.String("operation", string(attr.GetOperation())),
|
||||
attribute.String("UID", string(uid)))
|
||||
defer span.End(500 * time.Millisecond)
|
||||
|
||||
// if the webhook has a specific timeout, wrap the context to apply it
|
||||
if h.TimeoutSeconds != nil {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ctx, time.Duration(*h.TimeoutSeconds)*time.Second)
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
r := client.Post().Body(request)
|
||||
|
||||
// if the context has a deadline, set it as a parameter to inform the backend
|
||||
if deadline, hasDeadline := ctx.Deadline(); hasDeadline {
|
||||
// compute the timeout
|
||||
if timeout := time.Until(deadline); timeout > 0 {
|
||||
// if it's not an even number of seconds, round up to the nearest second
|
||||
if truncated := timeout.Truncate(time.Second); truncated != timeout {
|
||||
timeout = truncated + time.Second
|
||||
}
|
||||
// set the timeout
|
||||
r.Timeout(timeout)
|
||||
}
|
||||
}
|
||||
|
||||
do := func() { err = r.Do(ctx).Into(response) }
|
||||
if wd, ok := endpointsrequest.LatencyTrackersFrom(ctx); ok {
|
||||
tmp := do
|
||||
do = func() { wd.ValidatingWebhookTracker.Track(tmp) }
|
||||
}
|
||||
do()
|
||||
if err != nil {
|
||||
var status *apierrors.StatusError
|
||||
if se, ok := err.(*apierrors.StatusError); ok {
|
||||
status = se
|
||||
} else {
|
||||
status = apierrors.NewBadRequest("error calling webhook")
|
||||
}
|
||||
return &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("failed to call webhook: %w", err), Status: status}
|
||||
}
|
||||
span.AddEvent("Request completed")
|
||||
|
||||
result, err := webhookrequest.VerifyAdmissionResponse(uid, false, response)
|
||||
if err != nil {
|
||||
return &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("received invalid webhook response: %w", err), Status: apierrors.NewServiceUnavailable("error validating webhook response")}
|
||||
}
|
||||
|
||||
for k, v := range result.AuditAnnotations {
|
||||
key := h.Name + "/" + k
|
||||
if err := attr.Attributes.AddAnnotation(key, v); err != nil {
|
||||
klog.Warningf("Failed to set admission audit annotation %s to %s for validating webhook %s: %v", key, v, h.Name, err)
|
||||
}
|
||||
}
|
||||
for _, w := range result.Warnings {
|
||||
warning.AddWarning(ctx, "", w)
|
||||
}
|
||||
if result.Allowed {
|
||||
return nil
|
||||
}
|
||||
return &webhookutil.ErrWebhookRejection{Status: webhookerrors.ToStatusErr(h.Name, result.Result)}
|
||||
}
|
19
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/doc.go
generated
vendored
Normal file
19
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/doc.go
generated
vendored
Normal 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"
|
67
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/plugin.go
generated
vendored
Normal file
67
e2e/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/plugin.go
generated
vendored
Normal file
@ -0,0 +1,67 @@
|
||||
/*
|
||||
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 (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/configuration"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
|
||||
)
|
||||
|
||||
const (
|
||||
// PluginName indicates the name of admission plug-in
|
||||
PluginName = "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)
|
||||
p := &Plugin{}
|
||||
var err error
|
||||
p.Webhook, err = generic.NewWebhook(handler, configFile, configuration.NewValidatingWebhookConfigurationManager, newValidatingDispatcher(p))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// Validate makes an admission decision based on the request attributes.
|
||||
func (a *Plugin) Validate(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces) error {
|
||||
return a.Webhook.Dispatch(ctx, attr, o)
|
||||
}
|
207
e2e/vendor/k8s.io/apiserver/pkg/admission/plugins.go
generated
vendored
Normal file
207
e2e/vendor/k8s.io/apiserver/pkg/admission/plugins.go
generated
vendored
Normal file
@ -0,0 +1,207 @@
|
||||
/*
|
||||
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"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
// 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).InfoS("Registered admission plugin", "plugin", name)
|
||||
ps.registry[name] = plugin
|
||||
}
|
||||
|
||||
// getPlugin creates an instance of the named plugin. It returns `false` if
|
||||
// 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 := io.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 newReinvocationHandler(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)
|
||||
}
|
||||
}
|
64
e2e/vendor/k8s.io/apiserver/pkg/admission/reinvocation.go
generated
vendored
Normal file
64
e2e/vendor/k8s.io/apiserver/pkg/admission/reinvocation.go
generated
vendored
Normal file
@ -0,0 +1,64 @@
|
||||
/*
|
||||
Copyright 2019 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package admission
|
||||
|
||||
import "context"
|
||||
|
||||
// newReinvocationHandler creates a handler that wraps the provided admission chain and reinvokes it
|
||||
// if needed according to re-invocation policy of the webhooks.
|
||||
func newReinvocationHandler(admissionChain Interface) Interface {
|
||||
return &reinvoker{admissionChain}
|
||||
}
|
||||
|
||||
type reinvoker struct {
|
||||
admissionChain Interface
|
||||
}
|
||||
|
||||
// Admit performs an admission control check using the wrapped admission chain, reinvoking the
|
||||
// admission chain if needed according to the reinvocation policy. Plugins are expected to check
|
||||
// the admission attributes' reinvocation context against their reinvocation policy to decide if
|
||||
// they should re-run, and to update the reinvocation context if they perform any mutations.
|
||||
func (r *reinvoker) Admit(ctx context.Context, a Attributes, o ObjectInterfaces) error {
|
||||
if mutator, ok := r.admissionChain.(MutationInterface); ok {
|
||||
err := mutator.Admit(ctx, a, o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s := a.GetReinvocationContext()
|
||||
if s.ShouldReinvoke() {
|
||||
s.SetIsReinvoke()
|
||||
// Calling admit a second time will reinvoke all in-tree plugins
|
||||
// as well as any webhook plugins that need to be reinvoked based on the
|
||||
// reinvocation policy.
|
||||
return mutator.Admit(ctx, a, o)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate performs an admission control check using the wrapped admission chain, and returns immediately on first error.
|
||||
func (r *reinvoker) Validate(ctx context.Context, a Attributes, o ObjectInterfaces) error {
|
||||
if validator, ok := r.admissionChain.(ValidationInterface); ok {
|
||||
return validator.Validate(ctx, a, o)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Handles will return true if any of the admission chain handlers handle the given operation.
|
||||
func (r *reinvoker) Handles(operation Operation) bool {
|
||||
return r.admissionChain.Handles(operation)
|
||||
}
|
47
e2e/vendor/k8s.io/apiserver/pkg/admission/util.go
generated
vendored
Normal file
47
e2e/vendor/k8s.io/apiserver/pkg/admission/util.go
generated
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
Copyright 2019 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package admission
|
||||
|
||||
import "k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
type RuntimeObjectInterfaces struct {
|
||||
runtime.ObjectCreater
|
||||
runtime.ObjectTyper
|
||||
runtime.ObjectDefaulter
|
||||
runtime.ObjectConvertor
|
||||
runtime.EquivalentResourceMapper
|
||||
}
|
||||
|
||||
func NewObjectInterfacesFromScheme(scheme *runtime.Scheme) ObjectInterfaces {
|
||||
return &RuntimeObjectInterfaces{scheme, scheme, scheme, scheme, runtime.NewEquivalentResourceRegistry()}
|
||||
}
|
||||
|
||||
func (r *RuntimeObjectInterfaces) GetObjectCreater() runtime.ObjectCreater {
|
||||
return r.ObjectCreater
|
||||
}
|
||||
func (r *RuntimeObjectInterfaces) GetObjectTyper() runtime.ObjectTyper {
|
||||
return r.ObjectTyper
|
||||
}
|
||||
func (r *RuntimeObjectInterfaces) GetObjectDefaulter() runtime.ObjectDefaulter {
|
||||
return r.ObjectDefaulter
|
||||
}
|
||||
func (r *RuntimeObjectInterfaces) GetObjectConvertor() runtime.ObjectConvertor {
|
||||
return r.ObjectConvertor
|
||||
}
|
||||
func (r *RuntimeObjectInterfaces) GetEquivalentResourceMapper() runtime.EquivalentResourceMapper {
|
||||
return r.EquivalentResourceMapper
|
||||
}
|
Reference in New Issue
Block a user