mirror of https://github.com/k3s-io/k3s
commit
e4dcd4a131
|
@ -145,6 +145,12 @@ func main() {
|
|||
}
|
||||
|
||||
n := net.IPNet(portalNet)
|
||||
|
||||
authorizer, err := apiserver.NewAuthorizerFromAuthorizationConfig(*authorizationMode)
|
||||
if err != nil {
|
||||
glog.Fatalf("Invalid Authorization Config: %v", err)
|
||||
}
|
||||
|
||||
config := &master.Config{
|
||||
Client: client,
|
||||
Cloud: cloud,
|
||||
|
@ -161,7 +167,7 @@ func main() {
|
|||
ReadOnlyPort: *readOnlyPort,
|
||||
ReadWritePort: *port,
|
||||
PublicAddress: *publicAddressOverride,
|
||||
AuthorizationMode: *authorizationMode,
|
||||
Authorizer: authorizer,
|
||||
}
|
||||
m := master.New(config)
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ import (
|
|||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||
minionControllerPkg "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/controller"
|
||||
replicationControllerPkg "github.com/GoogleCloudPlatform/kubernetes/pkg/controller"
|
||||
|
@ -146,7 +147,7 @@ func startComponents(manifestURL string) (apiServerURL string) {
|
|||
KubeletClient: fakeKubeletClient{},
|
||||
EnableLogsSupport: false,
|
||||
APIPrefix: "/api",
|
||||
AuthorizationMode: "AlwaysAllow",
|
||||
Authorizer: apiserver.NewAlwaysAllowAuthorizer(),
|
||||
|
||||
ReadWritePort: portNumber,
|
||||
ReadOnlyPort: portNumber,
|
||||
|
|
|
@ -107,6 +107,7 @@ func (g *APIGroup) InstallREST(mux Mux, paths ...string) {
|
|||
prefix = strings.TrimRight(prefix, "/")
|
||||
proxyHandler := &ProxyHandler{prefix + "/proxy/", g.handler.storage, g.handler.codec}
|
||||
mux.Handle(prefix+"/", http.StripPrefix(prefix, restHandler))
|
||||
// Note: update GetAttribs() when adding a handler.
|
||||
mux.Handle(prefix+"/watch/", http.StripPrefix(prefix+"/watch/", watchHandler))
|
||||
mux.Handle(prefix+"/proxy/", http.StripPrefix(prefix+"/proxy/", proxyHandler))
|
||||
mux.Handle(prefix+"/redirect/", http.StripPrefix(prefix+"/redirect/", redirectHandler))
|
||||
|
|
|
@ -36,6 +36,10 @@ func (alwaysAllowAuthorizer) Authorize(a authorizer.Attributes) (err error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
func NewAlwaysAllowAuthorizer() authorizer.Authorizer {
|
||||
return new(alwaysAllowAuthorizer)
|
||||
}
|
||||
|
||||
// alwaysDenyAuthorizer is an implementation of authorizer.Attributes
|
||||
// which always says no to an authorization request.
|
||||
// It is useful in unit tests to force an operation to be forbidden.
|
||||
|
@ -45,6 +49,10 @@ func (alwaysDenyAuthorizer) Authorize(a authorizer.Attributes) (err error) {
|
|||
return errors.New("Everything is forbidden.")
|
||||
}
|
||||
|
||||
func NewAlwaysDenyAuthorizer() authorizer.Authorizer {
|
||||
return new(alwaysDenyAuthorizer)
|
||||
}
|
||||
|
||||
const (
|
||||
ModeAlwaysAllow string = "AlwaysAllow"
|
||||
ModeAlwaysDeny string = "AlwaysDeny"
|
||||
|
@ -59,9 +67,9 @@ func NewAuthorizerFromAuthorizationConfig(authorizationMode string) (authorizer.
|
|||
// Keep cases in sync with constant list above.
|
||||
switch authorizationMode {
|
||||
case ModeAlwaysAllow:
|
||||
return new(alwaysAllowAuthorizer), nil
|
||||
return NewAlwaysAllowAuthorizer(), nil
|
||||
case ModeAlwaysDeny:
|
||||
return new(alwaysDenyAuthorizer), nil
|
||||
return NewAlwaysDenyAuthorizer(), nil
|
||||
default:
|
||||
return nil, errors.New("Unknown authorization mode")
|
||||
}
|
||||
|
|
|
@ -30,10 +30,49 @@ import (
|
|||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
// specialVerbs contains just strings which are used in REST paths for special actions that don't fall under the normal
|
||||
// CRUDdy GET/POST/PUT/DELETE actions on REST objects.
|
||||
// TODO: find a way to keep this up to date automatically. Maybe dynamically populate list as handlers added to
|
||||
// master's Mux.
|
||||
var specialVerbs = map[string]bool{
|
||||
"proxy": true,
|
||||
"redirect": true,
|
||||
"watch": true,
|
||||
}
|
||||
|
||||
// KindFromRequest returns Kind if Kind can be extracted from the request. Otherwise, the empty string.
|
||||
func KindFromRequest(req http.Request) string {
|
||||
// TODO: find a way to keep this code's assumptions about paths up to date with changes in the code. Maybe instead
|
||||
// of directly adding handler's code to the master's Mux, have a function which forces the structure when adding
|
||||
// them.
|
||||
parts := splitPath(req.URL.Path)
|
||||
if len(parts) > 2 && parts[0] == "api" {
|
||||
if _, ok := specialVerbs[parts[2]]; ok {
|
||||
if len(parts) > 3 {
|
||||
return parts[3]
|
||||
}
|
||||
} else {
|
||||
return parts[2]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// IsReadOnlyReq() is true for any (or at least many) request which has no observable
|
||||
// side effects on state of apiserver (though there may be internal side effects like
|
||||
// caching and logging).
|
||||
func IsReadOnlyReq(req http.Request) bool {
|
||||
if req.Method == "GET" {
|
||||
// TODO: add OPTIONS and HEAD if we ever support those.
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ReadOnly passes all GET requests on to handler, and returns an error on all other requests.
|
||||
func ReadOnly(handler http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
if req.Method == "GET" {
|
||||
if IsReadOnlyReq(*req) {
|
||||
handler.ServeHTTP(w, req)
|
||||
return
|
||||
}
|
||||
|
@ -143,6 +182,17 @@ func (r *requestAttributeGetter) GetAttribs(req *http.Request) authorizer.Attrib
|
|||
attribs.User = user
|
||||
}
|
||||
|
||||
attribs.ReadOnly = IsReadOnlyReq(*req)
|
||||
|
||||
// If a path follows the conventions of the REST object store, then
|
||||
// we can extract the object Kind. Otherwise, not.
|
||||
attribs.Kind = KindFromRequest(*req)
|
||||
|
||||
// If the request specifies a namespace, then the namespace is filled in.
|
||||
// Assumes there is no empty string namespace. Unspecified results
|
||||
// in empty (does not understand defaulting rules.)
|
||||
attribs.Namespace = req.URL.Query().Get("namespace")
|
||||
|
||||
return &attribs
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,20 @@ import (
|
|||
// Attributes is an interface used by an Authorizer to get information about a request
|
||||
// that is used to make an authorization decision.
|
||||
type Attributes interface {
|
||||
// The user string which the request was authenticated as, or empty if
|
||||
// no authentication occured and the request was allowed to proceed.
|
||||
GetUserName() string
|
||||
// TODO: add groups, e.g. GetGroups() []string
|
||||
|
||||
// When IsReadOnly() == true, the request has no side effects, other than
|
||||
// caching, logging, and other incidentals.
|
||||
IsReadOnly() bool
|
||||
|
||||
// The namespace of the object, if a request is for a REST object.
|
||||
GetNamespace() string
|
||||
|
||||
// The kind of object, if a request is for a REST object.
|
||||
GetKind() string
|
||||
}
|
||||
|
||||
// Authorizer makes an authorization decision based on information gained by making
|
||||
|
@ -35,9 +48,24 @@ type Authorizer interface {
|
|||
|
||||
// AttributesRecord implements Attributes interface.
|
||||
type AttributesRecord struct {
|
||||
User user.Info
|
||||
User user.Info
|
||||
ReadOnly bool
|
||||
Namespace string
|
||||
Kind string
|
||||
}
|
||||
|
||||
func (a *AttributesRecord) GetUserName() string {
|
||||
return a.User.GetName()
|
||||
}
|
||||
|
||||
func (a *AttributesRecord) IsReadOnly() bool {
|
||||
return a.ReadOnly
|
||||
}
|
||||
|
||||
func (a *AttributesRecord) GetNamespace() string {
|
||||
return a.Namespace
|
||||
}
|
||||
|
||||
func (a *AttributesRecord) GetKind() string {
|
||||
return a.Kind
|
||||
}
|
||||
|
|
|
@ -67,8 +67,7 @@ type Config struct {
|
|||
APIPrefix string
|
||||
CorsAllowedOriginList util.StringList
|
||||
TokenAuthFile string
|
||||
AuthorizationMode string
|
||||
AuthorizerForTesting authorizer.Authorizer
|
||||
Authorizer authorizer.Authorizer
|
||||
|
||||
// Number of masters running; all masters must be started with the
|
||||
// same value for this field. (Numbers > 1 currently untested.)
|
||||
|
@ -104,7 +103,7 @@ type Master struct {
|
|||
apiPrefix string
|
||||
corsAllowedOriginList util.StringList
|
||||
tokenAuthFile string
|
||||
authorizationzMode string
|
||||
authorizer authorizer.Authorizer
|
||||
masterCount int
|
||||
|
||||
// "Outputs"
|
||||
|
@ -227,7 +226,7 @@ func New(c *Config) *Master {
|
|||
apiPrefix: c.APIPrefix,
|
||||
corsAllowedOriginList: c.CorsAllowedOriginList,
|
||||
tokenAuthFile: c.TokenAuthFile,
|
||||
authorizationzMode: c.AuthorizationMode,
|
||||
authorizer: c.Authorizer,
|
||||
|
||||
masterCount: c.MasterCount,
|
||||
readOnlyServer: net.JoinHostPort(c.PublicAddress, strconv.Itoa(int(c.ReadOnlyPort))),
|
||||
|
@ -319,19 +318,8 @@ func (m *Master) init(c *Config) {
|
|||
handler = apiserver.CORS(handler, allowedOriginRegexps, nil, nil, "true")
|
||||
}
|
||||
|
||||
// Install Authorizer
|
||||
var authorizer authorizer.Authorizer
|
||||
if c.AuthorizerForTesting != nil {
|
||||
authorizer = c.AuthorizerForTesting
|
||||
} else {
|
||||
var err error
|
||||
authorizer, err = apiserver.NewAuthorizerFromAuthorizationConfig(m.authorizationzMode)
|
||||
if err != nil {
|
||||
glog.Fatal(err)
|
||||
}
|
||||
}
|
||||
attributeGetter := apiserver.NewRequestAttributeGetter(userContexts)
|
||||
handler = apiserver.WithAuthorizationCheck(handler, attributeGetter, authorizer)
|
||||
handler = apiserver.WithAuthorizationCheck(handler, attributeGetter, m.authorizer)
|
||||
|
||||
// Install Authenticator
|
||||
if authenticator != nil {
|
||||
|
|
|
@ -32,6 +32,7 @@ import (
|
|||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authorizer"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/master"
|
||||
|
@ -88,7 +89,7 @@ func TestWhoAmI(t *testing.T) {
|
|||
EnableUISupport: false,
|
||||
APIPrefix: "/api",
|
||||
TokenAuthFile: tokenFilename,
|
||||
AuthorizationMode: "AlwaysAllow",
|
||||
Authorizer: apiserver.NewAlwaysAllowAuthorizer(),
|
||||
})
|
||||
|
||||
s := httptest.NewServer(m.Handler)
|
||||
|
@ -237,6 +238,7 @@ var aEndpoints string = `
|
|||
|
||||
var code200or202 = map[int]bool{200: true, 202: true} // Unpredicatable which will be returned.
|
||||
var code400 = map[int]bool{400: true}
|
||||
var code403 = map[int]bool{403: true}
|
||||
var code404 = map[int]bool{404: true}
|
||||
var code409 = map[int]bool{409: true}
|
||||
var code422 = map[int]bool{422: true}
|
||||
|
@ -372,7 +374,7 @@ func TestAuthModeAlwaysAllow(t *testing.T) {
|
|||
EnableLogsSupport: false,
|
||||
EnableUISupport: false,
|
||||
APIPrefix: "/api",
|
||||
AuthorizationMode: "AlwaysAllow",
|
||||
Authorizer: apiserver.NewAlwaysAllowAuthorizer(),
|
||||
})
|
||||
|
||||
s := httptest.NewServer(m.Handler)
|
||||
|
@ -417,7 +419,7 @@ func TestAuthModeAlwaysDeny(t *testing.T) {
|
|||
EnableLogsSupport: false,
|
||||
EnableUISupport: false,
|
||||
APIPrefix: "/api",
|
||||
AuthorizationMode: "AlwaysDeny",
|
||||
Authorizer: apiserver.NewAlwaysDenyAuthorizer(),
|
||||
})
|
||||
|
||||
s := httptest.NewServer(m.Handler)
|
||||
|
@ -465,8 +467,6 @@ func TestAliceNotForbiddenOrUnauthorized(t *testing.T) {
|
|||
defer os.Remove(tokenFilename)
|
||||
// This file has alice and bob in it.
|
||||
|
||||
aaa := allowAliceAuthorizer{}
|
||||
|
||||
// Set up a master
|
||||
|
||||
helper, err := master.NewEtcdHelper(newEtcdClient(), "v1beta1")
|
||||
|
@ -475,22 +475,19 @@ func TestAliceNotForbiddenOrUnauthorized(t *testing.T) {
|
|||
}
|
||||
|
||||
m := master.New(&master.Config{
|
||||
EtcdHelper: helper,
|
||||
KubeletClient: client.FakeKubeletClient{},
|
||||
EnableLogsSupport: false,
|
||||
EnableUISupport: false,
|
||||
APIPrefix: "/api",
|
||||
TokenAuthFile: tokenFilename,
|
||||
AuthorizerForTesting: aaa,
|
||||
EtcdHelper: helper,
|
||||
KubeletClient: client.FakeKubeletClient{},
|
||||
EnableLogsSupport: false,
|
||||
EnableUISupport: false,
|
||||
APIPrefix: "/api",
|
||||
TokenAuthFile: tokenFilename,
|
||||
Authorizer: allowAliceAuthorizer{},
|
||||
})
|
||||
|
||||
s := httptest.NewServer(m.Handler)
|
||||
defer s.Close()
|
||||
transport := http.DefaultTransport
|
||||
|
||||
// Alice is authorized.
|
||||
|
||||
//
|
||||
for _, r := range getTestRequests() {
|
||||
token := AliceToken
|
||||
t.Logf("case %v", r)
|
||||
|
@ -524,8 +521,6 @@ func TestBobIsForbidden(t *testing.T) {
|
|||
defer os.Remove(tokenFilename)
|
||||
// This file has alice and bob in it.
|
||||
|
||||
aaa := allowAliceAuthorizer{}
|
||||
|
||||
// Set up a master
|
||||
|
||||
helper, err := master.NewEtcdHelper(newEtcdClient(), "v1beta1")
|
||||
|
@ -534,22 +529,19 @@ func TestBobIsForbidden(t *testing.T) {
|
|||
}
|
||||
|
||||
m := master.New(&master.Config{
|
||||
EtcdHelper: helper,
|
||||
KubeletClient: client.FakeKubeletClient{},
|
||||
EnableLogsSupport: false,
|
||||
EnableUISupport: false,
|
||||
APIPrefix: "/api",
|
||||
TokenAuthFile: tokenFilename,
|
||||
AuthorizerForTesting: aaa,
|
||||
EtcdHelper: helper,
|
||||
KubeletClient: client.FakeKubeletClient{},
|
||||
EnableLogsSupport: false,
|
||||
EnableUISupport: false,
|
||||
APIPrefix: "/api",
|
||||
TokenAuthFile: tokenFilename,
|
||||
Authorizer: allowAliceAuthorizer{},
|
||||
})
|
||||
|
||||
s := httptest.NewServer(m.Handler)
|
||||
defer s.Close()
|
||||
transport := http.DefaultTransport
|
||||
|
||||
// Alice is authorized.
|
||||
|
||||
//
|
||||
for _, r := range getTestRequests() {
|
||||
token := BobToken
|
||||
t.Logf("case %v", r)
|
||||
|
@ -585,8 +577,6 @@ func TestUnknownUserIsUnauthorized(t *testing.T) {
|
|||
defer os.Remove(tokenFilename)
|
||||
// This file has alice and bob in it.
|
||||
|
||||
aaa := allowAliceAuthorizer{}
|
||||
|
||||
// Set up a master
|
||||
|
||||
helper, err := master.NewEtcdHelper(newEtcdClient(), "v1beta1")
|
||||
|
@ -595,13 +585,13 @@ func TestUnknownUserIsUnauthorized(t *testing.T) {
|
|||
}
|
||||
|
||||
m := master.New(&master.Config{
|
||||
EtcdHelper: helper,
|
||||
KubeletClient: client.FakeKubeletClient{},
|
||||
EnableLogsSupport: false,
|
||||
EnableUISupport: false,
|
||||
APIPrefix: "/api",
|
||||
TokenAuthFile: tokenFilename,
|
||||
AuthorizerForTesting: aaa,
|
||||
EtcdHelper: helper,
|
||||
KubeletClient: client.FakeKubeletClient{},
|
||||
EnableLogsSupport: false,
|
||||
EnableUISupport: false,
|
||||
APIPrefix: "/api",
|
||||
TokenAuthFile: tokenFilename,
|
||||
Authorizer: allowAliceAuthorizer{},
|
||||
})
|
||||
|
||||
s := httptest.NewServer(m.Handler)
|
||||
|
@ -625,7 +615,248 @@ func TestUnknownUserIsUnauthorized(t *testing.T) {
|
|||
}
|
||||
// Expect all of unauthenticated user's request to be "Unauthorized"
|
||||
if resp.StatusCode != http.StatusUnauthorized {
|
||||
t.Errorf("Expected status Unauthorized, but got %s", resp.Status)
|
||||
t.Errorf("Expected status %v, but got %v", http.StatusUnauthorized, resp.StatusCode)
|
||||
b, _ := ioutil.ReadAll(resp.Body)
|
||||
t.Errorf("Body: %v", string(b))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Inject into master an authorizer that uses namespace information.
|
||||
// TODO(etune): remove this test once a more comprehensive built-in authorizer is implemented.
|
||||
type allowFooNamespaceAuthorizer struct{}
|
||||
|
||||
func (allowFooNamespaceAuthorizer) Authorize(a authorizer.Attributes) error {
|
||||
if a.GetNamespace() == "foo" {
|
||||
return nil
|
||||
}
|
||||
return errors.New("I can't allow that. Try another namespace, buddy.")
|
||||
}
|
||||
|
||||
// TestNamespaceAuthorization tests that authorization can be controlled
|
||||
// by namespace.
|
||||
func TestNamespaceAuthorization(t *testing.T) {
|
||||
deleteAllEtcdKeys()
|
||||
|
||||
tokenFilename := writeTestTokenFile()
|
||||
defer os.Remove(tokenFilename)
|
||||
// This file has alice and bob in it.
|
||||
|
||||
// Set up a master
|
||||
|
||||
helper, err := master.NewEtcdHelper(newEtcdClient(), "v1beta1")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
m := master.New(&master.Config{
|
||||
EtcdHelper: helper,
|
||||
KubeletClient: client.FakeKubeletClient{},
|
||||
EnableLogsSupport: false,
|
||||
EnableUISupport: false,
|
||||
APIPrefix: "/api",
|
||||
TokenAuthFile: tokenFilename,
|
||||
Authorizer: allowFooNamespaceAuthorizer{},
|
||||
})
|
||||
|
||||
s := httptest.NewServer(m.Handler)
|
||||
defer s.Close()
|
||||
transport := http.DefaultTransport
|
||||
|
||||
requests := []struct {
|
||||
verb string
|
||||
URL string
|
||||
body string
|
||||
statusCodes map[int]bool // allowed status codes.
|
||||
}{
|
||||
{"POST", "/api/v1beta1/pods?namespace=foo", aPod, code200or202},
|
||||
{"GET", "/api/v1beta1/pods?namespace=foo", "", code200or202},
|
||||
{"GET", "/api/v1beta1/pods/a?namespace=foo", "", code200or202},
|
||||
{"DELETE", "/api/v1beta1/pods/a?namespace=foo", "", code200or202},
|
||||
|
||||
{"POST", "/api/v1beta1/pods?namespace=bar", aPod, code403},
|
||||
{"GET", "/api/v1beta1/pods?namespace=bar", "", code403},
|
||||
{"GET", "/api/v1beta1/pods/a?namespace=bar", "", code403},
|
||||
{"DELETE", "/api/v1beta1/pods/a?namespace=bar", "", code403},
|
||||
|
||||
{"POST", "/api/v1beta1/pods", aPod, code403},
|
||||
{"GET", "/api/v1beta1/pods", "", code403},
|
||||
{"GET", "/api/v1beta1/pods/a", "", code403},
|
||||
{"DELETE", "/api/v1beta1/pods/a", "", code403},
|
||||
}
|
||||
|
||||
for _, r := range requests {
|
||||
token := BobToken
|
||||
t.Logf("case %v", r)
|
||||
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))
|
||||
{
|
||||
resp, err := transport.RoundTrip(req)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if _, ok := r.statusCodes[resp.StatusCode]; !ok {
|
||||
t.Errorf("Expected status one of %v, but got %v", r.statusCodes, resp.StatusCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Inject into master an authorizer that uses kind information.
|
||||
// TODO(etune): remove this test once a more comprehensive built-in authorizer is implemented.
|
||||
type allowServicesAuthorizer struct{}
|
||||
|
||||
func (allowServicesAuthorizer) Authorize(a authorizer.Attributes) error {
|
||||
if a.GetKind() == "services" {
|
||||
return nil
|
||||
}
|
||||
return errors.New("I can't allow that. Hint: try services.")
|
||||
}
|
||||
|
||||
// TestKindAuthorization tests that authorization can be controlled
|
||||
// by namespace.
|
||||
func TestKindAuthorization(t *testing.T) {
|
||||
deleteAllEtcdKeys()
|
||||
|
||||
tokenFilename := writeTestTokenFile()
|
||||
defer os.Remove(tokenFilename)
|
||||
// This file has alice and bob in it.
|
||||
|
||||
// Set up a master
|
||||
|
||||
helper, err := master.NewEtcdHelper(newEtcdClient(), "v1beta1")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
m := master.New(&master.Config{
|
||||
EtcdHelper: helper,
|
||||
KubeletClient: client.FakeKubeletClient{},
|
||||
EnableLogsSupport: false,
|
||||
EnableUISupport: false,
|
||||
APIPrefix: "/api",
|
||||
TokenAuthFile: tokenFilename,
|
||||
Authorizer: allowServicesAuthorizer{},
|
||||
})
|
||||
|
||||
s := httptest.NewServer(m.Handler)
|
||||
defer s.Close()
|
||||
transport := http.DefaultTransport
|
||||
|
||||
requests := []struct {
|
||||
verb string
|
||||
URL string
|
||||
body string
|
||||
statusCodes map[int]bool // allowed status codes.
|
||||
}{
|
||||
{"POST", "/api/v1beta1/services", aService, code200or202},
|
||||
{"GET", "/api/v1beta1/services", "", code200or202},
|
||||
{"GET", "/api/v1beta1/services/a", "", code200or202},
|
||||
{"DELETE", "/api/v1beta1/services/a", "", code200or202},
|
||||
|
||||
{"POST", "/api/v1beta1/pods", aPod, code403},
|
||||
{"GET", "/api/v1beta1/pods", "", code403},
|
||||
{"GET", "/api/v1beta1/pods/a", "", code403},
|
||||
{"DELETE", "/api/v1beta1/pods/a", "", code403},
|
||||
}
|
||||
|
||||
for _, r := range requests {
|
||||
token := BobToken
|
||||
t.Logf("case %v", r)
|
||||
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))
|
||||
{
|
||||
resp, err := transport.RoundTrip(req)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if _, ok := r.statusCodes[resp.StatusCode]; !ok {
|
||||
t.Errorf("Expected status one of %v, but got %v", r.statusCodes, resp.StatusCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Inject into master an authorizer that uses ReadOnly information.
|
||||
// TODO(etune): remove this test once a more comprehensive built-in authorizer is implemented.
|
||||
type allowReadAuthorizer struct{}
|
||||
|
||||
func (allowReadAuthorizer) Authorize(a authorizer.Attributes) error {
|
||||
if a.IsReadOnly() {
|
||||
return nil
|
||||
}
|
||||
return errors.New("I'm afraid I can't let you do that.")
|
||||
}
|
||||
|
||||
// TestReadOnlyAuthorization tests that authorization can be controlled
|
||||
// by namespace.
|
||||
func TestReadOnlyAuthorization(t *testing.T) {
|
||||
deleteAllEtcdKeys()
|
||||
|
||||
tokenFilename := writeTestTokenFile()
|
||||
defer os.Remove(tokenFilename)
|
||||
// This file has alice and bob in it.
|
||||
|
||||
// Set up a master
|
||||
|
||||
helper, err := master.NewEtcdHelper(newEtcdClient(), "v1beta1")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
m := master.New(&master.Config{
|
||||
EtcdHelper: helper,
|
||||
KubeletClient: client.FakeKubeletClient{},
|
||||
EnableLogsSupport: false,
|
||||
EnableUISupport: false,
|
||||
APIPrefix: "/api",
|
||||
TokenAuthFile: tokenFilename,
|
||||
Authorizer: allowReadAuthorizer{},
|
||||
})
|
||||
|
||||
s := httptest.NewServer(m.Handler)
|
||||
defer s.Close()
|
||||
transport := http.DefaultTransport
|
||||
|
||||
requests := []struct {
|
||||
verb string
|
||||
URL string
|
||||
body string
|
||||
statusCodes map[int]bool // allowed status codes.
|
||||
}{
|
||||
{"POST", "/api/v1beta1/pods", aPod, code403},
|
||||
{"GET", "/api/v1beta1/pods", "", code200or202},
|
||||
{"GET", "/api/v1beta1/pods/a", "", code404},
|
||||
}
|
||||
|
||||
for _, r := range requests {
|
||||
token := BobToken
|
||||
t.Logf("case %v", r)
|
||||
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))
|
||||
{
|
||||
resp, err := transport.RoundTrip(req)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if _, ok := r.statusCodes[resp.StatusCode]; !ok {
|
||||
t.Errorf("Expected status one of %v, but got %v", r.statusCodes, resp.StatusCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/master"
|
||||
|
@ -45,7 +46,7 @@ func TestClient(t *testing.T) {
|
|||
EnableLogsSupport: false,
|
||||
EnableUISupport: false,
|
||||
APIPrefix: "/api",
|
||||
AuthorizationMode: "AlwaysAllow",
|
||||
Authorizer: apiserver.NewAlwaysAllowAuthorizer(),
|
||||
})
|
||||
|
||||
s := httptest.NewServer(m.Handler)
|
||||
|
|
Loading…
Reference in New Issue