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 }