...

Source file src/k8s.io/kubernetes/test/integration/auth/auth_test.go

Documentation: k8s.io/kubernetes/test/integration/auth

     1  /*
     2  Copyright 2014 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 auth
    18  
    19  // This file tests authentication and (soon) authorization of HTTP requests to an API server object.
    20  // It does not use the client in pkg/client/... because authentication and authorization needs
    21  // to work for any client of the HTTP interface.
    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" // username: alice.  Present in token file.
    82  	BobToken     string = "xyz987" // username: bob.  Present in token file.
    83  	UnknownToken string = "qwerty" // Not present in token file.
    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  	// Resource names are lower case.
   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  // Bodies for requests used in subsequent tests.
   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  // To ensure that a POST completes before a dependent GET, set a timeout.
   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 // allowed status codes.
   340  }
   341  
   342  func getTestRequests(namespace string) []testRequest {
   343  	requests := []testRequest{
   344  		// Normal methods on pods
   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  		// GET and POST for /exec should return Bad Request (400) since the pod has not been assigned a node yet.
   351  		{"GET", path("pods", namespace, "a") + "/exec", "", integration.Code400},
   352  		{"POST", path("pods", namespace, "a") + "/exec", "", integration.Code400},
   353  		// PUT for /exec should return Method Not Allowed (405).
   354  		{"PUT", path("pods", namespace, "a") + "/exec", "", integration.Code405},
   355  		// GET and POST for /portforward should return Bad Request (400) since the pod has not been assigned a node yet.
   356  		{"GET", path("pods", namespace, "a") + "/portforward", "", integration.Code400},
   357  		{"POST", path("pods", namespace, "a") + "/portforward", "", integration.Code400},
   358  		// PUT for /portforward should return Method Not Allowed (405).
   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  		// Non-standard methods (not expected to work,
   364  		// but expected to pass/fail authorization prior to
   365  		// failing validation.
   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  		// Normal methods on services
   375  		{"GET", path("services", "", ""), "", integration.Code200},
   376  		{"GET", path("services", namespace, ""), "", integration.Code200},
   377  		{"POST", timeoutPath("services", namespace, ""), aService, integration.Code201},
   378  		// Create an endpoint for the service (this is done automatically by endpoint controller
   379  		// whenever a service is created, but this test does not run that controller)
   380  		{"POST", timeoutPath("endpoints", namespace, ""), emptyEndpoints, integration.Code201},
   381  		// Should return service unavailable when endpoint.subset is empty.
   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  		// Normal methods on replicationControllers
   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  		// Normal methods on endpoints
   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  		// Normal methods on nodes
   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  		// Normal methods on events
   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  		// Normal methods on bindings
   420  		{"GET", path("bindings", namespace, ""), "", integration.Code405},
   421  		{"POST", timeoutPath("pods", namespace, ""), aPod, integration.Code201}, // Need a pod to bind or you get a 404
   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}, // No bindings instances
   425  		{"DELETE", timeoutPath("bindings", namespace, "a"), "", integration.Code404},
   426  
   427  		// Non-existent object type.
   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  		// Special verbs on nodes
   435  		{"GET", pathWithSubResource("nodes", namespace, "a", "proxy"), "", integration.Code404},
   436  		{"GET", pathWithPrefix("redirect", "nodes", namespace, "a"), "", integration.Code404},
   437  		// TODO: test .../watch/..., which doesn't end before the test timeout.
   438  		// TODO: figure out how to create a node so that it can successfully proxy/redirect.
   439  
   440  		// Non-object endpoints
   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  // The TestAuthMode* tests a large number of URLs and checks that they
   451  // are FORBIDDEN or not, depending on the mode.  They do not attempt to do
   452  // detailed verification of behaviour beyond authorization.  They are not
   453  // fuzz tests.
   454  //
   455  // TODO(etune): write a fuzz test of the REST API.
   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  			// Disable ServiceAccount admission plugin as we don't have serviceaccount controller running.
   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  				// For update operations, insert previous resource version
   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  					// For successful create operations, extract resourceVersion
   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  			// Disable ServiceAccount admission plugin as we don't have serviceaccount controller running.
   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  // TestAliceNotForbiddenOrUnauthorized tests a user who is known to
   605  // the authentication system and authorized to do any actions.
   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  			// Disable ServiceAccount admission plugin as we don't have serviceaccount controller running.
   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  				// For update operations, insert previous resource version
   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  					// For successful create operations, extract resourceVersion
   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  // TestBobIsForbidden tests that a user who is known to
   681  // the authentication system but not authorized to do any actions
   682  // should receive "Forbidden".
   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  			// Disable ServiceAccount admission plugin as we don't have serviceaccount controller running.
   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  			// Expect all of bob's actions to return Forbidden
   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  // TestUnknownUserIsUnauthorized tests that a user who is unknown
   730  // to the authentication system get status code "Unauthorized".
   731  // An authorization module is installed in this scenario for integration
   732  // test purposes, but requests aren't expected to reach it.
   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  			// Disable ServiceAccount admission plugin as we don't have serviceaccount controller running.
   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  			// Expect all of unauthenticated user's request to be "Unauthorized"
   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  // alice can't act as anyone and bob can't do anything but act-as someone
   783  func (impersonateAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
   784  	// alice can impersonate service accounts and do other actions
   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  	// bob can impersonate anyone, but that's it
   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  	// service accounts can do everything
   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  			// Disable ServiceAccount admission plugin as we don't have serviceaccount controller running.
   811  			opts.Admission.GenericAdmission.DisablePlugins = []string{"ServiceAccount"}
   812  			opts.Authentication.TokenFile.TokenFile = "testdata/tokens.csv"
   813  		},
   814  		ModifyServerConfig: func(config *controlplane.Config) {
   815  			// Prepend an impersonation authorizer with specific opinions about alice and bob
   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  	// bob can't perform actions himself
   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  			// Expect all of bob's actions to return Forbidden
   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  	// bob can impersonate alice to do other things
   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  			// Expect all the requests to be allowed, don't care what they actually do
   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  	// alice can't impersonate bob
   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  			// Expect all of bob's actions to return Forbidden
   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  	// bob can impersonate a service account
   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  			// Expect all the requests to be allowed, don't care what they actually do
   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  		// require that all the original fields and the impersonated user's info
   986  		// is in the returned spec.
   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  // TestAuthorizationAttributeDetermination tests that authorization attributes are built correctly
  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  			// Disable ServiceAccount admission plugin as we don't have serviceaccount controller running.
  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  // TestNamespaceAuthorization tests that authorization can be controlled
  1182  // by namespace.
  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  			// Disable ServiceAccount admission plugin as we don't have serviceaccount controller running.
  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 // allowed status codes.
  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  				// For update operations, insert previous resource version
  1237  				if resVersion := previousResourceVersion[getPreviousResourceVersionKey(r.URL, "")]; resVersion != 0 {
  1238  					sub += fmt.Sprintf(",\r\n\"resourceVersion\": \"%v\"", resVersion)
  1239  				}
  1240  				namespace := r.namespace
  1241  				// FIXME: Is that correct?
  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  					// For successful create operations, extract resourceVersion
  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  // TestKindAuthorization tests that authorization can be controlled
  1285  // by namespace.
  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  			// Disable ServiceAccount admission plugin as we don't have serviceaccount controller running.
  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  				// For update operations, insert previous resource version
  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  					// For successful create operations, extract resourceVersion
  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  // TestReadOnlyAuthorization tests that authorization can be controlled
  1370  // by namespace.
  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  			// Disable ServiceAccount admission plugin as we don't have serviceaccount controller running.
  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  // TestWebhookTokenAuthenticator tests that a control plane can use the webhook token
  1424  // authenticator to call out to a remote web server for authentication
  1425  // decisions.
  1426  func TestWebhookTokenAuthenticator(t *testing.T) {
  1427  	testWebhookTokenAuthenticator(false, t)
  1428  }
  1429  
  1430  // TestWebhookTokenAuthenticatorCustomDial is the same as TestWebhookTokenAuthenticator, but uses a
  1431  // custom dialer
  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  			// Disable ServiceAccount admission plugin as we don't have serviceaccount controller running.
  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  			// Disable checking API audiences that is set by testserver by default.
  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  		// Expect Bob's requests to all fail.
  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  			// Expect all of Bob's actions to return Forbidden
  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  		// Expect Alice's requests to succeed.
  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  			// Expect all of Alice's actions to at least get past authn/authz.
  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  // newTestWebhookTokenAuthServer creates an http token authentication server
  1525  // that knows about both Alice and Bob.
  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