1
16
17 package approver
18
19 import (
20 "context"
21 "crypto/ecdsa"
22 "crypto/elliptic"
23 "crypto/x509"
24 "crypto/x509/pkix"
25 "encoding/pem"
26 "fmt"
27 "math/rand"
28 "net"
29 "testing"
30
31 authorization "k8s.io/api/authorization/v1"
32 capi "k8s.io/api/certificates/v1"
33 "k8s.io/apimachinery/pkg/runtime"
34 "k8s.io/apimachinery/pkg/runtime/schema"
35 "k8s.io/client-go/kubernetes/fake"
36 testclient "k8s.io/client-go/testing"
37 k8s_certificates_v1 "k8s.io/kubernetes/pkg/apis/certificates/v1"
38 )
39
40 func TestHandle(t *testing.T) {
41 cases := []struct {
42 allowed bool
43 recognized bool
44 err bool
45 verify func(*testing.T, []testclient.Action)
46 }{
47 {
48 recognized: false,
49 allowed: false,
50 verify: func(t *testing.T, as []testclient.Action) {
51 if len(as) != 0 {
52 t.Errorf("expected no client calls but got: %#v", as)
53 }
54 },
55 },
56 {
57 recognized: false,
58 allowed: true,
59 verify: func(t *testing.T, as []testclient.Action) {
60 if len(as) != 0 {
61 t.Errorf("expected no client calls but got: %#v", as)
62 }
63 },
64 },
65 {
66 recognized: true,
67 allowed: false,
68 verify: func(t *testing.T, as []testclient.Action) {
69 if len(as) != 1 {
70 t.Errorf("expected 1 call but got: %#v", as)
71 return
72 }
73 _ = as[0].(testclient.CreateActionImpl)
74 },
75 err: true,
76 },
77 {
78 recognized: true,
79 allowed: true,
80 verify: func(t *testing.T, as []testclient.Action) {
81 if len(as) != 2 {
82 t.Errorf("expected two calls but got: %#v", as)
83 return
84 }
85 _ = as[0].(testclient.CreateActionImpl)
86 a := as[1].(testclient.UpdateActionImpl)
87 if got, expected := a.Verb, "update"; got != expected {
88 t.Errorf("got: %v, expected: %v", got, expected)
89 }
90 if got, expected := a.Resource, (schema.GroupVersionResource{Group: "certificates.k8s.io", Version: "v1", Resource: "certificatesigningrequests"}); got != expected {
91 t.Errorf("got: %v, expected: %v", got, expected)
92 }
93 if got, expected := a.Subresource, "approval"; got != expected {
94 t.Errorf("got: %v, expected: %v", got, expected)
95 }
96 csr := a.Object.(*capi.CertificateSigningRequest)
97 if len(csr.Status.Conditions) != 1 {
98 t.Errorf("expected CSR to have approved condition: %#v", csr)
99 }
100 c := csr.Status.Conditions[0]
101 if got, expected := c.Type, capi.CertificateApproved; got != expected {
102 t.Errorf("got: %v, expected: %v", got, expected)
103 }
104 if got, expected := c.Reason, "AutoApproved"; got != expected {
105 t.Errorf("got: %v, expected: %v", got, expected)
106 }
107 },
108 },
109 }
110
111 for _, c := range cases {
112 t.Run(fmt.Sprintf("recognized:%v,allowed: %v,err: %v", c.recognized, c.allowed, c.err), func(t *testing.T) {
113 client := &fake.Clientset{}
114 client.AddReactor("create", "subjectaccessreviews", func(action testclient.Action) (handled bool, ret runtime.Object, err error) {
115 return true, &authorization.SubjectAccessReview{
116 Status: authorization.SubjectAccessReviewStatus{
117 Allowed: c.allowed,
118 },
119 }, nil
120 })
121 approver := sarApprover{
122 client: client,
123 recognizers: []csrRecognizer{
124 {
125 successMessage: "tester",
126 permission: authorization.ResourceAttributes{Group: "foo", Resource: "bar", Subresource: "baz"},
127 recognize: func(csr *capi.CertificateSigningRequest, x509cr *x509.CertificateRequest) bool {
128 return c.recognized
129 },
130 },
131 },
132 }
133 csr := makeTestCsr()
134 ctx := context.TODO()
135 if err := approver.handle(ctx, csr); err != nil && !c.err {
136 t.Errorf("unexpected err: %v", err)
137 }
138 c.verify(t, client.Actions())
139 })
140 }
141 }
142
143 func TestRecognizers(t *testing.T) {
144 goodCases := []func(b *csrBuilder){
145 func(b *csrBuilder) {
146 },
147 }
148
149 testRecognizer(t, goodCases, isNodeClientCert, true)
150 testRecognizer(t, goodCases, isSelfNodeClientCert, true)
151
152 badCases := []func(b *csrBuilder){
153 func(b *csrBuilder) {
154 b.cn = "mike"
155 },
156 func(b *csrBuilder) {
157 b.orgs = nil
158 },
159 func(b *csrBuilder) {
160 b.orgs = []string{"system:master"}
161 },
162 func(b *csrBuilder) {
163 b.usages = append(b.usages, capi.UsageServerAuth)
164 },
165 func(b *csrBuilder) {
166 b.signerName = "example.com/not-correct"
167 },
168 func(b *csrBuilder) {
169 b.signerName = capi.KubeletServingSignerName
170 },
171 }
172
173 testRecognizer(t, badCases, isNodeClientCert, false)
174 testRecognizer(t, badCases, isSelfNodeClientCert, false)
175
176
177 differentCN := []func(b *csrBuilder){
178 func(b *csrBuilder) {
179 b.requestor = "joe"
180 },
181 func(b *csrBuilder) {
182 b.cn = "system:node:bar"
183 },
184 }
185
186 testRecognizer(t, differentCN, isNodeClientCert, true)
187 testRecognizer(t, differentCN, isSelfNodeClientCert, false)
188 }
189
190 func testRecognizer(t *testing.T, cases []func(b *csrBuilder), recognizeFunc func(csr *capi.CertificateSigningRequest, x509cr *x509.CertificateRequest) bool, shouldRecognize bool) {
191 for _, c := range cases {
192 b := csrBuilder{
193 signerName: capi.KubeAPIServerClientKubeletSignerName,
194 cn: "system:node:foo",
195 orgs: []string{"system:nodes"},
196 requestor: "system:node:foo",
197 usages: []capi.KeyUsage{
198 capi.UsageKeyEncipherment,
199 capi.UsageDigitalSignature,
200 capi.UsageClientAuth,
201 },
202 }
203 c(&b)
204 t.Run(fmt.Sprintf("csr:%#v", b), func(t *testing.T) {
205 csr := makeFancyTestCsr(b)
206 x509cr, err := k8s_certificates_v1.ParseCSR(csr.Spec.Request)
207 if err != nil {
208 t.Errorf("unexpected err: %v", err)
209 }
210 if recognizeFunc(csr, x509cr) != shouldRecognize {
211 t.Errorf("expected recognized to be %v", shouldRecognize)
212 }
213 })
214
215 d := csrBuilder{
216 signerName: capi.KubeAPIServerClientKubeletSignerName,
217 cn: "system:node:foo",
218 orgs: []string{"system:nodes"},
219 requestor: "system:node:foo",
220 usages: []capi.KeyUsage{
221 capi.UsageDigitalSignature,
222 capi.UsageClientAuth,
223 },
224 }
225 c(&d)
226 t.Run(fmt.Sprintf("csr:%#v", d), func(t *testing.T) {
227 csr := makeFancyTestCsr(d)
228 x509cr, err := k8s_certificates_v1.ParseCSR(csr.Spec.Request)
229 if err != nil {
230 t.Errorf("unexpected err: %v", err)
231 }
232 if recognizeFunc(csr, x509cr) != shouldRecognize {
233 t.Errorf("expected recognized to be %v", shouldRecognize)
234 }
235 })
236 }
237 }
238
239
240
241 var insecureRand = rand.New(rand.NewSource(0))
242
243 func makeTestCsr() *capi.CertificateSigningRequest {
244 return makeFancyTestCsr(csrBuilder{cn: "test-cert"})
245 }
246
247 type csrBuilder struct {
248 cn string
249 orgs []string
250 requestor string
251 usages []capi.KeyUsage
252 dns []string
253 emails []string
254 ips []net.IP
255 signerName string
256 }
257
258 func makeFancyTestCsr(b csrBuilder) *capi.CertificateSigningRequest {
259 pk, err := ecdsa.GenerateKey(elliptic.P256(), insecureRand)
260 if err != nil {
261 panic(err)
262 }
263 csrb, err := x509.CreateCertificateRequest(insecureRand, &x509.CertificateRequest{
264 Subject: pkix.Name{
265 CommonName: b.cn,
266 Organization: b.orgs,
267 },
268 DNSNames: b.dns,
269 EmailAddresses: b.emails,
270 IPAddresses: b.ips,
271 }, pk)
272 if err != nil {
273 panic(err)
274 }
275 return &capi.CertificateSigningRequest{
276 Spec: capi.CertificateSigningRequestSpec{
277 Username: b.requestor,
278 Usages: b.usages,
279 SignerName: b.signerName,
280 Request: pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrb}),
281 },
282 }
283 }
284
View as plain text