Merge pull request #2117 from erictune/get_user_attrib

Use user-string in authentication attributes.
This commit is contained in:
Dawn Chen 2014-11-03 17:23:37 -08:00
commit d6e36a4756
5 changed files with 240 additions and 11 deletions

View File

@ -24,6 +24,7 @@ import (
"strings"
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authorizer"
authhandlers "github.com/GoogleCloudPlatform/kubernetes/pkg/auth/handlers"
"github.com/GoogleCloudPlatform/kubernetes/pkg/httplog"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/golang/glog"
@ -121,18 +122,34 @@ func CORS(handler http.Handler, allowedOriginPatterns []*regexp.Regexp, allowedM
}
// RequestAttributeGetter is a function that extracts authorizer.Attributes from an http.Request
type RequestAttributeGetter func(req *http.Request) (attribs authorizer.Attributes)
type RequestAttributeGetter interface {
GetAttribs(req *http.Request) (attribs authorizer.Attributes)
}
// BasicAttributeGetter gets authorizer.Attributes from an http.Request.
func BasicAttributeGetter(req *http.Request) (attribs authorizer.Attributes) {
// TODO: fill in attributes once attributes are defined.
return
type requestAttributeGetter struct {
userContexts authhandlers.RequestContext
}
// NewAttributeGetter returns an object which implements the RequestAttributeGetter interface.
func NewRequestAttributeGetter(userContexts authhandlers.RequestContext) RequestAttributeGetter {
return &requestAttributeGetter{userContexts}
}
func (r *requestAttributeGetter) GetAttribs(req *http.Request) authorizer.Attributes {
attribs := authorizer.AttributesRecord{}
user, ok := r.userContexts.Get(req)
if ok {
attribs.User = user
}
return &attribs
}
// WithAuthorizationCheck passes all authorized requests on to handler, and returns a forbidden error otherwise.
func WithAuthorizationCheck(handler http.Handler, getAttribs RequestAttributeGetter, a authorizer.Authorizer) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
err := a.Authorize(getAttribs(req))
err := a.Authorize(getAttribs.GetAttribs(req))
if err == nil {
handler.ServeHTTP(w, req)
return

View File

@ -16,10 +16,14 @@ limitations under the License.
package authorizer
import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/user"
)
// 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 {
// TODO: add attribute getter functions, e.g. GetUserName(), per #1430.
GetUserName() string
}
// Authorizer makes an authorization decision based on information gained by making
@ -28,3 +32,12 @@ type Attributes interface {
type Authorizer interface {
Authorize(a Attributes) (err error)
}
// AttributesRecord implements Attributes interface.
type AttributesRecord struct {
User user.Info
}
func (a *AttributesRecord) GetUserName() string {
return a.User.GetName()
}

View File

@ -28,6 +28,7 @@ import (
// RequestContext is the interface used to associate a user with an http Request.
type RequestContext interface {
Set(*http.Request, user.Info)
Get(req *http.Request) (user.Info, bool)
Remove(*http.Request)
}

View File

@ -30,6 +30,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator"
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator/bearertoken"
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator/tokenfile"
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authorizer"
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/handlers"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider"
@ -67,6 +68,7 @@ type Config struct {
CorsAllowedOriginList util.StringList
TokenAuthFile string
AuthorizationMode string
AuthorizerForTesting authorizer.Authorizer
// Number of masters running; all masters must be started with the
// same value for this field. (Numbers > 1 currently untested.)
@ -318,11 +320,18 @@ func (m *Master) init(c *Config) {
}
// Install Authorizer
authorizer, err := apiserver.NewAuthorizerFromAuthorizationConfig(m.authorizationzMode)
if err != nil {
glog.Fatal(err)
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)
}
}
handler = apiserver.WithAuthorizationCheck(handler, apiserver.BasicAttributeGetter, authorizer)
attributeGetter := apiserver.NewRequestAttributeGetter(userContexts)
handler = apiserver.WithAuthorizationCheck(handler, attributeGetter, authorizer)
// Install Authenticator
if authenticator != nil {

View File

@ -24,6 +24,7 @@ package integration
import (
"bytes"
"errors"
"fmt"
"io/ioutil"
"net/http"
@ -31,6 +32,7 @@ import (
"os"
"testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authorizer"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/master"
@ -441,3 +443,190 @@ func TestAuthModeAlwaysDeny(t *testing.T) {
}
}
}
// 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(a authorizer.Attributes) error {
if a.GetUserName() == "alice" {
return nil
}
return errors.New("I can't allow that. Go ask alice.")
}
// TestAliceNotForbiddenOrUnauthorized tests a user who is known to
// the authentication system and authorized to do any actions.
func TestAliceNotForbiddenOrUnauthorized(t *testing.T) {
deleteAllEtcdKeys()
tokenFilename := writeTestTokenFile()
defer os.Remove(tokenFilename)
// This file has alice and bob in it.
aaa := allowAliceAuthorizer{}
// 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,
AuthorizerForTesting: aaa,
})
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)
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)
}
}
}
}
// 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) {
deleteAllEtcdKeys()
tokenFilename := writeTestTokenFile()
defer os.Remove(tokenFilename)
// This file has alice and bob in it.
aaa := allowAliceAuthorizer{}
// 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,
AuthorizerForTesting: aaa,
})
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)
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)
}
// Expect all of bob's actions to return Forbidden
if resp.StatusCode != http.StatusForbidden {
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) {
deleteAllEtcdKeys()
tokenFilename := writeTestTokenFile()
defer os.Remove(tokenFilename)
// This file has alice and bob in it.
aaa := allowAliceAuthorizer{}
// 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,
AuthorizerForTesting: aaa,
})
s := httptest.NewServer(m.Handler)
defer s.Close()
transport := http.DefaultTransport
for _, r := range getTestRequests() {
token := UnknownToken
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)
}
// 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)
}
}
}
}