Browse code

block setting ownerReferences and finalizers

deads2k authored on 2016/08/17 04:15:20
Showing 8 changed files
... ...
@@ -26,7 +26,7 @@ This command launches an instance of the Kubernetes apiserver (kube\-apiserver).
26 26
 
27 27
 .PP
28 28
 \fB\-\-admission\-control\fP="AlwaysAdmit"
29
-    Ordered list of plug\-ins to do admission control of resources into cluster. Comma\-delimited list of: AlwaysAdmit, AlwaysDeny, AlwaysPullImages, BuildByStrategy, BuildDefaults, BuildOverrides, ClusterResourceOverride, DenyEscalatingExec, DenyExecOnPrivileged, ExternalIPRanger, InitialResources, LimitPodHardAntiAffinityTopology, LimitRanger, NamespaceAutoProvision, NamespaceExists, NamespaceLifecycle, OriginNamespaceLifecycle, OriginPodNodeEnvironment, PersistentVolumeLabel, PodNodeConstraints, PodSecurityPolicy, ProjectRequestLimit, ResourceQuota, RunOnceDuration, SCCExecRestrictions, SecurityContextConstraint, SecurityContextDeny, ServiceAccount, openshift.io/ClusterResourceQuota, openshift.io/ImageLimitRange, openshift.io/ImagePolicy, openshift.io/JenkinsBootstrapper, openshift.io/OriginResourceQuota, openshift.io/RestrictedEndpointsAdmission
29
+    Ordered list of plug\-ins to do admission control of resources into cluster. Comma\-delimited list of: AlwaysAdmit, AlwaysDeny, AlwaysPullImages, BuildByStrategy, BuildDefaults, BuildOverrides, ClusterResourceOverride, DenyEscalatingExec, DenyExecOnPrivileged, ExternalIPRanger, InitialResources, LimitPodHardAntiAffinityTopology, LimitRanger, NamespaceAutoProvision, NamespaceExists, NamespaceLifecycle, OriginNamespaceLifecycle, OriginPodNodeEnvironment, PersistentVolumeLabel, PodNodeConstraints, PodSecurityPolicy, ProjectRequestLimit, ResourceQuota, RunOnceDuration, SCCExecRestrictions, SecurityContextConstraint, SecurityContextDeny, ServiceAccount, openshift.io/ClusterResourceQuota, openshift.io/ImageLimitRange, openshift.io/ImagePolicy, openshift.io/JenkinsBootstrapper, openshift.io/OriginResourceQuota, openshift.io/OwnerReference, openshift.io/RestrictedEndpointsAdmission
30 30
 
31 31
 .PP
32 32
 \fB\-\-admission\-control\-config\-file\fP=""
... ...
@@ -122,6 +122,7 @@ func TestExampleObjectSchemas(t *testing.T) {
122 122
 			"test-image-stream-mapping":           nil, // skip &imageapi.ImageStreamMapping{},
123 123
 			"test-route":                          &routeapi.Route{},
124 124
 			"test-service":                        &kapi.Service{},
125
+			"test-service-with-finalizer":         &kapi.Service{},
125 126
 			"test-buildcli":                       &kapi.List{},
126 127
 			"test-buildcli-beta2":                 &kapi.List{},
127 128
 		},
