mirror of https://github.com/k3s-io/k3s
Merge pull request #1529 from smarterclayton/add_auth_interfaces
Add simple Bearer authenticator filter for Kubepull/6/head
commit
5503e95c1d
|
@ -29,6 +29,9 @@ import (
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator/bearertoken"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator/tokenfile"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/handlers"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/capabilities"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/capabilities"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider"
|
||||||
|
@ -51,6 +54,7 @@ var (
|
||||||
minionPort = flag.Uint("minion_port", 10250, "The port at which kubelet will be listening on the minions.")
|
minionPort = flag.Uint("minion_port", 10250, "The port at which kubelet will be listening on the minions.")
|
||||||
healthCheckMinions = flag.Bool("health_check_minions", true, "If true, health check minions and filter unhealthy ones. Default true")
|
healthCheckMinions = flag.Bool("health_check_minions", true, "If true, health check minions and filter unhealthy ones. Default true")
|
||||||
minionCacheTTL = flag.Duration("minion_cache_ttl", 30*time.Second, "Duration of time to cache minion information. Default 30 seconds")
|
minionCacheTTL = flag.Duration("minion_cache_ttl", 30*time.Second, "Duration of time to cache minion information. Default 30 seconds")
|
||||||
|
tokenAuthFile = flag.String("token_auth_file", "", "If set, the file that will be used to secure the API server via token authentication")
|
||||||
etcdServerList util.StringList
|
etcdServerList util.StringList
|
||||||
machineList util.StringList
|
machineList util.StringList
|
||||||
corsAllowedOriginList util.StringList
|
corsAllowedOriginList util.StringList
|
||||||
|
@ -172,6 +176,7 @@ func main() {
|
||||||
ui.InstallSupport(mux)
|
ui.InstallSupport(mux)
|
||||||
|
|
||||||
handler := http.Handler(mux)
|
handler := http.Handler(mux)
|
||||||
|
|
||||||
if len(corsAllowedOriginList) > 0 {
|
if len(corsAllowedOriginList) > 0 {
|
||||||
allowedOriginRegexps, err := util.CompileRegexps(corsAllowedOriginList)
|
allowedOriginRegexps, err := util.CompileRegexps(corsAllowedOriginList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -179,6 +184,16 @@ func main() {
|
||||||
}
|
}
|
||||||
handler = apiserver.CORS(handler, allowedOriginRegexps, nil, nil, "true")
|
handler = apiserver.CORS(handler, allowedOriginRegexps, nil, nil, "true")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(*tokenAuthFile) != 0 {
|
||||||
|
auth, err := tokenfile.New(*tokenAuthFile)
|
||||||
|
if err != nil {
|
||||||
|
glog.Fatalf("Unable to load the token authentication file '%s': %v", *tokenAuthFile, err)
|
||||||
|
}
|
||||||
|
userContexts := handlers.NewUserRequestContext()
|
||||||
|
handler = handlers.NewRequestAuthenticator(userContexts, bearertoken.New(auth), handlers.Unauthorized, handler)
|
||||||
|
}
|
||||||
|
|
||||||
handler = apiserver.RecoverPanics(handler)
|
handler = apiserver.RecoverPanics(handler)
|
||||||
|
|
||||||
s := &http.Server{
|
s := &http.Server{
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package bearertoken
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Authenticator struct {
|
||||||
|
auth authenticator.Token
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(auth authenticator.Token) *Authenticator {
|
||||||
|
return &Authenticator{auth}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Authenticator) AuthenticateRequest(req *http.Request) (user.Info, bool, error) {
|
||||||
|
auth := strings.TrimSpace(req.Header.Get("Authorization"))
|
||||||
|
if auth == "" {
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
parts := strings.Split(auth, " ")
|
||||||
|
if len(parts) < 2 || strings.ToLower(parts[0]) != "bearer" {
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
token := parts[1]
|
||||||
|
return a.auth.AuthenticateToken(token)
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package bearertoken
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAuthenticateRequest(t *testing.T) {
|
||||||
|
auth := New(authenticator.TokenFunc(func(token string) (user.Info, bool, error) {
|
||||||
|
if token != "token" {
|
||||||
|
t.Errorf("unexpected token: %s", token)
|
||||||
|
}
|
||||||
|
return &user.DefaultInfo{Name: "user"}, true, nil
|
||||||
|
}))
|
||||||
|
user, ok, err := auth.AuthenticateRequest(&http.Request{
|
||||||
|
Header: http.Header{"Authorization": []string{"Bearer token"}},
|
||||||
|
})
|
||||||
|
if !ok || user == nil || err != nil {
|
||||||
|
t.Errorf("expected valid user")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthenticateRequestTokenInvalid(t *testing.T) {
|
||||||
|
auth := New(authenticator.TokenFunc(func(token string) (user.Info, bool, error) {
|
||||||
|
return nil, false, nil
|
||||||
|
}))
|
||||||
|
user, ok, err := auth.AuthenticateRequest(&http.Request{
|
||||||
|
Header: http.Header{"Authorization": []string{"Bearer token"}},
|
||||||
|
})
|
||||||
|
if ok || user != nil || err != nil {
|
||||||
|
t.Errorf("expected not authenticated user")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthenticateRequestTokenError(t *testing.T) {
|
||||||
|
auth := New(authenticator.TokenFunc(func(token string) (user.Info, bool, error) {
|
||||||
|
return nil, false, errors.New("error")
|
||||||
|
}))
|
||||||
|
user, ok, err := auth.AuthenticateRequest(&http.Request{
|
||||||
|
Header: http.Header{"Authorization": []string{"Bearer token"}},
|
||||||
|
})
|
||||||
|
if ok || user != nil || err == nil {
|
||||||
|
t.Errorf("expected error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthenticateRequestBadValue(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
Req *http.Request
|
||||||
|
}{
|
||||||
|
{Req: &http.Request{}},
|
||||||
|
{Req: &http.Request{Header: http.Header{"Authorization": []string{"Bearer"}}}},
|
||||||
|
{Req: &http.Request{Header: http.Header{"Authorization": []string{"bear token"}}}},
|
||||||
|
{Req: &http.Request{Header: http.Header{"Authorization": []string{"Bearer: token"}}}},
|
||||||
|
}
|
||||||
|
for i, testCase := range testCases {
|
||||||
|
auth := New(authenticator.TokenFunc(func(token string) (user.Info, bool, error) {
|
||||||
|
t.Errorf("authentication should not have been called")
|
||||||
|
return nil, false, nil
|
||||||
|
}))
|
||||||
|
user, ok, err := auth.AuthenticateRequest(testCase.Req)
|
||||||
|
if ok || user != nil || err != nil {
|
||||||
|
t.Errorf("%d: expected not authenticated (no token)", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package authenticator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Token checks a string value against a backing authentication store and returns
|
||||||
|
// information about the current user and true if successful, false if not successful,
|
||||||
|
// or an error if the token could not be checked.
|
||||||
|
type Token interface {
|
||||||
|
AuthenticateToken(token string) (user.Info, bool, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request attempts to extract authentication information from a request and returns
|
||||||
|
// information about the current user and true if successful, false if not successful,
|
||||||
|
// or an error if the token could not be checked.
|
||||||
|
type Request interface {
|
||||||
|
AuthenticateRequest(req *http.Request) (user.Info, bool, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TokenFunc is a function that implements the Token interface.
|
||||||
|
type TokenFunc func(token string) (user.Info, bool, error)
|
||||||
|
|
||||||
|
// AuthenticateToken implements authenticator.Token.
|
||||||
|
func (f TokenFunc) AuthenticateToken(token string) (user.Info, bool, error) {
|
||||||
|
return f(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestFunc is a function that implements the Request interface.
|
||||||
|
type RequestFunc func(req *http.Request) (user.Info, bool, error)
|
||||||
|
|
||||||
|
// AuthenticateRequest implements authenticator.Request.
|
||||||
|
func (f RequestFunc) AuthenticateRequest(req *http.Request) (user.Info, bool, error) {
|
||||||
|
return f(req)
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package tokenfile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/csv"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TokenAuthenticator struct {
|
||||||
|
tokens map[string]*user.DefaultInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(path string) (*TokenAuthenticator, error) {
|
||||||
|
file, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
tokens := make(map[string]*user.DefaultInfo)
|
||||||
|
reader := csv.NewReader(file)
|
||||||
|
for {
|
||||||
|
record, err := reader.Read()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(record) < 3 {
|
||||||
|
return nil, fmt.Errorf("token file '%s' must have at least 3 columns (token, user name, user uid), found %d", path, len(record))
|
||||||
|
}
|
||||||
|
obj := &user.DefaultInfo{
|
||||||
|
Name: record[1],
|
||||||
|
UID: record[2],
|
||||||
|
}
|
||||||
|
tokens[record[0]] = obj
|
||||||
|
}
|
||||||
|
|
||||||
|
return &TokenAuthenticator{
|
||||||
|
tokens: tokens,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *TokenAuthenticator) AuthenticateToken(value string) (user.Info, bool, error) {
|
||||||
|
user, ok := a.tokens[value]
|
||||||
|
if !ok {
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
return user, true, nil
|
||||||
|
}
|
|
@ -0,0 +1,113 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package tokenfile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTokenFile(t *testing.T) {
|
||||||
|
auth, err := newWithContents(t, `
|
||||||
|
token1,user1,uid1
|
||||||
|
token2,user2,uid2
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to read tokenfile: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
Token string
|
||||||
|
User *user.DefaultInfo
|
||||||
|
Ok bool
|
||||||
|
Err bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Token: "token1",
|
||||||
|
User: &user.DefaultInfo{Name: "user1", UID: "uid1"},
|
||||||
|
Ok: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Token: "token2",
|
||||||
|
User: &user.DefaultInfo{Name: "user2", UID: "uid2"},
|
||||||
|
Ok: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Token: "token3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Token: "token4",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for i, testCase := range testCases {
|
||||||
|
user, ok, err := auth.AuthenticateToken(testCase.Token)
|
||||||
|
if testCase.User == nil {
|
||||||
|
if user != nil {
|
||||||
|
t.Errorf("%d: unexpected non-nil user %#v", i, user)
|
||||||
|
}
|
||||||
|
} else if !reflect.DeepEqual(testCase.User, user) {
|
||||||
|
t.Errorf("%d: expected user %#v, got %#v", i, testCase.User, user)
|
||||||
|
}
|
||||||
|
if testCase.Ok != ok {
|
||||||
|
t.Errorf("%d: expected auth %f, got %f", i, testCase.Ok, ok)
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case err == nil && testCase.Err:
|
||||||
|
t.Errorf("%d: unexpected nil error", i)
|
||||||
|
case err != nil && !testCase.Err:
|
||||||
|
t.Errorf("%d: unexpected error: %v", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBadTokenFile(t *testing.T) {
|
||||||
|
_, err := newWithContents(t, `
|
||||||
|
token1,user1,uid1
|
||||||
|
token2,user2,uid2
|
||||||
|
token3,user3
|
||||||
|
token4
|
||||||
|
`)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("unexpected non error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInsufficientColumnsTokenFile(t *testing.T) {
|
||||||
|
_, err := newWithContents(t, "token4\n")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("unexpected non error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newWithContents(t *testing.T, contents string) (auth *TokenAuthenticator, err error) {
|
||||||
|
f, err := ioutil.TempFile("", "tokenfile_test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error creating tokenfile: %v", err)
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
defer os.Remove(f.Name())
|
||||||
|
|
||||||
|
if err := ioutil.WriteFile(f.Name(), []byte(contents), 0700); err != nil {
|
||||||
|
t.Fatalf("unexpected error writing tokenfile: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return New(f.Name())
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/user"
|
||||||
|
"github.com/golang/glog"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RequestContext is the interface used to associate a user with an http Request.
|
||||||
|
type RequestContext interface {
|
||||||
|
Set(*http.Request, user.Info)
|
||||||
|
Remove(*http.Request)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRequestAuthenticator creates an http handler that tries to authenticate the given request as a user, and then
|
||||||
|
// stores any such user found onto the provided context for the request. If authentication fails or returns an error
|
||||||
|
// the failed handler is used. On success, handler is invoked to serve the request.
|
||||||
|
func NewRequestAuthenticator(context RequestContext, auth authenticator.Request, failed http.Handler, handler http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
user, ok, err := auth.AuthenticateRequest(req)
|
||||||
|
if err != nil || !ok {
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("Unable to authenticate the request due to an error: %v", err)
|
||||||
|
}
|
||||||
|
failed.ServeHTTP(w, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Set(req, user)
|
||||||
|
defer context.Remove(req)
|
||||||
|
|
||||||
|
handler.ServeHTTP(w, req)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var Unauthorized http.HandlerFunc = unauthorized
|
||||||
|
|
||||||
|
// unauthorized serves an unauthorized message to clients.
|
||||||
|
func unauthorized(w http.ResponseWriter, req *http.Request) {
|
||||||
|
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserRequestContext allows different levels of a call stack to store/retrieve info about the
|
||||||
|
// current user associated with an http.Request.
|
||||||
|
type UserRequestContext struct {
|
||||||
|
requests map[*http.Request]user.Info
|
||||||
|
lock sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUserRequestContext provides a map for storing and retrieving users associated with requests.
|
||||||
|
// Be sure to pair each `context.Set(req, user)` call with a `defer context.Remove(req)` call or
|
||||||
|
// you will leak requests. It implements the RequestContext interface.
|
||||||
|
func NewUserRequestContext() *UserRequestContext {
|
||||||
|
return &UserRequestContext{
|
||||||
|
requests: make(map[*http.Request]user.Info),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *UserRequestContext) Get(req *http.Request) (user.Info, bool) {
|
||||||
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
|
user, ok := c.requests[req]
|
||||||
|
return user, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *UserRequestContext) Set(req *http.Request, user user.Info) {
|
||||||
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
|
c.requests[req] = user
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *UserRequestContext) Remove(req *http.Request) {
|
||||||
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
|
delete(c.requests, req)
|
||||||
|
}
|
|
@ -0,0 +1,102 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAuthenticateRequest(t *testing.T) {
|
||||||
|
success := make(chan struct{})
|
||||||
|
context := NewUserRequestContext()
|
||||||
|
auth := NewRequestAuthenticator(
|
||||||
|
context,
|
||||||
|
authenticator.RequestFunc(func(req *http.Request) (user.Info, bool, error) {
|
||||||
|
return &user.DefaultInfo{Name: "user"}, true, nil
|
||||||
|
}),
|
||||||
|
http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {
|
||||||
|
t.Errorf("unexpected call to failed")
|
||||||
|
}),
|
||||||
|
http.HandlerFunc(func(_ http.ResponseWriter, req *http.Request) {
|
||||||
|
if user, ok := context.Get(req); user == nil || !ok {
|
||||||
|
t.Errorf("no user stored on context: %#v", context)
|
||||||
|
}
|
||||||
|
close(success)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
auth.ServeHTTP(httptest.NewRecorder(), &http.Request{})
|
||||||
|
|
||||||
|
<-success
|
||||||
|
if len(context.requests) > 0 {
|
||||||
|
t.Errorf("context should have no stored requests", context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthenticateRequestFailed(t *testing.T) {
|
||||||
|
failed := make(chan struct{})
|
||||||
|
context := NewUserRequestContext()
|
||||||
|
auth := NewRequestAuthenticator(
|
||||||
|
context,
|
||||||
|
authenticator.RequestFunc(func(req *http.Request) (user.Info, bool, error) {
|
||||||
|
return nil, false, nil
|
||||||
|
}),
|
||||||
|
http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {
|
||||||
|
close(failed)
|
||||||
|
}),
|
||||||
|
http.HandlerFunc(func(_ http.ResponseWriter, req *http.Request) {
|
||||||
|
t.Errorf("unexpected call to handler")
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
auth.ServeHTTP(httptest.NewRecorder(), &http.Request{})
|
||||||
|
|
||||||
|
<-failed
|
||||||
|
if len(context.requests) > 0 {
|
||||||
|
t.Errorf("context should have no stored requests", context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthenticateRequestError(t *testing.T) {
|
||||||
|
failed := make(chan struct{})
|
||||||
|
context := NewUserRequestContext()
|
||||||
|
auth := NewRequestAuthenticator(
|
||||||
|
context,
|
||||||
|
authenticator.RequestFunc(func(req *http.Request) (user.Info, bool, error) {
|
||||||
|
return nil, false, errors.New("failure")
|
||||||
|
}),
|
||||||
|
http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {
|
||||||
|
close(failed)
|
||||||
|
}),
|
||||||
|
http.HandlerFunc(func(_ http.ResponseWriter, req *http.Request) {
|
||||||
|
t.Errorf("unexpected call to handler")
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
auth.ServeHTTP(httptest.NewRecorder(), &http.Request{})
|
||||||
|
|
||||||
|
<-failed
|
||||||
|
if len(context.requests) > 0 {
|
||||||
|
t.Errorf("context should have no stored requests", context)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package user contains utilities for dealing with simple user exchange in the auth
|
||||||
|
// packages. The user.Info interface defines an interface for exchanging that info.
|
||||||
|
package user
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package user
|
||||||
|
|
||||||
|
// UserInfo describes a user that has been authenticated to the system.
|
||||||
|
type Info interface {
|
||||||
|
// GetName returns the name that uniquely identifies this user among all
|
||||||
|
// other active users.
|
||||||
|
GetName() string
|
||||||
|
// GetUID returns a unique value for a particular user that will change
|
||||||
|
// if the user is removed from the system and another user is added with
|
||||||
|
// the same name.
|
||||||
|
GetUID() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultInfo provides a simple user information exchange object
|
||||||
|
// for components that implement the UserInfo interface.
|
||||||
|
type DefaultInfo struct {
|
||||||
|
Name string
|
||||||
|
UID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *DefaultInfo) GetName() string {
|
||||||
|
return i.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *DefaultInfo) GetUID() string {
|
||||||
|
return i.UID
|
||||||
|
}
|
Loading…
Reference in New Issue