mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 19:56:01 +00:00
Merge pull request #2117 from erictune/get_user_attrib
Use user-string in authentication attributes.
This commit is contained in:
commit
d6e36a4756
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user