k8s.io/apiserver@v0.31.1/pkg/admission/attributes.go (about)

     1  /*
     2  Copyright 2014 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package admission
    18  
    19  import (
    20  	"fmt"
    21  	"strings"
    22  	"sync"
    23  
    24  	"k8s.io/apimachinery/pkg/runtime"
    25  	"k8s.io/apimachinery/pkg/runtime/schema"
    26  	"k8s.io/apimachinery/pkg/util/validation"
    27  	auditinternal "k8s.io/apiserver/pkg/apis/audit"
    28  	"k8s.io/apiserver/pkg/authentication/user"
    29  )
    30  
    31  type attributesRecord struct {
    32  	kind        schema.GroupVersionKind
    33  	namespace   string
    34  	name        string
    35  	resource    schema.GroupVersionResource
    36  	subresource string
    37  	operation   Operation
    38  	options     runtime.Object
    39  	dryRun      bool
    40  	object      runtime.Object
    41  	oldObject   runtime.Object
    42  	userInfo    user.Info
    43  
    44  	// other elements are always accessed in single goroutine.
    45  	// But ValidatingAdmissionWebhook add annotations concurrently.
    46  	annotations     map[string]annotation
    47  	annotationsLock sync.RWMutex
    48  
    49  	reinvocationContext ReinvocationContext
    50  }
    51  
    52  type annotation struct {
    53  	level auditinternal.Level
    54  	value string
    55  }
    56  
    57  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 {
    58  	return &attributesRecord{
    59  		kind:                kind,
    60  		namespace:           namespace,
    61  		name:                name,
    62  		resource:            resource,
    63  		subresource:         subresource,
    64  		operation:           operation,
    65  		options:             operationOptions,
    66  		dryRun:              dryRun,
    67  		object:              object,
    68  		oldObject:           oldObject,
    69  		userInfo:            userInfo,
    70  		reinvocationContext: &reinvocationContext{},
    71  	}
    72  }
    73  
    74  func (record *attributesRecord) GetKind() schema.GroupVersionKind {
    75  	return record.kind
    76  }
    77  
    78  func (record *attributesRecord) GetNamespace() string {
    79  	return record.namespace
    80  }
    81  
    82  func (record *attributesRecord) GetName() string {
    83  	return record.name
    84  }
    85  
    86  func (record *attributesRecord) GetResource() schema.GroupVersionResource {
    87  	return record.resource
    88  }
    89  
    90  func (record *attributesRecord) GetSubresource() string {
    91  	return record.subresource
    92  }
    93  
    94  func (record *attributesRecord) GetOperation() Operation {
    95  	return record.operation
    96  }
    97  
    98  func (record *attributesRecord) GetOperationOptions() runtime.Object {
    99  	return record.options
   100  }
   101  
   102  func (record *attributesRecord) IsDryRun() bool {
   103  	return record.dryRun
   104  }
   105  
   106  func (record *attributesRecord) GetObject() runtime.Object {
   107  	return record.object
   108  }
   109  
   110  func (record *attributesRecord) GetOldObject() runtime.Object {
   111  	return record.oldObject
   112  }
   113  
   114  func (record *attributesRecord) GetUserInfo() user.Info {
   115  	return record.userInfo
   116  }
   117  
   118  // getAnnotations implements privateAnnotationsGetter.It's a private method used
   119  // by WithAudit decorator.
   120  func (record *attributesRecord) getAnnotations(maxLevel auditinternal.Level) map[string]string {
   121  	record.annotationsLock.RLock()
   122  	defer record.annotationsLock.RUnlock()
   123  
   124  	if record.annotations == nil {
   125  		return nil
   126  	}
   127  	cp := make(map[string]string, len(record.annotations))
   128  	for key, value := range record.annotations {
   129  		if value.level.Less(maxLevel) || value.level == maxLevel {
   130  			cp[key] = value.value
   131  		}
   132  	}
   133  	return cp
   134  }
   135  
   136  // AddAnnotation adds an annotation to attributesRecord with Metadata audit level
   137  func (record *attributesRecord) AddAnnotation(key, value string) error {
   138  	return record.AddAnnotationWithLevel(key, value, auditinternal.LevelMetadata)
   139  }
   140  
   141  func (record *attributesRecord) AddAnnotationWithLevel(key, value string, level auditinternal.Level) error {
   142  	if err := checkKeyFormat(key); err != nil {
   143  		return err
   144  	}
   145  	if level.Less(auditinternal.LevelMetadata) {
   146  		return fmt.Errorf("admission annotations are not allowed to be set at audit level lower than Metadata, key: %q, level: %s", key, level)
   147  	}
   148  	record.annotationsLock.Lock()
   149  	defer record.annotationsLock.Unlock()
   150  
   151  	if record.annotations == nil {
   152  		record.annotations = make(map[string]annotation)
   153  	}
   154  	annotation := annotation{level: level, value: value}
   155  	if v, ok := record.annotations[key]; ok && v != annotation {
   156  		return fmt.Errorf("admission annotations are not allowd to be overwritten, key:%q, old value: %v, new value: %v", key, record.annotations[key], annotation)
   157  	}
   158  	record.annotations[key] = annotation
   159  	return nil
   160  }
   161  
   162  func (record *attributesRecord) GetReinvocationContext() ReinvocationContext {
   163  	return record.reinvocationContext
   164  }
   165  
   166  type reinvocationContext struct {
   167  	// isReinvoke is true when admission plugins are being reinvoked
   168  	isReinvoke bool
   169  	// reinvokeRequested is true when an admission plugin requested a re-invocation of the chain
   170  	reinvokeRequested bool
   171  	// values stores reinvoke context values per plugin.
   172  	values map[string]interface{}
   173  }
   174  
   175  func (rc *reinvocationContext) IsReinvoke() bool {
   176  	return rc.isReinvoke
   177  }
   178  
   179  func (rc *reinvocationContext) SetIsReinvoke() {
   180  	rc.isReinvoke = true
   181  }
   182  
   183  func (rc *reinvocationContext) ShouldReinvoke() bool {
   184  	return rc.reinvokeRequested
   185  }
   186  
   187  func (rc *reinvocationContext) SetShouldReinvoke() {
   188  	rc.reinvokeRequested = true
   189  }
   190  
   191  func (rc *reinvocationContext) SetValue(plugin string, v interface{}) {
   192  	if rc.values == nil {
   193  		rc.values = map[string]interface{}{}
   194  	}
   195  	rc.values[plugin] = v
   196  }
   197  
   198  func (rc *reinvocationContext) Value(plugin string) interface{} {
   199  	return rc.values[plugin]
   200  }
   201  
   202  func checkKeyFormat(key string) error {
   203  	parts := strings.Split(key, "/")
   204  	if len(parts) != 2 {
   205  		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')")
   206  	}
   207  	if msgs := validation.IsQualifiedName(key); len(msgs) != 0 {
   208  		return fmt.Errorf("annotation key has invalid format %s. A qualified name like 'podsecuritypolicy.admission.k8s.io/admit-policy' is required.", strings.Join(msgs, ","))
   209  	}
   210  	return nil
   211  }