diff --git a/pkg/auth/authenticator/interfaces.go b/pkg/auth/authenticator/interfaces.go index 8c9cbe73f3c..701dcd2a4c5 100644 --- a/pkg/auth/authenticator/interfaces.go +++ b/pkg/auth/authenticator/interfaces.go @@ -31,11 +31,18 @@ type Token interface { // 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. +// or an error if the request could not be checked. type Request interface { AuthenticateRequest(req *http.Request) (user.Info, bool, error) } +// Password checks a username and password against a backing authentication store and +// returns information about the user and true if successful, false if not successful, +// or an error if the username and password could not be checked +type Password interface { + AuthenticatePassword(user, password string) (user.Info, bool, error) +} + // TokenFunc is a function that implements the Token interface. type TokenFunc func(token string) (user.Info, bool, error) @@ -51,3 +58,11 @@ type RequestFunc func(req *http.Request) (user.Info, bool, error) func (f RequestFunc) AuthenticateRequest(req *http.Request) (user.Info, bool, error) { return f(req) } + +// PasswordFunc is a function that implements the Password interface. +type PasswordFunc func(user, password string) (user.Info, bool, error) + +// AuthenticatePassword implements authenticator.Password. +func (f PasswordFunc) AuthenticatePassword(user, password string) (user.Info, bool, error) { + return f(user, password) +} diff --git a/pkg/auth/user/user.go b/pkg/auth/user/user.go index 0b52b9c3301..e38da660ef6 100644 --- a/pkg/auth/user/user.go +++ b/pkg/auth/user/user.go @@ -16,7 +16,7 @@ limitations under the License. package user -// UserInfo describes a user that has been authenticated to the system. +// Info 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. diff --git a/plugin/pkg/auth/authenticator/doc.go b/plugin/pkg/auth/authenticator/doc.go new file mode 100644 index 00000000000..9185097657f --- /dev/null +++ b/plugin/pkg/auth/authenticator/doc.go @@ -0,0 +1,18 @@ +/* +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 contains implementations for pkg/auth/authenticator interfaces +package authenticator diff --git a/plugin/pkg/auth/authenticator/password/allow/allow.go b/plugin/pkg/auth/authenticator/password/allow/allow.go new file mode 100644 index 00000000000..45a8fde797f --- /dev/null +++ b/plugin/pkg/auth/authenticator/password/allow/allow.go @@ -0,0 +1,38 @@ +/* +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 allow + +import ( + "github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator" + "github.com/GoogleCloudPlatform/kubernetes/pkg/auth/user" +) + +type allowAuthenticator struct{} + +// NewAllow returns a password authenticator that allows any non-empty username +func NewAllow() authenticator.Password { + return allowAuthenticator{} +} + +// AuthenticatePassword implements authenticator.Password to allow any non-empty username, +// using the specified username as the name and UID +func (allowAuthenticator) AuthenticatePassword(username, password string) (user.Info, bool, error) { + if username == "" { + return nil, false, nil + } + return &user.DefaultInfo{Name: username, UID: username}, true, nil +} diff --git a/plugin/pkg/auth/authenticator/password/allow/allow_test.go b/plugin/pkg/auth/authenticator/password/allow/allow_test.go new file mode 100644 index 00000000000..65542cbf75a --- /dev/null +++ b/plugin/pkg/auth/authenticator/password/allow/allow_test.go @@ -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 allow + +import "testing" + +func TestAllowEmpty(t *testing.T) { + allow := NewAllow() + user, ok, err := allow.AuthenticatePassword("", "") + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if ok { + t.Fatalf("Unexpected success") + } + if user != nil { + t.Fatalf("Unexpected user: %v", user) + } +} + +func TestAllowPresent(t *testing.T) { + allow := NewAllow() + user, ok, err := allow.AuthenticatePassword("myuser", "") + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if !ok { + t.Fatalf("Unexpected failure") + } + if user.GetName() != "myuser" || user.GetUID() != "myuser" { + t.Fatalf("Unexpected user name or uid: %v", user) + } +} diff --git a/plugin/pkg/auth/authenticator/password/doc.go b/plugin/pkg/auth/authenticator/password/doc.go new file mode 100644 index 00000000000..9689a1a1617 --- /dev/null +++ b/plugin/pkg/auth/authenticator/password/doc.go @@ -0,0 +1,18 @@ +/* +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 password contains authenticator.Password implementations +package password diff --git a/plugin/pkg/auth/authenticator/request/basicauth/basicauth.go b/plugin/pkg/auth/authenticator/request/basicauth/basicauth.go new file mode 100644 index 00000000000..7269055c7d7 --- /dev/null +++ b/plugin/pkg/auth/authenticator/request/basicauth/basicauth.go @@ -0,0 +1,63 @@ +/* +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 basicauth + +import ( + "encoding/base64" + "errors" + "net/http" + "strings" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator" + "github.com/GoogleCloudPlatform/kubernetes/pkg/auth/user" +) + +// Authenticator authenticates requests using basic auth +type Authenticator struct { + auth authenticator.Password +} + +// New returns a request authenticator that validates credentials using the provided password authenticator +func New(auth authenticator.Password) *Authenticator { + return &Authenticator{auth} +} + +// AuthenticateRequest authenticates the request using the "Authorization: Basic" header in the request +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]) != "basic" { + return nil, false, nil + } + + payload, err := base64.StdEncoding.DecodeString(parts[1]) + if err != nil { + return nil, false, err + } + + pair := strings.SplitN(string(payload), ":", 2) + if len(pair) != 2 { + return nil, false, errors.New("malformed basic auth header") + } + + username := pair[0] + password := pair[1] + return a.auth.AuthenticatePassword(username, password) +} diff --git a/plugin/pkg/auth/authenticator/request/basicauth/basicauth_test.go b/plugin/pkg/auth/authenticator/request/basicauth/basicauth_test.go new file mode 100644 index 00000000000..57d5c807555 --- /dev/null +++ b/plugin/pkg/auth/authenticator/request/basicauth/basicauth_test.go @@ -0,0 +1,147 @@ +/* +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 basicauth + +import ( + "encoding/base64" + "errors" + "net/http" + "testing" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator" + "github.com/GoogleCloudPlatform/kubernetes/pkg/auth/user" +) + +type testPassword struct { + Username string + Password string + Called bool + + User user.Info + OK bool + Err error +} + +func (t *testPassword) AuthenticatePassword(user, password string) (user.Info, bool, error) { + t.Called = true + t.Username = user + t.Password = password + return t.User, t.OK, t.Err +} + +func TestBasicAuth(t *testing.T) { + testCases := map[string]struct { + Header string + Password testPassword + + ExpectedCalled bool + ExpectedUsername string + ExpectedPassword string + + ExpectedUser string + ExpectedOK bool + ExpectedErr bool + }{ + "no header": { + Header: "", + }, + "non-basic header": { + Header: "Bearer foo", + }, + "empty value basic header": { + Header: "Basic", + }, + "whitespace value basic header": { + Header: "Basic ", + }, + "non base-64 basic header": { + Header: "Basic !@#$", + ExpectedErr: true, + }, + "malformed basic header": { + Header: "Basic " + base64.StdEncoding.EncodeToString([]byte("user_without_password")), + ExpectedErr: true, + }, + "empty password basic header": { + Header: "Basic " + base64.StdEncoding.EncodeToString([]byte("user_with_empty_password:")), + ExpectedCalled: true, + ExpectedUsername: "user_with_empty_password", + ExpectedPassword: "", + }, + "valid basic header": { + Header: "Basic " + base64.StdEncoding.EncodeToString([]byte("myuser:mypassword:withcolon")), + ExpectedCalled: true, + ExpectedUsername: "myuser", + ExpectedPassword: "mypassword:withcolon", + }, + "password auth returned user": { + Header: "Basic " + base64.StdEncoding.EncodeToString([]byte("myuser:mypw")), + Password: testPassword{User: &user.DefaultInfo{Name: "returneduser"}, OK: true}, + ExpectedCalled: true, + ExpectedUsername: "myuser", + ExpectedPassword: "mypw", + ExpectedUser: "returneduser", + ExpectedOK: true, + }, + "password auth returned error": { + Header: "Basic " + base64.StdEncoding.EncodeToString([]byte("myuser:mypw")), + Password: testPassword{Err: errors.New("auth error")}, + ExpectedCalled: true, + ExpectedUsername: "myuser", + ExpectedPassword: "mypw", + ExpectedErr: true, + }, + } + + for k, testCase := range testCases { + password := testCase.Password + auth := authenticator.Request(New(&password)) + + req, _ := http.NewRequest("GET", "/", nil) + if testCase.Header != "" { + req.Header.Set("Authorization", testCase.Header) + } + + user, ok, err := auth.AuthenticateRequest(req) + + if testCase.ExpectedCalled != password.Called { + t.Fatalf("%s: Expected called=%v, got %v", k, testCase.ExpectedCalled, password.Called) + continue + } + if testCase.ExpectedUsername != password.Username { + t.Fatalf("%s: Expected called with username=%v, got %v", k, testCase.ExpectedUsername, password.Username) + continue + } + if testCase.ExpectedPassword != password.Password { + t.Fatalf("%s: Expected called with password=%v, got %v", k, testCase.ExpectedPassword, password.Password) + continue + } + + if testCase.ExpectedErr != (err != nil) { + t.Fatalf("%s: Expected err=%v, got err=%v", k, testCase.ExpectedErr, err) + continue + } + if testCase.ExpectedOK != ok { + t.Fatalf("%s: Expected ok=%v, got ok=%v", k, testCase.ExpectedOK, ok) + continue + } + if testCase.ExpectedUser != "" && testCase.ExpectedUser != user.GetName() { + t.Fatalf("%s: Expected user.GetName()=%v, got %v", k, testCase.ExpectedUser, user.GetName()) + continue + } + } +} diff --git a/plugin/pkg/auth/doc.go b/plugin/pkg/auth/doc.go new file mode 100644 index 00000000000..213fccd4304 --- /dev/null +++ b/plugin/pkg/auth/doc.go @@ -0,0 +1,18 @@ +/* +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 auth contains implementations for interfaces in the pkg/auth package +package auth