@@ -20,13 +20,20 @@ import (
20
20
"context"
21
21
"crypto/x509"
22
22
"crypto/x509/pkix"
23
+ "encoding/json"
23
24
"encoding/pem"
25
+ "fmt"
24
26
"time"
25
27
26
28
certificatesv1beta1 "k8s.io/api/certificates/v1beta1"
29
+ rbacv1 "k8s.io/api/rbac/v1"
30
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
27
31
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
32
+ types "k8s.io/apimachinery/pkg/types"
28
33
"k8s.io/apimachinery/pkg/util/wait"
29
- v1beta1client "k8s.io/client-go/kubernetes/typed/certificates/v1beta1"
34
+ "k8s.io/apimachinery/pkg/watch"
35
+ certificatesclient "k8s.io/client-go/kubernetes/typed/certificates/v1beta1"
36
+ "k8s.io/client-go/rest"
30
37
"k8s.io/client-go/util/cert"
31
38
"k8s.io/kubernetes/test/e2e/framework"
32
39
"k8s.io/kubernetes/test/utils"
@@ -37,9 +44,18 @@ import (
37
44
var _ = SIGDescribe ("Certificates API" , func () {
38
45
f := framework .NewDefaultFramework ("certificates" )
39
46
47
+ /*
48
+ Release: v1.19
49
+ Testname: CertificateSigningRequest API Client Certificate
50
+ Description:
51
+ - The certificatesigningrequests resource must accept a request for a certificate signed by kubernetes.io/kube-apiserver-client.
52
+ - The issued certificate must be valid as a client certificate used to authenticate to the kube-apiserver.
53
+ */
40
54
ginkgo .It ("should support building a client with a CSR" , func () {
41
55
const commonName = "tester-csr"
42
56
57
+ csrClient := f .ClientSet .CertificatesV1beta1 ().CertificateSigningRequests ()
58
+
43
59
pk , err := utils .NewPrivateKey ()
44
60
framework .ExpectNoError (err )
45
61
@@ -49,29 +65,59 @@ var _ = SIGDescribe("Certificates API", func() {
49
65
Bytes : pkder ,
50
66
})
51
67
52
- csrb , err := cert .MakeCSR (pk , & pkix.Name {CommonName : commonName , Organization : [] string { "system:masters" } }, nil , nil )
68
+ csrb , err := cert .MakeCSR (pk , & pkix.Name {CommonName : commonName }, nil , nil )
53
69
framework .ExpectNoError (err )
54
70
55
- csr := & certificatesv1beta1.CertificateSigningRequest {
71
+ apiserverClientSigner := certificatesv1beta1 .KubeAPIServerClientSignerName
72
+ csrTemplate := & certificatesv1beta1.CertificateSigningRequest {
56
73
ObjectMeta : metav1.ObjectMeta {
57
74
GenerateName : commonName + "-" ,
58
75
},
59
76
Spec : certificatesv1beta1.CertificateSigningRequestSpec {
60
77
Request : csrb ,
61
78
Usages : []certificatesv1beta1.KeyUsage {
62
- certificatesv1beta1 .UsageSigning ,
79
+ certificatesv1beta1 .UsageDigitalSignature ,
63
80
certificatesv1beta1 .UsageKeyEncipherment ,
64
81
certificatesv1beta1 .UsageClientAuth ,
65
82
},
83
+ SignerName : & apiserverClientSigner ,
66
84
},
67
85
}
68
- csrs := f .ClientSet .CertificatesV1beta1 ().CertificateSigningRequests ()
86
+
87
+ // Grant permissions to the new user
88
+ clusterRole , err := f .ClientSet .RbacV1 ().ClusterRoles ().Create (context .TODO (), & rbacv1.ClusterRole {
89
+ ObjectMeta : metav1.ObjectMeta {GenerateName : commonName + "-" },
90
+ Rules : []rbacv1.PolicyRule {{Verbs : []string {"create" }, APIGroups : []string {"certificates.k8s.io" }, Resources : []string {"certificatesigningrequests" }}},
91
+ }, metav1.CreateOptions {})
92
+ if err != nil {
93
+ // Tolerate RBAC not being enabled
94
+ framework .Logf ("error granting permissions to %s, create certificatesigningrequests permissions must be granted out of band: %v" , commonName , err )
95
+ } else {
96
+ defer func () {
97
+ framework .ExpectNoError (f .ClientSet .RbacV1 ().ClusterRoles ().Delete (context .TODO (), clusterRole .Name , metav1.DeleteOptions {}))
98
+ }()
99
+ }
100
+
101
+ clusterRoleBinding , err := f .ClientSet .RbacV1 ().ClusterRoleBindings ().Create (context .TODO (), & rbacv1.ClusterRoleBinding {
102
+ ObjectMeta : metav1.ObjectMeta {GenerateName : commonName + "-" },
103
+ RoleRef : rbacv1.RoleRef {APIGroup : "rbac.authorization.k8s.io" , Kind : "ClusterRole" , Name : clusterRole .Name },
104
+ Subjects : []rbacv1.Subject {{APIGroup : "rbac.authorization.k8s.io" , Kind : "User" , Name : commonName }},
105
+ }, metav1.CreateOptions {})
106
+ if err != nil {
107
+ // Tolerate RBAC not being enabled
108
+ framework .Logf ("error granting permissions to %s, create certificatesigningrequests permissions must be granted out of band: %v" , commonName , err )
109
+ } else {
110
+ defer func () {
111
+ framework .ExpectNoError (f .ClientSet .RbacV1 ().ClusterRoleBindings ().Delete (context .TODO (), clusterRoleBinding .Name , metav1.DeleteOptions {}))
112
+ }()
113
+ }
69
114
70
115
framework .Logf ("creating CSR" )
71
- csr , err = csrs .Create (context .TODO (), csr , metav1.CreateOptions {})
116
+ csr , err := csrClient .Create (context .TODO (), csrTemplate , metav1.CreateOptions {})
72
117
framework .ExpectNoError (err )
73
-
74
- csrName := csr .Name
118
+ defer func () {
119
+ framework .ExpectNoError (csrClient .Delete (context .TODO (), csr .Name , metav1.DeleteOptions {}))
120
+ }()
75
121
76
122
framework .Logf ("approving CSR" )
77
123
framework .ExpectNoError (wait .Poll (5 * time .Second , time .Minute , func () (bool , error ) {
@@ -82,9 +128,9 @@ var _ = SIGDescribe("Certificates API", func() {
82
128
Message : "Set from an e2e test" ,
83
129
},
84
130
}
85
- csr , err = csrs .UpdateApproval (context .TODO (), csr , metav1.UpdateOptions {})
131
+ csr , err = csrClient .UpdateApproval (context .TODO (), csr , metav1.UpdateOptions {})
86
132
if err != nil {
87
- csr , _ = csrs .Get (context .TODO (), csrName , metav1.GetOptions {})
133
+ csr , _ = csrClient .Get (context .TODO (), csr . Name , metav1.GetOptions {})
88
134
framework .Logf ("err updating approval: %v" , err )
89
135
return false , nil
90
136
}
@@ -93,7 +139,7 @@ var _ = SIGDescribe("Certificates API", func() {
93
139
94
140
framework .Logf ("waiting for CSR to be signed" )
95
141
framework .ExpectNoError (wait .Poll (5 * time .Second , time .Minute , func () (bool , error ) {
96
- csr , err = csrs .Get (context .TODO (), csrName , metav1.GetOptions {})
142
+ csr , err = csrClient .Get (context .TODO (), csr . Name , metav1.GetOptions {})
97
143
if err != nil {
98
144
framework .Logf ("error getting csr: %v" , err )
99
145
return false , nil
@@ -108,17 +154,247 @@ var _ = SIGDescribe("Certificates API", func() {
108
154
framework .Logf ("testing the client" )
109
155
rcfg , err := framework .LoadConfig ()
110
156
framework .ExpectNoError (err )
111
-
157
+ rcfg = rest . AnonymousClientConfig ( rcfg )
112
158
rcfg .TLSClientConfig .CertData = csr .Status .Certificate
113
159
rcfg .TLSClientConfig .KeyData = pkpem
114
- rcfg .TLSClientConfig .CertFile = ""
115
- rcfg .BearerToken = ""
116
- rcfg .AuthProvider = nil
117
- rcfg .Username = ""
118
- rcfg .Password = ""
119
160
120
- newClient , err := v1beta1client .NewForConfig (rcfg )
161
+ newClient , err := certificatesclient .NewForConfig (rcfg )
162
+ framework .ExpectNoError (err )
163
+
164
+ framework .Logf ("creating CSR as new client" )
165
+ newCSR , err := newClient .CertificateSigningRequests ().Create (context .TODO (), csrTemplate , metav1.CreateOptions {})
166
+ framework .ExpectNoError (err )
167
+ defer func () {
168
+ framework .ExpectNoError (csrClient .Delete (context .TODO (), newCSR .Name , metav1.DeleteOptions {}))
169
+ }()
170
+ framework .ExpectEqual (newCSR .Spec .Username , commonName )
171
+ })
172
+
173
+ /*
174
+ Release: v1.19
175
+ Testname: CertificateSigningRequest API
176
+ Description:
177
+ - The certificates.k8s.io API group MUST exists in the /apis discovery document.
178
+ - The certificates.k8s.io/v1beta1 API group/version MUST exist in the /apis/certificates.k8s.io discovery document.
179
+ - The certificatesigningrequests, certificatesigningrequests/approval, and certificatesigningrequests/status
180
+ resources MUST exist in the /apis/certificates.k8s.io/v1beta1 discovery document.
181
+ - The certificatesigningrequests resource must support create, get, list, watch, update, patch, delete, and deletecollection.
182
+ - The certificatesigningrequests/approval resource must support get, update, patch.
183
+ - The certificatesigningrequests/status resource must support get, update, patch.
184
+ */
185
+ ginkgo .It ("should support CSR API operations [Privileged:ClusterAdmin]" , func () {
186
+
187
+ // Setup
188
+ csrVersion := "v1beta1"
189
+ csrClient := f .ClientSet .CertificatesV1beta1 ().CertificateSigningRequests ()
190
+ csrResource := certificatesv1beta1 .SchemeGroupVersion .WithResource ("certificatesigningrequests" )
191
+
192
+ pk , err := utils .NewPrivateKey ()
193
+ framework .ExpectNoError (err )
194
+
195
+ csrData , err := cert .MakeCSR (pk , & pkix.Name {CommonName : "e2e.example.com" }, []string {"e2e.example.com" }, nil )
196
+ framework .ExpectNoError (err )
197
+
198
+ certificateData , _ , err := cert .GenerateSelfSignedCertKey ("e2e.example.com" , nil , []string {"e2e.example.com" })
199
+ framework .ExpectNoError (err )
200
+ certificateDataJSON , err := json .Marshal (certificateData )
201
+ framework .ExpectNoError (err )
202
+
203
+ signerName := "example.com/e2e-" + f .UniqueName
204
+ csrTemplate := & certificatesv1beta1.CertificateSigningRequest {
205
+ ObjectMeta : metav1.ObjectMeta {GenerateName : "e2e-example-csr-" },
206
+ Spec : certificatesv1beta1.CertificateSigningRequestSpec {
207
+ Request : csrData ,
208
+ SignerName : & signerName ,
209
+ Usages : []certificatesv1beta1.KeyUsage {certificatesv1beta1 .UsageDigitalSignature , certificatesv1beta1 .UsageKeyEncipherment , certificatesv1beta1 .UsageServerAuth },
210
+ },
211
+ }
212
+
213
+ // Discovery
214
+
215
+ ginkgo .By ("getting /apis" )
216
+ {
217
+ discoveryGroups , err := f .ClientSet .Discovery ().ServerGroups ()
218
+ framework .ExpectNoError (err )
219
+ found := false
220
+ for _ , group := range discoveryGroups .Groups {
221
+ if group .Name == certificatesv1beta1 .GroupName {
222
+ for _ , version := range group .Versions {
223
+ if version .Version == csrVersion {
224
+ found = true
225
+ break
226
+ }
227
+ }
228
+ }
229
+ }
230
+ framework .ExpectEqual (found , true , fmt .Sprintf ("expected certificates API group/version, got %#v" , discoveryGroups .Groups ))
231
+ }
232
+
233
+ ginkgo .By ("getting /apis/certificates.k8s.io" )
234
+ {
235
+ group := & metav1.APIGroup {}
236
+ err := f .ClientSet .Discovery ().RESTClient ().Get ().AbsPath ("/apis/certificates.k8s.io" ).Do (context .TODO ()).Into (group )
237
+ framework .ExpectNoError (err )
238
+ found := false
239
+ for _ , version := range group .Versions {
240
+ if version .Version == csrVersion {
241
+ found = true
242
+ break
243
+ }
244
+ }
245
+ framework .ExpectEqual (found , true , fmt .Sprintf ("expected certificates API version, got %#v" , group .Versions ))
246
+ }
247
+
248
+ ginkgo .By ("getting /apis/certificates.k8s.io/" + csrVersion )
249
+ {
250
+ resources , err := f .ClientSet .Discovery ().ServerResourcesForGroupVersion (certificatesv1beta1 .SchemeGroupVersion .String ())
251
+ framework .ExpectNoError (err )
252
+ foundCSR , foundApproval , foundStatus := false , false , false
253
+ for _ , resource := range resources .APIResources {
254
+ switch resource .Name {
255
+ case "certificatesigningrequests" :
256
+ foundCSR = true
257
+ case "certificatesigningrequests/approval" :
258
+ foundApproval = true
259
+ case "certificatesigningrequests/status" :
260
+ foundStatus = true
261
+ }
262
+ }
263
+ framework .ExpectEqual (foundCSR , true , fmt .Sprintf ("expected certificatesigningrequests, got %#v" , resources .APIResources ))
264
+ framework .ExpectEqual (foundApproval , true , fmt .Sprintf ("expected certificatesigningrequests/approval, got %#v" , resources .APIResources ))
265
+ framework .ExpectEqual (foundStatus , true , fmt .Sprintf ("expected certificatesigningrequests/status, got %#v" , resources .APIResources ))
266
+ }
267
+
268
+ // Main resource create/read/update/watch operations
269
+
270
+ ginkgo .By ("creating" )
271
+ _ , err = csrClient .Create (context .TODO (), csrTemplate , metav1.CreateOptions {})
272
+ framework .ExpectNoError (err )
273
+ _ , err = csrClient .Create (context .TODO (), csrTemplate , metav1.CreateOptions {})
274
+ framework .ExpectNoError (err )
275
+ createdCSR , err := csrClient .Create (context .TODO (), csrTemplate , metav1.CreateOptions {})
276
+ framework .ExpectNoError (err )
277
+
278
+ ginkgo .By ("getting" )
279
+ gottenCSR , err := csrClient .Get (context .TODO (), createdCSR .Name , metav1.GetOptions {})
280
+ framework .ExpectNoError (err )
281
+ framework .ExpectEqual (gottenCSR .UID , createdCSR .UID )
282
+
283
+ ginkgo .By ("listing" )
284
+ csrs , err := csrClient .List (context .TODO (), metav1.ListOptions {FieldSelector : "spec.signerName=" + signerName })
285
+ framework .ExpectNoError (err )
286
+ framework .ExpectEqual (len (csrs .Items ), 3 , "filtered list should have 3 items" )
287
+
288
+ ginkgo .By ("watching" )
289
+ framework .Logf ("starting watch" )
290
+ csrWatch , err := csrClient .Watch (context .TODO (), metav1.ListOptions {ResourceVersion : csrs .ResourceVersion , FieldSelector : "metadata.name=" + createdCSR .Name })
291
+ framework .ExpectNoError (err )
292
+
293
+ ginkgo .By ("patching" )
294
+ patchedCSR , err := csrClient .Patch (context .TODO (), createdCSR .Name , types .MergePatchType , []byte (`{"metadata":{"annotations":{"patched":"true"}}}` ), metav1.PatchOptions {})
295
+ framework .ExpectNoError (err )
296
+ framework .ExpectEqual (patchedCSR .Annotations ["patched" ], "true" , "patched object should have the applied annotation" )
297
+
298
+ ginkgo .By ("updating" )
299
+ csrToUpdate := patchedCSR .DeepCopy ()
300
+ csrToUpdate .Annotations ["updated" ] = "true"
301
+ updatedCSR , err := csrClient .Update (context .TODO (), csrToUpdate , metav1.UpdateOptions {})
302
+ framework .ExpectNoError (err )
303
+ framework .ExpectEqual (updatedCSR .Annotations ["updated" ], "true" , "updated object should have the applied annotation" )
304
+
305
+ framework .Logf ("waiting for watch events with expected annotations" )
306
+ for sawAnnotations := false ; ! sawAnnotations ; {
307
+ select {
308
+ case evt , ok := <- csrWatch .ResultChan ():
309
+ framework .ExpectEqual (ok , true , "watch channel should not close" )
310
+ framework .ExpectEqual (evt .Type , watch .Modified )
311
+ watchedCSR , isCSR := evt .Object .(* certificatesv1beta1.CertificateSigningRequest )
312
+ framework .ExpectEqual (isCSR , true , fmt .Sprintf ("expected CSR, got %T" , evt .Object ))
313
+ if watchedCSR .Annotations ["patched" ] == "true" {
314
+ framework .Logf ("saw patched and updated annotations" )
315
+ sawAnnotations = true
316
+ csrWatch .Stop ()
317
+ } else {
318
+ framework .Logf ("missing expected annotations, waiting: %#v" , watchedCSR .Annotations )
319
+ }
320
+ case <- time .After (wait .ForeverTestTimeout ):
321
+ framework .Fail ("timed out waiting for watch event" )
322
+ }
323
+ }
324
+
325
+ // /approval subresource operations
326
+
327
+ ginkgo .By ("getting /approval" )
328
+ gottenApproval , err := f .DynamicClient .Resource (csrResource ).Get (context .TODO (), createdCSR .Name , metav1.GetOptions {}, "approval" )
329
+ framework .ExpectNoError (err )
330
+ framework .ExpectEqual (gottenApproval .GetObjectKind ().GroupVersionKind (), certificatesv1beta1 .SchemeGroupVersion .WithKind ("CertificateSigningRequest" ))
331
+ framework .ExpectEqual (gottenApproval .GetUID (), createdCSR .UID )
332
+
333
+ ginkgo .By ("patching /approval" )
334
+ patchedApproval , err := csrClient .Patch (context .TODO (), createdCSR .Name , types .MergePatchType ,
335
+ []byte (`{"metadata":{"annotations":{"patchedapproval":"true"}},"status":{"conditions":[{"type":"ApprovalPatch","status":"True","reason":"e2e"}]}}` ),
336
+ metav1.PatchOptions {}, "approval" )
337
+ framework .ExpectNoError (err )
338
+ framework .ExpectEqual (len (patchedApproval .Status .Conditions ), 1 , fmt .Sprintf ("patched object should have the applied condition, got %#v" , patchedApproval .Status .Conditions ))
339
+ framework .ExpectEqual (string (patchedApproval .Status .Conditions [0 ].Type ), "ApprovalPatch" , fmt .Sprintf ("patched object should have the applied condition, got %#v" , patchedApproval .Status .Conditions ))
340
+ framework .ExpectEqual (patchedApproval .Annotations ["patchedapproval" ], "true" , "patched object should have the applied annotation" )
341
+
342
+ ginkgo .By ("updating /approval" )
343
+ approvalToUpdate := patchedApproval .DeepCopy ()
344
+ approvalToUpdate .Status .Conditions = append (approvalToUpdate .Status .Conditions , certificatesv1beta1.CertificateSigningRequestCondition {
345
+ Type : certificatesv1beta1 .CertificateApproved ,
346
+ Reason : "E2E" ,
347
+ Message : "Set from an e2e test" ,
348
+ })
349
+ updatedApproval , err := csrClient .UpdateApproval (context .TODO (), approvalToUpdate , metav1.UpdateOptions {})
350
+ framework .ExpectNoError (err )
351
+ framework .ExpectEqual (len (updatedApproval .Status .Conditions ), 2 , fmt .Sprintf ("updated object should have the applied condition, got %#v" , updatedApproval .Status .Conditions ))
352
+ framework .ExpectEqual (updatedApproval .Status .Conditions [1 ].Type , certificatesv1beta1 .CertificateApproved , fmt .Sprintf ("updated object should have the approved condition, got %#v" , updatedApproval .Status .Conditions ))
353
+
354
+ // /status subresource operations
355
+
356
+ ginkgo .By ("getting /status" )
357
+ gottenStatus , err := f .DynamicClient .Resource (csrResource ).Get (context .TODO (), createdCSR .Name , metav1.GetOptions {}, "status" )
358
+ framework .ExpectNoError (err )
359
+ framework .ExpectEqual (gottenStatus .GetObjectKind ().GroupVersionKind (), certificatesv1beta1 .SchemeGroupVersion .WithKind ("CertificateSigningRequest" ))
360
+ framework .ExpectEqual (gottenStatus .GetUID (), createdCSR .UID )
361
+
362
+ ginkgo .By ("patching /status" )
363
+ patchedStatus , err := csrClient .Patch (context .TODO (), createdCSR .Name , types .MergePatchType ,
364
+ []byte (`{"metadata":{"annotations":{"patchedstatus":"true"}},"status":{"certificate":` + string (certificateDataJSON )+ `}}` ),
365
+ metav1.PatchOptions {}, "status" )
366
+ framework .ExpectNoError (err )
367
+ framework .ExpectEqual (patchedStatus .Status .Certificate , certificateData , "patched object should have the applied certificate" )
368
+ framework .ExpectEqual (patchedStatus .Annotations ["patchedstatus" ], "true" , "patched object should have the applied annotation" )
369
+
370
+ ginkgo .By ("updating /status" )
371
+ statusToUpdate := patchedStatus .DeepCopy ()
372
+ statusToUpdate .Status .Conditions = append (statusToUpdate .Status .Conditions , certificatesv1beta1.CertificateSigningRequestCondition {
373
+ Type : "StatusUpdate" ,
374
+ Reason : "E2E" ,
375
+ Message : "Set from an e2e test" ,
376
+ })
377
+ updatedStatus , err := csrClient .UpdateStatus (context .TODO (), statusToUpdate , metav1.UpdateOptions {})
378
+ framework .ExpectNoError (err )
379
+ framework .ExpectEqual (len (updatedStatus .Status .Conditions ), len (statusToUpdate .Status .Conditions ), fmt .Sprintf ("updated object should have the applied condition, got %#v" , updatedStatus .Status .Conditions ))
380
+ framework .ExpectEqual (string (updatedStatus .Status .Conditions [len (updatedStatus .Status .Conditions )- 1 ].Type ), "StatusUpdate" , fmt .Sprintf ("updated object should have the approved condition, got %#v" , updatedStatus .Status .Conditions ))
381
+
382
+ // main resource delete operations
383
+
384
+ ginkgo .By ("deleting" )
385
+ err = csrClient .Delete (context .TODO (), createdCSR .Name , metav1.DeleteOptions {})
386
+ framework .ExpectNoError (err )
387
+ _ , err = csrClient .Get (context .TODO (), createdCSR .Name , metav1.GetOptions {})
388
+ framework .ExpectEqual (apierrors .IsNotFound (err ), true , fmt .Sprintf ("expected 404, got %#v" , err ))
389
+ csrs , err = csrClient .List (context .TODO (), metav1.ListOptions {FieldSelector : "spec.signerName=" + signerName })
390
+ framework .ExpectNoError (err )
391
+ framework .ExpectEqual (len (csrs .Items ), 2 , "filtered list should have 2 items" )
392
+
393
+ ginkgo .By ("deleting a collection" )
394
+ err = csrClient .DeleteCollection (context .TODO (), metav1.DeleteOptions {}, metav1.ListOptions {FieldSelector : "spec.signerName=" + signerName })
395
+ framework .ExpectNoError (err )
396
+ csrs , err = csrClient .List (context .TODO (), metav1.ListOptions {FieldSelector : "spec.signerName=" + signerName })
121
397
framework .ExpectNoError (err )
122
- framework .ExpectNoError ( newClient . CertificateSigningRequests (). Delete ( context . TODO ( ), csrName , metav1. DeleteOptions {}) )
398
+ framework .ExpectEqual ( len ( csrs . Items ), 0 , "filtered list should have 0 items" )
123
399
})
124
400
})
0 commit comments