1
16
17 package ctbattest
18
19 import (
20 "context"
21 "errors"
22 "fmt"
23 "testing"
24
25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26 "k8s.io/apimachinery/pkg/runtime"
27 "k8s.io/apimachinery/pkg/runtime/schema"
28 "k8s.io/apiserver/pkg/admission"
29 "k8s.io/apiserver/pkg/authentication/user"
30 "k8s.io/apiserver/pkg/authorization/authorizer"
31 "k8s.io/apiserver/pkg/util/feature"
32 featuregatetesting "k8s.io/component-base/featuregate/testing"
33 certificatesapi "k8s.io/kubernetes/pkg/apis/certificates"
34 "k8s.io/kubernetes/pkg/features"
35 )
36
37 func TestPluginValidate(t *testing.T) {
38 tests := []struct {
39 description string
40 clusterTrustBundleFeatureEnabled bool
41 attributes admission.Attributes
42 allowedName string
43 allowed bool
44 authzErr error
45 }{
46 {
47 description: "wrong type on create",
48 clusterTrustBundleFeatureEnabled: true,
49 attributes: &testAttributes{
50 resource: certificatesapi.Resource("clustertrustbundles"),
51 obj: &certificatesapi.ClusterTrustBundleList{},
52 operation: admission.Create,
53 },
54 allowed: false,
55 },
56 {
57 description: "wrong type on update",
58 clusterTrustBundleFeatureEnabled: true,
59 attributes: &testAttributes{
60 resource: certificatesapi.Resource("clustertrustbundles"),
61 obj: &certificatesapi.ClusterTrustBundleList{},
62 operation: admission.Update,
63 },
64 allowed: false,
65 },
66 {
67 description: "reject requests if looking up permissions fails",
68 clusterTrustBundleFeatureEnabled: true,
69 attributes: &testAttributes{
70 resource: certificatesapi.Resource("clustertrustbundles"),
71 obj: &certificatesapi.ClusterTrustBundle{
72 Spec: certificatesapi.ClusterTrustBundleSpec{
73 SignerName: "abc.com/xyz",
74 },
75 },
76 operation: admission.Update,
77 },
78 authzErr: errors.New("forced error"),
79 allowed: false,
80 },
81 {
82 description: "should allow create if no signer name is specified",
83 clusterTrustBundleFeatureEnabled: true,
84 allowedName: "abc.com/xyz",
85 attributes: &testAttributes{
86 resource: certificatesapi.Resource("clustertrustbundles"),
87 obj: &certificatesapi.ClusterTrustBundle{
88 Spec: certificatesapi.ClusterTrustBundleSpec{},
89 },
90 operation: admission.Create,
91 },
92 allowed: true,
93 },
94 {
95 description: "should allow update if no signer name is specified",
96 clusterTrustBundleFeatureEnabled: true,
97 allowedName: "abc.com/xyz",
98 attributes: &testAttributes{
99 resource: certificatesapi.Resource("clustertrustbundles"),
100 oldObj: &certificatesapi.ClusterTrustBundle{
101 Spec: certificatesapi.ClusterTrustBundleSpec{},
102 },
103 obj: &certificatesapi.ClusterTrustBundle{
104 Spec: certificatesapi.ClusterTrustBundleSpec{},
105 },
106 operation: admission.Update,
107 },
108 allowed: true,
109 },
110 {
111 description: "should allow create if user is authorized for specific signerName",
112 clusterTrustBundleFeatureEnabled: true,
113 allowedName: "abc.com/xyz",
114 attributes: &testAttributes{
115 resource: certificatesapi.Resource("clustertrustbundles"),
116 obj: &certificatesapi.ClusterTrustBundle{
117 Spec: certificatesapi.ClusterTrustBundleSpec{
118 SignerName: "abc.com/xyz",
119 },
120 },
121 operation: admission.Create,
122 },
123 allowed: true,
124 },
125 {
126 description: "should allow update if user is authorized for specific signerName",
127 clusterTrustBundleFeatureEnabled: true,
128 allowedName: "abc.com/xyz",
129 attributes: &testAttributes{
130 resource: certificatesapi.Resource("clustertrustbundles"),
131 oldObj: &certificatesapi.ClusterTrustBundle{
132 Spec: certificatesapi.ClusterTrustBundleSpec{
133 SignerName: "abc.com/xyz",
134 },
135 },
136 obj: &certificatesapi.ClusterTrustBundle{
137 Spec: certificatesapi.ClusterTrustBundleSpec{
138 SignerName: "abc.com/xyz",
139 },
140 },
141 operation: admission.Update,
142 },
143 allowed: true,
144 },
145 {
146 description: "should allow create if user is authorized with wildcard",
147 clusterTrustBundleFeatureEnabled: true,
148 allowedName: "abc.com/*",
149 attributes: &testAttributes{
150 resource: certificatesapi.Resource("clustertrustbundles"),
151 obj: &certificatesapi.ClusterTrustBundle{
152 Spec: certificatesapi.ClusterTrustBundleSpec{
153 SignerName: "abc.com/xyz",
154 },
155 },
156 operation: admission.Create,
157 },
158 allowed: true,
159 },
160 {
161 description: "should allow update if user is authorized with wildcard",
162 clusterTrustBundleFeatureEnabled: true,
163 allowedName: "abc.com/*",
164 attributes: &testAttributes{
165 resource: certificatesapi.Resource("clustertrustbundles"),
166 oldObj: &certificatesapi.ClusterTrustBundle{
167 Spec: certificatesapi.ClusterTrustBundleSpec{
168 SignerName: "abc.com/xyz",
169 },
170 },
171 obj: &certificatesapi.ClusterTrustBundle{
172 Spec: certificatesapi.ClusterTrustBundleSpec{
173 SignerName: "abc.com/xyz",
174 },
175 },
176 operation: admission.Update,
177 },
178 allowed: true,
179 },
180 {
181 description: "should deny create if user does not have permission for this signerName",
182 clusterTrustBundleFeatureEnabled: true,
183 allowedName: "notabc.com/xyz",
184 attributes: &testAttributes{
185 resource: certificatesapi.Resource("clustertrustbundles"),
186 obj: &certificatesapi.ClusterTrustBundle{
187 Spec: certificatesapi.ClusterTrustBundleSpec{
188 SignerName: "abc.com/xyz",
189 },
190 },
191 operation: admission.Create,
192 },
193 allowed: false,
194 },
195 {
196 description: "should deny update if user does not have permission for this signerName",
197 clusterTrustBundleFeatureEnabled: true,
198 allowedName: "notabc.com/xyz",
199 attributes: &testAttributes{
200 resource: certificatesapi.Resource("clustertrustbundles"),
201 obj: &certificatesapi.ClusterTrustBundle{
202 Spec: certificatesapi.ClusterTrustBundleSpec{
203 SignerName: "abc.com/xyz",
204 },
205 },
206 operation: admission.Update,
207 },
208 allowed: false,
209 },
210 {
211 description: "should always allow no-op update",
212 clusterTrustBundleFeatureEnabled: true,
213 authzErr: errors.New("broken"),
214 attributes: &testAttributes{
215 resource: certificatesapi.Resource("clustertrustbundles"),
216 oldObj: &certificatesapi.ClusterTrustBundle{
217 Spec: certificatesapi.ClusterTrustBundleSpec{
218 SignerName: "panda.com/foo",
219 },
220 },
221 obj: &certificatesapi.ClusterTrustBundle{
222 Spec: certificatesapi.ClusterTrustBundleSpec{
223 SignerName: "panda.com/foo",
224 },
225 },
226 operation: admission.Update,
227 },
228 allowed: true,
229 },
230 {
231 description: "should always allow finalizer update",
232 clusterTrustBundleFeatureEnabled: true,
233 authzErr: errors.New("broken"),
234 attributes: &testAttributes{
235 resource: certificatesapi.Resource("clustertrustbundles"),
236 oldObj: &certificatesapi.ClusterTrustBundle{
237 Spec: certificatesapi.ClusterTrustBundleSpec{
238 SignerName: "panda.com/foo",
239 },
240 },
241 obj: &certificatesapi.ClusterTrustBundle{
242 ObjectMeta: metav1.ObjectMeta{
243 OwnerReferences: []metav1.OwnerReference{
244 {APIVersion: "something"},
245 },
246 },
247 Spec: certificatesapi.ClusterTrustBundleSpec{
248 SignerName: "panda.com/foo",
249 },
250 },
251 operation: admission.Update,
252 },
253 allowed: true,
254 },
255 }
256
257 for _, tc := range tests {
258 t.Run(tc.description, func(t *testing.T) {
259 p := Plugin{
260 authz: fakeAuthorizer{
261 t: t,
262 verb: "attest",
263 allowedName: tc.allowedName,
264 decision: authorizer.DecisionAllow,
265 err: tc.authzErr,
266 },
267 }
268
269 defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.ClusterTrustBundle, tc.clusterTrustBundleFeatureEnabled)()
270 p.InspectFeatureGates(feature.DefaultFeatureGate)
271
272 err := p.Validate(context.Background(), tc.attributes, nil)
273 if err == nil && !tc.allowed {
274 t.Errorf("Expected authorization policy to reject ClusterTrustBundle but it was allowed")
275 }
276 if err != nil && tc.allowed {
277 t.Errorf("Expected authorization policy to accept ClusterTrustBundle but it was rejected: %v", err)
278 }
279 })
280 }
281 }
282
283 type fakeAuthorizer struct {
284 t *testing.T
285 verb string
286 allowedName string
287 decision authorizer.Decision
288 err error
289 }
290
291 func (f fakeAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
292 if f.err != nil {
293 return f.decision, "forced error", f.err
294 }
295 if a.GetVerb() != f.verb {
296 return authorizer.DecisionDeny, fmt.Sprintf("unrecognised verb '%s'", a.GetVerb()), nil
297 }
298 if a.GetAPIGroup() != "certificates.k8s.io" {
299 return authorizer.DecisionDeny, fmt.Sprintf("unrecognised groupName '%s'", a.GetAPIGroup()), nil
300 }
301 if a.GetAPIVersion() != "*" {
302 return authorizer.DecisionDeny, fmt.Sprintf("unrecognised apiVersion '%s'", a.GetAPIVersion()), nil
303 }
304 if a.GetResource() != "signers" {
305 return authorizer.DecisionDeny, fmt.Sprintf("unrecognised resource '%s'", a.GetResource()), nil
306 }
307 if a.GetName() != f.allowedName {
308 return authorizer.DecisionDeny, fmt.Sprintf("unrecognised resource name '%s'", a.GetName()), nil
309 }
310 if !a.IsResourceRequest() {
311 return authorizer.DecisionDeny, fmt.Sprintf("unrecognised IsResourceRequest '%t'", a.IsResourceRequest()), nil
312 }
313 return f.decision, "", nil
314 }
315
316 type testAttributes struct {
317 resource schema.GroupResource
318 subresource string
319 operation admission.Operation
320 obj, oldObj runtime.Object
321 name string
322
323 admission.Attributes
324 }
325
326 func (t *testAttributes) GetResource() schema.GroupVersionResource {
327 return t.resource.WithVersion("ignored")
328 }
329
330 func (t *testAttributes) GetSubresource() string {
331 return t.subresource
332 }
333
334 func (t *testAttributes) GetObject() runtime.Object {
335 return t.obj
336 }
337
338 func (t *testAttributes) GetOldObject() runtime.Object {
339 return t.oldObj
340 }
341
342 func (t *testAttributes) GetName() string {
343 return t.name
344 }
345
346 func (t *testAttributes) GetOperation() admission.Operation {
347 return t.operation
348 }
349
350 func (t *testAttributes) GetUserInfo() user.Info {
351 return &user.DefaultInfo{Name: "ignored"}
352 }
353
View as plain text