1/* 2Copyright 2016 The Kubernetes Authors. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package gc 18 19import ( 20 "context" 21 "fmt" 22 "io" 23 24 apiequality "k8s.io/apimachinery/pkg/api/equality" 25 "k8s.io/apimachinery/pkg/api/meta" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "k8s.io/apimachinery/pkg/runtime" 28 "k8s.io/apimachinery/pkg/runtime/schema" 29 "k8s.io/apimachinery/pkg/types" 30 "k8s.io/apiserver/pkg/admission" 31 "k8s.io/apiserver/pkg/authentication/user" 32 "k8s.io/apiserver/pkg/authorization/authorizer" 33) 34 35// PluginName indicates name of admission plugin. 36const PluginName = "OwnerReferencesPermissionEnforcement" 37 38// Register registers a plugin 39func Register(plugins *admission.Plugins) { 40 plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) { 41 // the pods/status endpoint is ignored by this plugin since old kubelets 42 // corrupt them. the pod status strategy ensures status updates cannot mutate 43 // ownerRef. 44 whiteList := []whiteListItem{ 45 { 46 groupResource: schema.GroupResource{Resource: "pods"}, 47 subresource: "status", 48 }, 49 } 50 return &gcPermissionsEnforcement{ 51 Handler: admission.NewHandler(admission.Create, admission.Update), 52 whiteList: whiteList, 53 }, nil 54 }) 55} 56 57// gcPermissionsEnforcement is an implementation of admission.Interface. 58type gcPermissionsEnforcement struct { 59 *admission.Handler 60 61 authorizer authorizer.Authorizer 62 63 restMapper meta.RESTMapper 64 65 // items in this whitelist are ignored upon admission. 66 // any item in this list must protect against ownerRef mutations 67 // via strategy enforcement. 68 whiteList []whiteListItem 69} 70 71var _ admission.ValidationInterface = &gcPermissionsEnforcement{} 72 73// whiteListItem describes an entry in a whitelist ignored by gc permission enforcement. 74type whiteListItem struct { 75 groupResource schema.GroupResource 76 subresource string 77} 78 79// isWhiteListed returns true if the specified item is in the whitelist. 80func (a *gcPermissionsEnforcement) isWhiteListed(groupResource schema.GroupResource, subresource string) bool { 81 for _, item := range a.whiteList { 82 if item.groupResource == groupResource && item.subresource == subresource { 83 return true 84 } 85 } 86 return false 87} 88 89func (a *gcPermissionsEnforcement) Validate(ctx context.Context, attributes admission.Attributes, o admission.ObjectInterfaces) (err error) { 90 // // if the request is in the whitelist, we skip mutation checks for this resource. 91 if a.isWhiteListed(attributes.GetResource().GroupResource(), attributes.GetSubresource()) { 92 return nil 93 } 94 95 // if we aren't changing owner references, then the edit is always allowed 96 if !isChangingOwnerReference(attributes.GetObject(), attributes.GetOldObject()) { 97 return nil 98 } 99 100 // if you are creating a thing, you should always be allowed to set an owner ref since you logically had the power 101 // to never create it. We still need to check block owner deletion below, because the power to delete does not 102 // imply the power to prevent deletion on other resources. 103 if attributes.GetOperation() != admission.Create { 104 deleteAttributes := authorizer.AttributesRecord{ 105 User: attributes.GetUserInfo(), 106 Verb: "delete", 107 Namespace: attributes.GetNamespace(), 108 APIGroup: attributes.GetResource().Group, 109 APIVersion: attributes.GetResource().Version, 110 Resource: attributes.GetResource().Resource, 111 Subresource: attributes.GetSubresource(), 112 Name: attributes.GetName(), 113 ResourceRequest: true, 114 Path: "", 115 } 116 decision, reason, err := a.authorizer.Authorize(ctx, deleteAttributes) 117 if decision != authorizer.DecisionAllow { 118 return admission.NewForbidden(attributes, fmt.Errorf("cannot set an ownerRef on a resource you can't delete: %v, %v", reason, err)) 119 } 120 } 121 122 // Further check if the user is setting ownerReference.blockOwnerDeletion to 123 // true. If so, only allows the change if the user has delete permission of 124 // the _OWNER_ 125 newBlockingRefs := newBlockingOwnerDeletionRefs(attributes.GetObject(), attributes.GetOldObject()) 126 if len(newBlockingRefs) == 0 { 127 return nil 128 } 129 130 // There can be a case where a restMapper tries to hit discovery endpoints and times out if the network is inaccessible. 131 // This can prevent creating the pod to run the network to be able to do discovery and it appears as a timeout, not a rejection. 132 // Because the timeout is wrapper on admission/request, we can run a single check to see if the user can finalize any 133 // possible resource. 134 if decision, _, _ := a.authorizer.Authorize(ctx, finalizeAnythingRecord(attributes.GetUserInfo())); decision == authorizer.DecisionAllow { 135 return nil 136 } 137 138 for _, ref := range newBlockingRefs { 139 records, err := a.ownerRefToDeleteAttributeRecords(ref, attributes) 140 if err != nil { 141 return admission.NewForbidden(attributes, fmt.Errorf("cannot set blockOwnerDeletion in this case because cannot find RESTMapping for APIVersion %s Kind %s: %v", ref.APIVersion, ref.Kind, err)) 142 } 143 // Multiple records are returned if ref.Kind could map to multiple 144 // resources. User needs to have delete permission on all the 145 // matched Resources. 146 for _, record := range records { 147 decision, reason, err := a.authorizer.Authorize(ctx, record) 148 if decision != authorizer.DecisionAllow { 149 return admission.NewForbidden(attributes, fmt.Errorf("cannot set blockOwnerDeletion if an ownerReference refers to a resource you can't set finalizers on: %v, %v", reason, err)) 150 } 151 } 152 } 153 154 return nil 155 156} 157 158func isChangingOwnerReference(newObj, oldObj runtime.Object) bool { 159 newMeta, err := meta.Accessor(newObj) 160 if err != nil { 161 // if we don't have objectmeta, we don't have the object reference 162 return false 163 } 164 165 if oldObj == nil { 166 return len(newMeta.GetOwnerReferences()) > 0 167 } 168 oldMeta, err := meta.Accessor(oldObj) 169 if err != nil { 170 // if we don't have objectmeta, we don't have the object reference 171 return false 172 } 173 174 // compare the old and new. If they aren't the same, then we're trying to change an ownerRef 175 oldOwners := oldMeta.GetOwnerReferences() 176 newOwners := newMeta.GetOwnerReferences() 177 if len(oldOwners) != len(newOwners) { 178 return true 179 } 180 for i := range oldOwners { 181 if !apiequality.Semantic.DeepEqual(oldOwners[i], newOwners[i]) { 182 return true 183 } 184 } 185 186 return false 187} 188 189func finalizeAnythingRecord(userInfo user.Info) authorizer.AttributesRecord { 190 return authorizer.AttributesRecord{ 191 User: userInfo, 192 Verb: "update", 193 APIGroup: "*", 194 APIVersion: "*", 195 Resource: "*", 196 Subresource: "finalizers", 197 Name: "*", 198 ResourceRequest: true, 199 Path: "", 200 } 201} 202 203// Translates ref to a DeleteAttribute deleting the object referred by the ref. 204// OwnerReference only records the object kind, which might map to multiple 205// resources, so multiple DeleteAttribute might be returned. 206func (a *gcPermissionsEnforcement) ownerRefToDeleteAttributeRecords(ref metav1.OwnerReference, attributes admission.Attributes) ([]authorizer.AttributesRecord, error) { 207 var ret []authorizer.AttributesRecord 208 groupVersion, err := schema.ParseGroupVersion(ref.APIVersion) 209 if err != nil { 210 return ret, err 211 } 212 mappings, err := a.restMapper.RESTMappings(schema.GroupKind{Group: groupVersion.Group, Kind: ref.Kind}, groupVersion.Version) 213 if err != nil { 214 return ret, err 215 } 216 for _, mapping := range mappings { 217 ar := authorizer.AttributesRecord{ 218 User: attributes.GetUserInfo(), 219 Verb: "update", 220 APIGroup: mapping.Resource.Group, 221 APIVersion: mapping.Resource.Version, 222 Resource: mapping.Resource.Resource, 223 Subresource: "finalizers", 224 Name: ref.Name, 225 ResourceRequest: true, 226 Path: "", 227 } 228 if mapping.Scope.Name() == meta.RESTScopeNameNamespace { 229 // if the owner is namespaced, it must be in the same namespace as the dependent is. 230 ar.Namespace = attributes.GetNamespace() 231 } 232 ret = append(ret, ar) 233 } 234 return ret, nil 235} 236 237// only keeps the blocking refs 238func blockingOwnerRefs(refs []metav1.OwnerReference) []metav1.OwnerReference { 239 var ret []metav1.OwnerReference 240 for _, ref := range refs { 241 if ref.BlockOwnerDeletion != nil && *ref.BlockOwnerDeletion == true { 242 ret = append(ret, ref) 243 } 244 } 245 return ret 246} 247 248func indexByUID(refs []metav1.OwnerReference) map[types.UID]metav1.OwnerReference { 249 ret := make(map[types.UID]metav1.OwnerReference) 250 for _, ref := range refs { 251 ret[ref.UID] = ref 252 } 253 return ret 254} 255 256// Returns new blocking ownerReferences, and references whose blockOwnerDeletion 257// field is changed from nil or false to true. 258func newBlockingOwnerDeletionRefs(newObj, oldObj runtime.Object) []metav1.OwnerReference { 259 newMeta, err := meta.Accessor(newObj) 260 if err != nil { 261 // if we don't have objectmeta, we don't have the object reference 262 return nil 263 } 264 newRefs := newMeta.GetOwnerReferences() 265 blockingNewRefs := blockingOwnerRefs(newRefs) 266 if len(blockingNewRefs) == 0 { 267 return nil 268 } 269 270 if oldObj == nil { 271 return blockingNewRefs 272 } 273 oldMeta, err := meta.Accessor(oldObj) 274 if err != nil { 275 // if we don't have objectmeta, treat it as if all the ownerReference are newly created 276 return blockingNewRefs 277 } 278 279 var ret []metav1.OwnerReference 280 indexedOldRefs := indexByUID(oldMeta.GetOwnerReferences()) 281 for _, ref := range blockingNewRefs { 282 oldRef, ok := indexedOldRefs[ref.UID] 283 if !ok { 284 // if ref is newly added, and it's blocking, then returns it. 285 ret = append(ret, ref) 286 continue 287 } 288 wasNotBlocking := oldRef.BlockOwnerDeletion == nil || *oldRef.BlockOwnerDeletion == false 289 if wasNotBlocking { 290 ret = append(ret, ref) 291 } 292 } 293 return ret 294} 295 296func (a *gcPermissionsEnforcement) SetAuthorizer(authorizer authorizer.Authorizer) { 297 a.authorizer = authorizer 298} 299 300func (a *gcPermissionsEnforcement) SetRESTMapper(restMapper meta.RESTMapper) { 301 a.restMapper = restMapper 302} 303 304func (a *gcPermissionsEnforcement) ValidateInitialization() error { 305 if a.authorizer == nil { 306 return fmt.Errorf("missing authorizer") 307 } 308 if a.restMapper == nil { 309 return fmt.Errorf("missing restMapper") 310 } 311 return nil 312} 313