128 129
new file mode 100644
... ...
@@ -0,0 +1,51 @@
0
+package ownerref
1
+
2
+import (
3
+	"fmt"
4
+	"io"
5
+
6
+	"k8s.io/kubernetes/pkg/admission"
7
+	"k8s.io/kubernetes/pkg/api/meta"
8
+	clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
9
+)
10
+
11
+func init() {
12
+	admission.RegisterPlugin("openshift.io/OwnerReference",
13
+		func(client clientset.Interface, config io.Reader) (admission.Interface, error) {
14
+			return NewOwnerReferenceBlocker()
15
+		})
16
+}
17
+
18
+// ownerReferenceAdmission implements an admission controller that stops anyone from setting owner references
19
+// TODO enforce this check based on the level of access allowed.  If you can delete the object, you can add an
20
+// owner reference.  We need to make sure the upstream controller works before allowing this.
21
+type ownerReferenceAdmission struct {
22
+	*admission.Handler
23
+}
24
+
25
+func NewOwnerReferenceBlocker() (admission.Interface, error) {
26
+	return &ownerReferenceAdmission{
27
+		Handler: admission.NewHandler(admission.Create, admission.Update),
28
+	}, nil
29
+}
30
+
31
+// Admit makes admission decisions while enforcing ownerReference
32
+func (q *ownerReferenceAdmission) Admit(a admission.Attributes) (err error) {
33
+	metadata, err := meta.Accessor(a.GetObject())
34
+	if err != nil {
35
+		// if we don't have object meta, we don't have fields we're trying to control
36
+		return nil
37
+	}
38
+
39
+	// TODO if we have an old object, only consider new owner references and finalizers
40
+	// this is critical when doing an actual authz check.
41
+
42
+	if ownerRefs := metadata.GetOwnerReferences(); len(ownerRefs) > 0 {
43
+		return admission.NewForbidden(a, fmt.Errorf("ownerReferences are disabled: %v", ownerRefs))
44
+	}
45
+	if finalizers := metadata.GetFinalizers(); len(finalizers) > 0 {
46
+		return admission.NewForbidden(a, fmt.Errorf("finalizers are disabled: %v", finalizers))
47
+	}
48
+
49
+	return nil
50
+}
0 51
new file mode 100644
... ...
@@ -0,0 +1,53 @@
0
+package ownerref
1
+
2
+import (
3
+	"testing"
4
+
5
+	"k8s.io/kubernetes/pkg/admission"
6
+	kapi "k8s.io/kubernetes/pkg/api"
7
+	"k8s.io/kubernetes/pkg/api/unversioned"
8
+)
9
+
10
+func TestAdmission(t *testing.T) {
11
+	testCases := []struct {
12
+		name          string
13
+		attributes    admission.Attributes
14
+		expectFailure bool
15
+	}{
16
+		{
17
+			name: "reject ownerref",
18
+			attributes: admission.NewAttributesRecord(&kapi.Pod{ObjectMeta: kapi.ObjectMeta{OwnerReferences: []kapi.OwnerReference{{}}}},
19
+				nil, unversioned.GroupVersionKind{}, "", "", unversioned.GroupVersionResource{}, "", admission.Create, nil),
20
+			expectFailure: true,
21
+		},
22
+		{
23
+			name: "reject finalizer",
24
+			attributes: admission.NewAttributesRecord(&kapi.Pod{ObjectMeta: kapi.ObjectMeta{Finalizers: []string{""}}},
25
+				nil, unversioned.GroupVersionKind{}, "", "", unversioned.GroupVersionResource{}, "", admission.Create, nil),
26
+			expectFailure: true,
27
+		},
28
+		{
29
+			name: "allow",
30
+			attributes: admission.NewAttributesRecord(&kapi.Pod{},
31
+				nil, unversioned.GroupVersionKind{}, "", "", unversioned.GroupVersionResource{}, "", admission.Create, nil),
32
+		},
33
+	}
34
+
35
+	for _, tc := range testCases {
36
+		admission, err := NewOwnerReferenceBlocker()
37
+		if err != nil {
38
+			t.Errorf("%v: unexpected error: %v", tc.name, err)
39
+			continue
40
+		}
41
+
42
+		admissionErr := admission.Admit(tc.attributes)
43
+		if admissionErr == nil && tc.expectFailure {
44
+			t.Errorf("%v: missing error", tc.name)
45
+			continue
46
+		}
47
+		if admissionErr != nil && !tc.expectFailure {
48
+			t.Errorf("%v: unexpected error: %v", tc.name, admissionErr)
49
+			continue
50
+		}
51
+	}
52
+}
... ...
@@ -315,6 +315,7 @@ var (
315 315
 		"openshift.io/JenkinsBootstrapper",
316 316
 		"BuildByStrategy",
317 317
 		imageadmission.PluginName,
318
+		"openshift.io/OwnerReference",
318 319
 		quotaadmission.PluginName,
319 320
 	}
