... | ... |
@@ -14743,6 +14743,8 @@ |
14743 | 14743 |
"required": [ |
14744 | 14744 |
"priority", |
14745 | 14745 |
"allowPrivilegedContainer", |
14746 |
+ "defaultAddCapabilities", |
|
14747 |
+ "requiredDropCapabilities", |
|
14746 | 14748 |
"allowedCapabilities", |
14747 | 14749 |
"allowHostDirVolumePlugin", |
14748 | 14750 |
"allowHostNetwork", |
... | ... |
@@ -14771,6 +14773,20 @@ |
14771 | 14771 |
"type": "boolean", |
14772 | 14772 |
"description": "allow containers to run as privileged" |
14773 | 14773 |
}, |
14774 |
+ "defaultAddCapabilities": { |
|
14775 |
+ "type": "array", |
|
14776 |
+ "items": { |
|
14777 |
+ "$ref": "v1.Capability" |
|
14778 |
+ }, |
|
14779 |
+ "description": "capabilities that are added by default but may be dropped" |
|
14780 |
+ }, |
|
14781 |
+ "requiredDropCapabilities": { |
|
14782 |
+ "type": "array", |
|
14783 |
+ "items": { |
|
14784 |
+ "$ref": "v1.Capability" |
|
14785 |
+ }, |
|
14786 |
+ "description": "capabilities that will be dropped by default and may not be added" |
|
14787 |
+ }, |
|
14774 | 14788 |
"allowedCapabilities": { |
14775 | 14789 |
"type": "array", |
14776 | 14790 |
"items": { |
... | ... |
@@ -27,6 +27,122 @@ func NewTestAdmission(store cache.Store, kclient client.Interface) kadmission.In |
27 | 27 |
} |
28 | 28 |
} |
29 | 29 |
|
30 |
+func TestAdmitCaps(t *testing.T) { |
|
31 |
+ createPodWithCaps := func(caps *kapi.Capabilities) *kapi.Pod { |
|
32 |
+ pod := goodPod() |
|
33 |
+ pod.Spec.Containers[0].SecurityContext.Capabilities = caps |
|
34 |
+ return pod |
|
35 |
+ } |
|
36 |
+ |
|
37 |
+ restricted := restrictiveSCC() |
|
38 |
+ |
|
39 |
+ allowsFooInAllowed := restrictiveSCC() |
|
40 |
+ allowsFooInAllowed.Name = "allowCapInAllowed" |
|
41 |
+ allowsFooInAllowed.AllowedCapabilities = []kapi.Capability{"foo"} |
|
42 |
+ |
|
43 |
+ allowsFooInRequired := restrictiveSCC() |
|
44 |
+ allowsFooInRequired.Name = "allowCapInRequired" |
|
45 |
+ allowsFooInRequired.DefaultAddCapabilities = []kapi.Capability{"foo"} |
|
46 |
+ |
|
47 |
+ requiresFooToBeDropped := restrictiveSCC() |
|
48 |
+ requiresFooToBeDropped.Name = "requireDrop" |
|
49 |
+ requiresFooToBeDropped.RequiredDropCapabilities = []kapi.Capability{"foo"} |
|
50 |
+ |
|
51 |
+ tc := map[string]struct { |
|
52 |
+ pod *kapi.Pod |
|
53 |
+ sccs []*kapi.SecurityContextConstraints |
|
54 |
+ shouldPass bool |
|
55 |
+ expectedCapabilities *kapi.Capabilities |
|
56 |
+ }{ |
|
57 |
+ // UC 1: if an SCC does not define allowed or required caps then a pod requesting a cap |
|
58 |
+ // should be rejected. |
|
59 |
+ "should reject cap add when not allowed or required": { |
|
60 |
+ pod: createPodWithCaps(&kapi.Capabilities{Add: []kapi.Capability{"foo"}}), |
|
61 |
+ sccs: []*kapi.SecurityContextConstraints{restricted}, |
|
62 |
+ shouldPass: false, |
|
63 |
+ }, |
|
64 |
+ // UC 2: if an SCC allows a cap in the allowed field it should accept the pod request |
|
65 |
+ // to add the cap. |
|
66 |
+ "should accept cap add when in allowed": { |
|
67 |
+ pod: createPodWithCaps(&kapi.Capabilities{Add: []kapi.Capability{"foo"}}), |
|
68 |
+ sccs: []*kapi.SecurityContextConstraints{restricted, allowsFooInAllowed}, |
|
69 |
+ shouldPass: true, |
|
70 |
+ }, |
|
71 |
+ // UC 3: if an SCC requires a cap then it should accept the pod request |
|
72 |
+ // to add the cap. |
|
73 |
+ "should accept cap add when in required": { |
|
74 |
+ pod: createPodWithCaps(&kapi.Capabilities{Add: []kapi.Capability{"foo"}}), |
|
75 |
+ sccs: []*kapi.SecurityContextConstraints{restricted, allowsFooInRequired}, |
|
76 |
+ shouldPass: true, |
|
77 |
+ }, |
|
78 |
+ // UC 4: if an SCC requires a cap to be dropped then it should fail both |
|
79 |
+ // in the verification of adds and verification of drops |
|
80 |
+ "should reject cap add when requested cap is required to be dropped": { |
|
81 |
+ pod: createPodWithCaps(&kapi.Capabilities{Add: []kapi.Capability{"foo"}}), |
|
82 |
+ sccs: []*kapi.SecurityContextConstraints{restricted, requiresFooToBeDropped}, |
|
83 |
+ shouldPass: false, |
|
84 |
+ }, |
|
85 |
+ // UC 5: if an SCC requires a cap to be dropped it should accept |
|
86 |
+ // a manual request to drop the cap. |
|
87 |
+ "should accept cap drop when cap is required to be dropped": { |
|
88 |
+ pod: createPodWithCaps(&kapi.Capabilities{Drop: []kapi.Capability{"foo"}}), |
|
89 |
+ sccs: []*kapi.SecurityContextConstraints{restricted, requiresFooToBeDropped}, |
|
90 |
+ shouldPass: true, |
|
91 |
+ }, |
|
92 |
+ // UC 6: required add is defaulted |
|
93 |
+ "required add is defaulted": { |
|
94 |
+ pod: goodPod(), |
|
95 |
+ sccs: []*kapi.SecurityContextConstraints{allowsFooInRequired}, |
|
96 |
+ shouldPass: true, |
|
97 |
+ expectedCapabilities: &kapi.Capabilities{ |
|
98 |
+ Add: []kapi.Capability{"foo"}, |
|
99 |
+ }, |
|
100 |
+ }, |
|
101 |
+ // UC 7: required drop is defaulted |
|
102 |
+ "required drop is defaulted": { |
|
103 |
+ pod: goodPod(), |
|
104 |
+ sccs: []*kapi.SecurityContextConstraints{requiresFooToBeDropped}, |
|
105 |
+ shouldPass: true, |
|
106 |
+ expectedCapabilities: &kapi.Capabilities{ |
|
107 |
+ Drop: []kapi.Capability{"foo"}, |
|
108 |
+ }, |
|
109 |
+ }, |
|
110 |
+ } |
|
111 |
+ |
|
112 |
+ for k, v := range tc { |
|
113 |
+ testSCCAdmit(k, v.sccs, v.pod, v.shouldPass, t) |
|
114 |
+ |
|
115 |
+ if v.expectedCapabilities != nil { |
|
116 |
+ if !reflect.DeepEqual(v.expectedCapabilities, v.pod.Spec.Containers[0].SecurityContext.Capabilities) { |
|
117 |
+ t.Errorf("%s resulted in caps that were not expected - expected: %v, received: %v", k, v.expectedCapabilities, v.pod.Spec.Containers[0].SecurityContext.Capabilities) |
|
118 |
+ } |
|
119 |
+ } |
|
120 |
+ } |
|
121 |
+} |
|
122 |
+ |
|
123 |
+func testSCCAdmit(testCaseName string, sccs []*kapi.SecurityContextConstraints, pod *kapi.Pod, shouldPass bool, t *testing.T) { |
|
124 |
+ namespace := createNamespaceForTest() |
|
125 |
+ serviceAccount := createSAForTest() |
|
126 |
+ tc := testclient.NewSimpleFake(namespace, serviceAccount) |
|
127 |
+ store := cache.NewStore(cache.MetaNamespaceKeyFunc) |
|
128 |
+ |
|
129 |
+ for _, scc := range sccs { |
|
130 |
+ store.Add(scc) |
|
131 |
+ } |
|
132 |
+ |
|
133 |
+ plugin := NewTestAdmission(store, tc) |
|
134 |
+ |
|
135 |
+ attrs := kadmission.NewAttributesRecord(pod, "Pod", "namespace", "", string(kapi.ResourcePods), "", kadmission.Create, &user.DefaultInfo{}) |
|
136 |
+ err := plugin.Admit(attrs) |
|
137 |
+ |
|
138 |
+ if shouldPass && err != nil { |
|
139 |
+ t.Errorf("%s expected no errors but received %v", testCaseName, err) |
|
140 |
+ } |
|
141 |
+ if !shouldPass && err == nil { |
|
142 |
+ t.Errorf("%s expected errors but received none", testCaseName) |
|
143 |
+ } |
|
144 |
+} |
|
145 |
+ |
|
30 | 146 |
func TestAdmit(t *testing.T) { |
31 | 147 |
// create the annotated namespace and add it to the fake client |
32 | 148 |
namespace := createNamespaceForTest() |