Merge pull request #2116 from erictune/improve_auth_integ

Improve integration test
This commit is contained in:
Brendan Burns 2014-11-03 08:26:54 -08:00
commit c92e15679a

View File

@ -32,32 +32,43 @@ import (
"testing" "testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/master" "github.com/GoogleCloudPlatform/kubernetes/pkg/master"
"github.com/golang/glog"
) )
func init() { func init() {
requireEtcd() requireEtcd()
} }
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.
// Keep file in sync with above constants.
TokenfileCSV string = `
abc123,alice,1
xyz987,bob,2
`
)
func writeTestTokenFile() string {
// Write a token file.
f, err := ioutil.TempFile("", "auth_integration_test")
f.Close()
if err != nil {
glog.Fatalf("unexpected error: %v", err)
}
if err := ioutil.WriteFile(f.Name(), []byte(TokenfileCSV), 0700); err != nil {
glog.Fatalf("unexpected error writing tokenfile: %v", err)
}
return f.Name()
}
// TestWhoAmI passes a known Bearer Token to the master's /_whoami endpoint and checks that // TestWhoAmI passes a known Bearer Token to the master's /_whoami endpoint and checks that
// the master authenticates the user. // the master authenticates the user.
func TestWhoAmI(t *testing.T) { func TestWhoAmI(t *testing.T) {
deleteAllEtcdKeys() deleteAllEtcdKeys()
// Write a token file.
json := `
abc123,alice,1
xyz987,bob,2
`
f, err := ioutil.TempFile("", "auth_integration_test")
f.Close()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
defer os.Remove(f.Name())
if err := ioutil.WriteFile(f.Name(), []byte(json), 0700); err != nil {
t.Fatalf("unexpected error writing tokenfile: %v", err)
}
// Set up a master // Set up a master
helper, err := master.NewEtcdHelper(newEtcdClient(), "v1beta1") helper, err := master.NewEtcdHelper(newEtcdClient(), "v1beta1")
@ -65,12 +76,14 @@ xyz987,bob,2
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
tokenFilename := writeTestTokenFile()
defer os.Remove(tokenFilename)
m := master.New(&master.Config{ m := master.New(&master.Config{
EtcdHelper: helper, EtcdHelper: helper,
EnableLogsSupport: false, EnableLogsSupport: false,
EnableUISupport: false, EnableUISupport: false,
APIPrefix: "/api", APIPrefix: "/api",
TokenAuthFile: f.Name(), TokenAuthFile: tokenFilename,
AuthorizationMode: "AlwaysAllow", AuthorizationMode: "AlwaysAllow",
}) })
@ -86,8 +99,8 @@ xyz987,bob,2
expected string expected string
succeeds bool succeeds bool
}{ }{
{"Valid token", "abc123", "AUTHENTICATED AS alice", true}, {"Valid token", AliceToken, "AUTHENTICATED AS alice", true},
{"Unknown token", "456jkl", "", false}, {"Unknown token", UnknownToken, "", false},
{"No token", "", "", false}, {"No token", "", "", false},
} }
for _, tc := range testCases { for _, tc := range testCases {
@ -96,8 +109,9 @@ xyz987,bob,2
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", tc.token)) req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", tc.token))
{
resp, err := transport.RoundTrip(req) resp, err := transport.RoundTrip(req)
defer resp.Body.Close()
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
@ -119,6 +133,7 @@ xyz987,bob,2
} }
} }
}
} }
// Bodies for requests used in subsequent tests. // Bodies for requests used in subsequent tests.
@ -181,15 +196,16 @@ var aMinion string = `
var aEvent string = ` var aEvent string = `
{ {
"kind": "Binding", "kind": "Event",
"apiVersion": "v1beta1", "apiVersion": "v1beta1",
"id": "a", "id": "a",
"involvedObject": { "involvedObject": {
{ {
"kind": "Minion", "kind": "Minion",
"name": "a" "name": "a",
"apiVersion": "v1beta1", "apiVersion": "v1beta1",
} }
}
} }
` `
@ -215,113 +231,116 @@ var aEndpoints string = `
// Requests to try. Each one should be forbidden or not forbidden // Requests to try. Each one should be forbidden or not forbidden
// depending on the authentication and authorization setup of the master. // depending on the authentication and authorization setup of the master.
var code200or202 = map[int]bool{200: true, 202: true} // Unpredicatable which will be returned.
var code404 = map[int]bool{404: true}
var code409 = map[int]bool{409: true}
var code422 = map[int]bool{422: true}
var code500 = map[int]bool{500: true}
func getTestRequests() []struct { func getTestRequests() []struct {
verb string verb string
URL string URL string
body string body string
statusCodes map[int]bool // allowed status codes.
} { } {
requests := []struct { requests := []struct {
verb string verb string
URL string URL string
body string body string
statusCodes map[int]bool // Set of expected resp.StatusCode if all goes well.
}{ }{
// Normal methods on pods // Normal methods on pods
{"GET", "/api/v1beta1/pods", ""}, {"GET", "/api/v1beta1/pods", "", code200or202},
{"GET", "/api/v1beta1/pods/a", ""}, {"POST", "/api/v1beta1/pods", aPod, code200or202},
{"POST", "/api/v1beta1/pods", aPod}, {"PUT", "/api/v1beta1/pods/a", aPod, code500}, // See #2114 about why 500
{"PUT", "/api/v1beta1/pods", aPod}, {"GET", "/api/v1beta1/pods", "", code200or202},
{"GET", "/api/v1beta1/pods", ""}, {"GET", "/api/v1beta1/pods/a", "", code200or202},
{"GET", "/api/v1beta1/pods/a", ""}, {"DELETE", "/api/v1beta1/pods/a", "", code200or202},
{"DELETE", "/api/v1beta1/pods", ""},
// Non-standard methods (not expected to work, // Non-standard methods (not expected to work,
// but expected to pass/fail authorization prior to // but expected to pass/fail authorization prior to
// failing validation. // failing validation.
{"PATCH", "/api/v1beta1/pods/a", ""}, {"PATCH", "/api/v1beta1/pods/a", "", code404},
{"OPTIONS", "/api/v1beta1/pods", ""}, {"OPTIONS", "/api/v1beta1/pods", "", code404},
{"OPTIONS", "/api/v1beta1/pods/a", ""}, {"OPTIONS", "/api/v1beta1/pods/a", "", code404},
{"HEAD", "/api/v1beta1/pods", ""}, {"HEAD", "/api/v1beta1/pods", "", code404},
{"HEAD", "/api/v1beta1/pods/a", ""}, {"HEAD", "/api/v1beta1/pods/a", "", code404},
{"TRACE", "/api/v1beta1/pods", ""}, {"TRACE", "/api/v1beta1/pods", "", code404},
{"TRACE", "/api/v1beta1/pods/a", ""}, {"TRACE", "/api/v1beta1/pods/a", "", code404},
{"NOSUCHVERB", "/api/v1beta1/pods", ""}, {"NOSUCHVERB", "/api/v1beta1/pods", "", code404},
// Normal methods on services // Normal methods on services
{"GET", "/api/v1beta1/services", ""}, {"GET", "/api/v1beta1/services", "", code200or202},
{"GET", "/api/v1beta1/services/a", ""}, {"POST", "/api/v1beta1/services", aService, code200or202},
{"POST", "/api/v1beta1/services", aService}, {"PUT", "/api/v1beta1/services/a", aService, code422}, // TODO: GET and put back server-provided fields to avoid a 422
{"PUT", "/api/v1beta1/services", aService}, {"GET", "/api/v1beta1/services", "", code200or202},
{"GET", "/api/v1beta1/services", ""}, {"GET", "/api/v1beta1/services/a", "", code200or202},
{"GET", "/api/v1beta1/services/a", ""}, {"DELETE", "/api/v1beta1/services/a", "", code200or202},
{"DELETE", "/api/v1beta1/services", ""},
// Normal methods on replicationControllers // Normal methods on replicationControllers
{"GET", "/api/v1beta1/replicationControllers", ""}, {"GET", "/api/v1beta1/replicationControllers", "", code200or202},
{"GET", "/api/v1beta1/replicationControllers/a", ""}, {"POST", "/api/v1beta1/replicationControllers", aRC, code200or202},
{"POST", "/api/v1beta1/replicationControllers", aRC}, {"PUT", "/api/v1beta1/replicationControllers/a", aRC, code409}, // See #2115 about why 409
{"PUT", "/api/v1beta1/replicationControllers", aRC}, {"GET", "/api/v1beta1/replicationControllers", "", code200or202},
{"GET", "/api/v1beta1/replicationControllers", ""}, {"GET", "/api/v1beta1/replicationControllers/a", "", code200or202},
{"GET", "/api/v1beta1/replicationControllers/a", ""}, {"DELETE", "/api/v1beta1/replicationControllers/a", "", code200or202},
{"DELETE", "/api/v1beta1/replicationControllers", ""},
// Normal methods on endpoints // Normal methods on endpoints
{"GET", "/api/v1beta1/endpoints", ""}, {"GET", "/api/v1beta1/endpoints", "", code200or202},
{"GET", "/api/v1beta1/endpoints/a", ""}, {"POST", "/api/v1beta1/endpoints", aEndpoints, code200or202},
{"POST", "/api/v1beta1/endpoints", aEndpoints}, {"PUT", "/api/v1beta1/endpoints/a", aEndpoints, code200or202},
{"PUT", "/api/v1beta1/endpoints", aEndpoints}, {"GET", "/api/v1beta1/endpoints", "", code200or202},
{"GET", "/api/v1beta1/endpoints", ""}, {"GET", "/api/v1beta1/endpoints/a", "", code200or202},
{"GET", "/api/v1beta1/endpoints/a", ""}, {"DELETE", "/api/v1beta1/endpoints/a", "", code500}, // Issue #2113.
{"DELETE", "/api/v1beta1/endpoints", ""},
// Normal methods on minions // Normal methods on minions
{"GET", "/api/v1beta1/minions", ""}, {"GET", "/api/v1beta1/minions", "", code200or202},
{"GET", "/api/v1beta1/minions/a", ""}, {"POST", "/api/v1beta1/minions", aMinion, code200or202},
{"POST", "/api/v1beta1/minions", aMinion}, {"PUT", "/api/v1beta1/minions/a", aMinion, code500}, // See #2114 about why 500
{"PUT", "/api/v1beta1/minions", aMinion}, {"GET", "/api/v1beta1/minions", "", code200or202},
{"GET", "/api/v1beta1/minions", ""}, {"GET", "/api/v1beta1/minions/a", "", code200or202},
{"GET", "/api/v1beta1/minions/a", ""}, {"DELETE", "/api/v1beta1/minions/a", "", code200or202},
{"DELETE", "/api/v1beta1/minions", ""},
// Normal methods on events // Normal methods on events
{"GET", "/api/v1beta1/events", ""}, {"GET", "/api/v1beta1/events", "", code200or202},
{"GET", "/api/v1beta1/events/a", ""}, {"POST", "/api/v1beta1/events", aEvent, code200or202},
{"POST", "/api/v1beta1/events", aEvent}, {"PUT", "/api/v1beta1/events/a", aEvent, code500}, // See #2114 about why 500
{"PUT", "/api/v1beta1/events", aEvent}, {"GET", "/api/v1beta1/events", "", code200or202},
{"GET", "/api/v1beta1/events", ""}, {"GET", "/api/v1beta1/events", "", code200or202},
{"GET", "/api/v1beta1/events/a", ""}, {"GET", "/api/v1beta1/events/a", "", code200or202},
{"DELETE", "/api/v1beta1/events", ""}, {"DELETE", "/api/v1beta1/events/a", "", code200or202},
// Normal methods on bindings // Normal methods on bindings
{"GET", "/api/v1beta1/events", ""}, {"GET", "/api/v1beta1/bindings", "", code404}, // Bindings are write-only, so 404
{"GET", "/api/v1beta1/events/a", ""}, {"POST", "/api/v1beta1/pods", aPod, code200or202}, // Need a pod to bind or you get a 404
{"POST", "/api/v1beta1/events", aBinding}, {"POST", "/api/v1beta1/bindings", aBinding, code200or202},
{"PUT", "/api/v1beta1/events", aBinding}, {"PUT", "/api/v1beta1/bindings/a", aBinding, code500}, // See #2114 about why 500
{"GET", "/api/v1beta1/events", ""}, {"GET", "/api/v1beta1/bindings", "", code404},
{"GET", "/api/v1beta1/events/a", ""}, {"GET", "/api/v1beta1/bindings/a", "", code404},
{"DELETE", "/api/v1beta1/events", ""}, {"DELETE", "/api/v1beta1/bindings/a", "", code404},
// Non-existent object type. // Non-existent object type.
{"GET", "/api/v1beta1/foo", ""}, {"GET", "/api/v1beta1/foo", "", code404},
{"GET", "/api/v1beta1/foo/a", ""}, {"POST", "/api/v1beta1/foo", `{"foo": "foo"}`, code404},
{"POST", "/api/v1beta1/foo", `{"foo": "foo"}`}, {"PUT", "/api/v1beta1/foo/a", `{"foo": "foo"}`, code404},
{"PUT", "/api/v1beta1/foo", `{"foo": "foo"}`}, {"GET", "/api/v1beta1/foo", "", code404},
{"GET", "/api/v1beta1/foo", ""}, {"GET", "/api/v1beta1/foo/a", "", code404},
{"GET", "/api/v1beta1/foo/a", ""}, {"DELETE", "/api/v1beta1/foo", "", code404},
{"DELETE", "/api/v1beta1/foo", ""},
// Operations // Operations
{"GET", "/api/v1beta1/operations", ""}, {"GET", "/api/v1beta1/operations", "", code200or202},
{"GET", "/api/v1beta1/operations/1234567890", ""}, {"GET", "/api/v1beta1/operations/1234567890", "", code404},
// Special verbs on pods // Special verbs on pods
{"GET", "/api/v1beta1/proxy/pods/a", ""}, {"GET", "/api/v1beta1/proxy/minions/a", "", code404},
{"GET", "/api/v1beta1/redirect/pods/a", ""}, {"GET", "/api/v1beta1/redirect/minions/a", "", code404},
// TODO: test .../watch/..., which doesn't end before the test timeout. // TODO: test .../watch/..., which doesn't end before the test timeout.
// TODO: figure out how to create a minion so that it can successfully proxy/redirect.
// Non-object endpoints // Non-object endpoints
{"GET", "/", ""}, {"GET", "/", "", code200or202},
{"GET", "/healthz", ""}, {"GET", "/healthz", "", code200or202},
{"GET", "/versions", ""}, {"GET", "/version", "", code200or202},
} }
return requests return requests
} }
@ -361,12 +380,17 @@ func TestAuthModeAlwaysAllow(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
{
resp, err := transport.RoundTrip(req) resp, err := transport.RoundTrip(req)
defer resp.Body.Close()
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
if resp.StatusCode == http.StatusForbidden { if _, ok := r.statusCodes[resp.StatusCode]; !ok {
t.Errorf("Expected status other than Forbidden") 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))
}
} }
} }
} }
@ -400,8 +424,9 @@ func TestAuthModeAlwaysDeny(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
{
resp, err := transport.RoundTrip(req) resp, err := transport.RoundTrip(req)
defer resp.Body.Close()
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
@ -409,4 +434,5 @@ func TestAuthModeAlwaysDeny(t *testing.T) {
t.Errorf("Expected status Forbidden but got status %v", resp.Status) t.Errorf("Expected status Forbidden but got status %v", resp.Status)
} }
} }
}
} }