1
16
17 package auth
18
19
20
21
22
23 import (
24 "bytes"
25 "context"
26 "crypto/ed25519"
27 "crypto/rand"
28 "crypto/x509"
29 "crypto/x509/pkix"
30 "encoding/json"
31 "encoding/pem"
32 "fmt"
33 "io"
34 "net/http"
35 "net/http/httptest"
36 "net/url"
37 "os"
38 "path/filepath"
39 "strconv"
40 "strings"
41 "testing"
42 "time"
43
44 utiltesting "k8s.io/client-go/util/testing"
45
46 "github.com/google/go-cmp/cmp"
47
48 authenticationv1beta1 "k8s.io/api/authentication/v1beta1"
49 certificatesv1 "k8s.io/api/certificates/v1"
50 rbacv1 "k8s.io/api/rbac/v1"
51 "k8s.io/apimachinery/pkg/api/errors"
52 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
53 utilnet "k8s.io/apimachinery/pkg/util/net"
54 "k8s.io/apimachinery/pkg/util/wait"
55 "k8s.io/apiserver/pkg/authentication/authenticator"
56 "k8s.io/apiserver/pkg/authentication/group"
57 "k8s.io/apiserver/pkg/authentication/request/bearertoken"
58 "k8s.io/apiserver/pkg/authentication/serviceaccount"
59 "k8s.io/apiserver/pkg/authentication/token/cache"
60 "k8s.io/apiserver/pkg/authorization/authorizer"
61 unionauthz "k8s.io/apiserver/pkg/authorization/union"
62 webhookutil "k8s.io/apiserver/pkg/util/webhook"
63 "k8s.io/apiserver/plugin/pkg/authenticator/token/webhook"
64 clientset "k8s.io/client-go/kubernetes"
65 "k8s.io/client-go/rest"
66 v1 "k8s.io/client-go/tools/clientcmd/api/v1"
67 resttransport "k8s.io/client-go/transport"
68 "k8s.io/kubernetes/cmd/kube-apiserver/app/options"
69 kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
70 "k8s.io/kubernetes/pkg/apis/autoscaling"
71 api "k8s.io/kubernetes/pkg/apis/core"
72 "k8s.io/kubernetes/pkg/apis/extensions"
73 "k8s.io/kubernetes/pkg/controlplane"
74 "k8s.io/kubernetes/test/integration"
75 "k8s.io/kubernetes/test/integration/authutil"
76 "k8s.io/kubernetes/test/integration/framework"
77 "k8s.io/kubernetes/test/utils/ktesting"
78 )
79
80 const (
81 AliceToken string = "abc123"
82 BobToken string = "xyz987"
83 UnknownToken string = "qwerty"
84 )
85
86 func getTestWebhookTokenAuth(serverURL string, customDial utilnet.DialFunc) (authenticator.Request, error) {
87 kubecfgFile, err := os.CreateTemp("", "webhook-kubecfg")
88 if err != nil {
89 return nil, err
90 }
91 defer utiltesting.CloseAndRemove(&testing.T{}, kubecfgFile)
92 config := v1.Config{
93 Clusters: []v1.NamedCluster{
94 {
95 Cluster: v1.Cluster{Server: serverURL},
96 },
97 },
98 }
99 if err := json.NewEncoder(kubecfgFile).Encode(config); err != nil {
100 return nil, err
101 }
102
103 retryBackoff := wait.Backoff{
104 Duration: 500 * time.Millisecond,
105 Factor: 1.5,
106 Jitter: 0.2,
107 Steps: 5,
108 }
109
110 clientConfig, err := webhookutil.LoadKubeconfig(kubecfgFile.Name(), customDial)
111 if err != nil {
112 return nil, err
113 }
114
115 webhookTokenAuth, err := webhook.New(clientConfig, "v1beta1", nil, retryBackoff)
116 if err != nil {
117 return nil, err
118 }
119 return bearertoken.New(cache.New(webhookTokenAuth, false, 2*time.Minute, 2*time.Minute)), nil
120 }
121
122 func getTestWebhookTokenAuthCustomDialer(serverURL string) (authenticator.Request, error) {
123 customDial := http.DefaultTransport.(*http.Transport).DialContext
124
125 return getTestWebhookTokenAuth(serverURL, customDial)
126 }
127
128 func path(resource, namespace, name string) string {
129 return pathWithPrefix("", resource, namespace, name)
130 }
131
132 func pathWithPrefix(prefix, resource, namespace, name string) string {
133 path := "/api/v1"
134 if prefix != "" {
135 path = path + "/" + prefix
136 }
137 if namespace != "" {
138 path = path + "/namespaces/" + namespace
139 }
140
141 resource = strings.ToLower(resource)
142 if resource != "" {
143 path = path + "/" + resource
144 }
145 if name != "" {
146 path = path + "/" + name
147 }
148 return path
149 }
150
151 func pathWithSubResource(resource, namespace, name, subresource string) string {
152 path := pathWithPrefix("", resource, namespace, name)
153 if subresource != "" {
154 path = path + "/" + subresource
155 }
156 return path
157 }
158
159 func timeoutPath(resource, namespace, name string) string {
160 return addTimeoutFlag(path(resource, namespace, name))
161 }
162
163
164 var aPod = `
165 {
166 "kind": "Pod",
167 "apiVersion": "v1",
168 "metadata": {
169 "name": "a",
170 "creationTimestamp": null%s
171 },
172 "spec": {
173 "containers": [
174 {
175 "name": "foo",
176 "image": "bar/foo"
177 }
178 ]
179 }
180 }
181 `
182 var aRC = `
183 {
184 "kind": "ReplicationController",
185 "apiVersion": "v1",
186 "metadata": {
187 "name": "a",
188 "labels": {
189 "name": "a"
190 }%s
191 },
192 "spec": {
193 "replicas": 2,
194 "selector": {
195 "name": "a"
196 },
197 "template": {
198 "metadata": {
199 "labels": {
200 "name": "a"
201 }
202 },
203 "spec": {
204 "containers": [
205 {
206 "name": "foo",
207 "image": "bar/foo"
208 }
209 ]
210 }
211 }
212 }
213 }
214 `
215 var aService = `
216 {
217 "kind": "Service",
218 "apiVersion": "v1",
219 "metadata": {
220 "name": "a",
221 "labels": {
222 "name": "a"
223 }%s
224 },
225 "spec": {
226 "ports": [
227 {
228 "protocol": "TCP",
229 "port": 8000
230 }
231 ],
232 "selector": {
233 "name": "a"
234 },
235 "clusterIP": "10.0.0.100"
236 }
237 }
238 `
239 var aNode = `
240 {
241 "kind": "Node",
242 "apiVersion": "v1",
243 "metadata": {
244 "name": "a"%s
245 },
246 "spec": {
247 "externalID": "external"
248 }
249 }
250 `
251
252 func aEvent(namespace string) string {
253 return `
254 {
255 "kind": "Event",
256 "apiVersion": "v1",
257 "metadata": {
258 "name": "a"%s
259 },
260 "involvedObject": {
261 "kind": "Pod",
262 "namespace": "` + namespace + `",
263 "name": "a",
264 "apiVersion": "v1"
265 }
266 }
267 `
268 }
269
270 var aBinding = `
271 {
272 "kind": "Binding",
273 "apiVersion": "v1",
274 "metadata": {
275 "name": "a"%s
276 },
277 "target": {
278 "name": "10.10.10.10"
279 }
280 }
281 `
282
283 var emptyEndpoints = `
284 {
285 "kind": "Endpoints",
286 "apiVersion": "v1",
287 "metadata": {
288 "name": "a"%s
289 }
290 }
291 `
292
293 var aEndpoints = `
294 {
295 "kind": "Endpoints",
296 "apiVersion": "v1",
297 "metadata": {
298 "name": "a"%s
299 },
300 "subsets": [
301 {
302 "addresses": [
303 {
304 "ip": "10.10.1.1"
305 }
306 ],
307 "ports": [
308 {
309 "port": 1909,
310 "protocol": "TCP"
311 }
312 ]
313 }
314 ]
315 }
316 `
317
318 var deleteNow = `
319 {
320 "kind": "DeleteOptions",
321 "apiVersion": "v1",
322 "gracePeriodSeconds": 0%s
323 }
324 `
325
326
327 func addTimeoutFlag(URLString string) string {
328 u, _ := url.Parse(URLString)
329 values := u.Query()
330 values.Set("timeout", "60s")
331 u.RawQuery = values.Encode()
332 return u.String()
333 }
334
335 type testRequest struct {
336 verb string
337 URL string
338 body string
339 statusCodes map[int]bool
340 }
341
342 func getTestRequests(namespace string) []testRequest {
343 requests := []testRequest{
344
345 {"GET", path("pods", "", ""), "", integration.Code200},
346 {"GET", path("pods", namespace, ""), "", integration.Code200},
347 {"POST", timeoutPath("pods", namespace, ""), aPod, integration.Code201},
348 {"PUT", timeoutPath("pods", namespace, "a"), aPod, integration.Code200},
349 {"GET", path("pods", namespace, "a"), "", integration.Code200},
350
351 {"GET", path("pods", namespace, "a") + "/exec", "", integration.Code400},
352 {"POST", path("pods", namespace, "a") + "/exec", "", integration.Code400},
353
354 {"PUT", path("pods", namespace, "a") + "/exec", "", integration.Code405},
355
356 {"GET", path("pods", namespace, "a") + "/portforward", "", integration.Code400},
357 {"POST", path("pods", namespace, "a") + "/portforward", "", integration.Code400},
358
359 {"PUT", path("pods", namespace, "a") + "/portforward", "", integration.Code405},
360 {"PATCH", path("pods", namespace, "a"), "{%v}", integration.Code200},
361 {"DELETE", timeoutPath("pods", namespace, "a"), deleteNow, integration.Code200},
362
363
364
365
366 {"OPTIONS", path("pods", namespace, ""), "", integration.Code405},
367 {"OPTIONS", path("pods", namespace, "a"), "", integration.Code405},
368 {"HEAD", path("pods", namespace, ""), "", integration.Code405},
369 {"HEAD", path("pods", namespace, "a"), "", integration.Code405},
370 {"TRACE", path("pods", namespace, ""), "", integration.Code405},
371 {"TRACE", path("pods", namespace, "a"), "", integration.Code405},
372 {"NOSUCHVERB", path("pods", namespace, ""), "", integration.Code405},
373
374
375 {"GET", path("services", "", ""), "", integration.Code200},
376 {"GET", path("services", namespace, ""), "", integration.Code200},
377 {"POST", timeoutPath("services", namespace, ""), aService, integration.Code201},
378
379
380 {"POST", timeoutPath("endpoints", namespace, ""), emptyEndpoints, integration.Code201},
381
382 {"GET", pathWithSubResource("services", namespace, "a", "proxy") + "/", "", integration.Code503},
383 {"PUT", timeoutPath("services", namespace, "a"), aService, integration.Code200},
384 {"GET", path("services", namespace, "a"), "", integration.Code200},
385 {"DELETE", timeoutPath("endpoints", namespace, "a"), "", integration.Code200},
386 {"DELETE", timeoutPath("services", namespace, "a"), "", integration.Code200},
387
388
389 {"GET", path("replicationControllers", "", ""), "", integration.Code200},
390 {"GET", path("replicationControllers", namespace, ""), "", integration.Code200},
391 {"POST", timeoutPath("replicationControllers", namespace, ""), aRC, integration.Code201},
392 {"PUT", timeoutPath("replicationControllers", namespace, "a"), aRC, integration.Code200},
393 {"GET", path("replicationControllers", namespace, "a"), "", integration.Code200},
394 {"DELETE", timeoutPath("replicationControllers", namespace, "a"), "", integration.Code200},
395
396
397 {"GET", path("endpoints", "", ""), "", integration.Code200},
398 {"GET", path("endpoints", namespace, ""), "", integration.Code200},
399 {"POST", timeoutPath("endpoints", namespace, ""), aEndpoints, integration.Code201},
400 {"PUT", timeoutPath("endpoints", namespace, "a"), aEndpoints, integration.Code200},
401 {"GET", path("endpoints", namespace, "a"), "", integration.Code200},
402 {"DELETE", timeoutPath("endpoints", namespace, "a"), "", integration.Code200},
403
404
405 {"GET", path("nodes", "", ""), "", integration.Code200},
406 {"POST", timeoutPath("nodes", "", ""), aNode, integration.Code201},
407 {"PUT", timeoutPath("nodes", "", "a"), aNode, integration.Code200},
408 {"GET", path("nodes", "", "a"), "", integration.Code200},
409 {"DELETE", timeoutPath("nodes", "", "a"), "", integration.Code200},
410
411
412 {"GET", path("events", "", ""), "", integration.Code200},
413 {"GET", path("events", namespace, ""), "", integration.Code200},
414 {"POST", timeoutPath("events", namespace, ""), aEvent(namespace), integration.Code201},
415 {"PUT", timeoutPath("events", namespace, "a"), aEvent(namespace), integration.Code200},
416 {"GET", path("events", namespace, "a"), "", integration.Code200},
417 {"DELETE", timeoutPath("events", namespace, "a"), "", integration.Code200},
418
419
420 {"GET", path("bindings", namespace, ""), "", integration.Code405},
421 {"POST", timeoutPath("pods", namespace, ""), aPod, integration.Code201},
422 {"POST", timeoutPath("bindings", namespace, ""), aBinding, integration.Code201},
423 {"PUT", timeoutPath("bindings", namespace, "a"), aBinding, integration.Code404},
424 {"GET", path("bindings", namespace, "a"), "", integration.Code404},
425 {"DELETE", timeoutPath("bindings", namespace, "a"), "", integration.Code404},
426
427
428 {"GET", path("foo", "", ""), "", integration.Code404},
429 {"POST", path("foo", namespace, ""), `{"foo": "foo"}`, integration.Code404},
430 {"PUT", path("foo", namespace, "a"), `{"foo": "foo"}`, integration.Code404},
431 {"GET", path("foo", namespace, "a"), "", integration.Code404},
432 {"DELETE", timeoutPath("foo", namespace, ""), "", integration.Code404},
433
434
435 {"GET", pathWithSubResource("nodes", namespace, "a", "proxy"), "", integration.Code404},
436 {"GET", pathWithPrefix("redirect", "nodes", namespace, "a"), "", integration.Code404},
437
438
439
440
441 {"GET", "/", "", integration.Code200},
442 {"GET", "/api", "", integration.Code200},
443 {"GET", "/healthz", "", integration.Code200},
444 {"GET", "/version", "", integration.Code200},
445 {"GET", "/invalidURL", "", integration.Code404},
446 }
447 return requests
448 }
449
450
451
452
453
454
455
456 func TestAuthModeAlwaysAllow(t *testing.T) {
457 tCtx := ktesting.Init(t)
458 kubeClient, kubeConfig, tearDownFn := framework.StartTestServer(tCtx, t, framework.TestServerSetup{
459 ModifyServerRunOptions: func(opts *options.ServerRunOptions) {
460
461 opts.Admission.GenericAdmission.DisablePlugins = []string{"ServiceAccount"}
462 opts.Authorization.Modes = []string{"AlwaysAllow"}
463 },
464 })
465 defer tearDownFn()
466
467 ns := framework.CreateNamespaceOrDie(kubeClient, "auth-always-allow", t)
468 defer framework.DeleteNamespaceOrDie(kubeClient, ns, t)
469
470 transport, err := rest.TransportFor(kubeConfig)
471 if err != nil {
472 t.Fatal(err)
473 }
474 previousResourceVersion := make(map[string]float64)
475
476 for _, r := range getTestRequests(ns.Name) {
477 var bodyStr string
478 if r.body != "" {
479 sub := ""
480 if r.verb == "PUT" {
481
482 if resVersion := previousResourceVersion[getPreviousResourceVersionKey(r.URL, "")]; resVersion != 0 {
483 sub += fmt.Sprintf(",\r\n\"resourceVersion\": \"%v\"", resVersion)
484 }
485 sub += fmt.Sprintf(",\r\n\"namespace\": %q", ns.Name)
486 }
487 bodyStr = fmt.Sprintf(r.body, sub)
488 }
489 r.body = bodyStr
490 bodyBytes := bytes.NewReader([]byte(bodyStr))
491 req, err := http.NewRequest(r.verb, kubeConfig.Host+r.URL, bodyBytes)
492 if err != nil {
493 t.Logf("case %v", r)
494 t.Fatalf("unexpected error: %v", err)
495 }
496 if r.verb == "PATCH" {
497 req.Header.Set("Content-Type", "application/merge-patch+json")
498 }
499 func() {
500 resp, err := transport.RoundTrip(req)
501 if err != nil {
502 t.Logf("case %v", r)
503 t.Fatalf("unexpected error: %v", err)
504 }
505 defer resp.Body.Close()
506 b, _ := io.ReadAll(resp.Body)
507 if _, ok := r.statusCodes[resp.StatusCode]; !ok {
508 t.Logf("case %v", r)
509 t.Errorf("Expected status one of %v, but got %v", r.statusCodes, resp.StatusCode)
510 t.Errorf("Body: %v", string(b))
511 } else {
512 if r.verb == "POST" {
513
514 id, currentResourceVersion, err := parseResourceVersion(b)
515 if err == nil {
516 key := getPreviousResourceVersionKey(r.URL, id)
517 previousResourceVersion[key] = currentResourceVersion
518 } else {
519 t.Logf("error in trying to extract resource version: %s", err)
520 }
521 }
522 }
523 }()
524 }
525 }
526
527 func parseResourceVersion(response []byte) (string, float64, error) {
528 var resultBodyMap map[string]interface{}
529 err := json.Unmarshal(response, &resultBodyMap)
530 if err != nil {
531 return "", 0, fmt.Errorf("unexpected error unmarshaling resultBody: %v", err)
532 }
533 metadata, ok := resultBodyMap["metadata"].(map[string]interface{})
534 if !ok {
535 return "", 0, fmt.Errorf("unexpected error, metadata not found in JSON response: %v", string(response))
536 }
537 id, ok := metadata["name"].(string)
538 if !ok {
539 return "", 0, fmt.Errorf("unexpected error, id not found in JSON response: %v", string(response))
540 }
541 resourceVersionString, ok := metadata["resourceVersion"].(string)
542 if !ok {
543 return "", 0, fmt.Errorf("unexpected error, resourceVersion not found in JSON response: %v", string(response))
544 }
545 resourceVersion, err := strconv.ParseFloat(resourceVersionString, 64)
546 if err != nil {
547 return "", 0, fmt.Errorf("unexpected error, could not parse resourceVersion as float64, err: %s. JSON response: %v", err, string(response))
548 }
549 return id, resourceVersion, nil
550 }
551
552 func getPreviousResourceVersionKey(url, id string) string {
553 baseURL := strings.Split(url, "?")[0]
554 key := baseURL
555 if id != "" {
556 key = fmt.Sprintf("%s/%v", baseURL, id)
557 }
558 return key
559 }
560
561 func TestAuthModeAlwaysDeny(t *testing.T) {
562 tCtx := ktesting.Init(t)
563 kubeClient, kubeConfig, tearDownFn := framework.StartTestServer(tCtx, t, framework.TestServerSetup{
564 ModifyServerRunOptions: func(opts *options.ServerRunOptions) {
565
566 opts.Admission.GenericAdmission.DisablePlugins = []string{"ServiceAccount"}
567 opts.Authorization.Modes = []string{"AlwaysDeny"}
568 opts.Authentication.TokenFile.TokenFile = "testdata/tokens.csv"
569 },
570 })
571 defer tearDownFn()
572
573 ns := framework.CreateNamespaceOrDie(kubeClient, "auth-always-deny", t)
574 defer framework.DeleteNamespaceOrDie(kubeClient, ns, t)
575
576 transport, err := rest.TransportFor(kubeConfig)
577 if err != nil {
578 t.Fatal(err)
579 }
580 transport = resttransport.NewBearerAuthRoundTripper(AliceToken, transport)
581
582 for _, r := range getTestRequests(ns.Name) {
583 bodyBytes := bytes.NewReader([]byte(r.body))
584 req, err := http.NewRequest(r.verb, kubeConfig.Host+r.URL, bodyBytes)
585 if err != nil {
586 t.Logf("case %v", r)
587 t.Fatalf("unexpected error: %v", err)
588 }
589 func() {
590 resp, err := transport.RoundTrip(req)
591 if err != nil {
592 t.Logf("case %v", r)
593 t.Fatalf("unexpected error: %v", err)
594 }
595 defer resp.Body.Close()
596 if resp.StatusCode != http.StatusForbidden {
597 t.Logf("case %v", r)
598 t.Errorf("Expected status Forbidden but got status %v", resp.Status)
599 }
600 }()
601 }
602 }
603
604
605
606 func TestAliceNotForbiddenOrUnauthorized(t *testing.T) {
607 tCtx := ktesting.Init(t)
608 kubeClient, kubeConfig, tearDownFn := framework.StartTestServer(tCtx, t, framework.TestServerSetup{
609 ModifyServerRunOptions: func(opts *options.ServerRunOptions) {
610
611 opts.Admission.GenericAdmission.DisablePlugins = []string{"ServiceAccount"}
612 opts.Authentication.TokenFile.TokenFile = "testdata/tokens.csv"
613 opts.Authorization.Modes = []string{"ABAC"}
614 opts.Authorization.PolicyFile = "testdata/allowalice.jsonl"
615 },
616 })
617 defer tearDownFn()
618
619 ns := framework.CreateNamespaceOrDie(kubeClient, "auth-alice-not-forbidden", t)
620 defer framework.DeleteNamespaceOrDie(kubeClient, ns, t)
621
622 previousResourceVersion := make(map[string]float64)
623 transport, err := rest.TransportFor(kubeConfig)
624 if err != nil {
625 t.Fatal(err)
626 }
627
628 for _, r := range getTestRequests(ns.Name) {
629 token := AliceToken
630 var bodyStr string
631 if r.body != "" {
632 sub := ""
633 if r.verb == "PUT" {
634
635 if resVersion := previousResourceVersion[getPreviousResourceVersionKey(r.URL, "")]; resVersion != 0 {
636 sub += fmt.Sprintf(",\r\n\"resourceVersion\": \"%v\"", resVersion)
637 }
638 sub += fmt.Sprintf(",\r\n\"namespace\": %q", ns.Name)
639 }
640 bodyStr = fmt.Sprintf(r.body, sub)
641 }
642 r.body = bodyStr
643 bodyBytes := bytes.NewReader([]byte(bodyStr))
644 req, err := http.NewRequest(r.verb, kubeConfig.Host+r.URL, bodyBytes)
645 if err != nil {
646 t.Fatalf("unexpected error: %v", err)
647 }
648 req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
649 if r.verb == "PATCH" {
650 req.Header.Set("Content-Type", "application/merge-patch+json")
651 }
652
653 func() {
654 resp, err := transport.RoundTrip(req)
655 if err != nil {
656 t.Logf("case %v", r)
657 t.Fatalf("unexpected error: %v", err)
658 }
659 defer resp.Body.Close()
660 b, _ := io.ReadAll(resp.Body)
661 if _, ok := r.statusCodes[resp.StatusCode]; !ok {
662 t.Logf("case %v", r)
663 t.Errorf("Expected status one of %v, but got %v", r.statusCodes, resp.StatusCode)
664 t.Errorf("Body: %v", string(b))
665 } else {
666 if r.verb == "POST" {
667
668 id, currentResourceVersion, err := parseResourceVersion(b)
669 if err == nil {
670 key := getPreviousResourceVersionKey(r.URL, id)
671 previousResourceVersion[key] = currentResourceVersion
672 }
673 }
674 }
675
676 }()
677 }
678 }
679
680
681
682
683 func TestBobIsForbidden(t *testing.T) {
684 tCtx := ktesting.Init(t)
685 kubeClient, kubeConfig, tearDownFn := framework.StartTestServer(tCtx, t, framework.TestServerSetup{
686 ModifyServerRunOptions: func(opts *options.ServerRunOptions) {
687
688 opts.Admission.GenericAdmission.DisablePlugins = []string{"ServiceAccount"}
689 opts.Authentication.TokenFile.TokenFile = "testdata/tokens.csv"
690 opts.Authorization.Modes = []string{"ABAC"}
691 opts.Authorization.PolicyFile = "testdata/allowalice.jsonl"
692 },
693 })
694 defer tearDownFn()
695
696 ns := framework.CreateNamespaceOrDie(kubeClient, "auth-bob-forbidden", t)
697 defer framework.DeleteNamespaceOrDie(kubeClient, ns, t)
698
699 transport, err := rest.TransportFor(kubeConfig)
700 if err != nil {
701 t.Fatal(err)
702 }
703
704 for _, r := range getTestRequests(ns.Name) {
705 token := BobToken
706 bodyBytes := bytes.NewReader([]byte(r.body))
707 req, err := http.NewRequest(r.verb, kubeConfig.Host+r.URL, bodyBytes)
708 if err != nil {
709 t.Fatalf("unexpected error: %v", err)
710 }
711 req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
712
713 func() {
714 resp, err := transport.RoundTrip(req)
715 if err != nil {
716 t.Logf("case %v", r)
717 t.Fatalf("unexpected error: %v", err)
718 }
719 defer resp.Body.Close()
720
721 if resp.StatusCode != http.StatusForbidden {
722 t.Logf("case %v", r)
723 t.Errorf("Expected not status Forbidden, but got %s", resp.Status)
724 }
725 }()
726 }
727 }
728
729
730
731
732
733 func TestUnknownUserIsUnauthorized(t *testing.T) {
734 tCtx := ktesting.Init(t)
735 kubeClient, kubeConfig, tearDownFn := framework.StartTestServer(tCtx, t, framework.TestServerSetup{
736 ModifyServerRunOptions: func(opts *options.ServerRunOptions) {
737
738 opts.Admission.GenericAdmission.DisablePlugins = []string{"ServiceAccount"}
739 opts.Authentication.TokenFile.TokenFile = "testdata/tokens.csv"
740 opts.Authorization.Modes = []string{"ABAC"}
741 opts.Authorization.PolicyFile = "testdata/allowalice.jsonl"
742 },
743 })
744 defer tearDownFn()
745
746 ns := framework.CreateNamespaceOrDie(kubeClient, "auth-unknown-unauthorized", t)
747 defer framework.DeleteNamespaceOrDie(kubeClient, ns, t)
748
749 transport, err := rest.TransportFor(kubeConfig)
750 if err != nil {
751 t.Fatal(err)
752 }
753
754 for _, r := range getTestRequests(ns.Name) {
755 token := UnknownToken
756 bodyBytes := bytes.NewReader([]byte(r.body))
757 req, err := http.NewRequest(r.verb, kubeConfig.Host+r.URL, bodyBytes)
758 if err != nil {
759 t.Fatalf("unexpected error: %v", err)
760 }
761 req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
762 func() {
763 resp, err := transport.RoundTrip(req)
764 if err != nil {
765 t.Logf("case %v", r)
766 t.Fatalf("unexpected error: %v", err)
767 }
768 defer resp.Body.Close()
769
770 if resp.StatusCode != http.StatusUnauthorized {
771 t.Logf("case %v", r)
772 t.Errorf("Expected status %v, but got %v", http.StatusUnauthorized, resp.StatusCode)
773 b, _ := io.ReadAll(resp.Body)
774 t.Errorf("Body: %v", string(b))
775 }
776 }()
777 }
778 }
779
780 type impersonateAuthorizer struct{}
781
782
783 func (impersonateAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
784
785 if a.GetUser() != nil && a.GetUser().GetName() == "alice" && a.GetVerb() == "impersonate" && a.GetResource() == "serviceaccounts" {
786 return authorizer.DecisionAllow, "", nil
787 }
788 if a.GetUser() != nil && a.GetUser().GetName() == "alice" && a.GetVerb() != "impersonate" {
789 return authorizer.DecisionAllow, "", nil
790 }
791
792 if a.GetUser() != nil && a.GetUser().GetName() == "bob" && a.GetVerb() == "impersonate" {
793 return authorizer.DecisionAllow, "", nil
794 }
795 if a.GetUser() != nil && a.GetUser().GetName() == "bob" && a.GetVerb() != "impersonate" {
796 return authorizer.DecisionDeny, "", nil
797 }
798
799 if a.GetUser() != nil && strings.HasPrefix(a.GetUser().GetName(), serviceaccount.ServiceAccountUsernamePrefix) {
800 return authorizer.DecisionAllow, "", nil
801 }
802
803 return authorizer.DecisionNoOpinion, "I can't allow that. Go ask alice.", nil
804 }
805
806 func TestImpersonateIsForbidden(t *testing.T) {
807 tCtx := ktesting.Init(t)
808 kubeClient, kubeConfig, tearDownFn := framework.StartTestServer(tCtx, t, framework.TestServerSetup{
809 ModifyServerRunOptions: func(opts *options.ServerRunOptions) {
810
811 opts.Admission.GenericAdmission.DisablePlugins = []string{"ServiceAccount"}
812 opts.Authentication.TokenFile.TokenFile = "testdata/tokens.csv"
813 },
814 ModifyServerConfig: func(config *controlplane.Config) {
815
816 config.GenericConfig.Authorization.Authorizer = unionauthz.New(impersonateAuthorizer{}, config.GenericConfig.Authorization.Authorizer)
817 },
818 })
819 defer tearDownFn()
820
821 ns := framework.CreateNamespaceOrDie(kubeClient, "auth-impersonate-forbidden", t)
822 defer framework.DeleteNamespaceOrDie(kubeClient, ns, t)
823
824 transport, err := rest.TransportFor(kubeConfig)
825 if err != nil {
826 t.Fatal(err)
827 }
828
829
830 for _, r := range getTestRequests(ns.Name) {
831 token := BobToken
832 bodyBytes := bytes.NewReader([]byte(r.body))
833 req, err := http.NewRequest(r.verb, kubeConfig.Host+r.URL, bodyBytes)
834 if err != nil {
835 t.Fatalf("unexpected error: %v", err)
836 }
837 req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
838
839 func() {
840 resp, err := transport.RoundTrip(req)
841 if err != nil {
842 t.Logf("case %v", r)
843 t.Fatalf("unexpected error: %v", err)
844 }
845 defer resp.Body.Close()
846
847 if resp.StatusCode != http.StatusForbidden {
848 t.Logf("case %v", r)
849 t.Errorf("Expected status Forbidden, but got %s", resp.Status)
850 }
851 }()
852 }
853
854
855 for _, r := range getTestRequests(ns.Name) {
856 token := BobToken
857 bodyBytes := bytes.NewReader([]byte(r.body))
858 req, err := http.NewRequest(r.verb, kubeConfig.Host+r.URL, bodyBytes)
859 if err != nil {
860 t.Fatalf("unexpected error: %v", err)
861 }
862 req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
863 req.Header.Set("Impersonate-User", "alice")
864 func() {
865 resp, err := transport.RoundTrip(req)
866 if err != nil {
867 t.Logf("case %v", r)
868 t.Fatalf("unexpected error: %v", err)
869 }
870 defer resp.Body.Close()
871
872 if resp.StatusCode == http.StatusForbidden {
873 t.Logf("case %v", r)
874 t.Errorf("Expected status not %v, but got %v", http.StatusForbidden, resp.StatusCode)
875 }
876 }()
877 }
878
879
880 for _, r := range getTestRequests(ns.Name) {
881 token := AliceToken
882 bodyBytes := bytes.NewReader([]byte(r.body))
883 req, err := http.NewRequest(r.verb, kubeConfig.Host+r.URL, bodyBytes)
884 if err != nil {
885 t.Fatalf("unexpected error: %v", err)
886 }
887 req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
888 req.Header.Set("Impersonate-User", "bob")
889
890 func() {
891 resp, err := transport.RoundTrip(req)
892 if err != nil {
893 t.Logf("case %v", r)
894 t.Fatalf("unexpected error: %v", err)
895 }
896 defer resp.Body.Close()
897
898 if resp.StatusCode != http.StatusForbidden {
899 t.Logf("case %v", r)
900 t.Errorf("Expected not status Forbidden, but got %s", resp.Status)
901 }
902 }()
903 }
904
905
906 for _, r := range getTestRequests(ns.Name) {
907 token := BobToken
908 bodyBytes := bytes.NewReader([]byte(r.body))
909 req, err := http.NewRequest(r.verb, kubeConfig.Host+r.URL, bodyBytes)
910 if err != nil {
911 t.Fatalf("unexpected error: %v", err)
912 }
913 req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
914 req.Header.Set("Impersonate-User", serviceaccount.MakeUsername("default", "default"))
915 func() {
916 resp, err := transport.RoundTrip(req)
917 if err != nil {
918 t.Logf("case %v", r)
919 t.Fatalf("unexpected error: %v", err)
920 }
921 defer resp.Body.Close()
922
923 if resp.StatusCode == http.StatusForbidden {
924 t.Logf("case %v", r)
925 t.Errorf("Expected status not %v, but got %v", http.StatusForbidden, resp.StatusCode)
926 }
927 }()
928 }
929
930 }
931
932 func TestImpersonateWithUID(t *testing.T) {
933 server := kubeapiservertesting.StartTestServerOrDie(
934 t,
935 nil,
936 []string{
937 "--authorization-mode=RBAC",
938 "--anonymous-auth",
939 },
940 framework.SharedEtcd(),
941 )
942 t.Cleanup(server.TearDownFn)
943
944 ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
945 t.Cleanup(cancel)
946
947 t.Run("impersonation with uid header", func(t *testing.T) {
948 adminClient := clientset.NewForConfigOrDie(server.ClientConfig)
949
950 authutil.GrantUserAuthorization(t, ctx, adminClient, "alice",
951 rbacv1.PolicyRule{
952 Verbs: []string{"create"},
953 APIGroups: []string{"certificates.k8s.io"},
954 Resources: []string{"certificatesigningrequests"},
955 },
956 )
957
958 req := csrPEM(t)
959
960 clientConfig := rest.CopyConfig(server.ClientConfig)
961 clientConfig.Impersonate = rest.ImpersonationConfig{
962 UserName: "alice",
963 UID: "1234",
964 }
965
966 client := clientset.NewForConfigOrDie(clientConfig)
967 createdCsr, err := client.CertificatesV1().CertificateSigningRequests().Create(
968 ctx,
969 &certificatesv1.CertificateSigningRequest{
970 Spec: certificatesv1.CertificateSigningRequestSpec{
971 SignerName: "kubernetes.io/kube-apiserver-client",
972 Request: req,
973 Usages: []certificatesv1.KeyUsage{"client auth"},
974 },
975 ObjectMeta: metav1.ObjectMeta{
976 Name: "impersonated-csr",
977 },
978 },
979 metav1.CreateOptions{},
980 )
981 if err != nil {
982 t.Fatalf("Unexpected error creating Certificate Signing Request: %v", err)
983 }
984
985
986
987 expectedCsrSpec := certificatesv1.CertificateSigningRequestSpec{
988 Groups: []string{"system:authenticated"},
989 SignerName: "kubernetes.io/kube-apiserver-client",
990 Request: req,
991 Usages: []certificatesv1.KeyUsage{"client auth"},
992 Username: "alice",
993 UID: "1234",
994 }
995 actualCsrSpec := createdCsr.Spec
996
997 if diff := cmp.Diff(expectedCsrSpec, actualCsrSpec); diff != "" {
998 t.Fatalf("CSR spec was different than expected, -got, +want:\n %s", diff)
999 }
1000 })
1001
1002 t.Run("impersonation with only UID fails", func(t *testing.T) {
1003 clientConfig := rest.CopyConfig(server.ClientConfig)
1004 clientConfig.Impersonate = rest.ImpersonationConfig{
1005 UID: "1234",
1006 }
1007
1008 client := clientset.NewForConfigOrDie(clientConfig)
1009 _, err := client.CoreV1().Nodes().List(ctx, metav1.ListOptions{})
1010
1011 if !errors.IsInternalError(err) {
1012 t.Fatalf("expected internal error, got %T %v", err, err)
1013 }
1014 if diff := cmp.Diff(
1015 `an error on the server ("Internal Server Error: \"/api/v1/nodes\": `+
1016 `requested [{UID 1234 authentication.k8s.io/v1 }] without impersonating a user") `+
1017 `has prevented the request from succeeding (get nodes)`,
1018 err.Error(),
1019 ); diff != "" {
1020 t.Fatalf("internal error different than expected, -got, +want:\n %s", diff)
1021 }
1022 })
1023
1024 t.Run("impersonating UID without authorization fails", func(t *testing.T) {
1025 adminClient := clientset.NewForConfigOrDie(server.ClientConfig)
1026
1027 authutil.GrantUserAuthorization(t, ctx, adminClient, "system:anonymous",
1028 rbacv1.PolicyRule{
1029 Verbs: []string{"impersonate"},
1030 APIGroups: []string{""},
1031 Resources: []string{"users"},
1032 ResourceNames: []string{"some-user-anonymous-can-impersonate"},
1033 },
1034 )
1035
1036 clientConfig := rest.AnonymousClientConfig(server.ClientConfig)
1037 clientConfig.Impersonate = rest.ImpersonationConfig{
1038 UserName: "some-user-anonymous-can-impersonate",
1039 UID: "1234",
1040 }
1041
1042 client := clientset.NewForConfigOrDie(clientConfig)
1043 _, err := client.CoreV1().Nodes().List(ctx, metav1.ListOptions{})
1044
1045 if !errors.IsForbidden(err) {
1046 t.Fatalf("expected forbidden error, got %T %v", err, err)
1047 }
1048 if diff := cmp.Diff(
1049 `uids.authentication.k8s.io "1234" is forbidden: `+
1050 `User "system:anonymous" cannot impersonate resource "uids" in API group "authentication.k8s.io" at the cluster scope`,
1051 err.Error(),
1052 ); diff != "" {
1053 t.Fatalf("forbidden error different than expected, -got, +want:\n %s", diff)
1054 }
1055 })
1056 }
1057
1058 func csrPEM(t *testing.T) []byte {
1059 t.Helper()
1060
1061 _, privateKey, err := ed25519.GenerateKey(rand.Reader)
1062 if err != nil {
1063 t.Fatalf("Unexpected error generating ed25519 key: %v", err)
1064 }
1065
1066 csrDER, err := x509.CreateCertificateRequest(
1067 rand.Reader,
1068 &x509.CertificateRequest{
1069 Subject: pkix.Name{
1070 Organization: []string{},
1071 },
1072 },
1073 privateKey)
1074 if err != nil {
1075 t.Fatalf("Unexpected error creating x509 certificate request: %v", err)
1076 }
1077
1078 csrPemBlock := &pem.Block{
1079 Type: "CERTIFICATE REQUEST",
1080 Bytes: csrDER,
1081 }
1082
1083 req := pem.EncodeToMemory(csrPemBlock)
1084 if req == nil {
1085 t.Fatalf("Failed to encode PEM to memory.")
1086 }
1087 return req
1088 }
1089
1090 func newABACFileWithContents(t *testing.T, contents string) string {
1091 dir := t.TempDir()
1092 file := filepath.Join(dir, "auth_test")
1093 if err := os.WriteFile(file, []byte(contents), 0700); err != nil {
1094 t.Fatalf("unexpected error writing policyfile: %v", err)
1095 }
1096 return file
1097 }
1098
1099 type trackingAuthorizer struct {
1100 requestAttributes []authorizer.Attributes
1101 }
1102
1103 func (a *trackingAuthorizer) Authorize(ctx context.Context, attributes authorizer.Attributes) (authorizer.Decision, string, error) {
1104 a.requestAttributes = append(a.requestAttributes, attributes)
1105 return authorizer.DecisionAllow, "", nil
1106 }
1107
1108
1109 func TestAuthorizationAttributeDetermination(t *testing.T) {
1110 tCtx := ktesting.Init(t)
1111
1112 trackingAuthorizer := &trackingAuthorizer{}
1113
1114 kubeClient, kubeConfig, tearDownFn := framework.StartTestServer(tCtx, t, framework.TestServerSetup{
1115 ModifyServerRunOptions: func(opts *options.ServerRunOptions) {
1116
1117 opts.Admission.GenericAdmission.DisablePlugins = []string{"ServiceAccount"}
1118 opts.Authentication.TokenFile.TokenFile = "testdata/tokens.csv"
1119 },
1120 ModifyServerConfig: func(config *controlplane.Config) {
1121 config.GenericConfig.Authorization.Authorizer = unionauthz.New(config.GenericConfig.Authorization.Authorizer, trackingAuthorizer)
1122 },
1123 })
1124 defer tearDownFn()
1125
1126 ns := framework.CreateNamespaceOrDie(kubeClient, "auth-attribute-determination", t)
1127 defer framework.DeleteNamespaceOrDie(kubeClient, ns, t)
1128
1129 transport, err := rest.TransportFor(kubeConfig)
1130 if err != nil {
1131 t.Fatal(err)
1132 }
1133
1134 requests := map[string]struct {
1135 verb string
1136 URL string
1137 expectedAttributes authorizer.Attributes
1138 }{
1139 "prefix/version/resource": {"GET", "/api/v1/pods", authorizer.AttributesRecord{APIGroup: api.GroupName, Resource: "pods"}},
1140 "prefix/group/version/resource": {"GET", "/apis/extensions/v1/pods", authorizer.AttributesRecord{APIGroup: extensions.GroupName, Resource: "pods"}},
1141 "prefix/group/version/resource2": {"GET", "/apis/autoscaling/v1/horizontalpodautoscalers", authorizer.AttributesRecord{APIGroup: autoscaling.GroupName, Resource: "horizontalpodautoscalers"}},
1142 }
1143
1144 currentAuthorizationAttributesIndex := 0
1145
1146 for testName, r := range requests {
1147 token := BobToken
1148 req, err := http.NewRequest(r.verb, kubeConfig.Host+r.URL, nil)
1149 if err != nil {
1150 t.Logf("case %v", testName)
1151 t.Fatalf("unexpected error: %v", err)
1152 }
1153 req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
1154 func() {
1155 resp, err := transport.RoundTrip(req)
1156 if err != nil {
1157 t.Logf("case %v", r)
1158 t.Fatalf("unexpected error: %v", err)
1159 }
1160 defer resp.Body.Close()
1161
1162 found := false
1163 for i := currentAuthorizationAttributesIndex; i < len(trackingAuthorizer.requestAttributes); i++ {
1164 if trackingAuthorizer.requestAttributes[i].GetAPIGroup() == r.expectedAttributes.GetAPIGroup() &&
1165 trackingAuthorizer.requestAttributes[i].GetResource() == r.expectedAttributes.GetResource() {
1166 found = true
1167 break
1168 }
1169
1170 t.Logf("%#v did not match %#v", r.expectedAttributes, trackingAuthorizer.requestAttributes[i].(*authorizer.AttributesRecord))
1171 }
1172 if !found {
1173 t.Errorf("did not find %#v in %#v", r.expectedAttributes, trackingAuthorizer.requestAttributes[currentAuthorizationAttributesIndex:])
1174 }
1175
1176 currentAuthorizationAttributesIndex = len(trackingAuthorizer.requestAttributes)
1177 }()
1178 }
1179 }
1180
1181
1182
1183 func TestNamespaceAuthorization(t *testing.T) {
1184 tCtx := ktesting.Init(t)
1185
1186 kubeClient, kubeConfig, tearDownFn := framework.StartTestServer(tCtx, t, framework.TestServerSetup{
1187 ModifyServerRunOptions: func(opts *options.ServerRunOptions) {
1188
1189 opts.Admission.GenericAdmission.DisablePlugins = []string{"ServiceAccount"}
1190 opts.Authentication.TokenFile.TokenFile = "testdata/tokens.csv"
1191 opts.Authorization.PolicyFile = newABACFileWithContents(t, `{"namespace": "auth-namespace"}`)
1192 opts.Authorization.Modes = []string{"ABAC"}
1193 },
1194 })
1195 defer tearDownFn()
1196
1197 ns := framework.CreateNamespaceOrDie(kubeClient, "auth-namespace", t)
1198 defer framework.DeleteNamespaceOrDie(kubeClient, ns, t)
1199
1200 previousResourceVersion := make(map[string]float64)
1201 transport, err := rest.TransportFor(kubeConfig)
1202 if err != nil {
1203 t.Fatal(err)
1204 }
1205
1206 requests := []struct {
1207 verb string
1208 URL string
1209 namespace string
1210 body string
1211 statusCodes map[int]bool
1212 }{
1213
1214 {"POST", timeoutPath("pods", ns.Name, ""), "foo", aPod, integration.Code201},
1215 {"GET", path("pods", ns.Name, ""), "foo", "", integration.Code200},
1216 {"GET", path("pods", ns.Name, "a"), "foo", "", integration.Code200},
1217 {"DELETE", timeoutPath("pods", ns.Name, "a"), "foo", "", integration.Code200},
1218
1219 {"POST", timeoutPath("pods", "foo", ""), "bar", aPod, integration.Code403},
1220 {"GET", path("pods", "foo", ""), "bar", "", integration.Code403},
1221 {"GET", path("pods", "foo", "a"), "bar", "", integration.Code403},
1222 {"DELETE", timeoutPath("pods", "foo", "a"), "bar", "", integration.Code403},
1223
1224 {"POST", timeoutPath("pods", metav1.NamespaceDefault, ""), "", aPod, integration.Code403},
1225 {"GET", path("pods", "", ""), "", "", integration.Code403},
1226 {"GET", path("pods", metav1.NamespaceDefault, "a"), "", "", integration.Code403},
1227 {"DELETE", timeoutPath("pods", metav1.NamespaceDefault, "a"), "", "", integration.Code403},
1228 }
1229
1230 for _, r := range requests {
1231 token := BobToken
1232 var bodyStr string
1233 if r.body != "" {
1234 sub := ""
1235 if r.verb == "PUT" && r.body != "" {
1236
1237 if resVersion := previousResourceVersion[getPreviousResourceVersionKey(r.URL, "")]; resVersion != 0 {
1238 sub += fmt.Sprintf(",\r\n\"resourceVersion\": \"%v\"", resVersion)
1239 }
1240 namespace := r.namespace
1241
1242 if len(namespace) == 0 {
1243 namespace = "default"
1244 }
1245 sub += fmt.Sprintf(",\r\n\"namespace\": %q", namespace)
1246 }
1247 bodyStr = fmt.Sprintf(r.body, sub)
1248 }
1249 r.body = bodyStr
1250 bodyBytes := bytes.NewReader([]byte(bodyStr))
1251 req, err := http.NewRequest(r.verb, kubeConfig.Host+r.URL, bodyBytes)
1252 if err != nil {
1253 t.Logf("case %v", r)
1254 t.Fatalf("unexpected error: %v", err)
1255 }
1256 req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
1257 func() {
1258 resp, err := transport.RoundTrip(req)
1259 if err != nil {
1260 t.Logf("case %v", r)
1261 t.Fatalf("unexpected error: %v", err)
1262 }
1263 defer resp.Body.Close()
1264 b, _ := io.ReadAll(resp.Body)
1265 if _, ok := r.statusCodes[resp.StatusCode]; !ok {
1266 t.Logf("case %v", r)
1267 t.Errorf("Expected status one of %v, but got %v", r.statusCodes, resp.StatusCode)
1268 t.Errorf("Body: %v", string(b))
1269 } else {
1270 if r.verb == "POST" {
1271
1272 id, currentResourceVersion, err := parseResourceVersion(b)
1273 if err == nil {
1274 key := getPreviousResourceVersionKey(r.URL, id)
1275 previousResourceVersion[key] = currentResourceVersion
1276 }
1277 }
1278 }
1279
1280 }()
1281 }
1282 }
1283
1284
1285
1286 func TestKindAuthorization(t *testing.T) {
1287 tCtx := ktesting.Init(t)
1288
1289 kubeClient, kubeConfig, tearDownFn := framework.StartTestServer(tCtx, t, framework.TestServerSetup{
1290 ModifyServerRunOptions: func(opts *options.ServerRunOptions) {
1291
1292 opts.Admission.GenericAdmission.DisablePlugins = []string{"ServiceAccount"}
1293 opts.Authentication.TokenFile.TokenFile = "testdata/tokens.csv"
1294 opts.Authorization.PolicyFile = newABACFileWithContents(t, `{"resource": "services"}`)
1295 opts.Authorization.Modes = []string{"ABAC"}
1296 },
1297 })
1298 defer tearDownFn()
1299
1300 ns := framework.CreateNamespaceOrDie(kubeClient, "auth-kind", t)
1301 defer framework.DeleteNamespaceOrDie(kubeClient, ns, t)
1302
1303 previousResourceVersion := make(map[string]float64)
1304 transport, err := rest.TransportFor(kubeConfig)
1305 if err != nil {
1306 t.Fatal(err)
1307 }
1308
1309 requests := []testRequest{
1310 {"POST", timeoutPath("services", ns.Name, ""), aService, integration.Code201},
1311 {"GET", path("services", ns.Name, ""), "", integration.Code200},
1312 {"GET", path("services", ns.Name, "a"), "", integration.Code200},
1313 {"DELETE", timeoutPath("services", ns.Name, "a"), "", integration.Code200},
1314
1315 {"POST", timeoutPath("pods", ns.Name, ""), aPod, integration.Code403},
1316 {"GET", path("pods", "", ""), "", integration.Code403},
1317 {"GET", path("pods", ns.Name, "a"), "", integration.Code403},
1318 {"DELETE", timeoutPath("pods", ns.Name, "a"), "", integration.Code403},
1319 }
1320
1321 for _, r := range requests {
1322 token := BobToken
1323 var bodyStr string
1324 if r.body != "" {
1325 bodyStr = fmt.Sprintf(r.body, "")
1326 if r.verb == "PUT" && r.body != "" {
1327
1328 if resVersion := previousResourceVersion[getPreviousResourceVersionKey(r.URL, "")]; resVersion != 0 {
1329 resourceVersionJSON := fmt.Sprintf(",\r\n\"resourceVersion\": \"%v\"", resVersion)
1330 bodyStr = fmt.Sprintf(r.body, resourceVersionJSON)
1331 }
1332 }
1333 }
1334 r.body = bodyStr
1335 bodyBytes := bytes.NewReader([]byte(bodyStr))
1336 req, err := http.NewRequest(r.verb, kubeConfig.Host+r.URL, bodyBytes)
1337 if err != nil {
1338 t.Logf("case %v", r)
1339 t.Fatalf("unexpected error: %v", err)
1340 }
1341 req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
1342 func() {
1343 resp, err := transport.RoundTrip(req)
1344 if err != nil {
1345 t.Logf("case %v", r)
1346 t.Fatalf("unexpected error: %v", err)
1347 }
1348 defer resp.Body.Close()
1349 b, _ := io.ReadAll(resp.Body)
1350 if _, ok := r.statusCodes[resp.StatusCode]; !ok {
1351 t.Logf("case %v", r)
1352 t.Errorf("Expected status one of %v, but got %v", r.statusCodes, resp.StatusCode)
1353 t.Errorf("Body: %v", string(b))
1354 } else {
1355 if r.verb == "POST" {
1356
1357 id, currentResourceVersion, err := parseResourceVersion(b)
1358 if err == nil {
1359 key := getPreviousResourceVersionKey(r.URL, id)
1360 previousResourceVersion[key] = currentResourceVersion
1361 }
1362 }
1363 }
1364
1365 }()
1366 }
1367 }
1368
1369
1370
1371 func TestReadOnlyAuthorization(t *testing.T) {
1372 tCtx := ktesting.Init(t)
1373 kubeClient, kubeConfig, tearDownFn := framework.StartTestServer(tCtx, t, framework.TestServerSetup{
1374 ModifyServerRunOptions: func(opts *options.ServerRunOptions) {
1375
1376 opts.Admission.GenericAdmission.DisablePlugins = []string{"ServiceAccount"}
1377 opts.Authentication.TokenFile.TokenFile = "testdata/tokens.csv"
1378 opts.Authorization.PolicyFile = newABACFileWithContents(t, `{"readonly": true}`)
1379 opts.Authorization.Modes = []string{"ABAC"}
1380 },
1381 })
1382 defer tearDownFn()
1383
1384 ns := framework.CreateNamespaceOrDie(kubeClient, "auth-read-only", t)
1385 defer framework.DeleteNamespaceOrDie(kubeClient, ns, t)
1386
1387 transport, err := rest.TransportFor(kubeConfig)
1388 if err != nil {
1389 t.Fatal(err)
1390 }
1391
1392 requests := []testRequest{
1393 {"POST", path("pods", ns.Name, ""), aPod, integration.Code403},
1394 {"GET", path("pods", ns.Name, ""), "", integration.Code200},
1395 {"GET", path("pods", metav1.NamespaceDefault, "a"), "", integration.Code404},
1396 }
1397
1398 for _, r := range requests {
1399 token := BobToken
1400 bodyBytes := bytes.NewReader([]byte(r.body))
1401 req, err := http.NewRequest(r.verb, kubeConfig.Host+r.URL, bodyBytes)
1402 if err != nil {
1403 t.Fatalf("unexpected error: %v", err)
1404 }
1405 req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
1406 func() {
1407 resp, err := transport.RoundTrip(req)
1408 if err != nil {
1409 t.Logf("case %v", r)
1410 t.Fatalf("unexpected error: %v", err)
1411 }
1412 defer resp.Body.Close()
1413 if _, ok := r.statusCodes[resp.StatusCode]; !ok {
1414 t.Logf("case %v", r)
1415 t.Errorf("Expected status one of %v, but got %v", r.statusCodes, resp.StatusCode)
1416 b, _ := io.ReadAll(resp.Body)
1417 t.Errorf("Body: %v", string(b))
1418 }
1419 }()
1420 }
1421 }
1422
1423
1424
1425
1426 func TestWebhookTokenAuthenticator(t *testing.T) {
1427 testWebhookTokenAuthenticator(false, t)
1428 }
1429
1430
1431
1432 func TestWebhookTokenAuthenticatorCustomDial(t *testing.T) {
1433 testWebhookTokenAuthenticator(true, t)
1434 }
1435
1436 func testWebhookTokenAuthenticator(customDialer bool, t *testing.T) {
1437 tCtx := ktesting.Init(t)
1438 authServer := newTestWebhookTokenAuthServer()
1439 defer authServer.Close()
1440 var authenticator authenticator.Request
1441 var err error
1442
1443 if customDialer == false {
1444 authenticator, err = getTestWebhookTokenAuth(authServer.URL, nil)
1445 } else {
1446 authenticator, err = getTestWebhookTokenAuthCustomDialer(authServer.URL)
1447 }
1448
1449 if err != nil {
1450 t.Fatalf("error starting webhook token authenticator server: %v", err)
1451 }
1452
1453 kubeClient, kubeConfig, tearDownFn := framework.StartTestServer(tCtx, t, framework.TestServerSetup{
1454 ModifyServerRunOptions: func(opts *options.ServerRunOptions) {
1455
1456 opts.Admission.GenericAdmission.DisablePlugins = []string{"ServiceAccount"}
1457 opts.Authorization.Modes = []string{"ABAC"}
1458 opts.Authorization.PolicyFile = "testdata/allowalice.jsonl"
1459 },
1460 ModifyServerConfig: func(config *controlplane.Config) {
1461 config.GenericConfig.Authentication.Authenticator = group.NewAuthenticatedGroupAdder(authenticator)
1462
1463 config.GenericConfig.Authentication.APIAudiences = nil
1464 },
1465 })
1466 defer tearDownFn()
1467
1468 ns := framework.CreateNamespaceOrDie(kubeClient, "auth-webhook-token", t)
1469 defer framework.DeleteNamespaceOrDie(kubeClient, ns, t)
1470
1471 transport, err := rest.TransportFor(kubeConfig)
1472 if err != nil {
1473 t.Fatal(err)
1474 }
1475
1476 for _, r := range getTestRequests(ns.Name) {
1477
1478 token := BobToken
1479 bodyBytes := bytes.NewReader([]byte(r.body))
1480 req, err := http.NewRequest(r.verb, kubeConfig.Host+r.URL, bodyBytes)
1481 if err != nil {
1482 t.Fatalf("unexpected error: %v", err)
1483 }
1484 req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
1485
1486 func() {
1487 resp, err := transport.RoundTrip(req)
1488 if err != nil {
1489 t.Logf("case %v", r)
1490 t.Fatalf("unexpected error: %v", err)
1491 }
1492 defer resp.Body.Close()
1493
1494 if resp.StatusCode != http.StatusForbidden {
1495 t.Logf("case %v", r)
1496 t.Errorf("Expected http.Forbidden, but got %s", resp.Status)
1497 }
1498 }()
1499
1500 token = AliceToken
1501 bodyBytes = bytes.NewReader([]byte(r.body))
1502 req, err = http.NewRequest(r.verb, kubeConfig.Host+r.URL, bodyBytes)
1503 if err != nil {
1504 t.Fatalf("unexpected error: %v", err)
1505 }
1506 req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
1507
1508 func() {
1509 resp, err := transport.RoundTrip(req)
1510 if err != nil {
1511 t.Logf("case %v", r)
1512 t.Fatalf("unexpected error: %v", err)
1513 }
1514 defer resp.Body.Close()
1515
1516 if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {
1517 t.Logf("case %v", r)
1518 t.Errorf("Expected something other than Unauthorized/Forbidden, but got %s", resp.Status)
1519 }
1520 }()
1521 }
1522 }
1523
1524
1525
1526 func newTestWebhookTokenAuthServer() *httptest.Server {
1527 serveHTTP := func(w http.ResponseWriter, r *http.Request) {
1528 var review authenticationv1beta1.TokenReview
1529 if err := json.NewDecoder(r.Body).Decode(&review); err != nil {
1530 http.Error(w, fmt.Sprintf("failed to decode body: %v", err), http.StatusBadRequest)
1531 return
1532 }
1533 type userInfo struct {
1534 Username string `json:"username"`
1535 UID string `json:"uid"`
1536 Groups []string `json:"groups"`
1537 }
1538 type status struct {
1539 Authenticated bool `json:"authenticated"`
1540 User userInfo `json:"user"`
1541 }
1542 var username, uid string
1543 authenticated := false
1544 if review.Spec.Token == AliceToken {
1545 authenticated, username, uid = true, "alice", "1"
1546 } else if review.Spec.Token == BobToken {
1547 authenticated, username, uid = true, "bob", "2"
1548 }
1549
1550 resp := struct {
1551 APIVersion string `json:"apiVersion"`
1552 Status status `json:"status"`
1553 }{
1554 APIVersion: authenticationv1beta1.SchemeGroupVersion.String(),
1555 Status: status{
1556 authenticated,
1557 userInfo{
1558 Username: username,
1559 UID: uid,
1560 },
1561 },
1562 }
1563 w.Header().Set("Content-Type", "application/json")
1564 json.NewEncoder(w).Encode(resp)
1565 }
1566
1567 server := httptest.NewUnstartedServer(http.HandlerFunc(serveHTTP))
1568 server.Start()
1569 return server
1570 }
1571
View as plain text