mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-01 07:47:56 +00:00
Merge pull request #2122 from erictune/moar_attribs
Moar authorization attributes
This commit is contained in:
commit
e4dcd4a131
@ -145,6 +145,12 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
n := net.IPNet(portalNet)
|
n := net.IPNet(portalNet)
|
||||||
|
|
||||||
|
authorizer, err := apiserver.NewAuthorizerFromAuthorizationConfig(*authorizationMode)
|
||||||
|
if err != nil {
|
||||||
|
glog.Fatalf("Invalid Authorization Config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
config := &master.Config{
|
config := &master.Config{
|
||||||
Client: client,
|
Client: client,
|
||||||
Cloud: cloud,
|
Cloud: cloud,
|
||||||
@ -161,7 +167,7 @@ func main() {
|
|||||||
ReadOnlyPort: *readOnlyPort,
|
ReadOnlyPort: *readOnlyPort,
|
||||||
ReadWritePort: *port,
|
ReadWritePort: *port,
|
||||||
PublicAddress: *publicAddressOverride,
|
PublicAddress: *publicAddressOverride,
|
||||||
AuthorizationMode: *authorizationMode,
|
Authorizer: authorizer,
|
||||||
}
|
}
|
||||||
m := master.New(config)
|
m := master.New(config)
|
||||||
|
|
||||||
|
@ -36,6 +36,7 @@ import (
|
|||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||||
minionControllerPkg "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/controller"
|
minionControllerPkg "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/controller"
|
||||||
replicationControllerPkg "github.com/GoogleCloudPlatform/kubernetes/pkg/controller"
|
replicationControllerPkg "github.com/GoogleCloudPlatform/kubernetes/pkg/controller"
|
||||||
@ -146,7 +147,7 @@ func startComponents(manifestURL string) (apiServerURL string) {
|
|||||||
KubeletClient: fakeKubeletClient{},
|
KubeletClient: fakeKubeletClient{},
|
||||||
EnableLogsSupport: false,
|
EnableLogsSupport: false,
|
||||||
APIPrefix: "/api",
|
APIPrefix: "/api",
|
||||||
AuthorizationMode: "AlwaysAllow",
|
Authorizer: apiserver.NewAlwaysAllowAuthorizer(),
|
||||||
|
|
||||||
ReadWritePort: portNumber,
|
ReadWritePort: portNumber,
|
||||||
ReadOnlyPort: portNumber,
|
ReadOnlyPort: portNumber,
|
||||||
|
@ -107,6 +107,7 @@ func (g *APIGroup) InstallREST(mux Mux, paths ...string) {
|
|||||||
prefix = strings.TrimRight(prefix, "/")
|
prefix = strings.TrimRight(prefix, "/")
|
||||||
proxyHandler := &ProxyHandler{prefix + "/proxy/", g.handler.storage, g.handler.codec}
|
proxyHandler := &ProxyHandler{prefix + "/proxy/", g.handler.storage, g.handler.codec}
|
||||||
mux.Handle(prefix+"/", http.StripPrefix(prefix, restHandler))
|
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+"/watch/", http.StripPrefix(prefix+"/watch/", watchHandler))
|
||||||
mux.Handle(prefix+"/proxy/", http.StripPrefix(prefix+"/proxy/", proxyHandler))
|
mux.Handle(prefix+"/proxy/", http.StripPrefix(prefix+"/proxy/", proxyHandler))
|
||||||
mux.Handle(prefix+"/redirect/", http.StripPrefix(prefix+"/redirect/", redirectHandler))
|
mux.Handle(prefix+"/redirect/", http.StripPrefix(prefix+"/redirect/", redirectHandler))
|
||||||
|
@ -36,6 +36,10 @@ func (alwaysAllowAuthorizer) Authorize(a authorizer.Attributes) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewAlwaysAllowAuthorizer() authorizer.Authorizer {
|
||||||
|
return new(alwaysAllowAuthorizer)
|
||||||
|
}
|
||||||
|
|
||||||
// alwaysDenyAuthorizer is an implementation of authorizer.Attributes
|
// alwaysDenyAuthorizer is an implementation of authorizer.Attributes
|
||||||
// which always says no to an authorization request.
|
// which always says no to an authorization request.
|
||||||
// It is useful in unit tests to force an operation to be forbidden.
|
// 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.")
|
return errors.New("Everything is forbidden.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewAlwaysDenyAuthorizer() authorizer.Authorizer {
|
||||||
|
return new(alwaysDenyAuthorizer)
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ModeAlwaysAllow string = "AlwaysAllow"
|
ModeAlwaysAllow string = "AlwaysAllow"
|
||||||
ModeAlwaysDeny string = "AlwaysDeny"
|
ModeAlwaysDeny string = "AlwaysDeny"
|
||||||
@ -59,9 +67,9 @@ func NewAuthorizerFromAuthorizationConfig(authorizationMode string) (authorizer.
|
|||||||
// Keep cases in sync with constant list above.
|
// Keep cases in sync with constant list above.
|
||||||
switch authorizationMode {
|
switch authorizationMode {
|
||||||
case ModeAlwaysAllow:
|
case ModeAlwaysAllow:
|
||||||
return new(alwaysAllowAuthorizer), nil
|
return NewAlwaysAllowAuthorizer(), nil
|
||||||
case ModeAlwaysDeny:
|
case ModeAlwaysDeny:
|
||||||
return new(alwaysDenyAuthorizer), nil
|
return NewAlwaysDenyAuthorizer(), nil
|
||||||
default:
|
default:
|
||||||
return nil, errors.New("Unknown authorization mode")
|
return nil, errors.New("Unknown authorization mode")
|
||||||
}
|
}
|
||||||
|
@ -30,10 +30,49 @@ import (
|
|||||||
"github.com/golang/glog"
|
"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.
|
// ReadOnly passes all GET requests on to handler, and returns an error on all other requests.
|
||||||
func ReadOnly(handler http.Handler) http.Handler {
|
func ReadOnly(handler http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
if req.Method == "GET" {
|
if IsReadOnlyReq(*req) {
|
||||||
handler.ServeHTTP(w, req)
|
handler.ServeHTTP(w, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -143,6 +182,17 @@ func (r *requestAttributeGetter) GetAttribs(req *http.Request) authorizer.Attrib
|
|||||||
attribs.User = user
|
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
|
return &attribs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,7 +23,20 @@ import (
|
|||||||
// Attributes is an interface used by an Authorizer to get information about a request
|
// Attributes is an interface used by an Authorizer to get information about a request
|
||||||
// that is used to make an authorization decision.
|
// that is used to make an authorization decision.
|
||||||
type Attributes interface {
|
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
|
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
|
// Authorizer makes an authorization decision based on information gained by making
|
||||||
@ -36,8 +49,23 @@ type Authorizer interface {
|
|||||||
// AttributesRecord implements Attributes interface.
|
// AttributesRecord implements Attributes interface.
|
||||||
type AttributesRecord struct {
|
type AttributesRecord struct {
|
||||||
User user.Info
|
User user.Info
|
||||||
|
ReadOnly bool
|
||||||
|
Namespace string
|
||||||
|
Kind string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AttributesRecord) GetUserName() string {
|
func (a *AttributesRecord) GetUserName() string {
|
||||||
return a.User.GetName()
|
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
|
APIPrefix string
|
||||||
CorsAllowedOriginList util.StringList
|
CorsAllowedOriginList util.StringList
|
||||||
TokenAuthFile string
|
TokenAuthFile string
|
||||||
AuthorizationMode string
|
Authorizer authorizer.Authorizer
|
||||||
AuthorizerForTesting authorizer.Authorizer
|
|
||||||
|
|
||||||
// Number of masters running; all masters must be started with the
|
// Number of masters running; all masters must be started with the
|
||||||
// same value for this field. (Numbers > 1 currently untested.)
|
// same value for this field. (Numbers > 1 currently untested.)
|
||||||
@ -104,7 +103,7 @@ type Master struct {
|
|||||||
apiPrefix string
|
apiPrefix string
|
||||||
corsAllowedOriginList util.StringList
|
corsAllowedOriginList util.StringList
|
||||||
tokenAuthFile string
|
tokenAuthFile string
|
||||||
authorizationzMode string
|
authorizer authorizer.Authorizer
|
||||||
masterCount int
|
masterCount int
|
||||||
|
|
||||||
// "Outputs"
|
// "Outputs"
|
||||||
@ -227,7 +226,7 @@ func New(c *Config) *Master {
|
|||||||
apiPrefix: c.APIPrefix,
|
apiPrefix: c.APIPrefix,
|
||||||
corsAllowedOriginList: c.CorsAllowedOriginList,
|
corsAllowedOriginList: c.CorsAllowedOriginList,
|
||||||
tokenAuthFile: c.TokenAuthFile,
|
tokenAuthFile: c.TokenAuthFile,
|
||||||
authorizationzMode: c.AuthorizationMode,
|
authorizer: c.Authorizer,
|
||||||
|
|
||||||
masterCount: c.MasterCount,
|
masterCount: c.MasterCount,
|
||||||
readOnlyServer: net.JoinHostPort(c.PublicAddress, strconv.Itoa(int(c.ReadOnlyPort))),
|
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")
|
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)
|
attributeGetter := apiserver.NewRequestAttributeGetter(userContexts)
|
||||||
handler = apiserver.WithAuthorizationCheck(handler, attributeGetter, authorizer)
|
handler = apiserver.WithAuthorizationCheck(handler, attributeGetter, m.authorizer)
|
||||||
|
|
||||||
// Install Authenticator
|
// Install Authenticator
|
||||||
if authenticator != nil {
|
if authenticator != nil {
|
||||||
|
@ -32,6 +32,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authorizer"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authorizer"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/master"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/master"
|
||||||
@ -88,7 +89,7 @@ func TestWhoAmI(t *testing.T) {
|
|||||||
EnableUISupport: false,
|
EnableUISupport: false,
|
||||||
APIPrefix: "/api",
|
APIPrefix: "/api",
|
||||||
TokenAuthFile: tokenFilename,
|
TokenAuthFile: tokenFilename,
|
||||||
AuthorizationMode: "AlwaysAllow",
|
Authorizer: apiserver.NewAlwaysAllowAuthorizer(),
|
||||||
})
|
})
|
||||||
|
|
||||||
s := httptest.NewServer(m.Handler)
|
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 code200or202 = map[int]bool{200: true, 202: true} // Unpredicatable which will be returned.
|
||||||
var code400 = map[int]bool{400: true}
|
var code400 = map[int]bool{400: true}
|
||||||
|
var code403 = map[int]bool{403: true}
|
||||||
var code404 = map[int]bool{404: true}
|
var code404 = map[int]bool{404: true}
|
||||||
var code409 = map[int]bool{409: true}
|
var code409 = map[int]bool{409: true}
|
||||||
var code422 = map[int]bool{422: true}
|
var code422 = map[int]bool{422: true}
|
||||||
@ -372,7 +374,7 @@ func TestAuthModeAlwaysAllow(t *testing.T) {
|
|||||||
EnableLogsSupport: false,
|
EnableLogsSupport: false,
|
||||||
EnableUISupport: false,
|
EnableUISupport: false,
|
||||||
APIPrefix: "/api",
|
APIPrefix: "/api",
|
||||||
AuthorizationMode: "AlwaysAllow",
|
Authorizer: apiserver.NewAlwaysAllowAuthorizer(),
|
||||||
})
|
})
|
||||||
|
|
||||||
s := httptest.NewServer(m.Handler)
|
s := httptest.NewServer(m.Handler)
|
||||||
@ -417,7 +419,7 @@ func TestAuthModeAlwaysDeny(t *testing.T) {
|
|||||||
EnableLogsSupport: false,
|
EnableLogsSupport: false,
|
||||||
EnableUISupport: false,
|
EnableUISupport: false,
|
||||||
APIPrefix: "/api",
|
APIPrefix: "/api",
|
||||||
AuthorizationMode: "AlwaysDeny",
|
Authorizer: apiserver.NewAlwaysDenyAuthorizer(),
|
||||||
})
|
})
|
||||||
|
|
||||||
s := httptest.NewServer(m.Handler)
|
s := httptest.NewServer(m.Handler)
|
||||||
@ -465,8 +467,6 @@ func TestAliceNotForbiddenOrUnauthorized(t *testing.T) {
|
|||||||
defer os.Remove(tokenFilename)
|
defer os.Remove(tokenFilename)
|
||||||
// This file has alice and bob in it.
|
// This file has alice and bob in it.
|
||||||
|
|
||||||
aaa := allowAliceAuthorizer{}
|
|
||||||
|
|
||||||
// Set up a master
|
// Set up a master
|
||||||
|
|
||||||
helper, err := master.NewEtcdHelper(newEtcdClient(), "v1beta1")
|
helper, err := master.NewEtcdHelper(newEtcdClient(), "v1beta1")
|
||||||
@ -481,16 +481,13 @@ func TestAliceNotForbiddenOrUnauthorized(t *testing.T) {
|
|||||||
EnableUISupport: false,
|
EnableUISupport: false,
|
||||||
APIPrefix: "/api",
|
APIPrefix: "/api",
|
||||||
TokenAuthFile: tokenFilename,
|
TokenAuthFile: tokenFilename,
|
||||||
AuthorizerForTesting: aaa,
|
Authorizer: allowAliceAuthorizer{},
|
||||||
})
|
})
|
||||||
|
|
||||||
s := httptest.NewServer(m.Handler)
|
s := httptest.NewServer(m.Handler)
|
||||||
defer s.Close()
|
defer s.Close()
|
||||||
transport := http.DefaultTransport
|
transport := http.DefaultTransport
|
||||||
|
|
||||||
// Alice is authorized.
|
|
||||||
|
|
||||||
//
|
|
||||||
for _, r := range getTestRequests() {
|
for _, r := range getTestRequests() {
|
||||||
token := AliceToken
|
token := AliceToken
|
||||||
t.Logf("case %v", r)
|
t.Logf("case %v", r)
|
||||||
@ -524,8 +521,6 @@ func TestBobIsForbidden(t *testing.T) {
|
|||||||
defer os.Remove(tokenFilename)
|
defer os.Remove(tokenFilename)
|
||||||
// This file has alice and bob in it.
|
// This file has alice and bob in it.
|
||||||
|
|
||||||
aaa := allowAliceAuthorizer{}
|
|
||||||
|
|
||||||
// Set up a master
|
// Set up a master
|
||||||
|
|
||||||
helper, err := master.NewEtcdHelper(newEtcdClient(), "v1beta1")
|
helper, err := master.NewEtcdHelper(newEtcdClient(), "v1beta1")
|
||||||
@ -540,16 +535,13 @@ func TestBobIsForbidden(t *testing.T) {
|
|||||||
EnableUISupport: false,
|
EnableUISupport: false,
|
||||||
APIPrefix: "/api",
|
APIPrefix: "/api",
|
||||||
TokenAuthFile: tokenFilename,
|
TokenAuthFile: tokenFilename,
|
||||||
AuthorizerForTesting: aaa,
|
Authorizer: allowAliceAuthorizer{},
|
||||||
})
|
})
|
||||||
|
|
||||||
s := httptest.NewServer(m.Handler)
|
s := httptest.NewServer(m.Handler)
|
||||||
defer s.Close()
|
defer s.Close()
|
||||||
transport := http.DefaultTransport
|
transport := http.DefaultTransport
|
||||||
|
|
||||||
// Alice is authorized.
|
|
||||||
|
|
||||||
//
|
|
||||||
for _, r := range getTestRequests() {
|
for _, r := range getTestRequests() {
|
||||||
token := BobToken
|
token := BobToken
|
||||||
t.Logf("case %v", r)
|
t.Logf("case %v", r)
|
||||||
@ -585,8 +577,6 @@ func TestUnknownUserIsUnauthorized(t *testing.T) {
|
|||||||
defer os.Remove(tokenFilename)
|
defer os.Remove(tokenFilename)
|
||||||
// This file has alice and bob in it.
|
// This file has alice and bob in it.
|
||||||
|
|
||||||
aaa := allowAliceAuthorizer{}
|
|
||||||
|
|
||||||
// Set up a master
|
// Set up a master
|
||||||
|
|
||||||
helper, err := master.NewEtcdHelper(newEtcdClient(), "v1beta1")
|
helper, err := master.NewEtcdHelper(newEtcdClient(), "v1beta1")
|
||||||
@ -601,7 +591,7 @@ func TestUnknownUserIsUnauthorized(t *testing.T) {
|
|||||||
EnableUISupport: false,
|
EnableUISupport: false,
|
||||||
APIPrefix: "/api",
|
APIPrefix: "/api",
|
||||||
TokenAuthFile: tokenFilename,
|
TokenAuthFile: tokenFilename,
|
||||||
AuthorizerForTesting: aaa,
|
Authorizer: allowAliceAuthorizer{},
|
||||||
})
|
})
|
||||||
|
|
||||||
s := httptest.NewServer(m.Handler)
|
s := httptest.NewServer(m.Handler)
|
||||||
@ -625,7 +615,248 @@ func TestUnknownUserIsUnauthorized(t *testing.T) {
|
|||||||
}
|
}
|
||||||
// Expect all of unauthenticated user's request to be "Unauthorized"
|
// Expect all of unauthenticated user's request to be "Unauthorized"
|
||||||
if resp.StatusCode != http.StatusUnauthorized {
|
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"
|
"testing"
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/master"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/master"
|
||||||
@ -45,7 +46,7 @@ func TestClient(t *testing.T) {
|
|||||||
EnableLogsSupport: false,
|
EnableLogsSupport: false,
|
||||||
EnableUISupport: false,
|
EnableUISupport: false,
|
||||||
APIPrefix: "/api",
|
APIPrefix: "/api",
|
||||||
AuthorizationMode: "AlwaysAllow",
|
Authorizer: apiserver.NewAlwaysAllowAuthorizer(),
|
||||||
})
|
})
|
||||||
|
|
||||||
s := httptest.NewServer(m.Handler)
|
s := httptest.NewServer(m.Handler)
|
||||||
|
Loading…
Reference in New Issue
Block a user