k8s.io/kubernetes@v1.29.3/test/integration/auth/auth_test.go (about)

     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  	_, ctx := ktesting.NewTestContext(t)
   458  	ctx, cancel := context.WithCancel(ctx)
   459  	defer cancel()
   460  
   461  	kubeClient, kubeConfig, tearDownFn := framework.StartTestServer(ctx, t, framework.TestServerSetup{
   462  		ModifyServerRunOptions: func(opts *options.ServerRunOptions) {
   463  			// Disable ServiceAccount admission plugin as we don't have serviceaccount controller running.
   464  			opts.Admission.GenericAdmission.DisablePlugins = []string{"ServiceAccount"}
   465  			opts.Authorization.Modes = []string{"AlwaysAllow"}
   466  		},
   467  	})
   468  	defer tearDownFn()
   469  
   470  	ns := framework.CreateNamespaceOrDie(kubeClient, "auth-always-allow", t)
   471  	defer framework.DeleteNamespaceOrDie(kubeClient, ns, t)
   472  
   473  	transport, err := rest.TransportFor(kubeConfig)
   474  	if err != nil {
   475  		t.Fatal(err)
   476  	}
   477  	previousResourceVersion := make(map[string]float64)
   478  
   479  	for _, r := range getTestRequests(ns.Name) {
   480  		var bodyStr string
   481  		if r.body != "" {
   482  			sub := ""
   483  			if r.verb == "PUT" {
   484  				// For update operations, insert previous resource version
   485  				if resVersion := previousResourceVersion[getPreviousResourceVersionKey(r.URL, "")]; resVersion != 0 {
   486  					sub += fmt.Sprintf(",\r\n\"resourceVersion\": \"%v\"", resVersion)
   487  				}
   488  				sub += fmt.Sprintf(",\r\n\"namespace\": %q", ns.Name)
   489  			}
   490  			bodyStr = fmt.Sprintf(r.body, sub)
   491  		}
   492  		r.body = bodyStr
   493  		bodyBytes := bytes.NewReader([]byte(bodyStr))
   494  		req, err := http.NewRequest(r.verb, kubeConfig.Host+r.URL, bodyBytes)
   495  		if err != nil {
   496  			t.Logf("case %v", r)
   497  			t.Fatalf("unexpected error: %v", err)
   498  		}
   499  		if r.verb == "PATCH" {
   500  			req.Header.Set("Content-Type", "application/merge-patch+json")
   501  		}
   502  		func() {
   503  			resp, err := transport.RoundTrip(req)
   504  			if err != nil {
   505  				t.Logf("case %v", r)
   506  				t.Fatalf("unexpected error: %v", err)
   507  			}
   508  			defer resp.Body.Close()
   509  			b, _ := io.ReadAll(resp.Body)
   510  			if _, ok := r.statusCodes[resp.StatusCode]; !ok {
   511  				t.Logf("case %v", r)
   512  				t.Errorf("Expected status one of %v, but got %v", r.statusCodes, resp.StatusCode)
   513  				t.Errorf("Body: %v", string(b))
   514  			} else {
   515  				if r.verb == "POST" {
   516  					// For successful create operations, extract resourceVersion
   517  					id, currentResourceVersion, err := parseResourceVersion(b)
   518  					if err == nil {
   519  						key := getPreviousResourceVersionKey(r.URL, id)
   520  						previousResourceVersion[key] = currentResourceVersion
   521  					} else {
   522  						t.Logf("error in trying to extract resource version: %s", err)
   523  					}
   524  				}
   525  			}
   526  		}()
   527  	}
   528  }
   529  
   530  func parseResourceVersion(response []byte) (string, float64, error) {
   531  	var resultBodyMap map[string]interface{}
   532  	err := json.Unmarshal(response, &resultBodyMap)
   533  	if err != nil {
   534  		return "", 0, fmt.Errorf("unexpected error unmarshaling resultBody: %v", err)
   535  	}
   536  	metadata, ok := resultBodyMap["metadata"].(map[string]interface{})
   537  	if !ok {
   538  		return "", 0, fmt.Errorf("unexpected error, metadata not found in JSON response: %v", string(response))
   539  	}
   540  	id, ok := metadata["name"].(string)
   541  	if !ok {
   542  		return "", 0, fmt.Errorf("unexpected error, id not found in JSON response: %v", string(response))
   543  	}
   544  	resourceVersionString, ok := metadata["resourceVersion"].(string)
   545  	if !ok {
   546  		return "", 0, fmt.Errorf("unexpected error, resourceVersion not found in JSON response: %v", string(response))
   547  	}
   548  	resourceVersion, err := strconv.ParseFloat(resourceVersionString, 64)
   549  	if err != nil {
   550  		return "", 0, fmt.Errorf("unexpected error, could not parse resourceVersion as float64, err: %s. JSON response: %v", err, string(response))
   551  	}
   552  	return id, resourceVersion, nil
   553  }
   554  
   555  func getPreviousResourceVersionKey(url, id string) string {
   556  	baseURL := strings.Split(url, "?")[0]
   557  	key := baseURL
   558  	if id != "" {
   559  		key = fmt.Sprintf("%s/%v", baseURL, id)
   560  	}
   561  	return key
   562  }
   563  
   564  func TestAuthModeAlwaysDeny(t *testing.T) {
   565  	_, ctx := ktesting.NewTestContext(t)
   566  	ctx, cancel := context.WithCancel(ctx)
   567  	defer cancel()
   568  
   569  	kubeClient, kubeConfig, tearDownFn := framework.StartTestServer(ctx, t, framework.TestServerSetup{
   570  		ModifyServerRunOptions: func(opts *options.ServerRunOptions) {
   571  			// Disable ServiceAccount admission plugin as we don't have serviceaccount controller running.
   572  			opts.Admission.GenericAdmission.DisablePlugins = []string{"ServiceAccount"}
   573  			opts.Authorization.Modes = []string{"AlwaysDeny"}
   574  			opts.Authentication.TokenFile.TokenFile = "testdata/tokens.csv"
   575  		},
   576  	})
   577  	defer tearDownFn()
   578  
   579  	ns := framework.CreateNamespaceOrDie(kubeClient, "auth-always-deny", t)
   580  	defer framework.DeleteNamespaceOrDie(kubeClient, ns, t)
   581  
   582  	transport, err := rest.TransportFor(kubeConfig)
   583  	if err != nil {
   584  		t.Fatal(err)
   585  	}
   586  	transport = resttransport.NewBearerAuthRoundTripper(AliceToken, transport)
   587  
   588  	for _, r := range getTestRequests(ns.Name) {
   589  		bodyBytes := bytes.NewReader([]byte(r.body))
   590  		req, err := http.NewRequest(r.verb, kubeConfig.Host+r.URL, bodyBytes)
   591  		if err != nil {
   592  			t.Logf("case %v", r)
   593  			t.Fatalf("unexpected error: %v", err)
   594  		}
   595  		func() {
   596  			resp, err := transport.RoundTrip(req)
   597  			if err != nil {
   598  				t.Logf("case %v", r)
   599  				t.Fatalf("unexpected error: %v", err)
   600  			}
   601  			defer resp.Body.Close()
   602  			if resp.StatusCode != http.StatusForbidden {
   603  				t.Logf("case %v", r)
   604  				t.Errorf("Expected status Forbidden but got status %v", resp.Status)
   605  			}
   606  		}()
   607  	}
   608  }
   609  
   610  // TestAliceNotForbiddenOrUnauthorized tests a user who is known to
   611  // the authentication system and authorized to do any actions.
   612  func TestAliceNotForbiddenOrUnauthorized(t *testing.T) {
   613  	_, ctx := ktesting.NewTestContext(t)
   614  	ctx, cancel := context.WithCancel(ctx)
   615  	defer cancel()
   616  
   617  	kubeClient, kubeConfig, tearDownFn := framework.StartTestServer(ctx, t, framework.TestServerSetup{
   618  		ModifyServerRunOptions: func(opts *options.ServerRunOptions) {
   619  			// Disable ServiceAccount admission plugin as we don't have serviceaccount controller running.
   620  			opts.Admission.GenericAdmission.DisablePlugins = []string{"ServiceAccount"}
   621  			opts.Authentication.TokenFile.TokenFile = "testdata/tokens.csv"
   622  			opts.Authorization.Modes = []string{"ABAC"}
   623  			opts.Authorization.PolicyFile = "testdata/allowalice.jsonl"
   624  		},
   625  	})
   626  	defer tearDownFn()
   627  
   628  	ns := framework.CreateNamespaceOrDie(kubeClient, "auth-alice-not-forbidden", t)
   629  	defer framework.DeleteNamespaceOrDie(kubeClient, ns, t)
   630  
   631  	previousResourceVersion := make(map[string]float64)
   632  	transport, err := rest.TransportFor(kubeConfig)
   633  	if err != nil {
   634  		t.Fatal(err)
   635  	}
   636  
   637  	for _, r := range getTestRequests(ns.Name) {
   638  		token := AliceToken
   639  		var bodyStr string
   640  		if r.body != "" {
   641  			sub := ""
   642  			if r.verb == "PUT" {
   643  				// For update operations, insert previous resource version
   644  				if resVersion := previousResourceVersion[getPreviousResourceVersionKey(r.URL, "")]; resVersion != 0 {
   645  					sub += fmt.Sprintf(",\r\n\"resourceVersion\": \"%v\"", resVersion)
   646  				}
   647  				sub += fmt.Sprintf(",\r\n\"namespace\": %q", ns.Name)
   648  			}
   649  			bodyStr = fmt.Sprintf(r.body, sub)
   650  		}
   651  		r.body = bodyStr
   652  		bodyBytes := bytes.NewReader([]byte(bodyStr))
   653  		req, err := http.NewRequest(r.verb, kubeConfig.Host+r.URL, bodyBytes)
   654  		if err != nil {
   655  			t.Fatalf("unexpected error: %v", err)
   656  		}
   657  		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
   658  		if r.verb == "PATCH" {
   659  			req.Header.Set("Content-Type", "application/merge-patch+json")
   660  		}
   661  
   662  		func() {
   663  			resp, err := transport.RoundTrip(req)
   664  			if err != nil {
   665  				t.Logf("case %v", r)
   666  				t.Fatalf("unexpected error: %v", err)
   667  			}
   668  			defer resp.Body.Close()
   669  			b, _ := io.ReadAll(resp.Body)
   670  			if _, ok := r.statusCodes[resp.StatusCode]; !ok {
   671  				t.Logf("case %v", r)
   672  				t.Errorf("Expected status one of %v, but got %v", r.statusCodes, resp.StatusCode)
   673  				t.Errorf("Body: %v", string(b))
   674  			} else {
   675  				if r.verb == "POST" {
   676  					// For successful create operations, extract resourceVersion
   677  					id, currentResourceVersion, err := parseResourceVersion(b)
   678  					if err == nil {
   679  						key := getPreviousResourceVersionKey(r.URL, id)
   680  						previousResourceVersion[key] = currentResourceVersion
   681  					}
   682  				}
   683  			}
   684  
   685  		}()
   686  	}
   687  }
   688  
   689  // TestBobIsForbidden tests that a user who is known to
   690  // the authentication system but not authorized to do any actions
   691  // should receive "Forbidden".
   692  func TestBobIsForbidden(t *testing.T) {
   693  	_, ctx := ktesting.NewTestContext(t)
   694  	ctx, cancel := context.WithCancel(ctx)
   695  	defer cancel()
   696  
   697  	kubeClient, kubeConfig, tearDownFn := framework.StartTestServer(ctx, t, framework.TestServerSetup{
   698  		ModifyServerRunOptions: func(opts *options.ServerRunOptions) {
   699  			// Disable ServiceAccount admission plugin as we don't have serviceaccount controller running.
   700  			opts.Admission.GenericAdmission.DisablePlugins = []string{"ServiceAccount"}
   701  			opts.Authentication.TokenFile.TokenFile = "testdata/tokens.csv"
   702  			opts.Authorization.Modes = []string{"ABAC"}
   703  			opts.Authorization.PolicyFile = "testdata/allowalice.jsonl"
   704  		},
   705  	})
   706  	defer tearDownFn()
   707  
   708  	ns := framework.CreateNamespaceOrDie(kubeClient, "auth-bob-forbidden", t)
   709  	defer framework.DeleteNamespaceOrDie(kubeClient, ns, t)
   710  
   711  	transport, err := rest.TransportFor(kubeConfig)
   712  	if err != nil {
   713  		t.Fatal(err)
   714  	}
   715  
   716  	for _, r := range getTestRequests(ns.Name) {
   717  		token := BobToken
   718  		bodyBytes := bytes.NewReader([]byte(r.body))
   719  		req, err := http.NewRequest(r.verb, kubeConfig.Host+r.URL, bodyBytes)
   720  		if err != nil {
   721  			t.Fatalf("unexpected error: %v", err)
   722  		}
   723  		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
   724  
   725  		func() {
   726  			resp, err := transport.RoundTrip(req)
   727  			if err != nil {
   728  				t.Logf("case %v", r)
   729  				t.Fatalf("unexpected error: %v", err)
   730  			}
   731  			defer resp.Body.Close()
   732  			// Expect all of bob's actions to return Forbidden
   733  			if resp.StatusCode != http.StatusForbidden {
   734  				t.Logf("case %v", r)
   735  				t.Errorf("Expected not status Forbidden, but got %s", resp.Status)
   736  			}
   737  		}()
   738  	}
   739  }
   740  
   741  // TestUnknownUserIsUnauthorized tests that a user who is unknown
   742  // to the authentication system get status code "Unauthorized".
   743  // An authorization module is installed in this scenario for integration
   744  // test purposes, but requests aren't expected to reach it.
   745  func TestUnknownUserIsUnauthorized(t *testing.T) {
   746  	_, ctx := ktesting.NewTestContext(t)
   747  	ctx, cancel := context.WithCancel(ctx)
   748  	defer cancel()
   749  
   750  	kubeClient, kubeConfig, tearDownFn := framework.StartTestServer(ctx, t, framework.TestServerSetup{
   751  		ModifyServerRunOptions: func(opts *options.ServerRunOptions) {
   752  			// Disable ServiceAccount admission plugin as we don't have serviceaccount controller running.
   753  			opts.Admission.GenericAdmission.DisablePlugins = []string{"ServiceAccount"}
   754  			opts.Authentication.TokenFile.TokenFile = "testdata/tokens.csv"
   755  			opts.Authorization.Modes = []string{"ABAC"}
   756  			opts.Authorization.PolicyFile = "testdata/allowalice.jsonl"
   757  		},
   758  	})
   759  	defer tearDownFn()
   760  
   761  	ns := framework.CreateNamespaceOrDie(kubeClient, "auth-unknown-unauthorized", t)
   762  	defer framework.DeleteNamespaceOrDie(kubeClient, ns, t)
   763  
   764  	transport, err := rest.TransportFor(kubeConfig)
   765  	if err != nil {
   766  		t.Fatal(err)
   767  	}
   768  
   769  	for _, r := range getTestRequests(ns.Name) {
   770  		token := UnknownToken
   771  		bodyBytes := bytes.NewReader([]byte(r.body))
   772  		req, err := http.NewRequest(r.verb, kubeConfig.Host+r.URL, bodyBytes)
   773  		if err != nil {
   774  			t.Fatalf("unexpected error: %v", err)
   775  		}
   776  		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
   777  		func() {
   778  			resp, err := transport.RoundTrip(req)
   779  			if err != nil {
   780  				t.Logf("case %v", r)
   781  				t.Fatalf("unexpected error: %v", err)
   782  			}
   783  			defer resp.Body.Close()
   784  			// Expect all of unauthenticated user's request to be "Unauthorized"
   785  			if resp.StatusCode != http.StatusUnauthorized {
   786  				t.Logf("case %v", r)
   787  				t.Errorf("Expected status %v, but got %v", http.StatusUnauthorized, resp.StatusCode)
   788  				b, _ := io.ReadAll(resp.Body)
   789  				t.Errorf("Body: %v", string(b))
   790  			}
   791  		}()
   792  	}
   793  }
   794  
   795  type impersonateAuthorizer struct{}
   796  
   797  // alice can't act as anyone and bob can't do anything but act-as someone
   798  func (impersonateAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
   799  	// alice can impersonate service accounts and do other actions
   800  	if a.GetUser() != nil && a.GetUser().GetName() == "alice" && a.GetVerb() == "impersonate" && a.GetResource() == "serviceaccounts" {
   801  		return authorizer.DecisionAllow, "", nil
   802  	}
   803  	if a.GetUser() != nil && a.GetUser().GetName() == "alice" && a.GetVerb() != "impersonate" {
   804  		return authorizer.DecisionAllow, "", nil
   805  	}
   806  	// bob can impersonate anyone, but that's it
   807  	if a.GetUser() != nil && a.GetUser().GetName() == "bob" && a.GetVerb() == "impersonate" {
   808  		return authorizer.DecisionAllow, "", nil
   809  	}
   810  	if a.GetUser() != nil && a.GetUser().GetName() == "bob" && a.GetVerb() != "impersonate" {
   811  		return authorizer.DecisionDeny, "", nil
   812  	}
   813  	// service accounts can do everything
   814  	if a.GetUser() != nil && strings.HasPrefix(a.GetUser().GetName(), serviceaccount.ServiceAccountUsernamePrefix) {
   815  		return authorizer.DecisionAllow, "", nil
   816  	}
   817  
   818  	return authorizer.DecisionNoOpinion, "I can't allow that.  Go ask alice.", nil
   819  }
   820  
   821  func TestImpersonateIsForbidden(t *testing.T) {
   822  	_, ctx := ktesting.NewTestContext(t)
   823  	ctx, cancel := context.WithCancel(ctx)
   824  	defer cancel()
   825  
   826  	kubeClient, kubeConfig, tearDownFn := framework.StartTestServer(ctx, t, framework.TestServerSetup{
   827  		ModifyServerRunOptions: func(opts *options.ServerRunOptions) {
   828  			// Disable ServiceAccount admission plugin as we don't have serviceaccount controller running.
   829  			opts.Admission.GenericAdmission.DisablePlugins = []string{"ServiceAccount"}
   830  			opts.Authentication.TokenFile.TokenFile = "testdata/tokens.csv"
   831  		},
   832  		ModifyServerConfig: func(config *controlplane.Config) {
   833  			// Prepend an impersonation authorizer with specific opinions about alice and bob
   834  			config.GenericConfig.Authorization.Authorizer = unionauthz.New(impersonateAuthorizer{}, config.GenericConfig.Authorization.Authorizer)
   835  		},
   836  	})
   837  	defer tearDownFn()
   838  
   839  	ns := framework.CreateNamespaceOrDie(kubeClient, "auth-impersonate-forbidden", t)
   840  	defer framework.DeleteNamespaceOrDie(kubeClient, ns, t)
   841  
   842  	transport, err := rest.TransportFor(kubeConfig)
   843  	if err != nil {
   844  		t.Fatal(err)
   845  	}
   846  
   847  	// bob can't perform actions himself
   848  	for _, r := range getTestRequests(ns.Name) {
   849  		token := BobToken
   850  		bodyBytes := bytes.NewReader([]byte(r.body))
   851  		req, err := http.NewRequest(r.verb, kubeConfig.Host+r.URL, bodyBytes)
   852  		if err != nil {
   853  			t.Fatalf("unexpected error: %v", err)
   854  		}
   855  		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
   856  
   857  		func() {
   858  			resp, err := transport.RoundTrip(req)
   859  			if err != nil {
   860  				t.Logf("case %v", r)
   861  				t.Fatalf("unexpected error: %v", err)
   862  			}
   863  			defer resp.Body.Close()
   864  			// Expect all of bob's actions to return Forbidden
   865  			if resp.StatusCode != http.StatusForbidden {
   866  				t.Logf("case %v", r)
   867  				t.Errorf("Expected status Forbidden, but got %s", resp.Status)
   868  			}
   869  		}()
   870  	}
   871  
   872  	// bob can impersonate alice to do other things
   873  	for _, r := range getTestRequests(ns.Name) {
   874  		token := BobToken
   875  		bodyBytes := bytes.NewReader([]byte(r.body))
   876  		req, err := http.NewRequest(r.verb, kubeConfig.Host+r.URL, bodyBytes)
   877  		if err != nil {
   878  			t.Fatalf("unexpected error: %v", err)
   879  		}
   880  		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
   881  		req.Header.Set("Impersonate-User", "alice")
   882  		func() {
   883  			resp, err := transport.RoundTrip(req)
   884  			if err != nil {
   885  				t.Logf("case %v", r)
   886  				t.Fatalf("unexpected error: %v", err)
   887  			}
   888  			defer resp.Body.Close()
   889  			// Expect all the requests to be allowed, don't care what they actually do
   890  			if resp.StatusCode == http.StatusForbidden {
   891  				t.Logf("case %v", r)
   892  				t.Errorf("Expected status not %v, but got %v", http.StatusForbidden, resp.StatusCode)
   893  			}
   894  		}()
   895  	}
   896  
   897  	// alice can't impersonate bob
   898  	for _, r := range getTestRequests(ns.Name) {
   899  		token := AliceToken
   900  		bodyBytes := bytes.NewReader([]byte(r.body))
   901  		req, err := http.NewRequest(r.verb, kubeConfig.Host+r.URL, bodyBytes)
   902  		if err != nil {
   903  			t.Fatalf("unexpected error: %v", err)
   904  		}
   905  		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
   906  		req.Header.Set("Impersonate-User", "bob")
   907  
   908  		func() {
   909  			resp, err := transport.RoundTrip(req)
   910  			if err != nil {
   911  				t.Logf("case %v", r)
   912  				t.Fatalf("unexpected error: %v", err)
   913  			}
   914  			defer resp.Body.Close()
   915  			// Expect all of bob's actions to return Forbidden
   916  			if resp.StatusCode != http.StatusForbidden {
   917  				t.Logf("case %v", r)
   918  				t.Errorf("Expected not status Forbidden, but got %s", resp.Status)
   919  			}
   920  		}()
   921  	}
   922  
   923  	// bob can impersonate a service account
   924  	for _, r := range getTestRequests(ns.Name) {
   925  		token := BobToken
   926  		bodyBytes := bytes.NewReader([]byte(r.body))
   927  		req, err := http.NewRequest(r.verb, kubeConfig.Host+r.URL, bodyBytes)
   928  		if err != nil {
   929  			t.Fatalf("unexpected error: %v", err)
   930  		}
   931  		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
   932  		req.Header.Set("Impersonate-User", serviceaccount.MakeUsername("default", "default"))
   933  		func() {
   934  			resp, err := transport.RoundTrip(req)
   935  			if err != nil {
   936  				t.Logf("case %v", r)
   937  				t.Fatalf("unexpected error: %v", err)
   938  			}
   939  			defer resp.Body.Close()
   940  			// Expect all the requests to be allowed, don't care what they actually do
   941  			if resp.StatusCode == http.StatusForbidden {
   942  				t.Logf("case %v", r)
   943  				t.Errorf("Expected status not %v, but got %v", http.StatusForbidden, resp.StatusCode)
   944  			}
   945  		}()
   946  	}
   947  
   948  }
   949  
   950  func TestImpersonateWithUID(t *testing.T) {
   951  	server := kubeapiservertesting.StartTestServerOrDie(
   952  		t,
   953  		nil,
   954  		[]string{
   955  			"--authorization-mode=RBAC",
   956  			"--anonymous-auth",
   957  		},
   958  		framework.SharedEtcd(),
   959  	)
   960  	t.Cleanup(server.TearDownFn)
   961  
   962  	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
   963  	t.Cleanup(cancel)
   964  
   965  	t.Run("impersonation with uid header", func(t *testing.T) {
   966  		adminClient := clientset.NewForConfigOrDie(server.ClientConfig)
   967  
   968  		authutil.GrantUserAuthorization(t, ctx, adminClient, "alice",
   969  			rbacv1.PolicyRule{
   970  				Verbs:     []string{"create"},
   971  				APIGroups: []string{"certificates.k8s.io"},
   972  				Resources: []string{"certificatesigningrequests"},
   973  			},
   974  		)
   975  
   976  		req := csrPEM(t)
   977  
   978  		clientConfig := rest.CopyConfig(server.ClientConfig)
   979  		clientConfig.Impersonate = rest.ImpersonationConfig{
   980  			UserName: "alice",
   981  			UID:      "1234",
   982  		}
   983  
   984  		client := clientset.NewForConfigOrDie(clientConfig)
   985  		createdCsr, err := client.CertificatesV1().CertificateSigningRequests().Create(
   986  			ctx,
   987  			&certificatesv1.CertificateSigningRequest{
   988  				Spec: certificatesv1.CertificateSigningRequestSpec{
   989  					SignerName: "kubernetes.io/kube-apiserver-client",
   990  					Request:    req,
   991  					Usages:     []certificatesv1.KeyUsage{"client auth"},
   992  				},
   993  				ObjectMeta: metav1.ObjectMeta{
   994  					Name: "impersonated-csr",
   995  				},
   996  			},
   997  			metav1.CreateOptions{},
   998  		)
   999  		if err != nil {
  1000  			t.Fatalf("Unexpected error creating Certificate Signing Request: %v", err)
  1001  		}
  1002  
  1003  		// require that all the original fields and the impersonated user's info
  1004  		// is in the returned spec.
  1005  		expectedCsrSpec := certificatesv1.CertificateSigningRequestSpec{
  1006  			Groups:     []string{"system:authenticated"},
  1007  			SignerName: "kubernetes.io/kube-apiserver-client",
  1008  			Request:    req,
  1009  			Usages:     []certificatesv1.KeyUsage{"client auth"},
  1010  			Username:   "alice",
  1011  			UID:        "1234",
  1012  		}
  1013  		actualCsrSpec := createdCsr.Spec
  1014  
  1015  		if diff := cmp.Diff(expectedCsrSpec, actualCsrSpec); diff != "" {
  1016  			t.Fatalf("CSR spec was different than expected, -got, +want:\n %s", diff)
  1017  		}
  1018  	})
  1019  
  1020  	t.Run("impersonation with only UID fails", func(t *testing.T) {
  1021  		clientConfig := rest.CopyConfig(server.ClientConfig)
  1022  		clientConfig.Impersonate = rest.ImpersonationConfig{
  1023  			UID: "1234",
  1024  		}
  1025  
  1026  		client := clientset.NewForConfigOrDie(clientConfig)
  1027  		_, err := client.CoreV1().Nodes().List(ctx, metav1.ListOptions{})
  1028  
  1029  		if !errors.IsInternalError(err) {
  1030  			t.Fatalf("expected internal error, got %T %v", err, err)
  1031  		}
  1032  		if diff := cmp.Diff(
  1033  			`an error on the server ("Internal Server Error: \"/api/v1/nodes\": `+
  1034  				`requested [{UID  1234  authentication.k8s.io/v1  }] without impersonating a user") `+
  1035  				`has prevented the request from succeeding (get nodes)`,
  1036  			err.Error(),
  1037  		); diff != "" {
  1038  			t.Fatalf("internal error different than expected, -got, +want:\n %s", diff)
  1039  		}
  1040  	})
  1041  
  1042  	t.Run("impersonating UID without authorization fails", func(t *testing.T) {
  1043  		adminClient := clientset.NewForConfigOrDie(server.ClientConfig)
  1044  
  1045  		authutil.GrantUserAuthorization(t, ctx, adminClient, "system:anonymous",
  1046  			rbacv1.PolicyRule{
  1047  				Verbs:         []string{"impersonate"},
  1048  				APIGroups:     []string{""},
  1049  				Resources:     []string{"users"},
  1050  				ResourceNames: []string{"some-user-anonymous-can-impersonate"},
  1051  			},
  1052  		)
  1053  
  1054  		clientConfig := rest.AnonymousClientConfig(server.ClientConfig)
  1055  		clientConfig.Impersonate = rest.ImpersonationConfig{
  1056  			UserName: "some-user-anonymous-can-impersonate",
  1057  			UID:      "1234",
  1058  		}
  1059  
  1060  		client := clientset.NewForConfigOrDie(clientConfig)
  1061  		_, err := client.CoreV1().Nodes().List(ctx, metav1.ListOptions{})
  1062  
  1063  		if !errors.IsForbidden(err) {
  1064  			t.Fatalf("expected forbidden error, got %T %v", err, err)
  1065  		}
  1066  		if diff := cmp.Diff(
  1067  			`uids.authentication.k8s.io "1234" is forbidden: `+
  1068  				`User "system:anonymous" cannot impersonate resource "uids" in API group "authentication.k8s.io" at the cluster scope`,
  1069  			err.Error(),
  1070  		); diff != "" {
  1071  			t.Fatalf("forbidden error different than expected, -got, +want:\n %s", diff)
  1072  		}
  1073  	})
  1074  }
  1075  
  1076  func csrPEM(t *testing.T) []byte {
  1077  	t.Helper()
  1078  
  1079  	_, privateKey, err := ed25519.GenerateKey(rand.Reader)
  1080  	if err != nil {
  1081  		t.Fatalf("Unexpected error generating ed25519 key: %v", err)
  1082  	}
  1083  
  1084  	csrDER, err := x509.CreateCertificateRequest(
  1085  		rand.Reader,
  1086  		&x509.CertificateRequest{
  1087  			Subject: pkix.Name{
  1088  				Organization: []string{},
  1089  			},
  1090  		},
  1091  		privateKey)
  1092  	if err != nil {
  1093  		t.Fatalf("Unexpected error creating x509 certificate request: %v", err)
  1094  	}
  1095  
  1096  	csrPemBlock := &pem.Block{
  1097  		Type:  "CERTIFICATE REQUEST",
  1098  		Bytes: csrDER,
  1099  	}
  1100  
  1101  	req := pem.EncodeToMemory(csrPemBlock)
  1102  	if req == nil {
  1103  		t.Fatalf("Failed to encode PEM to memory.")
  1104  	}
  1105  	return req
  1106  }
  1107  
  1108  func newABACFileWithContents(t *testing.T, contents string) string {
  1109  	dir := t.TempDir()
  1110  	file := filepath.Join(dir, "auth_test")
  1111  	if err := os.WriteFile(file, []byte(contents), 0700); err != nil {
  1112  		t.Fatalf("unexpected error writing policyfile: %v", err)
  1113  	}
  1114  	return file
  1115  }
  1116  
  1117  type trackingAuthorizer struct {
  1118  	requestAttributes []authorizer.Attributes
  1119  }
  1120  
  1121  func (a *trackingAuthorizer) Authorize(ctx context.Context, attributes authorizer.Attributes) (authorizer.Decision, string, error) {
  1122  	a.requestAttributes = append(a.requestAttributes, attributes)
  1123  	return authorizer.DecisionAllow, "", nil
  1124  }
  1125  
  1126  // TestAuthorizationAttributeDetermination tests that authorization attributes are built correctly
  1127  func TestAuthorizationAttributeDetermination(t *testing.T) {
  1128  	_, ctx := ktesting.NewTestContext(t)
  1129  	ctx, cancel := context.WithCancel(ctx)
  1130  	defer cancel()
  1131  
  1132  	trackingAuthorizer := &trackingAuthorizer{}
  1133  
  1134  	kubeClient, kubeConfig, tearDownFn := framework.StartTestServer(ctx, t, framework.TestServerSetup{
  1135  		ModifyServerRunOptions: func(opts *options.ServerRunOptions) {
  1136  			// Disable ServiceAccount admission plugin as we don't have serviceaccount controller running.
  1137  			opts.Admission.GenericAdmission.DisablePlugins = []string{"ServiceAccount"}
  1138  			opts.Authentication.TokenFile.TokenFile = "testdata/tokens.csv"
  1139  		},
  1140  		ModifyServerConfig: func(config *controlplane.Config) {
  1141  			config.GenericConfig.Authorization.Authorizer = unionauthz.New(config.GenericConfig.Authorization.Authorizer, trackingAuthorizer)
  1142  		},
  1143  	})
  1144  	defer tearDownFn()
  1145  
  1146  	ns := framework.CreateNamespaceOrDie(kubeClient, "auth-attribute-determination", t)
  1147  	defer framework.DeleteNamespaceOrDie(kubeClient, ns, t)
  1148  
  1149  	transport, err := rest.TransportFor(kubeConfig)
  1150  	if err != nil {
  1151  		t.Fatal(err)
  1152  	}
  1153  
  1154  	requests := map[string]struct {
  1155  		verb               string
  1156  		URL                string
  1157  		expectedAttributes authorizer.Attributes
  1158  	}{
  1159  		"prefix/version/resource":        {"GET", "/api/v1/pods", authorizer.AttributesRecord{APIGroup: api.GroupName, Resource: "pods"}},
  1160  		"prefix/group/version/resource":  {"GET", "/apis/extensions/v1/pods", authorizer.AttributesRecord{APIGroup: extensions.GroupName, Resource: "pods"}},
  1161  		"prefix/group/version/resource2": {"GET", "/apis/autoscaling/v1/horizontalpodautoscalers", authorizer.AttributesRecord{APIGroup: autoscaling.GroupName, Resource: "horizontalpodautoscalers"}},
  1162  	}
  1163  
  1164  	currentAuthorizationAttributesIndex := 0
  1165  
  1166  	for testName, r := range requests {
  1167  		token := BobToken
  1168  		req, err := http.NewRequest(r.verb, kubeConfig.Host+r.URL, nil)
  1169  		if err != nil {
  1170  			t.Logf("case %v", testName)
  1171  			t.Fatalf("unexpected error: %v", err)
  1172  		}
  1173  		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
  1174  		func() {
  1175  			resp, err := transport.RoundTrip(req)
  1176  			if err != nil {
  1177  				t.Logf("case %v", r)
  1178  				t.Fatalf("unexpected error: %v", err)
  1179  			}
  1180  			defer resp.Body.Close()
  1181  
  1182  			found := false
  1183  			for i := currentAuthorizationAttributesIndex; i < len(trackingAuthorizer.requestAttributes); i++ {
  1184  				if trackingAuthorizer.requestAttributes[i].GetAPIGroup() == r.expectedAttributes.GetAPIGroup() &&
  1185  					trackingAuthorizer.requestAttributes[i].GetResource() == r.expectedAttributes.GetResource() {
  1186  					found = true
  1187  					break
  1188  				}
  1189  
  1190  				t.Logf("%#v did not match %#v", r.expectedAttributes, trackingAuthorizer.requestAttributes[i].(*authorizer.AttributesRecord))
  1191  			}
  1192  			if !found {
  1193  				t.Errorf("did not find %#v in %#v", r.expectedAttributes, trackingAuthorizer.requestAttributes[currentAuthorizationAttributesIndex:])
  1194  			}
  1195  
  1196  			currentAuthorizationAttributesIndex = len(trackingAuthorizer.requestAttributes)
  1197  		}()
  1198  	}
  1199  }
  1200  
  1201  // TestNamespaceAuthorization tests that authorization can be controlled
  1202  // by namespace.
  1203  func TestNamespaceAuthorization(t *testing.T) {
  1204  	_, ctx := ktesting.NewTestContext(t)
  1205  	ctx, cancel := context.WithCancel(ctx)
  1206  	defer cancel()
  1207  
  1208  	kubeClient, kubeConfig, tearDownFn := framework.StartTestServer(ctx, t, framework.TestServerSetup{
  1209  		ModifyServerRunOptions: func(opts *options.ServerRunOptions) {
  1210  			// Disable ServiceAccount admission plugin as we don't have serviceaccount controller running.
  1211  			opts.Admission.GenericAdmission.DisablePlugins = []string{"ServiceAccount"}
  1212  			opts.Authentication.TokenFile.TokenFile = "testdata/tokens.csv"
  1213  			opts.Authorization.PolicyFile = newABACFileWithContents(t, `{"namespace": "auth-namespace"}`)
  1214  			opts.Authorization.Modes = []string{"ABAC"}
  1215  		},
  1216  	})
  1217  	defer tearDownFn()
  1218  
  1219  	ns := framework.CreateNamespaceOrDie(kubeClient, "auth-namespace", t)
  1220  	defer framework.DeleteNamespaceOrDie(kubeClient, ns, t)
  1221  
  1222  	previousResourceVersion := make(map[string]float64)
  1223  	transport, err := rest.TransportFor(kubeConfig)
  1224  	if err != nil {
  1225  		t.Fatal(err)
  1226  	}
  1227  
  1228  	requests := []struct {
  1229  		verb        string
  1230  		URL         string
  1231  		namespace   string
  1232  		body        string
  1233  		statusCodes map[int]bool // allowed status codes.
  1234  	}{
  1235  
  1236  		{"POST", timeoutPath("pods", ns.Name, ""), "foo", aPod, integration.Code201},
  1237  		{"GET", path("pods", ns.Name, ""), "foo", "", integration.Code200},
  1238  		{"GET", path("pods", ns.Name, "a"), "foo", "", integration.Code200},
  1239  		{"DELETE", timeoutPath("pods", ns.Name, "a"), "foo", "", integration.Code200},
  1240  
  1241  		{"POST", timeoutPath("pods", "foo", ""), "bar", aPod, integration.Code403},
  1242  		{"GET", path("pods", "foo", ""), "bar", "", integration.Code403},
  1243  		{"GET", path("pods", "foo", "a"), "bar", "", integration.Code403},
  1244  		{"DELETE", timeoutPath("pods", "foo", "a"), "bar", "", integration.Code403},
  1245  
  1246  		{"POST", timeoutPath("pods", metav1.NamespaceDefault, ""), "", aPod, integration.Code403},
  1247  		{"GET", path("pods", "", ""), "", "", integration.Code403},
  1248  		{"GET", path("pods", metav1.NamespaceDefault, "a"), "", "", integration.Code403},
  1249  		{"DELETE", timeoutPath("pods", metav1.NamespaceDefault, "a"), "", "", integration.Code403},
  1250  	}
  1251  
  1252  	for _, r := range requests {
  1253  		token := BobToken
  1254  		var bodyStr string
  1255  		if r.body != "" {
  1256  			sub := ""
  1257  			if r.verb == "PUT" && r.body != "" {
  1258  				// For update operations, insert previous resource version
  1259  				if resVersion := previousResourceVersion[getPreviousResourceVersionKey(r.URL, "")]; resVersion != 0 {
  1260  					sub += fmt.Sprintf(",\r\n\"resourceVersion\": \"%v\"", resVersion)
  1261  				}
  1262  				namespace := r.namespace
  1263  				// FIXME: Is that correct?
  1264  				if len(namespace) == 0 {
  1265  					namespace = "default"
  1266  				}
  1267  				sub += fmt.Sprintf(",\r\n\"namespace\": %q", namespace)
  1268  			}
  1269  			bodyStr = fmt.Sprintf(r.body, sub)
  1270  		}
  1271  		r.body = bodyStr
  1272  		bodyBytes := bytes.NewReader([]byte(bodyStr))
  1273  		req, err := http.NewRequest(r.verb, kubeConfig.Host+r.URL, bodyBytes)
  1274  		if err != nil {
  1275  			t.Logf("case %v", r)
  1276  			t.Fatalf("unexpected error: %v", err)
  1277  		}
  1278  		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
  1279  		func() {
  1280  			resp, err := transport.RoundTrip(req)
  1281  			if err != nil {
  1282  				t.Logf("case %v", r)
  1283  				t.Fatalf("unexpected error: %v", err)
  1284  			}
  1285  			defer resp.Body.Close()
  1286  			b, _ := io.ReadAll(resp.Body)
  1287  			if _, ok := r.statusCodes[resp.StatusCode]; !ok {
  1288  				t.Logf("case %v", r)
  1289  				t.Errorf("Expected status one of %v, but got %v", r.statusCodes, resp.StatusCode)
  1290  				t.Errorf("Body: %v", string(b))
  1291  			} else {
  1292  				if r.verb == "POST" {
  1293  					// For successful create operations, extract resourceVersion
  1294  					id, currentResourceVersion, err := parseResourceVersion(b)
  1295  					if err == nil {
  1296  						key := getPreviousResourceVersionKey(r.URL, id)
  1297  						previousResourceVersion[key] = currentResourceVersion
  1298  					}
  1299  				}
  1300  			}
  1301  
  1302  		}()
  1303  	}
  1304  }
  1305  
  1306  // TestKindAuthorization tests that authorization can be controlled
  1307  // by namespace.
  1308  func TestKindAuthorization(t *testing.T) {
  1309  	_, ctx := ktesting.NewTestContext(t)
  1310  	ctx, cancel := context.WithCancel(ctx)
  1311  	defer cancel()
  1312  
  1313  	kubeClient, kubeConfig, tearDownFn := framework.StartTestServer(ctx, t, framework.TestServerSetup{
  1314  		ModifyServerRunOptions: func(opts *options.ServerRunOptions) {
  1315  			// Disable ServiceAccount admission plugin as we don't have serviceaccount controller running.
  1316  			opts.Admission.GenericAdmission.DisablePlugins = []string{"ServiceAccount"}
  1317  			opts.Authentication.TokenFile.TokenFile = "testdata/tokens.csv"
  1318  			opts.Authorization.PolicyFile = newABACFileWithContents(t, `{"resource": "services"}`)
  1319  			opts.Authorization.Modes = []string{"ABAC"}
  1320  		},
  1321  	})
  1322  	defer tearDownFn()
  1323  
  1324  	ns := framework.CreateNamespaceOrDie(kubeClient, "auth-kind", t)
  1325  	defer framework.DeleteNamespaceOrDie(kubeClient, ns, t)
  1326  
  1327  	previousResourceVersion := make(map[string]float64)
  1328  	transport, err := rest.TransportFor(kubeConfig)
  1329  	if err != nil {
  1330  		t.Fatal(err)
  1331  	}
  1332  
  1333  	requests := []testRequest{
  1334  		{"POST", timeoutPath("services", ns.Name, ""), aService, integration.Code201},
  1335  		{"GET", path("services", ns.Name, ""), "", integration.Code200},
  1336  		{"GET", path("services", ns.Name, "a"), "", integration.Code200},
  1337  		{"DELETE", timeoutPath("services", ns.Name, "a"), "", integration.Code200},
  1338  
  1339  		{"POST", timeoutPath("pods", ns.Name, ""), aPod, integration.Code403},
  1340  		{"GET", path("pods", "", ""), "", integration.Code403},
  1341  		{"GET", path("pods", ns.Name, "a"), "", integration.Code403},
  1342  		{"DELETE", timeoutPath("pods", ns.Name, "a"), "", integration.Code403},
  1343  	}
  1344  
  1345  	for _, r := range requests {
  1346  		token := BobToken
  1347  		var bodyStr string
  1348  		if r.body != "" {
  1349  			bodyStr = fmt.Sprintf(r.body, "")
  1350  			if r.verb == "PUT" && r.body != "" {
  1351  				// For update operations, insert previous resource version
  1352  				if resVersion := previousResourceVersion[getPreviousResourceVersionKey(r.URL, "")]; resVersion != 0 {
  1353  					resourceVersionJSON := fmt.Sprintf(",\r\n\"resourceVersion\": \"%v\"", resVersion)
  1354  					bodyStr = fmt.Sprintf(r.body, resourceVersionJSON)
  1355  				}
  1356  			}
  1357  		}
  1358  		r.body = bodyStr
  1359  		bodyBytes := bytes.NewReader([]byte(bodyStr))
  1360  		req, err := http.NewRequest(r.verb, kubeConfig.Host+r.URL, bodyBytes)
  1361  		if err != nil {
  1362  			t.Logf("case %v", r)
  1363  			t.Fatalf("unexpected error: %v", err)
  1364  		}
  1365  		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
  1366  		func() {
  1367  			resp, err := transport.RoundTrip(req)
  1368  			if err != nil {
  1369  				t.Logf("case %v", r)
  1370  				t.Fatalf("unexpected error: %v", err)
  1371  			}
  1372  			defer resp.Body.Close()
  1373  			b, _ := io.ReadAll(resp.Body)
  1374  			if _, ok := r.statusCodes[resp.StatusCode]; !ok {
  1375  				t.Logf("case %v", r)
  1376  				t.Errorf("Expected status one of %v, but got %v", r.statusCodes, resp.StatusCode)
  1377  				t.Errorf("Body: %v", string(b))
  1378  			} else {
  1379  				if r.verb == "POST" {
  1380  					// For successful create operations, extract resourceVersion
  1381  					id, currentResourceVersion, err := parseResourceVersion(b)
  1382  					if err == nil {
  1383  						key := getPreviousResourceVersionKey(r.URL, id)
  1384  						previousResourceVersion[key] = currentResourceVersion
  1385  					}
  1386  				}
  1387  			}
  1388  
  1389  		}()
  1390  	}
  1391  }
  1392  
  1393  // TestReadOnlyAuthorization tests that authorization can be controlled
  1394  // by namespace.
  1395  func TestReadOnlyAuthorization(t *testing.T) {
  1396  	_, ctx := ktesting.NewTestContext(t)
  1397  	ctx, cancel := context.WithCancel(ctx)
  1398  	defer cancel()
  1399  
  1400  	kubeClient, kubeConfig, tearDownFn := framework.StartTestServer(ctx, t, framework.TestServerSetup{
  1401  		ModifyServerRunOptions: func(opts *options.ServerRunOptions) {
  1402  			// Disable ServiceAccount admission plugin as we don't have serviceaccount controller running.
  1403  			opts.Admission.GenericAdmission.DisablePlugins = []string{"ServiceAccount"}
  1404  			opts.Authentication.TokenFile.TokenFile = "testdata/tokens.csv"
  1405  			opts.Authorization.PolicyFile = newABACFileWithContents(t, `{"readonly": true}`)
  1406  			opts.Authorization.Modes = []string{"ABAC"}
  1407  		},
  1408  	})
  1409  	defer tearDownFn()
  1410  
  1411  	ns := framework.CreateNamespaceOrDie(kubeClient, "auth-read-only", t)
  1412  	defer framework.DeleteNamespaceOrDie(kubeClient, ns, t)
  1413  
  1414  	transport, err := rest.TransportFor(kubeConfig)
  1415  	if err != nil {
  1416  		t.Fatal(err)
  1417  	}
  1418  
  1419  	requests := []testRequest{
  1420  		{"POST", path("pods", ns.Name, ""), aPod, integration.Code403},
  1421  		{"GET", path("pods", ns.Name, ""), "", integration.Code200},
  1422  		{"GET", path("pods", metav1.NamespaceDefault, "a"), "", integration.Code404},
  1423  	}
  1424  
  1425  	for _, r := range requests {
  1426  		token := BobToken
  1427  		bodyBytes := bytes.NewReader([]byte(r.body))
  1428  		req, err := http.NewRequest(r.verb, kubeConfig.Host+r.URL, bodyBytes)
  1429  		if err != nil {
  1430  			t.Fatalf("unexpected error: %v", err)
  1431  		}
  1432  		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
  1433  		func() {
  1434  			resp, err := transport.RoundTrip(req)
  1435  			if err != nil {
  1436  				t.Logf("case %v", r)
  1437  				t.Fatalf("unexpected error: %v", err)
  1438  			}
  1439  			defer resp.Body.Close()
  1440  			if _, ok := r.statusCodes[resp.StatusCode]; !ok {
  1441  				t.Logf("case %v", r)
  1442  				t.Errorf("Expected status one of %v, but got %v", r.statusCodes, resp.StatusCode)
  1443  				b, _ := io.ReadAll(resp.Body)
  1444  				t.Errorf("Body: %v", string(b))
  1445  			}
  1446  		}()
  1447  	}
  1448  }
  1449  
  1450  // TestWebhookTokenAuthenticator tests that a control plane can use the webhook token
  1451  // authenticator to call out to a remote web server for authentication
  1452  // decisions.
  1453  func TestWebhookTokenAuthenticator(t *testing.T) {
  1454  	testWebhookTokenAuthenticator(false, t)
  1455  }
  1456  
  1457  // TestWebhookTokenAuthenticatorCustomDial is the same as TestWebhookTokenAuthenticator, but uses a
  1458  // custom dialer
  1459  func TestWebhookTokenAuthenticatorCustomDial(t *testing.T) {
  1460  	testWebhookTokenAuthenticator(true, t)
  1461  }
  1462  
  1463  func testWebhookTokenAuthenticator(customDialer bool, t *testing.T) {
  1464  	_, ctx := ktesting.NewTestContext(t)
  1465  	ctx, cancel := context.WithCancel(ctx)
  1466  	defer cancel()
  1467  
  1468  	authServer := newTestWebhookTokenAuthServer()
  1469  	defer authServer.Close()
  1470  	var authenticator authenticator.Request
  1471  	var err error
  1472  
  1473  	if customDialer == false {
  1474  		authenticator, err = getTestWebhookTokenAuth(authServer.URL, nil)
  1475  	} else {
  1476  		authenticator, err = getTestWebhookTokenAuthCustomDialer(authServer.URL)
  1477  	}
  1478  
  1479  	if err != nil {
  1480  		t.Fatalf("error starting webhook token authenticator server: %v", err)
  1481  	}
  1482  
  1483  	kubeClient, kubeConfig, tearDownFn := framework.StartTestServer(ctx, t, framework.TestServerSetup{
  1484  		ModifyServerRunOptions: func(opts *options.ServerRunOptions) {
  1485  			// Disable ServiceAccount admission plugin as we don't have serviceaccount controller running.
  1486  			opts.Admission.GenericAdmission.DisablePlugins = []string{"ServiceAccount"}
  1487  			opts.Authorization.Modes = []string{"ABAC"}
  1488  			opts.Authorization.PolicyFile = "testdata/allowalice.jsonl"
  1489  		},
  1490  		ModifyServerConfig: func(config *controlplane.Config) {
  1491  			config.GenericConfig.Authentication.Authenticator = group.NewAuthenticatedGroupAdder(authenticator)
  1492  			// Disable checking API audiences that is set by testserver by default.
  1493  			config.GenericConfig.Authentication.APIAudiences = nil
  1494  		},
  1495  	})
  1496  	defer tearDownFn()
  1497  
  1498  	ns := framework.CreateNamespaceOrDie(kubeClient, "auth-webhook-token", t)
  1499  	defer framework.DeleteNamespaceOrDie(kubeClient, ns, t)
  1500  
  1501  	transport, err := rest.TransportFor(kubeConfig)
  1502  	if err != nil {
  1503  		t.Fatal(err)
  1504  	}
  1505  
  1506  	for _, r := range getTestRequests(ns.Name) {
  1507  		// Expect Bob's requests to all fail.
  1508  		token := BobToken
  1509  		bodyBytes := bytes.NewReader([]byte(r.body))
  1510  		req, err := http.NewRequest(r.verb, kubeConfig.Host+r.URL, bodyBytes)
  1511  		if err != nil {
  1512  			t.Fatalf("unexpected error: %v", err)
  1513  		}
  1514  		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
  1515  
  1516  		func() {
  1517  			resp, err := transport.RoundTrip(req)
  1518  			if err != nil {
  1519  				t.Logf("case %v", r)
  1520  				t.Fatalf("unexpected error: %v", err)
  1521  			}
  1522  			defer resp.Body.Close()
  1523  			// Expect all of Bob's actions to return Forbidden
  1524  			if resp.StatusCode != http.StatusForbidden {
  1525  				t.Logf("case %v", r)
  1526  				t.Errorf("Expected http.Forbidden, but got %s", resp.Status)
  1527  			}
  1528  		}()
  1529  		// Expect Alice's requests to succeed.
  1530  		token = AliceToken
  1531  		bodyBytes = bytes.NewReader([]byte(r.body))
  1532  		req, err = http.NewRequest(r.verb, kubeConfig.Host+r.URL, bodyBytes)
  1533  		if err != nil {
  1534  			t.Fatalf("unexpected error: %v", err)
  1535  		}
  1536  		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
  1537  
  1538  		func() {
  1539  			resp, err := transport.RoundTrip(req)
  1540  			if err != nil {
  1541  				t.Logf("case %v", r)
  1542  				t.Fatalf("unexpected error: %v", err)
  1543  			}
  1544  			defer resp.Body.Close()
  1545  			// Expect all of Alice's actions to at least get past authn/authz.
  1546  			if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {
  1547  				t.Logf("case %v", r)
  1548  				t.Errorf("Expected something other than Unauthorized/Forbidden, but got %s", resp.Status)
  1549  			}
  1550  		}()
  1551  	}
  1552  }
  1553  
  1554  // newTestWebhookTokenAuthServer creates an http token authentication server
  1555  // that knows about both Alice and Bob.
  1556  func newTestWebhookTokenAuthServer() *httptest.Server {
  1557  	serveHTTP := func(w http.ResponseWriter, r *http.Request) {
  1558  		var review authenticationv1beta1.TokenReview
  1559  		if err := json.NewDecoder(r.Body).Decode(&review); err != nil {
  1560  			http.Error(w, fmt.Sprintf("failed to decode body: %v", err), http.StatusBadRequest)
  1561  			return
  1562  		}
  1563  		type userInfo struct {
  1564  			Username string   `json:"username"`
  1565  			UID      string   `json:"uid"`
  1566  			Groups   []string `json:"groups"`
  1567  		}
  1568  		type status struct {
  1569  			Authenticated bool     `json:"authenticated"`
  1570  			User          userInfo `json:"user"`
  1571  		}
  1572  		var username, uid string
  1573  		authenticated := false
  1574  		if review.Spec.Token == AliceToken {
  1575  			authenticated, username, uid = true, "alice", "1"
  1576  		} else if review.Spec.Token == BobToken {
  1577  			authenticated, username, uid = true, "bob", "2"
  1578  		}
  1579  
  1580  		resp := struct {
  1581  			APIVersion string `json:"apiVersion"`
  1582  			Status     status `json:"status"`
  1583  		}{
  1584  			APIVersion: authenticationv1beta1.SchemeGroupVersion.String(),
  1585  			Status: status{
  1586  				authenticated,
  1587  				userInfo{
  1588  					Username: username,
  1589  					UID:      uid,
  1590  				},
  1591  			},
  1592  		}
  1593  		w.Header().Set("Content-Type", "application/json")
  1594  		json.NewEncoder(w).Encode(resp)
  1595  	}
  1596  
  1597  	server := httptest.NewUnstartedServer(http.HandlerFunc(serveHTTP))
  1598  	server.Start()
  1599  	return server
  1600  }