Skip to content

Commit 0e2b13a

Browse files
committed
Add CertificateSigningRequest API coverage tests
1 parent 56ad0ce commit 0e2b13a

File tree

1 file changed

+295
-19
lines changed

1 file changed

+295
-19
lines changed

test/e2e/auth/certificates.go

Lines changed: 295 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,20 @@ import (
2020
"context"
2121
"crypto/x509"
2222
"crypto/x509/pkix"
23+
"encoding/json"
2324
"encoding/pem"
25+
"fmt"
2426
"time"
2527

2628
certificatesv1beta1 "k8s.io/api/certificates/v1beta1"
29+
rbacv1 "k8s.io/api/rbac/v1"
30+
apierrors "k8s.io/apimachinery/pkg/api/errors"
2731
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
32+
types "k8s.io/apimachinery/pkg/types"
2833
"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"
3037
"k8s.io/client-go/util/cert"
3138
"k8s.io/kubernetes/test/e2e/framework"
3239
"k8s.io/kubernetes/test/utils"
@@ -37,9 +44,18 @@ import (
3744
var _ = SIGDescribe("Certificates API", func() {
3845
f := framework.NewDefaultFramework("certificates")
3946

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+
*/
4054
ginkgo.It("should support building a client with a CSR", func() {
4155
const commonName = "tester-csr"
4256

57+
csrClient := f.ClientSet.CertificatesV1beta1().CertificateSigningRequests()
58+
4359
pk, err := utils.NewPrivateKey()
4460
framework.ExpectNoError(err)
4561

@@ -49,29 +65,59 @@ var _ = SIGDescribe("Certificates API", func() {
4965
Bytes: pkder,
5066
})
5167

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)
5369
framework.ExpectNoError(err)
5470

55-
csr := &certificatesv1beta1.CertificateSigningRequest{
71+
apiserverClientSigner := certificatesv1beta1.KubeAPIServerClientSignerName
72+
csrTemplate := &certificatesv1beta1.CertificateSigningRequest{
5673
ObjectMeta: metav1.ObjectMeta{
5774
GenerateName: commonName + "-",
5875
},
5976
Spec: certificatesv1beta1.CertificateSigningRequestSpec{
6077
Request: csrb,
6178
Usages: []certificatesv1beta1.KeyUsage{
62-
certificatesv1beta1.UsageSigning,
79+
certificatesv1beta1.UsageDigitalSignature,
6380
certificatesv1beta1.UsageKeyEncipherment,
6481
certificatesv1beta1.UsageClientAuth,
6582
},
83+
SignerName: &apiserverClientSigner,
6684
},
6785
}
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+
}
69114

70115
framework.Logf("creating CSR")
71-
csr, err = csrs.Create(context.TODO(), csr, metav1.CreateOptions{})
116+
csr, err := csrClient.Create(context.TODO(), csrTemplate, metav1.CreateOptions{})
72117
framework.ExpectNoError(err)
73-
74-
csrName := csr.Name
118+
defer func() {
119+
framework.ExpectNoError(csrClient.Delete(context.TODO(), csr.Name, metav1.DeleteOptions{}))
120+
}()
75121

76122
framework.Logf("approving CSR")
77123
framework.ExpectNoError(wait.Poll(5*time.Second, time.Minute, func() (bool, error) {
@@ -82,9 +128,9 @@ var _ = SIGDescribe("Certificates API", func() {
82128
Message: "Set from an e2e test",
83129
},
84130
}
85-
csr, err = csrs.UpdateApproval(context.TODO(), csr, metav1.UpdateOptions{})
131+
csr, err = csrClient.UpdateApproval(context.TODO(), csr, metav1.UpdateOptions{})
86132
if err != nil {
87-
csr, _ = csrs.Get(context.TODO(), csrName, metav1.GetOptions{})
133+
csr, _ = csrClient.Get(context.TODO(), csr.Name, metav1.GetOptions{})
88134
framework.Logf("err updating approval: %v", err)
89135
return false, nil
90136
}
@@ -93,7 +139,7 @@ var _ = SIGDescribe("Certificates API", func() {
93139

94140
framework.Logf("waiting for CSR to be signed")
95141
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{})
97143
if err != nil {
98144
framework.Logf("error getting csr: %v", err)
99145
return false, nil
@@ -108,17 +154,247 @@ var _ = SIGDescribe("Certificates API", func() {
108154
framework.Logf("testing the client")
109155
rcfg, err := framework.LoadConfig()
110156
framework.ExpectNoError(err)
111-
157+
rcfg = rest.AnonymousClientConfig(rcfg)
112158
rcfg.TLSClientConfig.CertData = csr.Status.Certificate
113159
rcfg.TLSClientConfig.KeyData = pkpem
114-
rcfg.TLSClientConfig.CertFile = ""
115-
rcfg.BearerToken = ""
116-
rcfg.AuthProvider = nil
117-
rcfg.Username = ""
118-
rcfg.Password = ""
119160

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})
121397
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")
123399
})
124400
})

0 commit comments

Comments
 (0)