mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-25 12:43:23 +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"
|
"strings"
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authorizer"
|
"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/httplog"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||||
"github.com/golang/glog"
|
"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
|
// 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.
|
type requestAttributeGetter struct {
|
||||||
func BasicAttributeGetter(req *http.Request) (attribs authorizer.Attributes) {
|
userContexts authhandlers.RequestContext
|
||||||
// TODO: fill in attributes once attributes are defined.
|
}
|
||||||
return
|
|
||||||
|
// 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.
|
// 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 {
|
func WithAuthorizationCheck(handler http.Handler, getAttribs RequestAttributeGetter, a authorizer.Authorizer) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
err := a.Authorize(getAttribs(req))
|
err := a.Authorize(getAttribs.GetAttribs(req))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
handler.ServeHTTP(w, req)
|
handler.ServeHTTP(w, req)
|
||||||
return
|
return
|
||||||
|
@ -16,10 +16,14 @@ limitations under the License.
|
|||||||
|
|
||||||
package authorizer
|
package authorizer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/user"
|
||||||
|
)
|
||||||
|
|
||||||
// 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 {
|
||||||
// TODO: add attribute getter functions, e.g. GetUserName(), per #1430.
|
GetUserName() string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Authorizer makes an authorization decision based on information gained by making
|
// Authorizer makes an authorization decision based on information gained by making
|
||||||
@ -28,3 +32,12 @@ type Attributes interface {
|
|||||||
type Authorizer interface {
|
type Authorizer interface {
|
||||||
Authorize(a Attributes) (err error)
|
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.
|
// RequestContext is the interface used to associate a user with an http Request.
|
||||||
type RequestContext interface {
|
type RequestContext interface {
|
||||||
Set(*http.Request, user.Info)
|
Set(*http.Request, user.Info)
|
||||||
|
Get(req *http.Request) (user.Info, bool)
|
||||||
Remove(*http.Request)
|
Remove(*http.Request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,6 +30,7 @@ import (
|
|||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator/bearertoken"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator/bearertoken"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator/tokenfile"
|
"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/auth/handlers"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider"
|
||||||
@ -67,6 +68,7 @@ type Config struct {
|
|||||||
CorsAllowedOriginList util.StringList
|
CorsAllowedOriginList util.StringList
|
||||||
TokenAuthFile string
|
TokenAuthFile string
|
||||||
AuthorizationMode string
|
AuthorizationMode string
|
||||||
|
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.)
|
||||||
@ -318,11 +320,18 @@ func (m *Master) init(c *Config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Install Authorizer
|
// Install Authorizer
|
||||||
authorizer, err := apiserver.NewAuthorizerFromAuthorizationConfig(m.authorizationzMode)
|
var authorizer authorizer.Authorizer
|
||||||
if err != nil {
|
if c.AuthorizerForTesting != nil {
|
||||||
glog.Fatal(err)
|
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
|
// Install Authenticator
|
||||||
if authenticator != nil {
|
if authenticator != nil {
|
||||||
|
@ -24,6 +24,7 @@ package integration
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -31,6 +32,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"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"
|
||||||
|
|
||||||
@ -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