320 321
 
... ...
@@ -337,6 +338,7 @@ var (
337 337
 		"LimitPodHardAntiAffinityTopology",
338 338
 		"SCCExecRestrictions",
339 339
 		"PersistentVolumeLabel",
340
+		"openshift.io/OwnerReference",
340 341
 		// NOTE: quotaadmission and ClusterResourceQuota must be the last 2 plugins.
341 342
 		// DO NOT ADD ANY PLUGINS AFTER THIS LINE!
342 343
 		quotaadmission.PluginName,
... ...
@@ -370,6 +372,7 @@ var (
370 370
 		"LimitPodHardAntiAffinityTopology",
371 371
 		"SCCExecRestrictions",
372 372
 		"PersistentVolumeLabel",
373
+		"openshift.io/OwnerReference",
373 374
 		// NOTE: quotaadmission and ClusterResourceQuota must be the last 2 plugins.
374 375
 		// DO NOT ADD ANY PLUGINS AFTER THIS LINE!
375 376
 		quotaadmission.PluginName,
... ...
@@ -9,6 +9,7 @@ import (
9 9
 	"k8s.io/kubernetes/pkg/util/sets"
10 10
 
11 11
 	// Admission control plug-ins used by OpenShift
12
+	_ "github.com/openshift/origin/pkg/api/admission/ownerref"
12 13
 	_ "github.com/openshift/origin/pkg/build/admission/defaults"
13 14
 	_ "github.com/openshift/origin/pkg/build/admission/jenkinsbootstrapper"
14 15
 	_ "github.com/openshift/origin/pkg/build/admission/overrides"
... ...
@@ -60,6 +61,7 @@ var (
60 60
 		"LimitPodHardAntiAffinityTopology",
61 61
 		"SCCExecRestrictions",
62 62
 		"PersistentVolumeLabel",
63
+		"openshift.io/OwnerReference",
63 64
 		quotaadmission.PluginName,
64 65
 		"openshift.io/ClusterResourceQuota",
65 66
 	)
... ...
@@ -129,6 +129,7 @@ os::test::junit::declare_suite_start "cmd/basicresources/services"
129 129
 os::cmd::expect_success 'oc get services'
130 130
 os::cmd::expect_success 'oc create -f test/integration/testdata/test-service.json'
131 131
 os::cmd::expect_success 'oc delete services frontend'
132
+os::cmd::expect_failure_and_text 'oc create -f test/integration/testdata/test-service-with-finalizer.json' "finalizers are disabled"
132 133
 echo "services: ok"
133 134
 os::test::junit::declare_suite_end
134 135
 
135 136
new file mode 100644
... ...
@@ -0,0 +1,31 @@
0
+{
1
+  "kind": "Service",
2
+  "apiVersion": "v1",
3
+  "metadata": {
4
+    "name": "frontend",
5
+    "creationTimestamp": null,
6
+    "labels": {
7
+      "name": "frontend"
8
+    },
9
+    "finalizers": ["fake/one"]
10
+  },
11
+  "spec": {
12
+    "ports": [
13
+      {
14
+        "protocol": "TCP",
15
+        "port": 9998,
16
+        "targetPort": 9998,
17
+        "nodePort": 0
18
+      }
19
+    ],
20
+    "selector": {
21
+      "name": "frontend"
22
+    },
23
+    "portalIP": "",
24
+    "type": "ClusterIP",
25
+    "sessionAffinity": "None"
26
+  },
27
+  "status": {
28
+    "loadBalancer": {}
29
+  }
30
+}
0 31
\ No newline at end of file