Commit fa0dd46a authored by Michal Rostecki's avatar Michal Rostecki

Return (bool, error) in Authorizer.Authorize()

Before this change, Authorize() method was just returning an error, regardless of whether the user is unauthorized or whether there is some other unrelated error. Returning boolean with information about user authorization and error (which should be unrelated to the authorization) separately will make it easier to debug. Fixes #27974
parent 524c5b53
...@@ -42,8 +42,8 @@ type Attributes struct { ...@@ -42,8 +42,8 @@ type Attributes struct {
// It is useful in tests and when using kubernetes in an open manner. // It is useful in tests and when using kubernetes in an open manner.
type alwaysAllowAuthorizer struct{} type alwaysAllowAuthorizer struct{}
func (alwaysAllowAuthorizer) Authorize(a authorizer.Attributes) (err error) { func (alwaysAllowAuthorizer) Authorize(a authorizer.Attributes) (authorized bool, reason string, err error) {
return nil return true, "", nil
} }
func NewAlwaysAllowAuthorizer() authorizer.Authorizer { func NewAlwaysAllowAuthorizer() authorizer.Authorizer {
...@@ -55,14 +55,27 @@ func NewAlwaysAllowAuthorizer() authorizer.Authorizer { ...@@ -55,14 +55,27 @@ func NewAlwaysAllowAuthorizer() authorizer.Authorizer {
// It is useful in unit tests to force an operation to be forbidden. // It is useful in unit tests to force an operation to be forbidden.
type alwaysDenyAuthorizer struct{} type alwaysDenyAuthorizer struct{}
func (alwaysDenyAuthorizer) Authorize(a authorizer.Attributes) (err error) { func (alwaysDenyAuthorizer) Authorize(a authorizer.Attributes) (authorized bool, reason string, err error) {
return errors.New("Everything is forbidden.") return false, "Everything is forbidden.", nil
} }
func NewAlwaysDenyAuthorizer() authorizer.Authorizer { func NewAlwaysDenyAuthorizer() authorizer.Authorizer {
return new(alwaysDenyAuthorizer) return new(alwaysDenyAuthorizer)
} }
// alwaysFailAuthorizer is an implementation of authorizer.Attributes
// which always says no to an authorization request.
// It is useful in unit tests to force an operation to fail with error.
type alwaysFailAuthorizer struct{}
func (alwaysFailAuthorizer) Authorize(a authorizer.Attributes) (authorized bool, reason string, err error) {
return false, "", errors.New("Authorization failure.")
}
func NewAlwaysFailAuthorizer() authorizer.Authorizer {
return new(alwaysFailAuthorizer)
}
const ( const (
ModeAlwaysAllow string = "AlwaysAllow" ModeAlwaysAllow string = "AlwaysAllow"
ModeAlwaysDeny string = "AlwaysDeny" ModeAlwaysDeny string = "AlwaysDeny"
......
...@@ -24,8 +24,8 @@ import ( ...@@ -24,8 +24,8 @@ import (
// and always return nil. // and always return nil.
func TestNewAlwaysAllowAuthorizer(t *testing.T) { func TestNewAlwaysAllowAuthorizer(t *testing.T) {
aaa := NewAlwaysAllowAuthorizer() aaa := NewAlwaysAllowAuthorizer()
if result := aaa.Authorize(nil); result != nil { if authorized, _, _ := aaa.Authorize(nil); !authorized {
t.Errorf("AlwaysAllowAuthorizer.Authorize did not return nil. (%s)", result) t.Errorf("AlwaysAllowAuthorizer.Authorize did not authorize successfully.")
} }
} }
...@@ -33,7 +33,7 @@ func TestNewAlwaysAllowAuthorizer(t *testing.T) { ...@@ -33,7 +33,7 @@ func TestNewAlwaysAllowAuthorizer(t *testing.T) {
// and always return an error as everything is forbidden. // and always return an error as everything is forbidden.
func TestNewAlwaysDenyAuthorizer(t *testing.T) { func TestNewAlwaysDenyAuthorizer(t *testing.T) {
ada := NewAlwaysDenyAuthorizer() ada := NewAlwaysDenyAuthorizer()
if result := ada.Authorize(nil); result == nil { if authorized, _, _ := ada.Authorize(nil); authorized {
t.Errorf("AlwaysDenyAuthorizer.Authorize returned nil instead of error.") t.Errorf("AlwaysDenyAuthorizer.Authorize returned nil instead of error.")
} }
} }
......
...@@ -88,6 +88,13 @@ func forbidden(w http.ResponseWriter, req *http.Request) { ...@@ -88,6 +88,13 @@ func forbidden(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "Forbidden: %#v", req.RequestURI) fmt.Fprintf(w, "Forbidden: %#v", req.RequestURI)
} }
// internalError renders a simple internal error
func internalError(w http.ResponseWriter, req *http.Request, err error) {
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(w, "Internal Server Error: %#v", req.RequestURI)
runtime.HandleError(err)
}
// errAPIPrefixNotFound indicates that a RequestInfo resolution failed because the request isn't under // errAPIPrefixNotFound indicates that a RequestInfo resolution failed because the request isn't under
// any known API prefixes // any known API prefixes
type errAPIPrefixNotFound struct { type errAPIPrefixNotFound struct {
......
...@@ -446,8 +446,13 @@ func WithImpersonation(handler http.Handler, requestContextMapper api.RequestCon ...@@ -446,8 +446,13 @@ func WithImpersonation(handler http.Handler, requestContextMapper api.RequestCon
actingAsAttributes.Name = name actingAsAttributes.Name = name
} }
err := a.Authorize(actingAsAttributes) authorized, reason, err := a.Authorize(actingAsAttributes)
if err != nil { if err != nil {
internalError(w, req, err)
return
}
if !authorized {
glog.V(4).Infof("Forbidden: %#v, Reason: %s", req.RequestURI, reason)
forbidden(w, req) forbidden(w, req)
return return
} }
...@@ -480,12 +485,17 @@ func WithImpersonation(handler http.Handler, requestContextMapper api.RequestCon ...@@ -480,12 +485,17 @@ func WithImpersonation(handler http.Handler, requestContextMapper api.RequestCon
// 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.GetAttribs(req)) authorized, reason, err := a.Authorize(getAttribs.GetAttribs(req))
if err == nil { if err != nil {
handler.ServeHTTP(w, req) internalError(w, req, err)
return return
} }
forbidden(w, req) if !authorized {
glog.V(4).Infof("Forbidden: %#v, Reason: %s", req.RequestURI, reason)
forbidden(w, req)
return
}
handler.ServeHTTP(w, req)
}) })
} }
......
...@@ -21,7 +21,6 @@ package abac ...@@ -21,7 +21,6 @@ package abac
import ( import (
"bufio" "bufio"
"errors"
"fmt" "fmt"
"os" "os"
"strings" "strings"
...@@ -222,13 +221,13 @@ func resourceMatches(p api.Policy, a authorizer.Attributes) bool { ...@@ -222,13 +221,13 @@ func resourceMatches(p api.Policy, a authorizer.Attributes) bool {
} }
// Authorizer implements authorizer.Authorize // Authorizer implements authorizer.Authorize
func (pl policyList) Authorize(a authorizer.Attributes) error { func (pl policyList) Authorize(a authorizer.Attributes) (bool, string, error) {
for _, p := range pl { for _, p := range pl {
if matches(*p, a) { if matches(*p, a) {
return nil return true, "", nil
} }
} }
return errors.New("No policy matched.") return false, "No policy matched.", nil
// TODO: Benchmark how much time policy matching takes with a medium size // TODO: Benchmark how much time policy matching takes with a medium size
// policy file, compared to other steps such as encoding/decoding. // policy file, compared to other steps such as encoding/decoding.
// Then, add Caching only if needed. // Then, add Caching only if needed.
......
...@@ -130,12 +130,11 @@ func TestAuthorizeV0(t *testing.T) { ...@@ -130,12 +130,11 @@ func TestAuthorizeV0(t *testing.T) {
ResourceRequest: len(tc.NS) > 0 || len(tc.Resource) > 0, ResourceRequest: len(tc.NS) > 0 || len(tc.Resource) > 0,
} }
err := a.Authorize(attr) authorized, _, _ := a.Authorize(attr)
actualAllow := bool(err == nil) if tc.ExpectAllow != authorized {
if tc.ExpectAllow != actualAllow {
t.Logf("tc: %v -> attr %v", tc, attr) t.Logf("tc: %v -> attr %v", tc, attr)
t.Errorf("%d: Expected allowed=%v but actually allowed=%v\n\t%v", t.Errorf("%d: Expected allowed=%v but actually allowed=%v\n\t%v",
i, tc.ExpectAllow, actualAllow, tc) i, tc.ExpectAllow, authorized, tc)
} }
} }
} }
...@@ -250,11 +249,10 @@ func TestAuthorizeV1beta1(t *testing.T) { ...@@ -250,11 +249,10 @@ func TestAuthorizeV1beta1(t *testing.T) {
Path: tc.Path, Path: tc.Path,
} }
// t.Logf("tc %2v: %v -> attr %v", i, tc, attr) // t.Logf("tc %2v: %v -> attr %v", i, tc, attr)
err := a.Authorize(attr) authorized, _, _ := a.Authorize(attr)
actualAllow := bool(err == nil) if tc.ExpectAllow != authorized {
if tc.ExpectAllow != actualAllow {
t.Errorf("%d: Expected allowed=%v but actually allowed=%v, for case %+v & %+v", t.Errorf("%d: Expected allowed=%v but actually allowed=%v, for case %+v & %+v",
i, tc.ExpectAllow, actualAllow, tc, attr) i, tc.ExpectAllow, authorized, tc, attr)
} }
} }
} }
......
...@@ -67,12 +67,12 @@ type Attributes interface { ...@@ -67,12 +67,12 @@ type Attributes interface {
// zero or more calls to methods of the Attributes interface. It returns nil when an action is // zero or more calls to methods of the Attributes interface. It returns nil when an action is
// authorized, otherwise it returns an error. // authorized, otherwise it returns an error.
type Authorizer interface { type Authorizer interface {
Authorize(a Attributes) (err error) Authorize(a Attributes) (authorized bool, reason string, err error)
} }
type AuthorizerFunc func(a Attributes) error type AuthorizerFunc func(a Attributes) (bool, string, error)
func (f AuthorizerFunc) Authorize(a Attributes) error { func (f AuthorizerFunc) Authorize(a Attributes) (bool, string, error) {
return f(a) return f(a)
} }
......
...@@ -17,6 +17,8 @@ limitations under the License. ...@@ -17,6 +17,8 @@ limitations under the License.
package union package union
import ( import (
"strings"
"k8s.io/kubernetes/pkg/auth/authorizer" "k8s.io/kubernetes/pkg/auth/authorizer"
utilerrors "k8s.io/kubernetes/pkg/util/errors" utilerrors "k8s.io/kubernetes/pkg/util/errors"
) )
...@@ -30,16 +32,26 @@ func New(authorizationHandlers ...authorizer.Authorizer) authorizer.Authorizer { ...@@ -30,16 +32,26 @@ func New(authorizationHandlers ...authorizer.Authorizer) authorizer.Authorizer {
} }
// Authorizes against a chain of authorizer.Authorizer objects and returns nil if successful and returns error if unsuccessful // Authorizes against a chain of authorizer.Authorizer objects and returns nil if successful and returns error if unsuccessful
func (authzHandler unionAuthzHandler) Authorize(a authorizer.Attributes) error { func (authzHandler unionAuthzHandler) Authorize(a authorizer.Attributes) (bool, string, error) {
var errlist []error var (
errlist []error
reasonlist []string
)
for _, currAuthzHandler := range authzHandler { for _, currAuthzHandler := range authzHandler {
err := currAuthzHandler.Authorize(a) authorized, reason, err := currAuthzHandler.Authorize(a)
if err != nil { if err != nil {
errlist = append(errlist, err) errlist = append(errlist, err)
continue continue
} }
return nil if !authorized {
if reason != "" {
reasonlist = append(reasonlist, reason)
}
continue
}
return true, reason, nil
} }
return utilerrors.NewAggregate(errlist) return false, strings.Join(reasonlist, "\n"), utilerrors.NewAggregate(errlist)
} }
...@@ -17,7 +17,7 @@ limitations under the License. ...@@ -17,7 +17,7 @@ limitations under the License.
package union package union
import ( import (
"errors" "fmt"
"testing" "testing"
"k8s.io/kubernetes/pkg/auth/authorizer" "k8s.io/kubernetes/pkg/auth/authorizer"
...@@ -28,15 +28,14 @@ type mockAuthzHandler struct { ...@@ -28,15 +28,14 @@ type mockAuthzHandler struct {
err error err error
} }
func (mock *mockAuthzHandler) Authorize(a authorizer.Attributes) error { func (mock *mockAuthzHandler) Authorize(a authorizer.Attributes) (bool, string, error) {
if mock.err != nil { if mock.err != nil {
return mock.err return false, "", mock.err
} }
if !mock.isAuthorized { if !mock.isAuthorized {
return errors.New("Request unauthorized") return false, "", nil
} else {
return nil
} }
return true, "", nil
} }
func TestAuthorizationSecondPasses(t *testing.T) { func TestAuthorizationSecondPasses(t *testing.T) {
...@@ -44,9 +43,9 @@ func TestAuthorizationSecondPasses(t *testing.T) { ...@@ -44,9 +43,9 @@ func TestAuthorizationSecondPasses(t *testing.T) {
handler2 := &mockAuthzHandler{isAuthorized: true} handler2 := &mockAuthzHandler{isAuthorized: true}
authzHandler := New(handler1, handler2) authzHandler := New(handler1, handler2)
err := authzHandler.Authorize(nil) authorized, _, _ := authzHandler.Authorize(nil)
if err != nil { if !authorized {
t.Errorf("Unexpected error: %v", err) t.Errorf("Unexpected authorization failure")
} }
} }
...@@ -55,9 +54,9 @@ func TestAuthorizationFirstPasses(t *testing.T) { ...@@ -55,9 +54,9 @@ func TestAuthorizationFirstPasses(t *testing.T) {
handler2 := &mockAuthzHandler{isAuthorized: false} handler2 := &mockAuthzHandler{isAuthorized: false}
authzHandler := New(handler1, handler2) authzHandler := New(handler1, handler2)
err := authzHandler.Authorize(nil) authorized, _, _ := authzHandler.Authorize(nil)
if err != nil { if !authorized {
t.Errorf("Unexpected error: %v", err) t.Errorf("Unexpected authorization failure")
} }
} }
...@@ -66,7 +65,18 @@ func TestAuthorizationNonePasses(t *testing.T) { ...@@ -66,7 +65,18 @@ func TestAuthorizationNonePasses(t *testing.T) {
handler2 := &mockAuthzHandler{isAuthorized: false} handler2 := &mockAuthzHandler{isAuthorized: false}
authzHandler := New(handler1, handler2) authzHandler := New(handler1, handler2)
err := authzHandler.Authorize(nil) authorized, _, _ := authzHandler.Authorize(nil)
if authorized {
t.Errorf("Expected failed authorization")
}
}
func TestAuthorizationError(t *testing.T) {
handler1 := &mockAuthzHandler{err: fmt.Errorf("foo")}
handler2 := &mockAuthzHandler{err: fmt.Errorf("foo")}
authzHandler := New(handler1, handler2)
_, _, err := authzHandler.Authorize(nil)
if err == nil { if err == nil {
t.Errorf("Expected error: %v", err) t.Errorf("Expected error: %v", err)
} }
......
...@@ -221,8 +221,15 @@ func (s *Server) InstallAuthFilter() { ...@@ -221,8 +221,15 @@ func (s *Server) InstallAuthFilter() {
attrs := s.auth.GetRequestAttributes(u, req.Request) attrs := s.auth.GetRequestAttributes(u, req.Request)
// Authorize // Authorize
if err := s.auth.Authorize(attrs); err != nil { authorized, reason, err := s.auth.Authorize(attrs)
msg := fmt.Sprintf("Forbidden (user=%s, verb=%s, namespace=%s, resource=%s)", u.GetName(), attrs.GetVerb(), attrs.GetNamespace(), attrs.GetResource()) if err != nil {
msg := fmt.Sprintf("Error (user=%s, verb=%s, namespace=%s, resource=%s)", u.GetName(), attrs.GetVerb(), attrs.GetNamespace(), attrs.GetResource())
glog.Errorf(msg, err)
resp.WriteErrorString(http.StatusInternalServerError, msg)
return
}
if !authorized {
msg := fmt.Sprintf("Forbidden (reason=%s, user=%s, verb=%s, namespace=%s, resource=%s)", reason, u.GetName(), attrs.GetVerb(), attrs.GetNamespace(), attrs.GetResource())
glog.V(2).Info(msg) glog.V(2).Info(msg)
resp.WriteErrorString(http.StatusForbidden, msg) resp.WriteErrorString(http.StatusForbidden, msg)
return return
......
...@@ -161,7 +161,7 @@ func (fk *fakeKubelet) ListVolumesForPod(podUID types.UID) (map[string]volume.Vo ...@@ -161,7 +161,7 @@ func (fk *fakeKubelet) ListVolumesForPod(podUID types.UID) (map[string]volume.Vo
type fakeAuth struct { type fakeAuth struct {
authenticateFunc func(*http.Request) (user.Info, bool, error) authenticateFunc func(*http.Request) (user.Info, bool, error)
attributesFunc func(user.Info, *http.Request) authorizer.Attributes attributesFunc func(user.Info, *http.Request) authorizer.Attributes
authorizeFunc func(authorizer.Attributes) (err error) authorizeFunc func(authorizer.Attributes) (authorized bool, reason string, err error)
} }
func (f *fakeAuth) AuthenticateRequest(req *http.Request) (user.Info, bool, error) { func (f *fakeAuth) AuthenticateRequest(req *http.Request) (user.Info, bool, error) {
...@@ -170,7 +170,7 @@ func (f *fakeAuth) AuthenticateRequest(req *http.Request) (user.Info, bool, erro ...@@ -170,7 +170,7 @@ func (f *fakeAuth) AuthenticateRequest(req *http.Request) (user.Info, bool, erro
func (f *fakeAuth) GetRequestAttributes(u user.Info, req *http.Request) authorizer.Attributes { func (f *fakeAuth) GetRequestAttributes(u user.Info, req *http.Request) authorizer.Attributes {
return f.attributesFunc(u, req) return f.attributesFunc(u, req)
} }
func (f *fakeAuth) Authorize(a authorizer.Attributes) (err error) { func (f *fakeAuth) Authorize(a authorizer.Attributes) (authorized bool, reason string, err error) {
return f.authorizeFunc(a) return f.authorizeFunc(a)
} }
...@@ -204,8 +204,8 @@ func newServerTest() *serverTestFramework { ...@@ -204,8 +204,8 @@ func newServerTest() *serverTestFramework {
attributesFunc: func(u user.Info, req *http.Request) authorizer.Attributes { attributesFunc: func(u user.Info, req *http.Request) authorizer.Attributes {
return &authorizer.AttributesRecord{User: u} return &authorizer.AttributesRecord{User: u}
}, },
authorizeFunc: func(a authorizer.Attributes) (err error) { authorizeFunc: func(a authorizer.Attributes) (authorized bool, reason string, err error) {
return nil return true, "", nil
}, },
} }
server := NewServer( server := NewServer(
...@@ -626,12 +626,12 @@ func TestAuthFilters(t *testing.T) { ...@@ -626,12 +626,12 @@ func TestAuthFilters(t *testing.T) {
} }
return expectedAttributes return expectedAttributes
} }
fw.fakeAuth.authorizeFunc = func(a authorizer.Attributes) (err error) { fw.fakeAuth.authorizeFunc = func(a authorizer.Attributes) (authorized bool, reason string, err error) {
calledAuthorize = true calledAuthorize = true
if a != expectedAttributes { if a != expectedAttributes {
t.Fatalf("%s: expected attributes %v, got %v", tc.Path, expectedAttributes, a) t.Fatalf("%s: expected attributes %v, got %v", tc.Path, expectedAttributes, a)
} }
return errors.New("Forbidden") return false, "", nil
} }
req, err := http.NewRequest(tc.Method, fw.testHTTPServer.URL+tc.Path, nil) req, err := http.NewRequest(tc.Method, fw.testHTTPServer.URL+tc.Path, nil)
...@@ -665,6 +665,44 @@ func TestAuthFilters(t *testing.T) { ...@@ -665,6 +665,44 @@ func TestAuthFilters(t *testing.T) {
} }
} }
func TestAuthenticationError(t *testing.T) {
var (
expectedUser = &user.DefaultInfo{Name: "test"}
expectedAttributes = &authorizer.AttributesRecord{User: expectedUser}
calledAuthenticate = false
calledAuthorize = false
calledAttributes = false
)
fw := newServerTest()
defer fw.testHTTPServer.Close()
fw.fakeAuth.authenticateFunc = func(req *http.Request) (user.Info, bool, error) {
calledAuthenticate = true
return expectedUser, true, nil
}
fw.fakeAuth.attributesFunc = func(u user.Info, req *http.Request) authorizer.Attributes {
calledAttributes = true
return expectedAttributes
}
fw.fakeAuth.authorizeFunc = func(a authorizer.Attributes) (authorized bool, reason string, err error) {
calledAuthorize = true
return false, "", errors.New("Failed")
}
assertHealthFails(t, fw.testHTTPServer.URL+"/healthz", http.StatusInternalServerError)
if !calledAuthenticate {
t.Fatalf("Authenticate was not called")
}
if !calledAttributes {
t.Fatalf("Attributes was not called")
}
if !calledAuthorize {
t.Fatalf("Authorize was not called")
}
}
func TestAuthenticationFailure(t *testing.T) { func TestAuthenticationFailure(t *testing.T) {
var ( var (
expectedUser = &user.DefaultInfo{Name: "test"} expectedUser = &user.DefaultInfo{Name: "test"}
...@@ -685,9 +723,9 @@ func TestAuthenticationFailure(t *testing.T) { ...@@ -685,9 +723,9 @@ func TestAuthenticationFailure(t *testing.T) {
calledAttributes = true calledAttributes = true
return expectedAttributes return expectedAttributes
} }
fw.fakeAuth.authorizeFunc = func(a authorizer.Attributes) (err error) { fw.fakeAuth.authorizeFunc = func(a authorizer.Attributes) (authorized bool, reason string, err error) {
calledAuthorize = true calledAuthorize = true
return errors.New("not allowed") return false, "", nil
} }
assertHealthFails(t, fw.testHTTPServer.URL+"/healthz", http.StatusUnauthorized) assertHealthFails(t, fw.testHTTPServer.URL+"/healthz", http.StatusUnauthorized)
...@@ -723,9 +761,9 @@ func TestAuthorizationSuccess(t *testing.T) { ...@@ -723,9 +761,9 @@ func TestAuthorizationSuccess(t *testing.T) {
calledAttributes = true calledAttributes = true
return expectedAttributes return expectedAttributes
} }
fw.fakeAuth.authorizeFunc = func(a authorizer.Attributes) (err error) { fw.fakeAuth.authorizeFunc = func(a authorizer.Attributes) (authorized bool, reason string, err error) {
calledAuthorize = true calledAuthorize = true
return nil return true, "", nil
} }
assertHealthIsOk(t, fw.testHTTPServer.URL+"/healthz") assertHealthIsOk(t, fw.testHTTPServer.URL+"/healthz")
......
...@@ -34,9 +34,9 @@ type RBACAuthorizer struct { ...@@ -34,9 +34,9 @@ type RBACAuthorizer struct {
authorizationRuleResolver validation.AuthorizationRuleResolver authorizationRuleResolver validation.AuthorizationRuleResolver
} }
func (r *RBACAuthorizer) Authorize(attr authorizer.Attributes) error { func (r *RBACAuthorizer) Authorize(attr authorizer.Attributes) (bool, string, error) {
if r.superUser != "" && attr.GetUser() != nil && attr.GetUser().GetName() == r.superUser { if r.superUser != "" && attr.GetUser() != nil && attr.GetUser().GetName() == r.superUser {
return nil return true, "", nil
} }
ctx := api.WithNamespace(api.WithUser(api.NewContext(), attr.GetUser()), attr.GetNamespace()) ctx := api.WithNamespace(api.WithUser(api.NewContext(), attr.GetUser()), attr.GetNamespace())
...@@ -57,7 +57,13 @@ func (r *RBACAuthorizer) Authorize(attr authorizer.Attributes) error { ...@@ -57,7 +57,13 @@ func (r *RBACAuthorizer) Authorize(attr authorizer.Attributes) error {
} }
} }
return validation.ConfirmNoEscalation(ctx, r.authorizationRuleResolver, []rbac.PolicyRule{requestedRule}) // TODO(nhlfr): Try to find more lightweight way to check attributes than escalation checks.
err := validation.ConfirmNoEscalation(ctx, r.authorizationRuleResolver, []rbac.PolicyRule{requestedRule})
if err != nil {
return false, err.Error(), nil
}
return true, "", nil
} }
func New(roleRegistry role.Registry, roleBindingRegistry rolebinding.Registry, clusterRoleRegistry clusterrole.Registry, clusterRoleBindingRegistry clusterrolebinding.Registry, superUser string) *RBACAuthorizer { func New(roleRegistry role.Registry, roleBindingRegistry rolebinding.Registry, clusterRoleRegistry clusterrole.Registry, clusterRoleBindingRegistry clusterrolebinding.Registry, superUser string) *RBACAuthorizer {
......
...@@ -192,13 +192,13 @@ func TestAuthorizer(t *testing.T) { ...@@ -192,13 +192,13 @@ func TestAuthorizer(t *testing.T) {
ruleResolver := validation.NewTestRuleResolver(tt.roles, tt.roleBindings, tt.clusterRoles, tt.clusterRoleBindings) ruleResolver := validation.NewTestRuleResolver(tt.roles, tt.roleBindings, tt.clusterRoles, tt.clusterRoleBindings)
a := RBACAuthorizer{tt.superUser, ruleResolver} a := RBACAuthorizer{tt.superUser, ruleResolver}
for _, attr := range tt.shouldPass { for _, attr := range tt.shouldPass {
if err := a.Authorize(attr); err != nil { if authorized, _, _ := a.Authorize(attr); !authorized {
t.Errorf("case %d: incorrectly restricted %s: %T %v", i, attr, err, err) t.Errorf("case %d: incorrectly restricted %s", i, attr)
} }
} }
for _, attr := range tt.shouldFail { for _, attr := range tt.shouldFail {
if err := a.Authorize(attr); err == nil { if authorized, _, _ := a.Authorize(attr); authorized {
t.Errorf("case %d: incorrectly passed %s", i, attr) t.Errorf("case %d: incorrectly passed %s", i, attr)
} }
} }
......
...@@ -19,7 +19,6 @@ package webhook ...@@ -19,7 +19,6 @@ package webhook
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"time" "time"
...@@ -128,7 +127,7 @@ func newWithBackoff(kubeConfigFile string, authorizedTTL, unauthorizedTTL, initi ...@@ -128,7 +127,7 @@ func newWithBackoff(kubeConfigFile string, authorizedTTL, unauthorizedTTL, initi
// } // }
// } // }
// //
func (w *WebhookAuthorizer) Authorize(attr authorizer.Attributes) (err error) { func (w *WebhookAuthorizer) Authorize(attr authorizer.Attributes) (authorized bool, reason string, err error) {
r := &v1beta1.SubjectAccessReview{} r := &v1beta1.SubjectAccessReview{}
if user := attr.GetUser(); user != nil { if user := attr.GetUser(); user != nil {
r.Spec = v1beta1.SubjectAccessReviewSpec{ r.Spec = v1beta1.SubjectAccessReviewSpec{
...@@ -156,7 +155,7 @@ func (w *WebhookAuthorizer) Authorize(attr authorizer.Attributes) (err error) { ...@@ -156,7 +155,7 @@ func (w *WebhookAuthorizer) Authorize(attr authorizer.Attributes) (err error) {
} }
key, err := json.Marshal(r.Spec) key, err := json.Marshal(r.Spec)
if err != nil { if err != nil {
return err return false, "", err
} }
if entry, ok := w.responseCache.Get(string(key)); ok { if entry, ok := w.responseCache.Get(string(key)); ok {
r.Status = entry.(v1beta1.SubjectAccessReviewStatus) r.Status = entry.(v1beta1.SubjectAccessReviewStatus)
...@@ -167,14 +166,17 @@ func (w *WebhookAuthorizer) Authorize(attr authorizer.Attributes) (err error) { ...@@ -167,14 +166,17 @@ func (w *WebhookAuthorizer) Authorize(attr authorizer.Attributes) (err error) {
if err := result.Error(); err != nil { if err := result.Error(); err != nil {
// An error here indicates bad configuration or an outage. Log for debugging. // An error here indicates bad configuration or an outage. Log for debugging.
glog.Errorf("Failed to make webhook authorizer request: %v", err) glog.Errorf("Failed to make webhook authorizer request: %v", err)
return err return false, "", err
} }
var statusCode int var statusCode int
if result.StatusCode(&statusCode); statusCode < 200 || statusCode >= 300 { result.StatusCode(&statusCode)
return fmt.Errorf("Error contacting webhook: %d", statusCode) switch {
case statusCode < 200,
statusCode >= 300:
return false, "", fmt.Errorf("Error contacting webhook: %d", statusCode)
} }
if err := result.Into(r); err != nil { if err := result.Into(r); err != nil {
return err return false, "", err
} }
if r.Status.Allowed { if r.Status.Allowed {
w.responseCache.Add(string(key), r.Status, w.authorizedTTL) w.responseCache.Add(string(key), r.Status, w.authorizedTTL)
...@@ -182,11 +184,5 @@ func (w *WebhookAuthorizer) Authorize(attr authorizer.Attributes) (err error) { ...@@ -182,11 +184,5 @@ func (w *WebhookAuthorizer) Authorize(attr authorizer.Attributes) (err error) {
w.responseCache.Add(string(key), r.Status, w.unauthorizedTTL) w.responseCache.Add(string(key), r.Status, w.unauthorizedTTL)
} }
} }
if r.Status.Allowed { return r.Status.Allowed, r.Status.Reason, nil
return nil
}
if r.Status.Reason != "" {
return errors.New(r.Status.Reason)
}
return errors.New("unauthorized")
} }
...@@ -299,22 +299,25 @@ func TestTLSConfig(t *testing.T) { ...@@ -299,22 +299,25 @@ func TestTLSConfig(t *testing.T) {
test string test string
clientCert, clientKey, clientCA []byte clientCert, clientKey, clientCA []byte
serverCert, serverKey, serverCA []byte serverCert, serverKey, serverCA []byte
wantErr bool wantAuth, wantErr bool
}{ }{
{ {
test: "TLS setup between client and server", test: "TLS setup between client and server",
clientCert: clientCert, clientKey: clientKey, clientCA: caCert, clientCert: clientCert, clientKey: clientKey, clientCA: caCert,
serverCert: serverCert, serverKey: serverKey, serverCA: caCert, serverCert: serverCert, serverKey: serverKey, serverCA: caCert,
wantAuth: true,
}, },
{ {
test: "Server does not require client auth", test: "Server does not require client auth",
clientCA: caCert, clientCA: caCert,
serverCert: serverCert, serverKey: serverKey, serverCert: serverCert, serverKey: serverKey,
wantAuth: true,
}, },
{ {
test: "Server does not require client auth, client provides it", test: "Server does not require client auth, client provides it",
clientCert: clientCert, clientKey: clientKey, clientCA: caCert, clientCert: clientCert, clientKey: clientKey, clientCA: caCert,
serverCert: serverCert, serverKey: serverKey, serverCert: serverCert, serverKey: serverKey,
wantAuth: true,
}, },
{ {
test: "Client does not trust server", test: "Client does not trust server",
...@@ -357,7 +360,16 @@ func TestTLSConfig(t *testing.T) { ...@@ -357,7 +360,16 @@ func TestTLSConfig(t *testing.T) {
// Allow all and see if we get an error. // Allow all and see if we get an error.
service.Allow() service.Allow()
err = wh.Authorize(attr) authorized, _, err := wh.Authorize(attr)
if tt.wantAuth {
if !authorized {
t.Errorf("expected successful authorization")
}
} else {
if authorized {
t.Errorf("expected failed authorization")
}
}
if tt.wantErr { if tt.wantErr {
if err == nil { if err == nil {
t.Errorf("expected error making authorization request: %v", err) t.Errorf("expected error making authorization request: %v", err)
...@@ -370,7 +382,7 @@ func TestTLSConfig(t *testing.T) { ...@@ -370,7 +382,7 @@ func TestTLSConfig(t *testing.T) {
} }
service.Deny() service.Deny()
if err := wh.Authorize(attr); err == nil { if authorized, _, _ := wh.Authorize(attr); authorized {
t.Errorf("%s: incorrectly authorized with DenyAll policy", tt.test) t.Errorf("%s: incorrectly authorized with DenyAll policy", tt.test)
} }
}() }()
...@@ -473,8 +485,12 @@ func TestWebhook(t *testing.T) { ...@@ -473,8 +485,12 @@ func TestWebhook(t *testing.T) {
} }
for i, tt := range tests { for i, tt := range tests {
if err := wh.Authorize(tt.attr); err != nil { authorized, _, err := wh.Authorize(tt.attr)
t.Errorf("case %d: authorization failed: %v", i, err) if err != nil {
t.Fatal(err)
}
if !authorized {
t.Errorf("case %d: authorization failed", i)
continue continue
} }
...@@ -489,6 +505,34 @@ func TestWebhook(t *testing.T) { ...@@ -489,6 +505,34 @@ func TestWebhook(t *testing.T) {
} }
} }
type webhookCacheTestCase struct {
statusCode int
expectedErr bool
expectedAuthorized bool
expectedCached bool
}
func testWebhookCacheCases(t *testing.T, serv *mockService, wh *WebhookAuthorizer, attr authorizer.AttributesRecord, tests []webhookCacheTestCase) {
for _, test := range tests {
serv.statusCode = test.statusCode
authorized, _, err := wh.Authorize(attr)
if test.expectedErr && err == nil {
t.Errorf("Expected error")
} else if !test.expectedErr && err != nil {
t.Fatal(err)
}
if test.expectedAuthorized && !authorized {
if test.expectedCached {
t.Errorf("Webhook should have successful response cached, but authorizer reported unauthorized.")
} else {
t.Errorf("Webhook returned HTTP %d, but authorizer reported unauthorized.", test.statusCode)
}
} else if !test.expectedAuthorized && authorized {
t.Errorf("Webhook returned HTTP %d, but authorizer reported success.", test.statusCode)
}
}
}
// TestWebhookCache verifies that error responses from the server are not // TestWebhookCache verifies that error responses from the server are not
// cached, but successful responses are. // cached, but successful responses are.
func TestWebhookCache(t *testing.T) { func TestWebhookCache(t *testing.T) {
...@@ -505,36 +549,27 @@ func TestWebhookCache(t *testing.T) { ...@@ -505,36 +549,27 @@ func TestWebhookCache(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
tests := []webhookCacheTestCase{
{statusCode: 500, expectedErr: true, expectedAuthorized: false, expectedCached: false},
{statusCode: 404, expectedErr: true, expectedAuthorized: false, expectedCached: false},
{statusCode: 403, expectedErr: true, expectedAuthorized: false, expectedCached: false},
{statusCode: 401, expectedErr: true, expectedAuthorized: false, expectedCached: false},
{statusCode: 200, expectedErr: false, expectedAuthorized: true, expectedCached: false},
{statusCode: 500, expectedErr: false, expectedAuthorized: true, expectedCached: true},
}
attr := authorizer.AttributesRecord{User: &user.DefaultInfo{Name: "alice"}} attr := authorizer.AttributesRecord{User: &user.DefaultInfo{Name: "alice"}}
serv.allow = true serv.allow = true
serv.statusCode = 500
if err := wh.Authorize(attr); err == nil { testWebhookCacheCases(t, serv, wh, attr, tests)
t.Errorf("Webhook returned HTTP 500, but authorizer reported success.")
}
serv.statusCode = 404
if err := wh.Authorize(attr); err == nil {
t.Errorf("Webhook returned HTTP 404, but authorizer reported success.")
}
serv.statusCode = 200
if err := wh.Authorize(attr); err != nil {
t.Errorf("Webhook returned HTTP 200, but authorizer reported unauthorized.")
}
serv.statusCode = 500
if err := wh.Authorize(attr); err != nil {
t.Errorf("Webhook should have successful response cached, but authorizer reported unauthorized.")
}
// For a different request, webhook should be called again. // For a different request, webhook should be called again.
attr = authorizer.AttributesRecord{User: &user.DefaultInfo{Name: "bob"}} tests = []webhookCacheTestCase{
serv.statusCode = 500 {statusCode: 500, expectedErr: true, expectedAuthorized: false, expectedCached: false},
if err := wh.Authorize(attr); err == nil { {statusCode: 200, expectedErr: false, expectedAuthorized: true, expectedCached: false},
t.Errorf("Webhook returned HTTP 500, but authorizer reported success.") {statusCode: 500, expectedErr: false, expectedAuthorized: true, expectedCached: true},
}
serv.statusCode = 200
if err := wh.Authorize(attr); err != nil {
t.Errorf("Webhook returned HTTP 200, but authorizer reported unauthorized.")
}
serv.statusCode = 500
if err := wh.Authorize(attr); err != nil {
t.Errorf("Webhook should have successful response cached, but authorizer reported unauthorized.")
} }
attr = authorizer.AttributesRecord{User: &user.DefaultInfo{Name: "bob"}}
testWebhookCacheCases(t, serv, wh, attr, tests)
} }
...@@ -25,7 +25,6 @@ package auth ...@@ -25,7 +25,6 @@ package auth
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
...@@ -536,11 +535,11 @@ func TestAuthModeAlwaysDeny(t *testing.T) { ...@@ -536,11 +535,11 @@ func TestAuthModeAlwaysDeny(t *testing.T) {
// TODO(etune): remove this test once a more comprehensive built-in authorizer is implemented. // TODO(etune): remove this test once a more comprehensive built-in authorizer is implemented.
type allowAliceAuthorizer struct{} type allowAliceAuthorizer struct{}
func (allowAliceAuthorizer) Authorize(a authorizer.Attributes) error { func (allowAliceAuthorizer) Authorize(a authorizer.Attributes) (bool, string, error) {
if a.GetUser() != nil && a.GetUser().GetName() == "alice" { if a.GetUser() != nil && a.GetUser().GetName() == "alice" {
return nil return true, "", nil
} }
return errors.New("I can't allow that. Go ask alice.") return false, "I can't allow that. Go ask alice.", nil
} }
// TestAliceNotForbiddenOrUnauthorized tests a user who is known to // TestAliceNotForbiddenOrUnauthorized tests a user who is known to
...@@ -703,24 +702,24 @@ func TestUnknownUserIsUnauthorized(t *testing.T) { ...@@ -703,24 +702,24 @@ func TestUnknownUserIsUnauthorized(t *testing.T) {
type impersonateAuthorizer struct{} type impersonateAuthorizer struct{}
// alice can't act as anyone and bob can't do anything but act-as someone // alice can't act as anyone and bob can't do anything but act-as someone
func (impersonateAuthorizer) Authorize(a authorizer.Attributes) error { func (impersonateAuthorizer) Authorize(a authorizer.Attributes) (bool, string, error) {
// alice can impersonate service accounts and do other actions // alice can impersonate service accounts and do other actions
if a.GetUser() != nil && a.GetUser().GetName() == "alice" && a.GetVerb() == "impersonate" && a.GetResource() == "serviceaccounts" { if a.GetUser() != nil && a.GetUser().GetName() == "alice" && a.GetVerb() == "impersonate" && a.GetResource() == "serviceaccounts" {
return nil return true, "", nil
} }
if a.GetUser() != nil && a.GetUser().GetName() == "alice" && a.GetVerb() != "impersonate" { if a.GetUser() != nil && a.GetUser().GetName() == "alice" && a.GetVerb() != "impersonate" {
return nil return true, "", nil
} }
// bob can impersonate anyone, but that it // bob can impersonate anyone, but that it
if a.GetUser() != nil && a.GetUser().GetName() == "bob" && a.GetVerb() == "impersonate" { if a.GetUser() != nil && a.GetUser().GetName() == "bob" && a.GetVerb() == "impersonate" {
return nil return true, "", nil
} }
// service accounts can do everything // service accounts can do everything
if a.GetUser() != nil && strings.HasPrefix(a.GetUser().GetName(), serviceaccount.ServiceAccountUsernamePrefix) { if a.GetUser() != nil && strings.HasPrefix(a.GetUser().GetName(), serviceaccount.ServiceAccountUsernamePrefix) {
return nil return true, "", nil
} }
return errors.New("I can't allow that. Go ask alice.") return false, "I can't allow that. Go ask alice.", nil
} }
func TestImpersonateIsForbidden(t *testing.T) { func TestImpersonateIsForbidden(t *testing.T) {
...@@ -862,9 +861,9 @@ type trackingAuthorizer struct { ...@@ -862,9 +861,9 @@ type trackingAuthorizer struct {
requestAttributes []authorizer.Attributes requestAttributes []authorizer.Attributes
} }
func (a *trackingAuthorizer) Authorize(attributes authorizer.Attributes) error { func (a *trackingAuthorizer) Authorize(attributes authorizer.Attributes) (bool, string, error) {
a.requestAttributes = append(a.requestAttributes, attributes) a.requestAttributes = append(a.requestAttributes, attributes)
return nil return true, "", nil
} }
// TestAuthorizationAttributeDetermination tests that authorization attributes are built correctly // TestAuthorizationAttributeDetermination tests that authorization attributes are built correctly
......
...@@ -369,7 +369,7 @@ func startServiceAccountTestServer(t *testing.T) (*clientset.Clientset, restclie ...@@ -369,7 +369,7 @@ func startServiceAccountTestServer(t *testing.T) (*clientset.Clientset, restclie
// 1. The "root" user is allowed to do anything // 1. The "root" user is allowed to do anything
// 2. ServiceAccounts named "ro" are allowed read-only operations in their namespace // 2. ServiceAccounts named "ro" are allowed read-only operations in their namespace
// 3. ServiceAccounts named "rw" are allowed any operation in their namespace // 3. ServiceAccounts named "rw" are allowed any operation in their namespace
authorizer := authorizer.AuthorizerFunc(func(attrs authorizer.Attributes) error { authorizer := authorizer.AuthorizerFunc(func(attrs authorizer.Attributes) (bool, string, error) {
username := "" username := ""
if user := attrs.GetUser(); user != nil { if user := attrs.GetUser(); user != nil {
username = user.GetName() username = user.GetName()
...@@ -379,7 +379,7 @@ func startServiceAccountTestServer(t *testing.T) (*clientset.Clientset, restclie ...@@ -379,7 +379,7 @@ func startServiceAccountTestServer(t *testing.T) (*clientset.Clientset, restclie
// If the user is "root"... // If the user is "root"...
if username == rootUserName { if username == rootUserName {
// allow them to do anything // allow them to do anything
return nil return true, "", nil
} }
// If the user is a service account... // If the user is a service account...
...@@ -389,15 +389,15 @@ func startServiceAccountTestServer(t *testing.T) (*clientset.Clientset, restclie ...@@ -389,15 +389,15 @@ func startServiceAccountTestServer(t *testing.T) (*clientset.Clientset, restclie
switch serviceAccountName { switch serviceAccountName {
case readOnlyServiceAccountName: case readOnlyServiceAccountName:
if attrs.IsReadOnly() { if attrs.IsReadOnly() {
return nil return true, "", nil
} }
case readWriteServiceAccountName: case readWriteServiceAccountName:
return nil return true, "", nil
} }
} }
} }
return fmt.Errorf("User %s is denied (ns=%s, readonly=%v, resource=%s)", username, ns, attrs.IsReadOnly(), attrs.GetResource()) return false, fmt.Sprintf("User %s is denied (ns=%s, readonly=%v, resource=%s)", username, ns, attrs.IsReadOnly(), attrs.GetResource()), nil
}) })
// Set up admission plugin to auto-assign serviceaccounts to pods // Set up admission plugin to auto-assign serviceaccounts to pods
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment