k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/controller/certificates/approver/sarapprove_test.go (about) 1 /* 2 Copyright 2017 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 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 // cn different then requestor 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 // reset the builder to run testcase without usage key encipherment 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 // noncryptographic for faster testing 240 // DO NOT COPY THIS CODE 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 }