1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309 |
- /*
- Copyright 2014 The Kubernetes Authors.
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package auth
- // This file tests authentication and (soon) authorization of HTTP requests to a master object.
- // It does not use the client in pkg/client/... because authentication and authorization needs
- // to work for any client of the HTTP interface.
- import (
- "bytes"
- "context"
- "encoding/json"
- "fmt"
- "io/ioutil"
- "net/http"
- "net/http/httptest"
- "net/url"
- "os"
- "strconv"
- "strings"
- "testing"
- "time"
- authenticationv1beta1 "k8s.io/api/authentication/v1beta1"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apiserver/pkg/authentication/authenticator"
- "k8s.io/apiserver/pkg/authentication/group"
- "k8s.io/apiserver/pkg/authentication/request/bearertoken"
- "k8s.io/apiserver/pkg/authentication/serviceaccount"
- "k8s.io/apiserver/pkg/authentication/token/cache"
- "k8s.io/apiserver/pkg/authentication/user"
- "k8s.io/apiserver/pkg/authorization/authorizer"
- "k8s.io/apiserver/pkg/authorization/authorizerfactory"
- "k8s.io/apiserver/plugin/pkg/authenticator/token/tokentest"
- "k8s.io/apiserver/plugin/pkg/authenticator/token/webhook"
- v1 "k8s.io/client-go/tools/clientcmd/api/v1"
- "k8s.io/kubernetes/pkg/apis/autoscaling"
- api "k8s.io/kubernetes/pkg/apis/core"
- "k8s.io/kubernetes/pkg/apis/extensions"
- "k8s.io/kubernetes/pkg/auth/authorizer/abac"
- "k8s.io/kubernetes/test/integration"
- "k8s.io/kubernetes/test/integration/framework"
- )
- const (
- AliceToken string = "abc123" // username: alice. Present in token file.
- BobToken string = "xyz987" // username: bob. Present in token file.
- UnknownToken string = "qwerty" // Not present in token file.
- )
- func getTestTokenAuth() authenticator.Request {
- tokenAuthenticator := tokentest.New()
- tokenAuthenticator.Tokens[AliceToken] = &user.DefaultInfo{Name: "alice", UID: "1"}
- tokenAuthenticator.Tokens[BobToken] = &user.DefaultInfo{Name: "bob", UID: "2"}
- return group.NewGroupAdder(bearertoken.New(tokenAuthenticator), []string{user.AllAuthenticated})
- }
- func getTestWebhookTokenAuth(serverURL string) (authenticator.Request, error) {
- kubecfgFile, err := ioutil.TempFile("", "webhook-kubecfg")
- if err != nil {
- return nil, err
- }
- defer os.Remove(kubecfgFile.Name())
- config := v1.Config{
- Clusters: []v1.NamedCluster{
- {
- Cluster: v1.Cluster{Server: serverURL},
- },
- },
- }
- if err := json.NewEncoder(kubecfgFile).Encode(config); err != nil {
- return nil, err
- }
- webhookTokenAuth, err := webhook.New(kubecfgFile.Name(), "v1beta1", nil)
- if err != nil {
- return nil, err
- }
- return bearertoken.New(cache.New(webhookTokenAuth, false, 2*time.Minute, 2*time.Minute)), nil
- }
- func path(resource, namespace, name string) string {
- return pathWithPrefix("", resource, namespace, name)
- }
- func pathWithPrefix(prefix, resource, namespace, name string) string {
- path := "/api/v1"
- if prefix != "" {
- path = path + "/" + prefix
- }
- if namespace != "" {
- path = path + "/namespaces/" + namespace
- }
- // Resource names are lower case.
- resource = strings.ToLower(resource)
- if resource != "" {
- path = path + "/" + resource
- }
- if name != "" {
- path = path + "/" + name
- }
- return path
- }
- func pathWithSubResource(resource, namespace, name, subresource string) string {
- path := pathWithPrefix("", resource, namespace, name)
- if subresource != "" {
- path = path + "/" + subresource
- }
- return path
- }
- func timeoutPath(resource, namespace, name string) string {
- return addTimeoutFlag(path(resource, namespace, name))
- }
- // Bodies for requests used in subsequent tests.
- var aPod = `
- {
- "kind": "Pod",
- "apiVersion": "v1",
- "metadata": {
- "name": "a",
- "creationTimestamp": null%s
- },
- "spec": {
- "containers": [
- {
- "name": "foo",
- "image": "bar/foo"
- }
- ]
- }
- }
- `
- var aRC = `
- {
- "kind": "ReplicationController",
- "apiVersion": "v1",
- "metadata": {
- "name": "a",
- "labels": {
- "name": "a"
- }%s
- },
- "spec": {
- "replicas": 2,
- "selector": {
- "name": "a"
- },
- "template": {
- "metadata": {
- "labels": {
- "name": "a"
- }
- },
- "spec": {
- "containers": [
- {
- "name": "foo",
- "image": "bar/foo"
- }
- ]
- }
- }
- }
- }
- `
- var aService = `
- {
- "kind": "Service",
- "apiVersion": "v1",
- "metadata": {
- "name": "a",
- "labels": {
- "name": "a"
- }%s
- },
- "spec": {
- "ports": [
- {
- "protocol": "TCP",
- "port": 8000
- }
- ],
- "selector": {
- "name": "a"
- },
- "clusterIP": "10.0.0.100"
- }
- }
- `
- var aNode = `
- {
- "kind": "Node",
- "apiVersion": "v1",
- "metadata": {
- "name": "a"%s
- },
- "spec": {
- "externalID": "external"
- }
- }
- `
- func aEvent(namespace string) string {
- return `
- {
- "kind": "Event",
- "apiVersion": "v1",
- "metadata": {
- "name": "a"%s
- },
- "involvedObject": {
- "kind": "Pod",
- "namespace": "` + namespace + `",
- "name": "a",
- "apiVersion": "v1"
- }
- }
- `
- }
- var aBinding = `
- {
- "kind": "Binding",
- "apiVersion": "v1",
- "metadata": {
- "name": "a"%s
- },
- "target": {
- "name": "10.10.10.10"
- }
- }
- `
- var emptyEndpoints = `
- {
- "kind": "Endpoints",
- "apiVersion": "v1",
- "metadata": {
- "name": "a"%s
- }
- }
- `
- var aEndpoints = `
- {
- "kind": "Endpoints",
- "apiVersion": "v1",
- "metadata": {
- "name": "a"%s
- },
- "subsets": [
- {
- "addresses": [
- {
- "ip": "10.10.1.1"
- }
- ],
- "ports": [
- {
- "port": 1909,
- "protocol": "TCP"
- }
- ]
- }
- ]
- }
- `
- var deleteNow = `
- {
- "kind": "DeleteOptions",
- "apiVersion": "v1",
- "gracePeriodSeconds": 0%s
- }
- `
- // To ensure that a POST completes before a dependent GET, set a timeout.
- func addTimeoutFlag(URLString string) string {
- u, _ := url.Parse(URLString)
- values := u.Query()
- values.Set("timeout", "60s")
- u.RawQuery = values.Encode()
- return u.String()
- }
- func getTestRequests(namespace string) []struct {
- verb string
- URL string
- body string
- statusCodes map[int]bool // allowed status codes.
- } {
- requests := []struct {
- verb string
- URL string
- body string
- statusCodes map[int]bool // Set of expected resp.StatusCode if all goes well.
- }{
- // Normal methods on pods
- {"GET", path("pods", "", ""), "", integration.Code200},
- {"GET", path("pods", namespace, ""), "", integration.Code200},
- {"POST", timeoutPath("pods", namespace, ""), aPod, integration.Code201},
- {"PUT", timeoutPath("pods", namespace, "a"), aPod, integration.Code200},
- {"GET", path("pods", namespace, "a"), "", integration.Code200},
- // GET and POST for /exec should return Bad Request (400) since the pod has not been assigned a node yet.
- {"GET", path("pods", namespace, "a") + "/exec", "", integration.Code400},
- {"POST", path("pods", namespace, "a") + "/exec", "", integration.Code400},
- // PUT for /exec should return Method Not Allowed (405).
- {"PUT", path("pods", namespace, "a") + "/exec", "", integration.Code405},
- // GET and POST for /portforward should return Bad Request (400) since the pod has not been assigned a node yet.
- {"GET", path("pods", namespace, "a") + "/portforward", "", integration.Code400},
- {"POST", path("pods", namespace, "a") + "/portforward", "", integration.Code400},
- // PUT for /portforward should return Method Not Allowed (405).
- {"PUT", path("pods", namespace, "a") + "/portforward", "", integration.Code405},
- {"PATCH", path("pods", namespace, "a"), "{%v}", integration.Code200},
- {"DELETE", timeoutPath("pods", namespace, "a"), deleteNow, integration.Code200},
- // Non-standard methods (not expected to work,
- // but expected to pass/fail authorization prior to
- // failing validation.
- {"OPTIONS", path("pods", namespace, ""), "", integration.Code405},
- {"OPTIONS", path("pods", namespace, "a"), "", integration.Code405},
- {"HEAD", path("pods", namespace, ""), "", integration.Code405},
- {"HEAD", path("pods", namespace, "a"), "", integration.Code405},
- {"TRACE", path("pods", namespace, ""), "", integration.Code405},
- {"TRACE", path("pods", namespace, "a"), "", integration.Code405},
- {"NOSUCHVERB", path("pods", namespace, ""), "", integration.Code405},
- // Normal methods on services
- {"GET", path("services", "", ""), "", integration.Code200},
- {"GET", path("services", namespace, ""), "", integration.Code200},
- {"POST", timeoutPath("services", namespace, ""), aService, integration.Code201},
- // Create an endpoint for the service (this is done automatically by endpoint controller
- // whenever a service is created, but this test does not run that controller)
- {"POST", timeoutPath("endpoints", namespace, ""), emptyEndpoints, integration.Code201},
- // Should return service unavailable when endpoint.subset is empty.
- {"GET", pathWithSubResource("services", namespace, "a", "proxy") + "/", "", integration.Code503},
- {"PUT", timeoutPath("services", namespace, "a"), aService, integration.Code200},
- {"GET", path("services", namespace, "a"), "", integration.Code200},
- {"DELETE", timeoutPath("endpoints", namespace, "a"), "", integration.Code200},
- {"DELETE", timeoutPath("services", namespace, "a"), "", integration.Code200},
- // Normal methods on replicationControllers
- {"GET", path("replicationControllers", "", ""), "", integration.Code200},
- {"GET", path("replicationControllers", namespace, ""), "", integration.Code200},
- {"POST", timeoutPath("replicationControllers", namespace, ""), aRC, integration.Code201},
- {"PUT", timeoutPath("replicationControllers", namespace, "a"), aRC, integration.Code200},
- {"GET", path("replicationControllers", namespace, "a"), "", integration.Code200},
- {"DELETE", timeoutPath("replicationControllers", namespace, "a"), "", integration.Code200},
- // Normal methods on endpoints
- {"GET", path("endpoints", "", ""), "", integration.Code200},
- {"GET", path("endpoints", namespace, ""), "", integration.Code200},
- {"POST", timeoutPath("endpoints", namespace, ""), aEndpoints, integration.Code201},
- {"PUT", timeoutPath("endpoints", namespace, "a"), aEndpoints, integration.Code200},
- {"GET", path("endpoints", namespace, "a"), "", integration.Code200},
- {"DELETE", timeoutPath("endpoints", namespace, "a"), "", integration.Code200},
- // Normal methods on nodes
- {"GET", path("nodes", "", ""), "", integration.Code200},
- {"POST", timeoutPath("nodes", "", ""), aNode, integration.Code201},
- {"PUT", timeoutPath("nodes", "", "a"), aNode, integration.Code200},
- {"GET", path("nodes", "", "a"), "", integration.Code200},
- {"DELETE", timeoutPath("nodes", "", "a"), "", integration.Code200},
- // Normal methods on events
- {"GET", path("events", "", ""), "", integration.Code200},
- {"GET", path("events", namespace, ""), "", integration.Code200},
- {"POST", timeoutPath("events", namespace, ""), aEvent(namespace), integration.Code201},
- {"PUT", timeoutPath("events", namespace, "a"), aEvent(namespace), integration.Code200},
- {"GET", path("events", namespace, "a"), "", integration.Code200},
- {"DELETE", timeoutPath("events", namespace, "a"), "", integration.Code200},
- // Normal methods on bindings
- {"GET", path("bindings", namespace, ""), "", integration.Code405},
- {"POST", timeoutPath("pods", namespace, ""), aPod, integration.Code201}, // Need a pod to bind or you get a 404
- {"POST", timeoutPath("bindings", namespace, ""), aBinding, integration.Code201},
- {"PUT", timeoutPath("bindings", namespace, "a"), aBinding, integration.Code404},
- {"GET", path("bindings", namespace, "a"), "", integration.Code404}, // No bindings instances
- {"DELETE", timeoutPath("bindings", namespace, "a"), "", integration.Code404},
- // Non-existent object type.
- {"GET", path("foo", "", ""), "", integration.Code404},
- {"POST", path("foo", namespace, ""), `{"foo": "foo"}`, integration.Code404},
- {"PUT", path("foo", namespace, "a"), `{"foo": "foo"}`, integration.Code404},
- {"GET", path("foo", namespace, "a"), "", integration.Code404},
- {"DELETE", timeoutPath("foo", namespace, ""), "", integration.Code404},
- // Special verbs on nodes
- {"GET", pathWithSubResource("nodes", namespace, "a", "proxy"), "", integration.Code404},
- {"GET", pathWithPrefix("redirect", "nodes", namespace, "a"), "", integration.Code404},
- // TODO: test .../watch/..., which doesn't end before the test timeout.
- // TODO: figure out how to create a node so that it can successfully proxy/redirect.
- // Non-object endpoints
- {"GET", "/", "", integration.Code200},
- {"GET", "/api", "", integration.Code200},
- {"GET", "/healthz", "", integration.Code200},
- {"GET", "/version", "", integration.Code200},
- {"GET", "/invalidURL", "", integration.Code404},
- }
- return requests
- }
- // The TestAuthMode* tests a large number of URLs and checks that they
- // are FORBIDDEN or not, depending on the mode. They do not attempt to do
- // detailed verification of behaviour beyond authorization. They are not
- // fuzz tests.
- //
- // TODO(etune): write a fuzz test of the REST API.
- func TestAuthModeAlwaysAllow(t *testing.T) {
- // Set up a master
- masterConfig := framework.NewIntegrationTestMasterConfig()
- _, s, closeFn := framework.RunAMaster(masterConfig)
- defer closeFn()
- ns := framework.CreateTestingNamespace("auth-always-allow", s, t)
- defer framework.DeleteTestingNamespace(ns, s, t)
- transport := http.DefaultTransport
- previousResourceVersion := make(map[string]float64)
- for _, r := range getTestRequests(ns.Name) {
- var bodyStr string
- if r.body != "" {
- sub := ""
- if r.verb == "PUT" {
- // For update operations, insert previous resource version
- if resVersion := previousResourceVersion[getPreviousResourceVersionKey(r.URL, "")]; resVersion != 0 {
- sub += fmt.Sprintf(",\r\n\"resourceVersion\": \"%v\"", resVersion)
- }
- sub += fmt.Sprintf(",\r\n\"namespace\": %q", ns.Name)
- }
- bodyStr = fmt.Sprintf(r.body, sub)
- }
- r.body = bodyStr
- bodyBytes := bytes.NewReader([]byte(bodyStr))
- req, err := http.NewRequest(r.verb, s.URL+r.URL, bodyBytes)
- if err != nil {
- t.Logf("case %v", r)
- t.Fatalf("unexpected error: %v", err)
- }
- if r.verb == "PATCH" {
- req.Header.Set("Content-Type", "application/merge-patch+json")
- }
- func() {
- resp, err := transport.RoundTrip(req)
- if err != nil {
- t.Logf("case %v", r)
- t.Fatalf("unexpected error: %v", err)
- }
- defer resp.Body.Close()
- b, _ := ioutil.ReadAll(resp.Body)
- if _, ok := r.statusCodes[resp.StatusCode]; !ok {
- t.Logf("case %v", r)
- t.Errorf("Expected status one of %v, but got %v", r.statusCodes, resp.StatusCode)
- t.Errorf("Body: %v", string(b))
- } else {
- if r.verb == "POST" {
- // For successful create operations, extract resourceVersion
- id, currentResourceVersion, err := parseResourceVersion(b)
- if err == nil {
- key := getPreviousResourceVersionKey(r.URL, id)
- previousResourceVersion[key] = currentResourceVersion
- } else {
- t.Logf("error in trying to extract resource version: %s", err)
- }
- }
- }
- }()
- }
- }
- func parseResourceVersion(response []byte) (string, float64, error) {
- var resultBodyMap map[string]interface{}
- err := json.Unmarshal(response, &resultBodyMap)
- if err != nil {
- return "", 0, fmt.Errorf("unexpected error unmarshaling resultBody: %v", err)
- }
- metadata, ok := resultBodyMap["metadata"].(map[string]interface{})
- if !ok {
- return "", 0, fmt.Errorf("unexpected error, metadata not found in JSON response: %v", string(response))
- }
- id, ok := metadata["name"].(string)
- if !ok {
- return "", 0, fmt.Errorf("unexpected error, id not found in JSON response: %v", string(response))
- }
- resourceVersionString, ok := metadata["resourceVersion"].(string)
- if !ok {
- return "", 0, fmt.Errorf("unexpected error, resourceVersion not found in JSON response: %v", string(response))
- }
- resourceVersion, err := strconv.ParseFloat(resourceVersionString, 64)
- if err != nil {
- return "", 0, fmt.Errorf("unexpected error, could not parse resourceVersion as float64, err: %s. JSON response: %v", err, string(response))
- }
- return id, resourceVersion, nil
- }
- func getPreviousResourceVersionKey(url, id string) string {
- baseURL := strings.Split(url, "?")[0]
- key := baseURL
- if id != "" {
- key = fmt.Sprintf("%s/%v", baseURL, id)
- }
- return key
- }
- func TestAuthModeAlwaysDeny(t *testing.T) {
- // Set up a master
- masterConfig := framework.NewIntegrationTestMasterConfig()
- masterConfig.GenericConfig.Authorization.Authorizer = authorizerfactory.NewAlwaysDenyAuthorizer()
- _, s, closeFn := framework.RunAMaster(masterConfig)
- defer closeFn()
- ns := framework.CreateTestingNamespace("auth-always-deny", s, t)
- defer framework.DeleteTestingNamespace(ns, s, t)
- transport := http.DefaultTransport
- for _, r := range getTestRequests(ns.Name) {
- bodyBytes := bytes.NewReader([]byte(r.body))
- req, err := http.NewRequest(r.verb, s.URL+r.URL, bodyBytes)
- if err != nil {
- t.Logf("case %v", r)
- t.Fatalf("unexpected error: %v", err)
- }
- func() {
- resp, err := transport.RoundTrip(req)
- if err != nil {
- t.Logf("case %v", r)
- t.Fatalf("unexpected error: %v", err)
- }
- defer resp.Body.Close()
- if resp.StatusCode != http.StatusForbidden {
- t.Logf("case %v", r)
- t.Errorf("Expected status Forbidden but got status %v", resp.Status)
- }
- }()
- }
- }
- // Inject into master an authorizer that uses user info.
- // TODO(etune): remove this test once a more comprehensive built-in authorizer is implemented.
- type allowAliceAuthorizer struct{}
- func (allowAliceAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
- if a.GetUser() != nil && a.GetUser().GetName() == "alice" {
- return authorizer.DecisionAllow, "", nil
- }
- return authorizer.DecisionNoOpinion, "I can't allow that. Go ask alice.", nil
- }
- // TestAliceNotForbiddenOrUnauthorized tests a user who is known to
- // the authentication system and authorized to do any actions.
- func TestAliceNotForbiddenOrUnauthorized(t *testing.T) {
- // This file has alice and bob in it.
- // Set up a master
- masterConfig := framework.NewIntegrationTestMasterConfig()
- masterConfig.GenericConfig.Authentication.Authenticator = getTestTokenAuth()
- masterConfig.GenericConfig.Authorization.Authorizer = allowAliceAuthorizer{}
- _, s, closeFn := framework.RunAMaster(masterConfig)
- defer closeFn()
- ns := framework.CreateTestingNamespace("auth-alice-not-forbidden", s, t)
- defer framework.DeleteTestingNamespace(ns, s, t)
- previousResourceVersion := make(map[string]float64)
- transport := http.DefaultTransport
- for _, r := range getTestRequests(ns.Name) {
- token := AliceToken
- var bodyStr string
- if r.body != "" {
- sub := ""
- if r.verb == "PUT" {
- // For update operations, insert previous resource version
- if resVersion := previousResourceVersion[getPreviousResourceVersionKey(r.URL, "")]; resVersion != 0 {
- sub += fmt.Sprintf(",\r\n\"resourceVersion\": \"%v\"", resVersion)
- }
- sub += fmt.Sprintf(",\r\n\"namespace\": %q", ns.Name)
- }
- bodyStr = fmt.Sprintf(r.body, sub)
- }
- r.body = bodyStr
- bodyBytes := bytes.NewReader([]byte(bodyStr))
- req, err := http.NewRequest(r.verb, s.URL+r.URL, bodyBytes)
- if err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
- req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
- if r.verb == "PATCH" {
- req.Header.Set("Content-Type", "application/merge-patch+json")
- }
- func() {
- resp, err := transport.RoundTrip(req)
- if err != nil {
- t.Logf("case %v", r)
- t.Fatalf("unexpected error: %v", err)
- }
- defer resp.Body.Close()
- b, _ := ioutil.ReadAll(resp.Body)
- if _, ok := r.statusCodes[resp.StatusCode]; !ok {
- t.Logf("case %v", r)
- t.Errorf("Expected status one of %v, but got %v", r.statusCodes, resp.StatusCode)
- t.Errorf("Body: %v", string(b))
- } else {
- if r.verb == "POST" {
- // For successful create operations, extract resourceVersion
- id, currentResourceVersion, err := parseResourceVersion(b)
- if err == nil {
- key := getPreviousResourceVersionKey(r.URL, id)
- previousResourceVersion[key] = currentResourceVersion
- }
- }
- }
- }()
- }
- }
- // TestBobIsForbidden tests that a user who is known to
- // the authentication system but not authorized to do any actions
- // should receive "Forbidden".
- func TestBobIsForbidden(t *testing.T) {
- // This file has alice and bob in it.
- masterConfig := framework.NewIntegrationTestMasterConfig()
- masterConfig.GenericConfig.Authentication.Authenticator = getTestTokenAuth()
- masterConfig.GenericConfig.Authorization.Authorizer = allowAliceAuthorizer{}
- _, s, closeFn := framework.RunAMaster(masterConfig)
- defer closeFn()
- ns := framework.CreateTestingNamespace("auth-bob-forbidden", s, t)
- defer framework.DeleteTestingNamespace(ns, s, t)
- transport := http.DefaultTransport
- for _, r := range getTestRequests(ns.Name) {
- token := BobToken
- bodyBytes := bytes.NewReader([]byte(r.body))
- req, err := http.NewRequest(r.verb, s.URL+r.URL, bodyBytes)
- if err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
- req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
- func() {
- resp, err := transport.RoundTrip(req)
- if err != nil {
- t.Logf("case %v", r)
- t.Fatalf("unexpected error: %v", err)
- }
- defer resp.Body.Close()
- // Expect all of bob's actions to return Forbidden
- if resp.StatusCode != http.StatusForbidden {
- t.Logf("case %v", r)
- t.Errorf("Expected not status Forbidden, but got %s", resp.Status)
- }
- }()
- }
- }
- // TestUnknownUserIsUnauthorized tests that a user who is unknown
- // to the authentication system get status code "Unauthorized".
- // An authorization module is installed in this scenario for integration
- // test purposes, but requests aren't expected to reach it.
- func TestUnknownUserIsUnauthorized(t *testing.T) {
- // This file has alice and bob in it.
- // Set up a master
- masterConfig := framework.NewIntegrationTestMasterConfig()
- masterConfig.GenericConfig.Authentication.Authenticator = getTestTokenAuth()
- masterConfig.GenericConfig.Authorization.Authorizer = allowAliceAuthorizer{}
- _, s, closeFn := framework.RunAMaster(masterConfig)
- defer closeFn()
- ns := framework.CreateTestingNamespace("auth-unknown-unauthorized", s, t)
- defer framework.DeleteTestingNamespace(ns, s, t)
- transport := http.DefaultTransport
- for _, r := range getTestRequests(ns.Name) {
- token := UnknownToken
- bodyBytes := bytes.NewReader([]byte(r.body))
- req, err := http.NewRequest(r.verb, s.URL+r.URL, bodyBytes)
- if err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
- req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
- func() {
- resp, err := transport.RoundTrip(req)
- if err != nil {
- t.Logf("case %v", r)
- t.Fatalf("unexpected error: %v", err)
- }
- defer resp.Body.Close()
- // Expect all of unauthenticated user's request to be "Unauthorized"
- if resp.StatusCode != http.StatusUnauthorized {
- t.Logf("case %v", r)
- t.Errorf("Expected status %v, but got %v", http.StatusUnauthorized, resp.StatusCode)
- b, _ := ioutil.ReadAll(resp.Body)
- t.Errorf("Body: %v", string(b))
- }
- }()
- }
- }
- type impersonateAuthorizer struct{}
- // alice can't act as anyone and bob can't do anything but act-as someone
- func (impersonateAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
- // alice can impersonate service accounts and do other actions
- if a.GetUser() != nil && a.GetUser().GetName() == "alice" && a.GetVerb() == "impersonate" && a.GetResource() == "serviceaccounts" {
- return authorizer.DecisionAllow, "", nil
- }
- if a.GetUser() != nil && a.GetUser().GetName() == "alice" && a.GetVerb() != "impersonate" {
- return authorizer.DecisionAllow, "", nil
- }
- // bob can impersonate anyone, but that it
- if a.GetUser() != nil && a.GetUser().GetName() == "bob" && a.GetVerb() == "impersonate" {
- return authorizer.DecisionAllow, "", nil
- }
- // service accounts can do everything
- if a.GetUser() != nil && strings.HasPrefix(a.GetUser().GetName(), serviceaccount.ServiceAccountUsernamePrefix) {
- return authorizer.DecisionAllow, "", nil
- }
- return authorizer.DecisionNoOpinion, "I can't allow that. Go ask alice.", nil
- }
- func TestImpersonateIsForbidden(t *testing.T) {
- // Set up a master
- masterConfig := framework.NewIntegrationTestMasterConfig()
- masterConfig.GenericConfig.Authentication.Authenticator = getTestTokenAuth()
- masterConfig.GenericConfig.Authorization.Authorizer = impersonateAuthorizer{}
- _, s, closeFn := framework.RunAMaster(masterConfig)
- defer closeFn()
- ns := framework.CreateTestingNamespace("auth-impersonate-forbidden", s, t)
- defer framework.DeleteTestingNamespace(ns, s, t)
- transport := http.DefaultTransport
- // bob can't perform actions himself
- for _, r := range getTestRequests(ns.Name) {
- token := BobToken
- bodyBytes := bytes.NewReader([]byte(r.body))
- req, err := http.NewRequest(r.verb, s.URL+r.URL, bodyBytes)
- if err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
- req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
- func() {
- resp, err := transport.RoundTrip(req)
- if err != nil {
- t.Logf("case %v", r)
- t.Fatalf("unexpected error: %v", err)
- }
- defer resp.Body.Close()
- // Expect all of bob's actions to return Forbidden
- if resp.StatusCode != http.StatusForbidden {
- t.Logf("case %v", r)
- t.Errorf("Expected not status Forbidden, but got %s", resp.Status)
- }
- }()
- }
- // bob can impersonate alice to do other things
- for _, r := range getTestRequests(ns.Name) {
- token := BobToken
- bodyBytes := bytes.NewReader([]byte(r.body))
- req, err := http.NewRequest(r.verb, s.URL+r.URL, bodyBytes)
- if err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
- req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
- req.Header.Set("Impersonate-User", "alice")
- func() {
- resp, err := transport.RoundTrip(req)
- if err != nil {
- t.Logf("case %v", r)
- t.Fatalf("unexpected error: %v", err)
- }
- defer resp.Body.Close()
- // Expect all the requests to be allowed, don't care what they actually do
- if resp.StatusCode == http.StatusForbidden {
- t.Logf("case %v", r)
- t.Errorf("Expected status not %v, but got %v", http.StatusForbidden, resp.StatusCode)
- }
- }()
- }
- // alice can't impersonate bob
- for _, r := range getTestRequests(ns.Name) {
- token := AliceToken
- bodyBytes := bytes.NewReader([]byte(r.body))
- req, err := http.NewRequest(r.verb, s.URL+r.URL, bodyBytes)
- if err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
- req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
- req.Header.Set("Impersonate-User", "bob")
- func() {
- resp, err := transport.RoundTrip(req)
- if err != nil {
- t.Logf("case %v", r)
- t.Fatalf("unexpected error: %v", err)
- }
- defer resp.Body.Close()
- // Expect all of bob's actions to return Forbidden
- if resp.StatusCode != http.StatusForbidden {
- t.Logf("case %v", r)
- t.Errorf("Expected not status Forbidden, but got %s", resp.Status)
- }
- }()
- }
- // alice can impersonate a service account
- for _, r := range getTestRequests(ns.Name) {
- token := BobToken
- bodyBytes := bytes.NewReader([]byte(r.body))
- req, err := http.NewRequest(r.verb, s.URL+r.URL, bodyBytes)
- if err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
- req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
- req.Header.Set("Impersonate-User", serviceaccount.MakeUsername("default", "default"))
- func() {
- resp, err := transport.RoundTrip(req)
- if err != nil {
- t.Logf("case %v", r)
- t.Fatalf("unexpected error: %v", err)
- }
- defer resp.Body.Close()
- // Expect all the requests to be allowed, don't care what they actually do
- if resp.StatusCode == http.StatusForbidden {
- t.Logf("case %v", r)
- t.Errorf("Expected status not %v, but got %v", http.StatusForbidden, resp.StatusCode)
- }
- }()
- }
- }
- func newAuthorizerWithContents(t *testing.T, contents string) authorizer.Authorizer {
- f, err := ioutil.TempFile("", "auth_test")
- if err != nil {
- t.Fatalf("unexpected error creating policyfile: %v", err)
- }
- f.Close()
- defer os.Remove(f.Name())
- if err := ioutil.WriteFile(f.Name(), []byte(contents), 0700); err != nil {
- t.Fatalf("unexpected error writing policyfile: %v", err)
- }
- pl, err := abac.NewFromFile(f.Name())
- if err != nil {
- t.Fatalf("unexpected error creating authorizer from policyfile: %v", err)
- }
- return pl
- }
- type trackingAuthorizer struct {
- requestAttributes []authorizer.Attributes
- }
- func (a *trackingAuthorizer) Authorize(ctx context.Context, attributes authorizer.Attributes) (authorizer.Decision, string, error) {
- a.requestAttributes = append(a.requestAttributes, attributes)
- return authorizer.DecisionAllow, "", nil
- }
- // TestAuthorizationAttributeDetermination tests that authorization attributes are built correctly
- func TestAuthorizationAttributeDetermination(t *testing.T) {
- trackingAuthorizer := &trackingAuthorizer{}
- // Set up a master
- masterConfig := framework.NewIntegrationTestMasterConfig()
- masterConfig.GenericConfig.Authentication.Authenticator = getTestTokenAuth()
- masterConfig.GenericConfig.Authorization.Authorizer = trackingAuthorizer
- _, s, closeFn := framework.RunAMaster(masterConfig)
- defer closeFn()
- ns := framework.CreateTestingNamespace("auth-attribute-determination", s, t)
- defer framework.DeleteTestingNamespace(ns, s, t)
- transport := http.DefaultTransport
- requests := map[string]struct {
- verb string
- URL string
- expectedAttributes authorizer.Attributes
- }{
- "prefix/version/resource": {"GET", "/api/v1/pods", authorizer.AttributesRecord{APIGroup: api.GroupName, Resource: "pods"}},
- "prefix/group/version/resource": {"GET", "/apis/extensions/v1/pods", authorizer.AttributesRecord{APIGroup: extensions.GroupName, Resource: "pods"}},
- "prefix/group/version/resource2": {"GET", "/apis/autoscaling/v1/horizontalpodautoscalers", authorizer.AttributesRecord{APIGroup: autoscaling.GroupName, Resource: "horizontalpodautoscalers"}},
- }
- currentAuthorizationAttributesIndex := 0
- for testName, r := range requests {
- token := BobToken
- req, err := http.NewRequest(r.verb, s.URL+r.URL, nil)
- if err != nil {
- t.Logf("case %v", testName)
- t.Fatalf("unexpected error: %v", err)
- }
- req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
- func() {
- resp, err := transport.RoundTrip(req)
- if err != nil {
- t.Logf("case %v", r)
- t.Fatalf("unexpected error: %v", err)
- }
- defer resp.Body.Close()
- found := false
- for i := currentAuthorizationAttributesIndex; i < len(trackingAuthorizer.requestAttributes); i++ {
- if trackingAuthorizer.requestAttributes[i].GetAPIGroup() == r.expectedAttributes.GetAPIGroup() &&
- trackingAuthorizer.requestAttributes[i].GetResource() == r.expectedAttributes.GetResource() {
- found = true
- break
- }
- t.Logf("%#v did not match %#v", r.expectedAttributes, trackingAuthorizer.requestAttributes[i].(*authorizer.AttributesRecord))
- }
- if !found {
- t.Errorf("did not find %#v in %#v", r.expectedAttributes, trackingAuthorizer.requestAttributes[currentAuthorizationAttributesIndex:])
- }
- currentAuthorizationAttributesIndex = len(trackingAuthorizer.requestAttributes)
- }()
- }
- }
- // TestNamespaceAuthorization tests that authorization can be controlled
- // by namespace.
- func TestNamespaceAuthorization(t *testing.T) {
- // This file has alice and bob in it.
- a := newAuthorizerWithContents(t, `{"namespace": "auth-namespace"}
- `)
- // Set up a master
- masterConfig := framework.NewIntegrationTestMasterConfig()
- masterConfig.GenericConfig.Authentication.Authenticator = getTestTokenAuth()
- masterConfig.GenericConfig.Authorization.Authorizer = a
- _, s, closeFn := framework.RunAMaster(masterConfig)
- defer closeFn()
- ns := framework.CreateTestingNamespace("auth-namespace", s, t)
- defer framework.DeleteTestingNamespace(ns, s, t)
- previousResourceVersion := make(map[string]float64)
- transport := http.DefaultTransport
- requests := []struct {
- verb string
- URL string
- namespace string
- body string
- statusCodes map[int]bool // allowed status codes.
- }{
- {"POST", timeoutPath("pods", ns.Name, ""), "foo", aPod, integration.Code201},
- {"GET", path("pods", ns.Name, ""), "foo", "", integration.Code200},
- {"GET", path("pods", ns.Name, "a"), "foo", "", integration.Code200},
- {"DELETE", timeoutPath("pods", ns.Name, "a"), "foo", "", integration.Code200},
- {"POST", timeoutPath("pods", "foo", ""), "bar", aPod, integration.Code403},
- {"GET", path("pods", "foo", ""), "bar", "", integration.Code403},
- {"GET", path("pods", "foo", "a"), "bar", "", integration.Code403},
- {"DELETE", timeoutPath("pods", "foo", "a"), "bar", "", integration.Code403},
- {"POST", timeoutPath("pods", metav1.NamespaceDefault, ""), "", aPod, integration.Code403},
- {"GET", path("pods", "", ""), "", "", integration.Code403},
- {"GET", path("pods", metav1.NamespaceDefault, "a"), "", "", integration.Code403},
- {"DELETE", timeoutPath("pods", metav1.NamespaceDefault, "a"), "", "", integration.Code403},
- }
- for _, r := range requests {
- token := BobToken
- var bodyStr string
- if r.body != "" {
- sub := ""
- if r.verb == "PUT" && r.body != "" {
- // For update operations, insert previous resource version
- if resVersion := previousResourceVersion[getPreviousResourceVersionKey(r.URL, "")]; resVersion != 0 {
- sub += fmt.Sprintf(",\r\n\"resourceVersion\": \"%v\"", resVersion)
- }
- namespace := r.namespace
- // FIXME: Is that correct?
- if len(namespace) == 0 {
- namespace = "default"
- }
- sub += fmt.Sprintf(",\r\n\"namespace\": %q", namespace)
- }
- bodyStr = fmt.Sprintf(r.body, sub)
- }
- r.body = bodyStr
- bodyBytes := bytes.NewReader([]byte(bodyStr))
- req, err := http.NewRequest(r.verb, s.URL+r.URL, bodyBytes)
- if err != nil {
- t.Logf("case %v", r)
- t.Fatalf("unexpected error: %v", err)
- }
- req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
- func() {
- resp, err := transport.RoundTrip(req)
- if err != nil {
- t.Logf("case %v", r)
- t.Fatalf("unexpected error: %v", err)
- }
- defer resp.Body.Close()
- b, _ := ioutil.ReadAll(resp.Body)
- if _, ok := r.statusCodes[resp.StatusCode]; !ok {
- t.Logf("case %v", r)
- t.Errorf("Expected status one of %v, but got %v", r.statusCodes, resp.StatusCode)
- t.Errorf("Body: %v", string(b))
- } else {
- if r.verb == "POST" {
- // For successful create operations, extract resourceVersion
- id, currentResourceVersion, err := parseResourceVersion(b)
- if err == nil {
- key := getPreviousResourceVersionKey(r.URL, id)
- previousResourceVersion[key] = currentResourceVersion
- }
- }
- }
- }()
- }
- }
- // TestKindAuthorization tests that authorization can be controlled
- // by namespace.
- func TestKindAuthorization(t *testing.T) {
- // This file has alice and bob in it.
- a := newAuthorizerWithContents(t, `{"resource": "services"}
- `)
- // Set up a master
- masterConfig := framework.NewIntegrationTestMasterConfig()
- masterConfig.GenericConfig.Authentication.Authenticator = getTestTokenAuth()
- masterConfig.GenericConfig.Authorization.Authorizer = a
- _, s, closeFn := framework.RunAMaster(masterConfig)
- defer closeFn()
- ns := framework.CreateTestingNamespace("auth-kind", s, t)
- defer framework.DeleteTestingNamespace(ns, s, t)
- previousResourceVersion := make(map[string]float64)
- transport := http.DefaultTransport
- requests := []struct {
- verb string
- URL string
- body string
- statusCodes map[int]bool // allowed status codes.
- }{
- {"POST", timeoutPath("services", ns.Name, ""), aService, integration.Code201},
- {"GET", path("services", ns.Name, ""), "", integration.Code200},
- {"GET", path("services", ns.Name, "a"), "", integration.Code200},
- {"DELETE", timeoutPath("services", ns.Name, "a"), "", integration.Code200},
- {"POST", timeoutPath("pods", ns.Name, ""), aPod, integration.Code403},
- {"GET", path("pods", "", ""), "", integration.Code403},
- {"GET", path("pods", ns.Name, "a"), "", integration.Code403},
- {"DELETE", timeoutPath("pods", ns.Name, "a"), "", integration.Code403},
- }
- for _, r := range requests {
- token := BobToken
- var bodyStr string
- if r.body != "" {
- bodyStr = fmt.Sprintf(r.body, "")
- if r.verb == "PUT" && r.body != "" {
- // For update operations, insert previous resource version
- if resVersion := previousResourceVersion[getPreviousResourceVersionKey(r.URL, "")]; resVersion != 0 {
- resourceVersionJSON := fmt.Sprintf(",\r\n\"resourceVersion\": \"%v\"", resVersion)
- bodyStr = fmt.Sprintf(r.body, resourceVersionJSON)
- }
- }
- }
- r.body = bodyStr
- bodyBytes := bytes.NewReader([]byte(bodyStr))
- req, err := http.NewRequest(r.verb, s.URL+r.URL, bodyBytes)
- if err != nil {
- t.Logf("case %v", r)
- t.Fatalf("unexpected error: %v", err)
- }
- req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
- {
- resp, err := transport.RoundTrip(req)
- if err != nil {
- t.Logf("case %v", r)
- t.Fatalf("unexpected error: %v", err)
- }
- defer resp.Body.Close()
- b, _ := ioutil.ReadAll(resp.Body)
- if _, ok := r.statusCodes[resp.StatusCode]; !ok {
- t.Logf("case %v", r)
- t.Errorf("Expected status one of %v, but got %v", r.statusCodes, resp.StatusCode)
- t.Errorf("Body: %v", string(b))
- } else {
- if r.verb == "POST" {
- // For successful create operations, extract resourceVersion
- id, currentResourceVersion, err := parseResourceVersion(b)
- if err == nil {
- key := getPreviousResourceVersionKey(r.URL, id)
- previousResourceVersion[key] = currentResourceVersion
- }
- }
- }
- }
- }
- }
- // TestReadOnlyAuthorization tests that authorization can be controlled
- // by namespace.
- func TestReadOnlyAuthorization(t *testing.T) {
- // This file has alice and bob in it.
- a := newAuthorizerWithContents(t, `{"readonly": true}`)
- // Set up a master
- masterConfig := framework.NewIntegrationTestMasterConfig()
- masterConfig.GenericConfig.Authentication.Authenticator = getTestTokenAuth()
- masterConfig.GenericConfig.Authorization.Authorizer = a
- _, s, closeFn := framework.RunAMaster(masterConfig)
- defer closeFn()
- ns := framework.CreateTestingNamespace("auth-read-only", s, t)
- defer framework.DeleteTestingNamespace(ns, s, t)
- transport := http.DefaultTransport
- requests := []struct {
- verb string
- URL string
- body string
- statusCodes map[int]bool // allowed status codes.
- }{
- {"POST", path("pods", ns.Name, ""), aPod, integration.Code403},
- {"GET", path("pods", ns.Name, ""), "", integration.Code200},
- {"GET", path("pods", metav1.NamespaceDefault, "a"), "", integration.Code404},
- }
- for _, r := range requests {
- token := BobToken
- bodyBytes := bytes.NewReader([]byte(r.body))
- req, err := http.NewRequest(r.verb, s.URL+r.URL, bodyBytes)
- if err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
- req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
- func() {
- resp, err := transport.RoundTrip(req)
- if err != nil {
- t.Logf("case %v", r)
- t.Fatalf("unexpected error: %v", err)
- }
- defer resp.Body.Close()
- if _, ok := r.statusCodes[resp.StatusCode]; !ok {
- t.Logf("case %v", r)
- t.Errorf("Expected status one of %v, but got %v", r.statusCodes, resp.StatusCode)
- b, _ := ioutil.ReadAll(resp.Body)
- t.Errorf("Body: %v", string(b))
- }
- }()
- }
- }
- // TestWebhookTokenAuthenticator tests that a master can use the webhook token
- // authenticator to call out to a remote web server for authentication
- // decisions.
- func TestWebhookTokenAuthenticator(t *testing.T) {
- authServer := newTestWebhookTokenAuthServer()
- defer authServer.Close()
- authenticator, err := getTestWebhookTokenAuth(authServer.URL)
- if err != nil {
- t.Fatalf("error starting webhook token authenticator server: %v", err)
- }
- // Set up a master
- masterConfig := framework.NewIntegrationTestMasterConfig()
- masterConfig.GenericConfig.Authentication.Authenticator = authenticator
- masterConfig.GenericConfig.Authorization.Authorizer = allowAliceAuthorizer{}
- _, s, closeFn := framework.RunAMaster(masterConfig)
- defer closeFn()
- ns := framework.CreateTestingNamespace("auth-webhook-token", s, t)
- defer framework.DeleteTestingNamespace(ns, s, t)
- transport := http.DefaultTransport
- for _, r := range getTestRequests(ns.Name) {
- // Expect Bob's requests to all fail.
- token := BobToken
- bodyBytes := bytes.NewReader([]byte(r.body))
- req, err := http.NewRequest(r.verb, s.URL+r.URL, bodyBytes)
- if err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
- req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
- func() {
- resp, err := transport.RoundTrip(req)
- if err != nil {
- t.Logf("case %v", r)
- t.Fatalf("unexpected error: %v", err)
- }
- defer resp.Body.Close()
- // Expect all of Bob's actions to return Forbidden
- if resp.StatusCode != http.StatusForbidden {
- t.Logf("case %v", r)
- t.Errorf("Expected http.Forbidden, but got %s", resp.Status)
- }
- }()
- // Expect Alice's requests to succeed.
- token = AliceToken
- bodyBytes = bytes.NewReader([]byte(r.body))
- req, err = http.NewRequest(r.verb, s.URL+r.URL, bodyBytes)
- if err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
- req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
- func() {
- resp, err := transport.RoundTrip(req)
- if err != nil {
- t.Logf("case %v", r)
- t.Fatalf("unexpected error: %v", err)
- }
- defer resp.Body.Close()
- // Expect all of Alice's actions to at least get past authn/authz.
- if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {
- t.Logf("case %v", r)
- t.Errorf("Expected something other than Unauthorized/Forbidden, but got %s", resp.Status)
- }
- }()
- }
- }
- // newTestWebhookTokenAuthServer creates an http token authentication server
- // that knows about both Alice and Bob.
- func newTestWebhookTokenAuthServer() *httptest.Server {
- serveHTTP := func(w http.ResponseWriter, r *http.Request) {
- var review authenticationv1beta1.TokenReview
- if err := json.NewDecoder(r.Body).Decode(&review); err != nil {
- http.Error(w, fmt.Sprintf("failed to decode body: %v", err), http.StatusBadRequest)
- return
- }
- type userInfo struct {
- Username string `json:"username"`
- UID string `json:"uid"`
- Groups []string `json:"groups"`
- }
- type status struct {
- Authenticated bool `json:"authenticated"`
- User userInfo `json:"user"`
- }
- var username, uid string
- authenticated := false
- if review.Spec.Token == AliceToken {
- authenticated, username, uid = true, "alice", "1"
- } else if review.Spec.Token == BobToken {
- authenticated, username, uid = true, "bob", "2"
- }
- resp := struct {
- APIVersion string `json:"apiVersion"`
- Status status `json:"status"`
- }{
- APIVersion: authenticationv1beta1.SchemeGroupVersion.String(),
- Status: status{
- authenticated,
- userInfo{
- Username: username,
- UID: uid,
- },
- },
- }
- w.Header().Set("Content-Type", "application/json")
- json.NewEncoder(w).Encode(resp)
- }
- server := httptest.NewUnstartedServer(http.HandlerFunc(serveHTTP))
- server.Start()
- return server
- }
|