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