| ... | ... |
@@ -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 |