Unverified Commit d3307752 authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #53273 from mikedanese/authtristate

Automatic merge from submit-queue. If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. add support for short-circuit deny in union authorizer This change has no behavioral changes. Fixes https://github.com/kubernetes/kubernetes/issues/51862 ```release-note Add support for the webhook authorizer to make a Deny decision that short-circuits the union authorizer and immediately returns Deny. ```
parents ef8746af 90d551a0
...@@ -69542,6 +69542,10 @@ ...@@ -69542,6 +69542,10 @@
"description": "Allowed is required. True if the action would be allowed, false otherwise.", "description": "Allowed is required. True if the action would be allowed, false otherwise.",
"type": "boolean" "type": "boolean"
}, },
"denied": {
"description": "Denied is optional. True if the action would be denied, otherwise false. If both allowed is false and denied is false, then the authorizer has no opinion on whether to authorize the action. Denied may not be true if Allowed is true.",
"type": "boolean"
},
"evaluationError": { "evaluationError": {
"description": "EvaluationError is an indication that some error occurred during the authorization check. It is entirely possible to get an error and be able to continue determine authorization status in spite of it. For instance, RBAC can be missing a role, but enough roles are still present and bound to reason about the request.", "description": "EvaluationError is an indication that some error occurred during the authorization check. It is entirely possible to get an error and be able to continue determine authorization status in spite of it. For instance, RBAC can be missing a role, but enough roles are still present and bound to reason about the request.",
"type": "string" "type": "string"
...@@ -69893,6 +69897,10 @@ ...@@ -69893,6 +69897,10 @@
"description": "Allowed is required. True if the action would be allowed, false otherwise.", "description": "Allowed is required. True if the action would be allowed, false otherwise.",
"type": "boolean" "type": "boolean"
}, },
"denied": {
"description": "Denied is optional. True if the action would be denied, otherwise false. If both allowed is false and denied is false, then the authorizer has no opinion on whether to authorize the action. Denied may not be true if Allowed is true.",
"type": "boolean"
},
"evaluationError": { "evaluationError": {
"description": "EvaluationError is an indication that some error occurred during the authorization check. It is entirely possible to get an error and be able to continue determine authorization status in spite of it. For instance, RBAC can be missing a role, but enough roles are still present and bound to reason about the request.", "description": "EvaluationError is an indication that some error occurred during the authorization check. It is entirely possible to get an error and be able to continue determine authorization status in spite of it. For instance, RBAC can be missing a role, but enough roles are still present and bound to reason about the request.",
"type": "string" "type": "string"
...@@ -637,6 +637,10 @@ ...@@ -637,6 +637,10 @@
"type": "boolean", "type": "boolean",
"description": "Allowed is required. True if the action would be allowed, false otherwise." "description": "Allowed is required. True if the action would be allowed, false otherwise."
}, },
"denied": {
"type": "boolean",
"description": "Denied is optional. True if the action would be denied, otherwise false. If both allowed is false and denied is false, then the authorizer has no opinion on whether to authorize the action. Denied may not be true if Allowed is true."
},
"reason": { "reason": {
"type": "string", "type": "string",
"description": "Reason is optional. It indicates why a request was allowed or denied." "description": "Reason is optional. It indicates why a request was allowed or denied."
......
...@@ -637,6 +637,10 @@ ...@@ -637,6 +637,10 @@
"type": "boolean", "type": "boolean",
"description": "Allowed is required. True if the action would be allowed, false otherwise." "description": "Allowed is required. True if the action would be allowed, false otherwise."
}, },
"denied": {
"type": "boolean",
"description": "Denied is optional. True if the action would be denied, otherwise false. If both allowed is false and denied is false, then the authorizer has no opinion on whether to authorize the action. Denied may not be true if Allowed is true."
},
"reason": { "reason": {
"type": "string", "type": "string",
"description": "Reason is optional. It indicates why a request was allowed or denied." "description": "Reason is optional. It indicates why a request was allowed or denied."
......
...@@ -856,6 +856,13 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } ...@@ -856,6 +856,13 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
</tr> </tr>
<tr> <tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">denied</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Denied is optional. True if the action would be denied, otherwise false. If both allowed is false and denied is false, then the authorizer has no opinion on whether to authorize the action. Denied may not be true if Allowed is true.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">boolean</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">reason</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">reason</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Reason is optional. It indicates why a request was allowed or denied.</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Reason is optional. It indicates why a request was allowed or denied.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
......
...@@ -1100,6 +1100,13 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } ...@@ -1100,6 +1100,13 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
</tr> </tr>
<tr> <tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">denied</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Denied is optional. True if the action would be denied, otherwise false. If both allowed is false and denied is false, then the authorizer has no opinion on whether to authorize the action. Denied may not be true if Allowed is true.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">boolean</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">reason</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">reason</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Reason is optional. It indicates why a request was allowed or denied.</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Reason is optional. It indicates why a request was allowed or denied.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
......
...@@ -142,6 +142,11 @@ type SelfSubjectAccessReviewSpec struct { ...@@ -142,6 +142,11 @@ type SelfSubjectAccessReviewSpec struct {
type SubjectAccessReviewStatus struct { type SubjectAccessReviewStatus struct {
// Allowed is required. True if the action would be allowed, false otherwise. // Allowed is required. True if the action would be allowed, false otherwise.
Allowed bool Allowed bool
// Denied is optional. True if the action would be denied, otherwise
// false. If both allowed is false and denied is false, then the
// authorizer has no opinion on whether to authorize the action. Denied
// may not be true if Allowed is true.
Denied bool
// Reason is optional. It indicates why a request was allowed or denied. // Reason is optional. It indicates why a request was allowed or denied.
Reason string Reason string
// EvaluationError is an indication that some error occurred during the authorization check. // EvaluationError is an indication that some error occurred during the authorization check.
......
...@@ -369,6 +369,7 @@ func Convert_authorization_SubjectAccessReviewSpec_To_v1_SubjectAccessReviewSpec ...@@ -369,6 +369,7 @@ func Convert_authorization_SubjectAccessReviewSpec_To_v1_SubjectAccessReviewSpec
func autoConvert_v1_SubjectAccessReviewStatus_To_authorization_SubjectAccessReviewStatus(in *v1.SubjectAccessReviewStatus, out *authorization.SubjectAccessReviewStatus, s conversion.Scope) error { func autoConvert_v1_SubjectAccessReviewStatus_To_authorization_SubjectAccessReviewStatus(in *v1.SubjectAccessReviewStatus, out *authorization.SubjectAccessReviewStatus, s conversion.Scope) error {
out.Allowed = in.Allowed out.Allowed = in.Allowed
out.Denied = in.Denied
out.Reason = in.Reason out.Reason = in.Reason
out.EvaluationError = in.EvaluationError out.EvaluationError = in.EvaluationError
return nil return nil
...@@ -381,6 +382,7 @@ func Convert_v1_SubjectAccessReviewStatus_To_authorization_SubjectAccessReviewSt ...@@ -381,6 +382,7 @@ func Convert_v1_SubjectAccessReviewStatus_To_authorization_SubjectAccessReviewSt
func autoConvert_authorization_SubjectAccessReviewStatus_To_v1_SubjectAccessReviewStatus(in *authorization.SubjectAccessReviewStatus, out *v1.SubjectAccessReviewStatus, s conversion.Scope) error { func autoConvert_authorization_SubjectAccessReviewStatus_To_v1_SubjectAccessReviewStatus(in *authorization.SubjectAccessReviewStatus, out *v1.SubjectAccessReviewStatus, s conversion.Scope) error {
out.Allowed = in.Allowed out.Allowed = in.Allowed
out.Denied = in.Denied
out.Reason = in.Reason out.Reason = in.Reason
out.EvaluationError = in.EvaluationError out.EvaluationError = in.EvaluationError
return nil return nil
......
...@@ -369,6 +369,7 @@ func Convert_authorization_SubjectAccessReviewSpec_To_v1beta1_SubjectAccessRevie ...@@ -369,6 +369,7 @@ func Convert_authorization_SubjectAccessReviewSpec_To_v1beta1_SubjectAccessRevie
func autoConvert_v1beta1_SubjectAccessReviewStatus_To_authorization_SubjectAccessReviewStatus(in *v1beta1.SubjectAccessReviewStatus, out *authorization.SubjectAccessReviewStatus, s conversion.Scope) error { func autoConvert_v1beta1_SubjectAccessReviewStatus_To_authorization_SubjectAccessReviewStatus(in *v1beta1.SubjectAccessReviewStatus, out *authorization.SubjectAccessReviewStatus, s conversion.Scope) error {
out.Allowed = in.Allowed out.Allowed = in.Allowed
out.Denied = in.Denied
out.Reason = in.Reason out.Reason = in.Reason
out.EvaluationError = in.EvaluationError out.EvaluationError = in.EvaluationError
return nil return nil
...@@ -381,6 +382,7 @@ func Convert_v1beta1_SubjectAccessReviewStatus_To_authorization_SubjectAccessRev ...@@ -381,6 +382,7 @@ func Convert_v1beta1_SubjectAccessReviewStatus_To_authorization_SubjectAccessRev
func autoConvert_authorization_SubjectAccessReviewStatus_To_v1beta1_SubjectAccessReviewStatus(in *authorization.SubjectAccessReviewStatus, out *v1beta1.SubjectAccessReviewStatus, s conversion.Scope) error { func autoConvert_authorization_SubjectAccessReviewStatus_To_v1beta1_SubjectAccessReviewStatus(in *authorization.SubjectAccessReviewStatus, out *v1beta1.SubjectAccessReviewStatus, s conversion.Scope) error {
out.Allowed = in.Allowed out.Allowed = in.Allowed
out.Denied = in.Denied
out.Reason = in.Reason out.Reason = in.Reason
out.EvaluationError = in.EvaluationError out.EvaluationError = in.EvaluationError
return nil return nil
......
...@@ -221,13 +221,13 @@ func resourceMatches(p abac.Policy, a authorizer.Attributes) bool { ...@@ -221,13 +221,13 @@ func resourceMatches(p abac.Policy, a authorizer.Attributes) bool {
} }
// Authorizer implements authorizer.Authorize // Authorizer implements authorizer.Authorize
func (pl policyList) Authorize(a authorizer.Attributes) (bool, string, error) { func (pl policyList) Authorize(a authorizer.Attributes) (authorizer.Decision, string, error) {
for _, p := range pl { for _, p := range pl {
if matches(*p, a) { if matches(*p, a) {
return true, "", nil return authorizer.DecisionAllow, "", nil
} }
} }
return false, "No policy matched.", nil return authorizer.DecisionNoOpinion, "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.
......
...@@ -236,14 +236,14 @@ func (s *Server) InstallAuthFilter() { ...@@ -236,14 +236,14 @@ func (s *Server) InstallAuthFilter() {
attrs := s.auth.GetRequestAttributes(u, req.Request) attrs := s.auth.GetRequestAttributes(u, req.Request)
// Authorize // Authorize
authorized, _, err := s.auth.Authorize(attrs) decision, _, err := s.auth.Authorize(attrs)
if err != nil { if err != nil {
msg := fmt.Sprintf("Authorization error (user=%s, verb=%s, resource=%s, subresource=%s)", u.GetName(), attrs.GetVerb(), attrs.GetResource(), attrs.GetSubresource()) msg := fmt.Sprintf("Authorization error (user=%s, verb=%s, resource=%s, subresource=%s)", u.GetName(), attrs.GetVerb(), attrs.GetResource(), attrs.GetSubresource())
glog.Errorf(msg, err) glog.Errorf(msg, err)
resp.WriteErrorString(http.StatusInternalServerError, msg) resp.WriteErrorString(http.StatusInternalServerError, msg)
return return
} }
if !authorized { if decision != authorizer.DecisionAllow {
msg := fmt.Sprintf("Forbidden (user=%s, verb=%s, resource=%s, subresource=%s)", u.GetName(), attrs.GetVerb(), attrs.GetResource(), attrs.GetSubresource()) msg := fmt.Sprintf("Forbidden (user=%s, verb=%s, resource=%s, subresource=%s)", u.GetName(), attrs.GetVerb(), attrs.GetResource(), attrs.GetSubresource())
glog.V(2).Info(msg) glog.V(2).Info(msg)
resp.WriteErrorString(http.StatusForbidden, msg) resp.WriteErrorString(http.StatusForbidden, msg)
......
...@@ -183,7 +183,7 @@ func (_ *fakeKubelet) GetCgroupStats(cgroupName string) (*statsapi.ContainerStat ...@@ -183,7 +183,7 @@ func (_ *fakeKubelet) GetCgroupStats(cgroupName string) (*statsapi.ContainerStat
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) (authorized bool, reason string, err error) authorizeFunc func(authorizer.Attributes) (authorized authorizer.Decision, 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) {
...@@ -192,7 +192,7 @@ func (f *fakeAuth) AuthenticateRequest(req *http.Request) (user.Info, bool, erro ...@@ -192,7 +192,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) (authorized bool, reason string, err error) { func (f *fakeAuth) Authorize(a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
return f.authorizeFunc(a) return f.authorizeFunc(a)
} }
...@@ -232,8 +232,8 @@ func newServerTestWithDebug(enableDebugging bool) *serverTestFramework { ...@@ -232,8 +232,8 @@ func newServerTestWithDebug(enableDebugging bool) *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) (authorized bool, reason string, err error) { authorizeFunc: func(a authorizer.Attributes) (decision authorizer.Decision, reason string, err error) {
return true, "", nil return authorizer.DecisionAllow, "", nil
}, },
} }
fw.criHandler = &utiltesting.FakeHandler{ fw.criHandler = &utiltesting.FakeHandler{
...@@ -688,12 +688,12 @@ Otherwise, add it to the expected list of paths that map to the "proxy" subresou ...@@ -688,12 +688,12 @@ Otherwise, add it to the expected list of paths that map to the "proxy" subresou
} }
return attributesGetter.GetRequestAttributes(u, req) return attributesGetter.GetRequestAttributes(u, req)
} }
fw.fakeAuth.authorizeFunc = func(a authorizer.Attributes) (authorized bool, reason string, err error) { fw.fakeAuth.authorizeFunc = func(a authorizer.Attributes) (decision authorizer.Decision, reason string, err error) {
calledAuthorize = true calledAuthorize = true
if a != expectedAttributes { if a != expectedAttributes {
t.Fatalf("%s: expected attributes\n\t%#v\ngot\n\t%#v", tc.Path, expectedAttributes, a) t.Fatalf("%s: expected attributes\n\t%#v\ngot\n\t%#v", tc.Path, expectedAttributes, a)
} }
return false, "", nil return authorizer.DecisionNoOpinion, "", 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)
...@@ -747,9 +747,9 @@ func TestAuthenticationError(t *testing.T) { ...@@ -747,9 +747,9 @@ func TestAuthenticationError(t *testing.T) {
calledAttributes = true calledAttributes = true
return expectedAttributes return expectedAttributes
} }
fw.fakeAuth.authorizeFunc = func(a authorizer.Attributes) (authorized bool, reason string, err error) { fw.fakeAuth.authorizeFunc = func(a authorizer.Attributes) (decision authorizer.Decision, reason string, err error) {
calledAuthorize = true calledAuthorize = true
return false, "", errors.New("Failed") return authorizer.DecisionNoOpinion, "", errors.New("Failed")
} }
assertHealthFails(t, fw.testHTTPServer.URL+"/healthz", http.StatusInternalServerError) assertHealthFails(t, fw.testHTTPServer.URL+"/healthz", http.StatusInternalServerError)
...@@ -785,9 +785,9 @@ func TestAuthenticationFailure(t *testing.T) { ...@@ -785,9 +785,9 @@ func TestAuthenticationFailure(t *testing.T) {
calledAttributes = true calledAttributes = true
return expectedAttributes return expectedAttributes
} }
fw.fakeAuth.authorizeFunc = func(a authorizer.Attributes) (authorized bool, reason string, err error) { fw.fakeAuth.authorizeFunc = func(a authorizer.Attributes) (decision authorizer.Decision, reason string, err error) {
calledAuthorize = true calledAuthorize = true
return false, "", nil return authorizer.DecisionNoOpinion, "", nil
} }
assertHealthFails(t, fw.testHTTPServer.URL+"/healthz", http.StatusUnauthorized) assertHealthFails(t, fw.testHTTPServer.URL+"/healthz", http.StatusUnauthorized)
...@@ -823,9 +823,9 @@ func TestAuthorizationSuccess(t *testing.T) { ...@@ -823,9 +823,9 @@ func TestAuthorizationSuccess(t *testing.T) {
calledAttributes = true calledAttributes = true
return expectedAttributes return expectedAttributes
} }
fw.fakeAuth.authorizeFunc = func(a authorizer.Attributes) (authorized bool, reason string, err error) { fw.fakeAuth.authorizeFunc = func(a authorizer.Attributes) (decision authorizer.Decision, reason string, err error) {
calledAuthorize = true calledAuthorize = true
return true, "", nil return authorizer.DecisionAllow, "", nil
} }
assertHealthIsOk(t, fw.testHTTPServer.URL+"/healthz") assertHealthIsOk(t, fw.testHTTPServer.URL+"/healthz")
......
...@@ -58,10 +58,11 @@ func (r *REST) Create(ctx genericapirequest.Context, obj runtime.Object, createV ...@@ -58,10 +58,11 @@ func (r *REST) Create(ctx genericapirequest.Context, obj runtime.Object, createV
} }
authorizationAttributes := authorizationutil.AuthorizationAttributesFrom(localSubjectAccessReview.Spec) authorizationAttributes := authorizationutil.AuthorizationAttributesFrom(localSubjectAccessReview.Spec)
allowed, reason, evaluationErr := r.authorizer.Authorize(authorizationAttributes) decision, reason, evaluationErr := r.authorizer.Authorize(authorizationAttributes)
localSubjectAccessReview.Status = authorizationapi.SubjectAccessReviewStatus{ localSubjectAccessReview.Status = authorizationapi.SubjectAccessReviewStatus{
Allowed: allowed, Allowed: (decision == authorizer.DecisionAllow),
Denied: (decision == authorizer.DecisionDeny),
Reason: reason, Reason: reason,
} }
if evaluationErr != nil { if evaluationErr != nil {
......
...@@ -61,10 +61,11 @@ func (r *REST) Create(ctx genericapirequest.Context, obj runtime.Object, createV ...@@ -61,10 +61,11 @@ func (r *REST) Create(ctx genericapirequest.Context, obj runtime.Object, createV
authorizationAttributes = authorizationutil.NonResourceAttributesFrom(userToCheck, *selfSAR.Spec.NonResourceAttributes) authorizationAttributes = authorizationutil.NonResourceAttributesFrom(userToCheck, *selfSAR.Spec.NonResourceAttributes)
} }
allowed, reason, evaluationErr := r.authorizer.Authorize(authorizationAttributes) decision, reason, evaluationErr := r.authorizer.Authorize(authorizationAttributes)
selfSAR.Status = authorizationapi.SubjectAccessReviewStatus{ selfSAR.Status = authorizationapi.SubjectAccessReviewStatus{
Allowed: allowed, Allowed: (decision == authorizer.DecisionAllow),
Denied: (decision == authorizer.DecisionDeny),
Reason: reason, Reason: reason,
} }
if evaluationErr != nil { if evaluationErr != nil {
......
...@@ -51,10 +51,11 @@ func (r *REST) Create(ctx genericapirequest.Context, obj runtime.Object, createV ...@@ -51,10 +51,11 @@ func (r *REST) Create(ctx genericapirequest.Context, obj runtime.Object, createV
} }
authorizationAttributes := authorizationutil.AuthorizationAttributesFrom(subjectAccessReview.Spec) authorizationAttributes := authorizationutil.AuthorizationAttributesFrom(subjectAccessReview.Spec)
allowed, reason, evaluationErr := r.authorizer.Authorize(authorizationAttributes) decision, reason, evaluationErr := r.authorizer.Authorize(authorizationAttributes)
subjectAccessReview.Status = authorizationapi.SubjectAccessReviewStatus{ subjectAccessReview.Status = authorizationapi.SubjectAccessReviewStatus{
Allowed: allowed, Allowed: (decision == authorizer.DecisionAllow),
Denied: (decision == authorizer.DecisionDeny),
Reason: reason, Reason: reason,
} }
if evaluationErr != nil { if evaluationErr != nil {
......
...@@ -33,20 +33,20 @@ import ( ...@@ -33,20 +33,20 @@ import (
type fakeAuthorizer struct { type fakeAuthorizer struct {
attrs authorizer.Attributes attrs authorizer.Attributes
ok bool decision authorizer.Decision
reason string reason string
err error err error
} }
func (f *fakeAuthorizer) Authorize(attrs authorizer.Attributes) (bool, string, error) { func (f *fakeAuthorizer) Authorize(attrs authorizer.Attributes) (authorizer.Decision, string, error) {
f.attrs = attrs f.attrs = attrs
return f.ok, f.reason, f.err return f.decision, f.reason, f.err
} }
func TestCreate(t *testing.T) { func TestCreate(t *testing.T) {
testcases := map[string]struct { testcases := map[string]struct {
spec authorizationapi.SubjectAccessReviewSpec spec authorizationapi.SubjectAccessReviewSpec
ok bool decision authorizer.Decision
reason string reason string
err error err error
...@@ -63,7 +63,7 @@ func TestCreate(t *testing.T) { ...@@ -63,7 +63,7 @@ func TestCreate(t *testing.T) {
User: "bob", User: "bob",
NonResourceAttributes: &authorizationapi.NonResourceAttributes{Verb: "get", Path: "/mypath"}, NonResourceAttributes: &authorizationapi.NonResourceAttributes{Verb: "get", Path: "/mypath"},
}, },
ok: false, decision: authorizer.DecisionNoOpinion,
reason: "myreason", reason: "myreason",
err: errors.New("myerror"), err: errors.New("myerror"),
expectedAttrs: authorizer.AttributesRecord{ expectedAttrs: authorizer.AttributesRecord{
...@@ -84,7 +84,7 @@ func TestCreate(t *testing.T) { ...@@ -84,7 +84,7 @@ func TestCreate(t *testing.T) {
User: "bob", User: "bob",
NonResourceAttributes: &authorizationapi.NonResourceAttributes{Verb: "get", Path: "/mypath"}, NonResourceAttributes: &authorizationapi.NonResourceAttributes{Verb: "get", Path: "/mypath"},
}, },
ok: true, decision: authorizer.DecisionAllow,
reason: "allowed", reason: "allowed",
err: nil, err: nil,
expectedAttrs: authorizer.AttributesRecord{ expectedAttrs: authorizer.AttributesRecord{
...@@ -113,7 +113,7 @@ func TestCreate(t *testing.T) { ...@@ -113,7 +113,7 @@ func TestCreate(t *testing.T) {
Name: "mydeployment", Name: "mydeployment",
}, },
}, },
ok: false, decision: authorizer.DecisionNoOpinion,
reason: "myreason", reason: "myreason",
err: errors.New("myerror"), err: errors.New("myerror"),
expectedAttrs: authorizer.AttributesRecord{ expectedAttrs: authorizer.AttributesRecord{
...@@ -129,6 +129,7 @@ func TestCreate(t *testing.T) { ...@@ -129,6 +129,7 @@ func TestCreate(t *testing.T) {
}, },
expectedStatus: authorizationapi.SubjectAccessReviewStatus{ expectedStatus: authorizationapi.SubjectAccessReviewStatus{
Allowed: false, Allowed: false,
Denied: false,
Reason: "myreason", Reason: "myreason",
EvaluationError: "myerror", EvaluationError: "myerror",
}, },
...@@ -147,7 +148,7 @@ func TestCreate(t *testing.T) { ...@@ -147,7 +148,7 @@ func TestCreate(t *testing.T) {
Name: "mydeployment", Name: "mydeployment",
}, },
}, },
ok: true, decision: authorizer.DecisionAllow,
reason: "allowed", reason: "allowed",
err: nil, err: nil,
expectedAttrs: authorizer.AttributesRecord{ expectedAttrs: authorizer.AttributesRecord{
...@@ -163,15 +164,32 @@ func TestCreate(t *testing.T) { ...@@ -163,15 +164,32 @@ func TestCreate(t *testing.T) {
}, },
expectedStatus: authorizationapi.SubjectAccessReviewStatus{ expectedStatus: authorizationapi.SubjectAccessReviewStatus{
Allowed: true, Allowed: true,
Denied: false,
Reason: "allowed", Reason: "allowed",
EvaluationError: "", EvaluationError: "",
}, },
}, },
"resource denied": {
spec: authorizationapi.SubjectAccessReviewSpec{
User: "bob",
ResourceAttributes: &authorizationapi.ResourceAttributes{},
},
decision: authorizer.DecisionDeny,
expectedAttrs: authorizer.AttributesRecord{
User: &user.DefaultInfo{Name: "bob"},
ResourceRequest: true,
},
expectedStatus: authorizationapi.SubjectAccessReviewStatus{
Allowed: false,
Denied: true,
},
},
} }
for k, tc := range testcases { for k, tc := range testcases {
auth := &fakeAuthorizer{ auth := &fakeAuthorizer{
ok: tc.ok, decision: tc.decision,
reason: tc.reason, reason: tc.reason,
err: tc.err, err: tc.err,
} }
......
...@@ -79,12 +79,12 @@ func BindingAuthorized(ctx genericapirequest.Context, roleRef rbac.RoleRef, bind ...@@ -79,12 +79,12 @@ func BindingAuthorized(ctx genericapirequest.Context, roleRef rbac.RoleRef, bind
return false return false
} }
ok, _, err := a.Authorize(attrs) decision, _, err := a.Authorize(attrs)
if err != nil { if err != nil {
utilruntime.HandleError(fmt.Errorf( utilruntime.HandleError(fmt.Errorf(
"error authorizing user %#v to bind %#v in namespace %s: %v", "error authorizing user %#v to bind %#v in namespace %s: %v",
user, roleRef, bindingNamespace, err, user, roleRef, bindingNamespace, err,
)) ))
} }
return ok return decision == authorizer.DecisionAllow
} }
...@@ -104,8 +104,8 @@ func (a *gcPermissionsEnforcement) Validate(attributes admission.Attributes) (er ...@@ -104,8 +104,8 @@ func (a *gcPermissionsEnforcement) Validate(attributes admission.Attributes) (er
ResourceRequest: true, ResourceRequest: true,
Path: "", Path: "",
} }
allowed, reason, err := a.authorizer.Authorize(deleteAttributes) decision, reason, err := a.authorizer.Authorize(deleteAttributes)
if !allowed { if decision != authorizer.DecisionAllow {
return admission.NewForbidden(attributes, fmt.Errorf("cannot set an ownerRef on a resource you can't delete: %v, %v", reason, err)) return admission.NewForbidden(attributes, fmt.Errorf("cannot set an ownerRef on a resource you can't delete: %v, %v", reason, err))
} }
...@@ -122,8 +122,8 @@ func (a *gcPermissionsEnforcement) Validate(attributes admission.Attributes) (er ...@@ -122,8 +122,8 @@ func (a *gcPermissionsEnforcement) Validate(attributes admission.Attributes) (er
// resources. User needs to have delete permission on all the // resources. User needs to have delete permission on all the
// matched Resources. // matched Resources.
for _, record := range records { for _, record := range records {
allowed, reason, err := a.authorizer.Authorize(record) decision, reason, err := a.authorizer.Authorize(record)
if !allowed { if decision != authorizer.DecisionAllow {
return admission.NewForbidden(attributes, fmt.Errorf("cannot set blockOwnerDeletion if an ownerReference refers to a resource you can't set finalizers on: %v, %v", reason, err)) return admission.NewForbidden(attributes, fmt.Errorf("cannot set blockOwnerDeletion if an ownerReference refers to a resource you can't set finalizers on: %v, %v", reason, err))
} }
} }
......
...@@ -34,40 +34,40 @@ import ( ...@@ -34,40 +34,40 @@ import (
type fakeAuthorizer struct{} type fakeAuthorizer struct{}
func (fakeAuthorizer) Authorize(a authorizer.Attributes) (bool, string, error) { func (fakeAuthorizer) Authorize(a authorizer.Attributes) (authorizer.Decision, string, error) {
username := a.GetUser().GetName() username := a.GetUser().GetName()
if username == "non-deleter" { if username == "non-deleter" {
if a.GetVerb() == "delete" { if a.GetVerb() == "delete" {
return false, "", nil return authorizer.DecisionNoOpinion, "", nil
} }
if a.GetVerb() == "update" && a.GetSubresource() == "finalizers" { if a.GetVerb() == "update" && a.GetSubresource() == "finalizers" {
return false, "", nil return authorizer.DecisionNoOpinion, "", nil
} }
return true, "", nil return authorizer.DecisionAllow, "", nil
} }
if username == "non-pod-deleter" { if username == "non-pod-deleter" {
if a.GetVerb() == "delete" && a.GetResource() == "pods" { if a.GetVerb() == "delete" && a.GetResource() == "pods" {
return false, "", nil return authorizer.DecisionNoOpinion, "", nil
} }
if a.GetVerb() == "update" && a.GetResource() == "pods" && a.GetSubresource() == "finalizers" { if a.GetVerb() == "update" && a.GetResource() == "pods" && a.GetSubresource() == "finalizers" {
return false, "", nil return authorizer.DecisionNoOpinion, "", nil
} }
return true, "", nil return authorizer.DecisionAllow, "", nil
} }
if username == "non-rc-deleter" { if username == "non-rc-deleter" {
if a.GetVerb() == "delete" && a.GetResource() == "replicationcontrollers" { if a.GetVerb() == "delete" && a.GetResource() == "replicationcontrollers" {
return false, "", nil return authorizer.DecisionNoOpinion, "", nil
} }
if a.GetVerb() == "update" && a.GetResource() == "replicationcontrollers" && a.GetSubresource() == "finalizers" { if a.GetVerb() == "update" && a.GetResource() == "replicationcontrollers" && a.GetSubresource() == "finalizers" {
return false, "", nil return authorizer.DecisionNoOpinion, "", nil
} }
return true, "", nil return authorizer.DecisionAllow, "", nil
} }
return true, "", nil return authorizer.DecisionAllow, "", nil
} }
// newGCPermissionsEnforcement returns the admission controller configured for testing. // newGCPermissionsEnforcement returns the admission controller configured for testing.
......
...@@ -313,11 +313,11 @@ func authorizedForPolicy(info user.Info, namespace string, policy *extensions.Po ...@@ -313,11 +313,11 @@ func authorizedForPolicy(info user.Info, namespace string, policy *extensions.Po
return false return false
} }
attr := buildAttributes(info, namespace, policy) attr := buildAttributes(info, namespace, policy)
allowed, reason, err := authz.Authorize(attr) decision, reason, err := authz.Authorize(attr)
if err != nil { if err != nil {
glog.V(5).Infof("cannot authorize for policy: %v,%v", reason, err) glog.V(5).Infof("cannot authorize for policy: %v,%v", reason, err)
} }
return allowed return (decision == authorizer.DecisionAllow)
} }
// buildAttributes builds an attributes record for a SAR based on the user info and policy. // buildAttributes builds an attributes record for a SAR based on the user info and policy.
......
...@@ -66,13 +66,16 @@ type TestAuthorizer struct { ...@@ -66,13 +66,16 @@ type TestAuthorizer struct {
usernameToNamespaceToAllowedPSPs map[string]map[string]map[string]bool usernameToNamespaceToAllowedPSPs map[string]map[string]map[string]bool
} }
func (t *TestAuthorizer) Authorize(a authorizer.Attributes) (authorized bool, reason string, err error) { func (t *TestAuthorizer) Authorize(a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
if t.usernameToNamespaceToAllowedPSPs == nil { if t.usernameToNamespaceToAllowedPSPs == nil {
return true, "", nil return authorizer.DecisionAllow, "", nil
} }
allowedInNamespace := t.usernameToNamespaceToAllowedPSPs[a.GetUser().GetName()][a.GetNamespace()][a.GetName()] allowedInNamespace := t.usernameToNamespaceToAllowedPSPs[a.GetUser().GetName()][a.GetNamespace()][a.GetName()]
allowedClusterWide := t.usernameToNamespaceToAllowedPSPs[a.GetUser().GetName()][""][a.GetName()] allowedClusterWide := t.usernameToNamespaceToAllowedPSPs[a.GetUser().GetName()][""][a.GetName()]
return (allowedInNamespace || allowedClusterWide), "", nil if allowedInNamespace || allowedClusterWide {
return authorizer.DecisionAllow, "", nil
}
return authorizer.DecisionNoOpinion, "", nil
} }
var _ authorizer.Authorizer = &TestAuthorizer{} var _ authorizer.Authorizer = &TestAuthorizer{}
......
...@@ -64,16 +64,16 @@ var ( ...@@ -64,16 +64,16 @@ var (
pvResource = api.Resource("persistentvolumes") pvResource = api.Resource("persistentvolumes")
) )
func (r *NodeAuthorizer) Authorize(attrs authorizer.Attributes) (bool, string, error) { func (r *NodeAuthorizer) Authorize(attrs authorizer.Attributes) (authorizer.Decision, string, error) {
nodeName, isNode := r.identifier.NodeIdentity(attrs.GetUser()) nodeName, isNode := r.identifier.NodeIdentity(attrs.GetUser())
if !isNode { if !isNode {
// reject requests from non-nodes // reject requests from non-nodes
return false, "", nil return authorizer.DecisionNoOpinion, "", nil
} }
if len(nodeName) == 0 { if len(nodeName) == 0 {
// reject requests from unidentifiable nodes // reject requests from unidentifiable nodes
glog.V(2).Infof("NODE DENY: unknown node for user %q", attrs.GetUser().GetName()) glog.V(2).Infof("NODE DENY: unknown node for user %q", attrs.GetUser().GetName())
return false, fmt.Sprintf("unknown node for user %q", attrs.GetUser().GetName()), nil return authorizer.DecisionNoOpinion, fmt.Sprintf("unknown node for user %q", attrs.GetUser().GetName()), nil
} }
// subdivide access to specific resources // subdivide access to specific resources
...@@ -92,31 +92,34 @@ func (r *NodeAuthorizer) Authorize(attrs authorizer.Attributes) (bool, string, e ...@@ -92,31 +92,34 @@ func (r *NodeAuthorizer) Authorize(attrs authorizer.Attributes) (bool, string, e
} }
// Access to other resources is not subdivided, so just evaluate against the statically defined node rules // Access to other resources is not subdivided, so just evaluate against the statically defined node rules
return rbac.RulesAllow(attrs, r.nodeRules...), "", nil if rbac.RulesAllow(attrs, r.nodeRules...) {
return authorizer.DecisionAllow, "", nil
}
return authorizer.DecisionNoOpinion, "", nil
} }
// authorizeGet authorizes "get" requests to objects of the specified type if they are related to the specified node // authorizeGet authorizes "get" requests to objects of the specified type if they are related to the specified node
func (r *NodeAuthorizer) authorizeGet(nodeName string, startingType vertexType, attrs authorizer.Attributes) (bool, string, error) { func (r *NodeAuthorizer) authorizeGet(nodeName string, startingType vertexType, attrs authorizer.Attributes) (authorizer.Decision, string, error) {
if attrs.GetVerb() != "get" || len(attrs.GetName()) == 0 { if attrs.GetVerb() != "get" || len(attrs.GetName()) == 0 {
glog.V(2).Infof("NODE DENY: %s %#v", nodeName, attrs) glog.V(2).Infof("NODE DENY: %s %#v", nodeName, attrs)
return false, "can only get individual resources of this type", nil return authorizer.DecisionNoOpinion, "can only get individual resources of this type", nil
} }
if len(attrs.GetSubresource()) > 0 { if len(attrs.GetSubresource()) > 0 {
glog.V(2).Infof("NODE DENY: %s %#v", nodeName, attrs) glog.V(2).Infof("NODE DENY: %s %#v", nodeName, attrs)
return false, "cannot get subresource", nil return authorizer.DecisionNoOpinion, "cannot get subresource", nil
} }
ok, err := r.hasPathFrom(nodeName, startingType, attrs.GetNamespace(), attrs.GetName()) ok, err := r.hasPathFrom(nodeName, startingType, attrs.GetNamespace(), attrs.GetName())
if err != nil { if err != nil {
glog.V(2).Infof("NODE DENY: %v", err) glog.V(2).Infof("NODE DENY: %v", err)
return false, "no path found to object", nil return authorizer.DecisionNoOpinion, "no path found to object", nil
} }
if !ok { if !ok {
glog.V(2).Infof("NODE DENY: %q %#v", nodeName, attrs) glog.V(2).Infof("NODE DENY: %q %#v", nodeName, attrs)
return false, "no path found to object", nil return authorizer.DecisionNoOpinion, "no path found to object", nil
} }
return ok, "", nil return authorizer.DecisionAllow, "", nil
} }
// hasPathFrom returns true if there is a directed path from the specified type/namespace/name to the specified Node // hasPathFrom returns true if there is a directed path from the specified type/namespace/name to the specified Node
......
...@@ -57,71 +57,71 @@ func TestAuthorizer(t *testing.T) { ...@@ -57,71 +57,71 @@ func TestAuthorizer(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
attrs authorizer.AttributesRecord attrs authorizer.AttributesRecord
expect bool expect authorizer.Decision
}{ }{
{ {
name: "allowed configmap", name: "allowed configmap",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "configmaps", Name: "configmap0-pod0-node0", Namespace: "ns0"}, attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "configmaps", Name: "configmap0-pod0-node0", Namespace: "ns0"},
expect: true, expect: authorizer.DecisionAllow,
}, },
{ {
name: "allowed secret via pod", name: "allowed secret via pod",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "secrets", Name: "secret0-pod0-node0", Namespace: "ns0"}, attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "secrets", Name: "secret0-pod0-node0", Namespace: "ns0"},
expect: true, expect: authorizer.DecisionAllow,
}, },
{ {
name: "allowed shared secret via pod", name: "allowed shared secret via pod",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "secrets", Name: "secret0-shared", Namespace: "ns0"}, attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "secrets", Name: "secret0-shared", Namespace: "ns0"},
expect: true, expect: authorizer.DecisionAllow,
}, },
{ {
name: "allowed shared secret via pvc", name: "allowed shared secret via pvc",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "secrets", Name: "secret-pv0-pod0-node0-ns0", Namespace: "ns0"}, attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "secrets", Name: "secret-pv0-pod0-node0-ns0", Namespace: "ns0"},
expect: true, expect: authorizer.DecisionAllow,
}, },
{ {
name: "allowed pvc", name: "allowed pvc",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "persistentvolumeclaims", Name: "pvc0-pod0-node0", Namespace: "ns0"}, attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "persistentvolumeclaims", Name: "pvc0-pod0-node0", Namespace: "ns0"},
expect: true, expect: authorizer.DecisionAllow,
}, },
{ {
name: "allowed pv", name: "allowed pv",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "persistentvolumes", Name: "pv0-pod0-node0-ns0", Namespace: ""}, attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "persistentvolumes", Name: "pv0-pod0-node0-ns0", Namespace: ""},
expect: true, expect: authorizer.DecisionAllow,
}, },
{ {
name: "disallowed configmap", name: "disallowed configmap",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "configmaps", Name: "configmap0-pod0-node1", Namespace: "ns0"}, attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "configmaps", Name: "configmap0-pod0-node1", Namespace: "ns0"},
expect: false, expect: authorizer.DecisionNoOpinion,
}, },
{ {
name: "disallowed secret via pod", name: "disallowed secret via pod",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "secrets", Name: "secret0-pod0-node1", Namespace: "ns0"}, attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "secrets", Name: "secret0-pod0-node1", Namespace: "ns0"},
expect: false, expect: authorizer.DecisionNoOpinion,
}, },
{ {
name: "disallowed shared secret via pvc", name: "disallowed shared secret via pvc",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "secrets", Name: "secret-pv0-pod0-node1-ns0", Namespace: "ns0"}, attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "secrets", Name: "secret-pv0-pod0-node1-ns0", Namespace: "ns0"},
expect: false, expect: authorizer.DecisionNoOpinion,
}, },
{ {
name: "disallowed pvc", name: "disallowed pvc",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "persistentvolumeclaims", Name: "pvc0-pod0-node1", Namespace: "ns0"}, attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "persistentvolumeclaims", Name: "pvc0-pod0-node1", Namespace: "ns0"},
expect: false, expect: authorizer.DecisionNoOpinion,
}, },
{ {
name: "disallowed pv", name: "disallowed pv",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "persistentvolumes", Name: "pv0-pod0-node1-ns0", Namespace: ""}, attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "persistentvolumes", Name: "pv0-pod0-node1-ns0", Namespace: ""},
expect: false, expect: authorizer.DecisionNoOpinion,
}, },
} }
for _, tc := range tests { for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
ok, _, _ := authz.Authorize(tc.attrs) decision, _, _ := authz.Authorize(tc.attrs)
if ok != tc.expect { if decision != tc.expect {
t.Errorf("expected %v, got %v", tc.expect, ok) t.Errorf("expected %v, got %v", tc.expect, decision)
} }
}) })
} }
...@@ -186,13 +186,13 @@ func TestAuthorizerSharedResources(t *testing.T) { ...@@ -186,13 +186,13 @@ func TestAuthorizerSharedResources(t *testing.T) {
} }
for i, tc := range testcases { for i, tc := range testcases {
ok, _, err := authz.Authorize(authorizer.AttributesRecord{User: tc.User, ResourceRequest: true, Verb: "get", Resource: "secrets", Namespace: "ns1", Name: tc.Secret}) decision, _, err := authz.Authorize(authorizer.AttributesRecord{User: tc.User, ResourceRequest: true, Verb: "get", Resource: "secrets", Namespace: "ns1", Name: tc.Secret})
if err != nil { if err != nil {
t.Errorf("%d: unexpected error: %v", i, err) t.Errorf("%d: unexpected error: %v", i, err)
continue continue
} }
if ok != tc.ExpectAllowed { if (decision == authorizer.DecisionAllow) != tc.ExpectAllowed {
t.Errorf("%d: expected %v, got %v", i, tc.ExpectAllowed, ok) t.Errorf("%d: expected %v, got %v", i, tc.ExpectAllowed, decision)
} }
} }
} }
...@@ -301,47 +301,47 @@ func BenchmarkAuthorization(b *testing.B) { ...@@ -301,47 +301,47 @@ func BenchmarkAuthorization(b *testing.B) {
tests := []struct { tests := []struct {
name string name string
attrs authorizer.AttributesRecord attrs authorizer.AttributesRecord
expect bool expect authorizer.Decision
}{ }{
{ {
name: "allowed configmap", name: "allowed configmap",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "configmaps", Name: "configmap0-pod0-node0", Namespace: "ns0"}, attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "configmaps", Name: "configmap0-pod0-node0", Namespace: "ns0"},
expect: true, expect: authorizer.DecisionAllow,
}, },
{ {
name: "allowed secret via pod", name: "allowed secret via pod",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "secrets", Name: "secret0-pod0-node0", Namespace: "ns0"}, attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "secrets", Name: "secret0-pod0-node0", Namespace: "ns0"},
expect: true, expect: authorizer.DecisionAllow,
}, },
{ {
name: "allowed shared secret via pod", name: "allowed shared secret via pod",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "secrets", Name: "secret0-shared", Namespace: "ns0"}, attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "secrets", Name: "secret0-shared", Namespace: "ns0"},
expect: true, expect: authorizer.DecisionAllow,
}, },
{ {
name: "disallowed configmap", name: "disallowed configmap",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "configmaps", Name: "configmap0-pod0-node1", Namespace: "ns0"}, attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "configmaps", Name: "configmap0-pod0-node1", Namespace: "ns0"},
expect: false, expect: authorizer.DecisionNoOpinion,
}, },
{ {
name: "disallowed secret via pod", name: "disallowed secret via pod",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "secrets", Name: "secret0-pod0-node1", Namespace: "ns0"}, attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "secrets", Name: "secret0-pod0-node1", Namespace: "ns0"},
expect: false, expect: authorizer.DecisionNoOpinion,
}, },
{ {
name: "disallowed shared secret via pvc", name: "disallowed shared secret via pvc",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "secrets", Name: "secret-pv0-pod0-node1-ns0", Namespace: "ns0"}, attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "secrets", Name: "secret-pv0-pod0-node1-ns0", Namespace: "ns0"},
expect: false, expect: authorizer.DecisionNoOpinion,
}, },
{ {
name: "disallowed pvc", name: "disallowed pvc",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "persistentvolumeclaims", Name: "pvc0-pod0-node1", Namespace: "ns0"}, attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "persistentvolumeclaims", Name: "pvc0-pod0-node1", Namespace: "ns0"},
expect: false, expect: authorizer.DecisionNoOpinion,
}, },
{ {
name: "disallowed pv", name: "disallowed pv",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "persistentvolumes", Name: "pv0-pod0-node1-ns0", Namespace: ""}, attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "persistentvolumes", Name: "pv0-pod0-node1-ns0", Namespace: ""},
expect: false, expect: authorizer.DecisionNoOpinion,
}, },
} }
...@@ -349,9 +349,9 @@ func BenchmarkAuthorization(b *testing.B) { ...@@ -349,9 +349,9 @@ func BenchmarkAuthorization(b *testing.B) {
for _, tc := range tests { for _, tc := range tests {
b.Run(tc.name, func(b *testing.B) { b.Run(tc.name, func(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
ok, _, _ := authz.Authorize(tc.attrs) decision, _, _ := authz.Authorize(tc.attrs)
if ok != tc.expect { if decision != tc.expect {
b.Errorf("expected %v, got %v", tc.expect, ok) b.Errorf("expected %v, got %v", tc.expect, decision)
} }
} }
}) })
......
...@@ -69,12 +69,12 @@ func (v *authorizingVisitor) visit(rule *rbac.PolicyRule, err error) bool { ...@@ -69,12 +69,12 @@ func (v *authorizingVisitor) visit(rule *rbac.PolicyRule, err error) bool {
return true return true
} }
func (r *RBACAuthorizer) Authorize(requestAttributes authorizer.Attributes) (bool, string, error) { func (r *RBACAuthorizer) Authorize(requestAttributes authorizer.Attributes) (authorizer.Decision, string, error) {
ruleCheckingVisitor := &authorizingVisitor{requestAttributes: requestAttributes} ruleCheckingVisitor := &authorizingVisitor{requestAttributes: requestAttributes}
r.authorizationRuleResolver.VisitRulesFor(requestAttributes.GetUser(), requestAttributes.GetNamespace(), ruleCheckingVisitor.visit) r.authorizationRuleResolver.VisitRulesFor(requestAttributes.GetUser(), requestAttributes.GetNamespace(), ruleCheckingVisitor.visit)
if ruleCheckingVisitor.allowed { if ruleCheckingVisitor.allowed {
return true, "", nil return authorizer.DecisionAllow, "", nil
} }
// Build a detailed log of the denial. // Build a detailed log of the denial.
...@@ -120,7 +120,7 @@ func (r *RBACAuthorizer) Authorize(requestAttributes authorizer.Attributes) (boo ...@@ -120,7 +120,7 @@ func (r *RBACAuthorizer) Authorize(requestAttributes authorizer.Attributes) (boo
if len(ruleCheckingVisitor.errors) > 0 { if len(ruleCheckingVisitor.errors) > 0 {
reason = fmt.Sprintf("%v", utilerrors.NewAggregate(ruleCheckingVisitor.errors)) reason = fmt.Sprintf("%v", utilerrors.NewAggregate(ruleCheckingVisitor.errors))
} }
return false, reason, nil return authorizer.DecisionNoOpinion, reason, nil
} }
func (r *RBACAuthorizer) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) { func (r *RBACAuthorizer) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
......
...@@ -247,13 +247,13 @@ func TestAuthorizer(t *testing.T) { ...@@ -247,13 +247,13 @@ func TestAuthorizer(t *testing.T) {
ruleResolver, _ := rbacregistryvalidation.NewTestRuleResolver(tt.roles, tt.roleBindings, tt.clusterRoles, tt.clusterRoleBindings) ruleResolver, _ := rbacregistryvalidation.NewTestRuleResolver(tt.roles, tt.roleBindings, tt.clusterRoles, tt.clusterRoleBindings)
a := RBACAuthorizer{ruleResolver} a := RBACAuthorizer{ruleResolver}
for _, attr := range tt.shouldPass { for _, attr := range tt.shouldPass {
if authorized, _, _ := a.Authorize(attr); !authorized { if decision, _, _ := a.Authorize(attr); decision != authorizer.DecisionAllow {
t.Errorf("case %d: incorrectly restricted %s", i, attr) t.Errorf("case %d: incorrectly restricted %s", i, attr)
} }
} }
for _, attr := range tt.shouldFail { for _, attr := range tt.shouldFail {
if authorized, _, _ := a.Authorize(attr); authorized { if decision, _, _ := a.Authorize(attr); decision == authorizer.DecisionAllow {
t.Errorf("case %d: incorrectly passed %s", i, attr) t.Errorf("case %d: incorrectly passed %s", i, attr)
} }
} }
......
...@@ -229,6 +229,13 @@ message SubjectAccessReviewStatus { ...@@ -229,6 +229,13 @@ message SubjectAccessReviewStatus {
// Allowed is required. True if the action would be allowed, false otherwise. // Allowed is required. True if the action would be allowed, false otherwise.
optional bool allowed = 1; optional bool allowed = 1;
// Denied is optional. True if the action would be denied, otherwise
// false. If both allowed is false and denied is false, then the
// authorizer has no opinion on whether to authorize the action. Denied
// may not be true if Allowed is true.
// +optional
optional bool denied = 4;
// Reason is optional. It indicates why a request was allowed or denied. // Reason is optional. It indicates why a request was allowed or denied.
// +optional // +optional
optional string reason = 2; optional string reason = 2;
......
...@@ -171,6 +171,12 @@ type SelfSubjectAccessReviewSpec struct { ...@@ -171,6 +171,12 @@ type SelfSubjectAccessReviewSpec struct {
type SubjectAccessReviewStatus struct { type SubjectAccessReviewStatus struct {
// Allowed is required. True if the action would be allowed, false otherwise. // Allowed is required. True if the action would be allowed, false otherwise.
Allowed bool `json:"allowed" protobuf:"varint,1,opt,name=allowed"` Allowed bool `json:"allowed" protobuf:"varint,1,opt,name=allowed"`
// Denied is optional. True if the action would be denied, otherwise
// false. If both allowed is false and denied is false, then the
// authorizer has no opinion on whether to authorize the action. Denied
// may not be true if Allowed is true.
// +optional
Denied bool `json:"denied,omitempty" protobuf:"varint,4,opt,name=denied"`
// Reason is optional. It indicates why a request was allowed or denied. // Reason is optional. It indicates why a request was allowed or denied.
// +optional // +optional
Reason string `json:"reason,omitempty" protobuf:"bytes,2,opt,name=reason"` Reason string `json:"reason,omitempty" protobuf:"bytes,2,opt,name=reason"`
......
...@@ -149,6 +149,7 @@ func (SubjectAccessReviewSpec) SwaggerDoc() map[string]string { ...@@ -149,6 +149,7 @@ func (SubjectAccessReviewSpec) SwaggerDoc() map[string]string {
var map_SubjectAccessReviewStatus = map[string]string{ var map_SubjectAccessReviewStatus = map[string]string{
"": "SubjectAccessReviewStatus", "": "SubjectAccessReviewStatus",
"allowed": "Allowed is required. True if the action would be allowed, false otherwise.", "allowed": "Allowed is required. True if the action would be allowed, false otherwise.",
"denied": "Denied is optional. True if the action would be denied, otherwise false. If both allowed is false and denied is false, then the authorizer has no opinion on whether to authorize the action. Denied may not be true if Allowed is true.",
"reason": "Reason is optional. It indicates why a request was allowed or denied.", "reason": "Reason is optional. It indicates why a request was allowed or denied.",
"evaluationError": "EvaluationError is an indication that some error occurred during the authorization check. It is entirely possible to get an error and be able to continue determine authorization status in spite of it. For instance, RBAC can be missing a role, but enough roles are still present and bound to reason about the request.", "evaluationError": "EvaluationError is an indication that some error occurred during the authorization check. It is entirely possible to get an error and be able to continue determine authorization status in spite of it. For instance, RBAC can be missing a role, but enough roles are still present and bound to reason about the request.",
} }
......
...@@ -229,6 +229,13 @@ message SubjectAccessReviewStatus { ...@@ -229,6 +229,13 @@ message SubjectAccessReviewStatus {
// Allowed is required. True if the action would be allowed, false otherwise. // Allowed is required. True if the action would be allowed, false otherwise.
optional bool allowed = 1; optional bool allowed = 1;
// Denied is optional. True if the action would be denied, otherwise
// false. If both allowed is false and denied is false, then the
// authorizer has no opinion on whether to authorize the action. Denied
// may not be true if Allowed is true.
// +optional
optional bool denied = 4;
// Reason is optional. It indicates why a request was allowed or denied. // Reason is optional. It indicates why a request was allowed or denied.
// +optional // +optional
optional string reason = 2; optional string reason = 2;
......
...@@ -171,6 +171,12 @@ type SelfSubjectAccessReviewSpec struct { ...@@ -171,6 +171,12 @@ type SelfSubjectAccessReviewSpec struct {
type SubjectAccessReviewStatus struct { type SubjectAccessReviewStatus struct {
// Allowed is required. True if the action would be allowed, false otherwise. // Allowed is required. True if the action would be allowed, false otherwise.
Allowed bool `json:"allowed" protobuf:"varint,1,opt,name=allowed"` Allowed bool `json:"allowed" protobuf:"varint,1,opt,name=allowed"`
// Denied is optional. True if the action would be denied, otherwise
// false. If both allowed is false and denied is false, then the
// authorizer has no opinion on whether to authorize the action. Denied
// may not be true if Allowed is true.
// +optional
Denied bool `json:"denied,omitempty" protobuf:"varint,4,opt,name=denied"`
// Reason is optional. It indicates why a request was allowed or denied. // Reason is optional. It indicates why a request was allowed or denied.
// +optional // +optional
Reason string `json:"reason,omitempty" protobuf:"bytes,2,opt,name=reason"` Reason string `json:"reason,omitempty" protobuf:"bytes,2,opt,name=reason"`
......
...@@ -149,6 +149,7 @@ func (SubjectAccessReviewSpec) SwaggerDoc() map[string]string { ...@@ -149,6 +149,7 @@ func (SubjectAccessReviewSpec) SwaggerDoc() map[string]string {
var map_SubjectAccessReviewStatus = map[string]string{ var map_SubjectAccessReviewStatus = map[string]string{
"": "SubjectAccessReviewStatus", "": "SubjectAccessReviewStatus",
"allowed": "Allowed is required. True if the action would be allowed, false otherwise.", "allowed": "Allowed is required. True if the action would be allowed, false otherwise.",
"denied": "Denied is optional. True if the action would be denied, otherwise false. If both allowed is false and denied is false, then the authorizer has no opinion on whether to authorize the action. Denied may not be true if Allowed is true.",
"reason": "Reason is optional. It indicates why a request was allowed or denied.", "reason": "Reason is optional. It indicates why a request was allowed or denied.",
"evaluationError": "EvaluationError is an indication that some error occurred during the authorization check. It is entirely possible to get an error and be able to continue determine authorization status in spite of it. For instance, RBAC can be missing a role, but enough roles are still present and bound to reason about the request.", "evaluationError": "EvaluationError is an indication that some error occurred during the authorization check. It is entirely possible to get an error and be able to continue determine authorization status in spite of it. For instance, RBAC can be missing a role, but enough roles are still present and bound to reason about the request.",
} }
......
...@@ -133,8 +133,8 @@ var _ initializer.WantsAuthorizer = &WantAuthorizerAdmission{} ...@@ -133,8 +133,8 @@ var _ initializer.WantsAuthorizer = &WantAuthorizerAdmission{}
// TestAuthorizer is a test stub that fulfills the WantsAuthorizer interface. // TestAuthorizer is a test stub that fulfills the WantsAuthorizer interface.
type TestAuthorizer struct{} type TestAuthorizer struct{}
func (t *TestAuthorizer) Authorize(a authorizer.Attributes) (authorized bool, reason string, err error) { func (t *TestAuthorizer) Authorize(a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
return false, "", nil return authorizer.DecisionNoOpinion, "", nil
} }
// wantClientCert is a test stub for testing that fulfulls the WantsClientCert interface. // wantClientCert is a test stub for testing that fulfulls the WantsClientCert interface.
......
...@@ -260,7 +260,7 @@ func (i *initializer) Admit(a admission.Attributes) (err error) { ...@@ -260,7 +260,7 @@ func (i *initializer) Admit(a admission.Attributes) (err error) {
func (i *initializer) canInitialize(a admission.Attributes, message string) error { func (i *initializer) canInitialize(a admission.Attributes, message string) error {
// caller must have the ability to mutate un-initialized resources // caller must have the ability to mutate un-initialized resources
authorized, reason, err := i.authorizer.Authorize(authorizer.AttributesRecord{ decision, reason, err := i.authorizer.Authorize(authorizer.AttributesRecord{
Name: a.GetName(), Name: a.GetName(),
ResourceRequest: true, ResourceRequest: true,
User: a.GetUserInfo(), User: a.GetUserInfo(),
...@@ -273,7 +273,7 @@ func (i *initializer) canInitialize(a admission.Attributes, message string) erro ...@@ -273,7 +273,7 @@ func (i *initializer) canInitialize(a admission.Attributes, message string) erro
if err != nil { if err != nil {
return err return err
} }
if !authorized { if decision != authorizer.DecisionAllow {
return errors.NewForbidden(a.GetResource().GroupResource(), a.GetName(), fmt.Errorf("%s: %s", message, reason)) return errors.NewForbidden(a.GetResource().GroupResource(), a.GetName(), fmt.Errorf("%s: %s", message, reason))
} }
return nil return nil
......
...@@ -109,11 +109,11 @@ type fakeAuthorizer struct { ...@@ -109,11 +109,11 @@ type fakeAuthorizer struct {
accept bool accept bool
} }
func (f *fakeAuthorizer) Authorize(a authorizer.Attributes) (bool, string, error) { func (f *fakeAuthorizer) Authorize(a authorizer.Attributes) (authorizer.Decision, string, error) {
if f.accept { if f.accept {
return true, "", nil return authorizer.DecisionAllow, "", nil
} }
return false, "denied", nil return authorizer.DecisionNoOpinion, "denied", nil
} }
func TestAdmitUpdate(t *testing.T) { func TestAdmitUpdate(t *testing.T) {
......
...@@ -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) (authorized bool, reason string, err error) Authorize(a Attributes) (authorized Decision, reason string, err error)
} }
type AuthorizerFunc func(a Attributes) (bool, string, error) type AuthorizerFunc func(a Attributes) (Decision, string, error)
func (f AuthorizerFunc) Authorize(a Attributes) (bool, string, error) { func (f AuthorizerFunc) Authorize(a Attributes) (Decision, string, error) {
return f(a) return f(a)
} }
...@@ -144,3 +144,15 @@ func (a AttributesRecord) IsResourceRequest() bool { ...@@ -144,3 +144,15 @@ func (a AttributesRecord) IsResourceRequest() bool {
func (a AttributesRecord) GetPath() string { func (a AttributesRecord) GetPath() string {
return a.Path return a.Path
} }
type Decision int
const (
// DecisionDeny means that an authorizer decided to deny the action.
DecisionDeny Decision = iota
// DecisionAllow means that an authorizer decided to allow the action.
DecisionAllow
// DecisionNoOpionion means that an authorizer has no opinion on wether
// to allow or deny an action.
DecisionNoOpinion
)
...@@ -27,7 +27,7 @@ import ( ...@@ -27,7 +27,7 @@ import (
// and always return nil. // and always return nil.
func TestNewAlwaysAllowAuthorizer(t *testing.T) { func TestNewAlwaysAllowAuthorizer(t *testing.T) {
aaa := NewAlwaysAllowAuthorizer() aaa := NewAlwaysAllowAuthorizer()
if authorized, _, _ := aaa.Authorize(nil); !authorized { if decision, _, _ := aaa.Authorize(nil); decision != authorizer.DecisionAllow {
t.Errorf("AlwaysAllowAuthorizer.Authorize did not authorize successfully.") t.Errorf("AlwaysAllowAuthorizer.Authorize did not authorize successfully.")
} }
} }
...@@ -36,7 +36,7 @@ func TestNewAlwaysAllowAuthorizer(t *testing.T) { ...@@ -36,7 +36,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 authorized, _, _ := ada.Authorize(nil); authorized { if decision, _, _ := ada.Authorize(nil); decision == authorizer.DecisionAllow {
t.Errorf("AlwaysDenyAuthorizer.Authorize returned nil instead of error.") t.Errorf("AlwaysDenyAuthorizer.Authorize returned nil instead of error.")
} }
} }
...@@ -47,10 +47,10 @@ func TestPrivilegedGroupAuthorizer(t *testing.T) { ...@@ -47,10 +47,10 @@ func TestPrivilegedGroupAuthorizer(t *testing.T) {
yes := authorizer.AttributesRecord{User: &user.DefaultInfo{Groups: []string{"no", "allow-01"}}} yes := authorizer.AttributesRecord{User: &user.DefaultInfo{Groups: []string{"no", "allow-01"}}}
no := authorizer.AttributesRecord{User: &user.DefaultInfo{Groups: []string{"no", "deny-01"}}} no := authorizer.AttributesRecord{User: &user.DefaultInfo{Groups: []string{"no", "deny-01"}}}
if authorized, _, _ := auth.Authorize(yes); !authorized { if authorized, _, _ := auth.Authorize(yes); authorized != authorizer.DecisionAllow {
t.Errorf("failed") t.Errorf("failed")
} }
if authorized, _, _ := auth.Authorize(no); authorized { if authorized, _, _ := auth.Authorize(no); authorized == authorizer.DecisionAllow {
t.Errorf("failed") t.Errorf("failed")
} }
} }
...@@ -28,8 +28,8 @@ import ( ...@@ -28,8 +28,8 @@ import (
// 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) (authorized bool, reason string, err error) { func (alwaysAllowAuthorizer) Authorize(a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
return true, "", nil return authorizer.DecisionAllow, "", nil
} }
func (alwaysAllowAuthorizer) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) { func (alwaysAllowAuthorizer) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
...@@ -56,8 +56,8 @@ func NewAlwaysAllowAuthorizer() *alwaysAllowAuthorizer { ...@@ -56,8 +56,8 @@ func NewAlwaysAllowAuthorizer() *alwaysAllowAuthorizer {
// 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) (authorized bool, reason string, err error) { func (alwaysDenyAuthorizer) Authorize(a authorizer.Attributes) (decision authorizer.Decision, reason string, err error) {
return false, "Everything is forbidden.", nil return authorizer.DecisionNoOpinion, "Everything is forbidden.", nil
} }
func (alwaysDenyAuthorizer) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) { func (alwaysDenyAuthorizer) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
...@@ -73,8 +73,8 @@ func NewAlwaysDenyAuthorizer() *alwaysDenyAuthorizer { ...@@ -73,8 +73,8 @@ func NewAlwaysDenyAuthorizer() *alwaysDenyAuthorizer {
// It is useful in unit tests to force an operation to fail with error. // It is useful in unit tests to force an operation to fail with error.
type alwaysFailAuthorizer struct{} type alwaysFailAuthorizer struct{}
func (alwaysFailAuthorizer) Authorize(a authorizer.Attributes) (authorized bool, reason string, err error) { func (alwaysFailAuthorizer) Authorize(a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
return false, "", errors.New("Authorization failure.") return authorizer.DecisionNoOpinion, "", errors.New("Authorization failure.")
} }
func NewAlwaysFailAuthorizer() authorizer.Authorizer { func NewAlwaysFailAuthorizer() authorizer.Authorizer {
...@@ -85,18 +85,18 @@ type privilegedGroupAuthorizer struct { ...@@ -85,18 +85,18 @@ type privilegedGroupAuthorizer struct {
groups []string groups []string
} }
func (r *privilegedGroupAuthorizer) Authorize(attr authorizer.Attributes) (bool, string, error) { func (r *privilegedGroupAuthorizer) Authorize(attr authorizer.Attributes) (authorizer.Decision, string, error) {
if attr.GetUser() == nil { if attr.GetUser() == nil {
return false, "Error", errors.New("no user on request.") return authorizer.DecisionNoOpinion, "Error", errors.New("no user on request.")
} }
for _, attr_group := range attr.GetUser().GetGroups() { for _, attr_group := range attr.GetUser().GetGroups() {
for _, priv_group := range r.groups { for _, priv_group := range r.groups {
if priv_group == attr_group { if priv_group == attr_group {
return true, "", nil return authorizer.DecisionAllow, "", nil
} }
} }
} }
return false, "", nil return authorizer.DecisionNoOpinion, "", nil
} }
// NewPrivilegedGroups is for use in loopback scenarios // NewPrivilegedGroups is for use in loopback scenarios
......
...@@ -14,6 +14,14 @@ See the License for the specific language governing permissions and ...@@ -14,6 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
// Package union implements an authorizer that combines multiple subauthorizer.
// The union authorizer iterates over each subauthorizer and returns the first
// decision that is either an Allow decision or a Deny decision. If a
// subauthorizer returns a NoOpinion, then the union authorizer moves onto the
// next authorizer or, if the subauthorizer was the last authorizer, returns
// NoOpinion as the aggregate decision. I.e. union authorizer creates an
// aggregate decision and supports short-circut allows and denies from
// subauthorizers.
package union package union
import ( import (
...@@ -33,14 +41,14 @@ func New(authorizationHandlers ...authorizer.Authorizer) authorizer.Authorizer { ...@@ -33,14 +41,14 @@ 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) (bool, string, error) { func (authzHandler unionAuthzHandler) Authorize(a authorizer.Attributes) (authorizer.Decision, string, error) {
var ( var (
errlist []error errlist []error
reasonlist []string reasonlist []string
) )
for _, currAuthzHandler := range authzHandler { for _, currAuthzHandler := range authzHandler {
authorized, reason, err := currAuthzHandler.Authorize(a) decision, reason, err := currAuthzHandler.Authorize(a)
if err != nil { if err != nil {
errlist = append(errlist, err) errlist = append(errlist, err)
...@@ -48,13 +56,15 @@ func (authzHandler unionAuthzHandler) Authorize(a authorizer.Attributes) (bool, ...@@ -48,13 +56,15 @@ func (authzHandler unionAuthzHandler) Authorize(a authorizer.Attributes) (bool,
if len(reason) != 0 { if len(reason) != 0 {
reasonlist = append(reasonlist, reason) reasonlist = append(reasonlist, reason)
} }
if !authorized { switch decision {
continue case authorizer.DecisionAllow, authorizer.DecisionDeny:
return decision, reason, err
case authorizer.DecisionNoOpinion:
// continue to the next authorizer
} }
return true, reason, nil
} }
return false, strings.Join(reasonlist, "\n"), utilerrors.NewAggregate(errlist) return authorizer.DecisionNoOpinion, strings.Join(reasonlist, "\n"), utilerrors.NewAggregate(errlist)
} }
// unionAuthzRulesHandler authorizer against a chain of authorizer.RuleResolver // unionAuthzRulesHandler authorizer against a chain of authorizer.RuleResolver
......
...@@ -17,6 +17,7 @@ limitations under the License. ...@@ -17,6 +17,7 @@ limitations under the License.
package union package union
import ( import (
"errors"
"fmt" "fmt"
"reflect" "reflect"
"testing" "testing"
...@@ -26,49 +27,43 @@ import ( ...@@ -26,49 +27,43 @@ import (
) )
type mockAuthzHandler struct { type mockAuthzHandler struct {
isAuthorized bool decision authorizer.Decision
err error err error
} }
func (mock *mockAuthzHandler) Authorize(a authorizer.Attributes) (bool, string, error) { func (mock *mockAuthzHandler) Authorize(a authorizer.Attributes) (authorizer.Decision, string, error) {
if mock.err != nil { return mock.decision, "", mock.err
return false, "", mock.err
}
if !mock.isAuthorized {
return false, "", nil
}
return true, "", nil
} }
func TestAuthorizationSecondPasses(t *testing.T) { func TestAuthorizationSecondPasses(t *testing.T) {
handler1 := &mockAuthzHandler{isAuthorized: false} handler1 := &mockAuthzHandler{decision: authorizer.DecisionNoOpinion}
handler2 := &mockAuthzHandler{isAuthorized: true} handler2 := &mockAuthzHandler{decision: authorizer.DecisionAllow}
authzHandler := New(handler1, handler2) authzHandler := New(handler1, handler2)
authorized, _, _ := authzHandler.Authorize(nil) authorized, _, _ := authzHandler.Authorize(nil)
if !authorized { if authorized != authorizer.DecisionAllow {
t.Errorf("Unexpected authorization failure") t.Errorf("Unexpected authorization failure")
} }
} }
func TestAuthorizationFirstPasses(t *testing.T) { func TestAuthorizationFirstPasses(t *testing.T) {
handler1 := &mockAuthzHandler{isAuthorized: true} handler1 := &mockAuthzHandler{decision: authorizer.DecisionAllow}
handler2 := &mockAuthzHandler{isAuthorized: false} handler2 := &mockAuthzHandler{decision: authorizer.DecisionNoOpinion}
authzHandler := New(handler1, handler2) authzHandler := New(handler1, handler2)
authorized, _, _ := authzHandler.Authorize(nil) authorized, _, _ := authzHandler.Authorize(nil)
if !authorized { if authorized != authorizer.DecisionAllow {
t.Errorf("Unexpected authorization failure") t.Errorf("Unexpected authorization failure")
} }
} }
func TestAuthorizationNonePasses(t *testing.T) { func TestAuthorizationNonePasses(t *testing.T) {
handler1 := &mockAuthzHandler{isAuthorized: false} handler1 := &mockAuthzHandler{decision: authorizer.DecisionNoOpinion}
handler2 := &mockAuthzHandler{isAuthorized: false} handler2 := &mockAuthzHandler{decision: authorizer.DecisionNoOpinion}
authzHandler := New(handler1, handler2) authzHandler := New(handler1, handler2)
authorized, _, _ := authzHandler.Authorize(nil) authorized, _, _ := authzHandler.Authorize(nil)
if authorized { if authorized == authorizer.DecisionAllow {
t.Errorf("Expected failed authorization") t.Errorf("Expected failed authorization")
} }
} }
...@@ -223,3 +218,49 @@ func getNonResourceRules(infos []authorizer.NonResourceRuleInfo) []authorizer.De ...@@ -223,3 +218,49 @@ func getNonResourceRules(infos []authorizer.NonResourceRuleInfo) []authorizer.De
} }
return rules return rules
} }
func TestAuthorizationUnequivocalDeny(t *testing.T) {
cs := []struct {
authorizers []authorizer.Authorizer
decision authorizer.Decision
}{
{
authorizers: []authorizer.Authorizer{},
decision: authorizer.DecisionNoOpinion,
},
{
authorizers: []authorizer.Authorizer{
&mockAuthzHandler{decision: authorizer.DecisionNoOpinion},
&mockAuthzHandler{decision: authorizer.DecisionAllow},
&mockAuthzHandler{decision: authorizer.DecisionDeny},
},
decision: authorizer.DecisionAllow,
},
{
authorizers: []authorizer.Authorizer{
&mockAuthzHandler{decision: authorizer.DecisionNoOpinion},
&mockAuthzHandler{decision: authorizer.DecisionDeny},
&mockAuthzHandler{decision: authorizer.DecisionAllow},
},
decision: authorizer.DecisionDeny,
},
{
authorizers: []authorizer.Authorizer{
&mockAuthzHandler{decision: authorizer.DecisionNoOpinion},
&mockAuthzHandler{decision: authorizer.DecisionDeny, err: errors.New("webhook failed closed")},
&mockAuthzHandler{decision: authorizer.DecisionAllow},
},
decision: authorizer.DecisionDeny,
},
}
for i, c := range cs {
t.Run(fmt.Sprintf("case %v", i), func(t *testing.T) {
authzHandler := New(c.authorizers...)
decision, _, _ := authzHandler.Authorize(nil)
if decision != c.decision {
t.Errorf("Unexpected authorization failure: %v, expected: %v", decision, c.decision)
}
})
}
}
...@@ -47,7 +47,7 @@ func WithAuthorization(handler http.Handler, requestContextMapper request.Reques ...@@ -47,7 +47,7 @@ func WithAuthorization(handler http.Handler, requestContextMapper request.Reques
return return
} }
authorized, reason, err := a.Authorize(attributes) authorized, reason, err := a.Authorize(attributes)
if authorized { if authorized == authorizer.DecisionAllow {
handler.ServeHTTP(w, req) handler.ServeHTTP(w, req)
return return
} }
......
...@@ -110,8 +110,8 @@ func WithImpersonation(handler http.Handler, requestContextMapper request.Reques ...@@ -110,8 +110,8 @@ func WithImpersonation(handler http.Handler, requestContextMapper request.Reques
return return
} }
allowed, reason, err := a.Authorize(actingAsAttributes) decision, reason, err := a.Authorize(actingAsAttributes)
if err != nil || !allowed { if err != nil || decision != authorizer.DecisionAllow {
glog.V(4).Infof("Forbidden: %#v, Reason: %s, Error: %v", req.RequestURI, reason, err) glog.V(4).Infof("Forbidden: %#v, Reason: %s, Error: %v", req.RequestURI, reason, err)
responsewriters.Forbidden(ctx, actingAsAttributes, w, req, reason, s) responsewriters.Forbidden(ctx, actingAsAttributes, w, req, reason, s)
return return
......
...@@ -35,50 +35,50 @@ import ( ...@@ -35,50 +35,50 @@ import (
type impersonateAuthorizer struct{} type impersonateAuthorizer struct{}
func (impersonateAuthorizer) Authorize(a authorizer.Attributes) (authorized bool, reason string, err error) { func (impersonateAuthorizer) Authorize(a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
user := a.GetUser() user := a.GetUser()
switch { switch {
case user.GetName() == "system:admin": case user.GetName() == "system:admin":
return true, "", nil return authorizer.DecisionAllow, "", nil
case user.GetName() == "tester": case user.GetName() == "tester":
return false, "", fmt.Errorf("works on my machine") return authorizer.DecisionNoOpinion, "", fmt.Errorf("works on my machine")
case user.GetName() == "deny-me": case user.GetName() == "deny-me":
return false, "denied", nil return authorizer.DecisionNoOpinion, "denied", nil
} }
if len(user.GetGroups()) > 0 && user.GetGroups()[0] == "wheel" && a.GetVerb() == "impersonate" && a.GetResource() == "users" { if len(user.GetGroups()) > 0 && user.GetGroups()[0] == "wheel" && a.GetVerb() == "impersonate" && a.GetResource() == "users" {
return true, "", nil return authorizer.DecisionAllow, "", nil
} }
if len(user.GetGroups()) > 0 && user.GetGroups()[0] == "sa-impersonater" && a.GetVerb() == "impersonate" && a.GetResource() == "serviceaccounts" { if len(user.GetGroups()) > 0 && user.GetGroups()[0] == "sa-impersonater" && a.GetVerb() == "impersonate" && a.GetResource() == "serviceaccounts" {
return true, "", nil return authorizer.DecisionAllow, "", nil
} }
if len(user.GetGroups()) > 0 && user.GetGroups()[0] == "regular-impersonater" && a.GetVerb() == "impersonate" && a.GetResource() == "users" { if len(user.GetGroups()) > 0 && user.GetGroups()[0] == "regular-impersonater" && a.GetVerb() == "impersonate" && a.GetResource() == "users" {
return true, "", nil return authorizer.DecisionAllow, "", nil
} }
if len(user.GetGroups()) > 1 && user.GetGroups()[1] == "group-impersonater" && a.GetVerb() == "impersonate" && a.GetResource() == "groups" { if len(user.GetGroups()) > 1 && user.GetGroups()[1] == "group-impersonater" && a.GetVerb() == "impersonate" && a.GetResource() == "groups" {
return true, "", nil return authorizer.DecisionAllow, "", nil
} }
if len(user.GetGroups()) > 1 && user.GetGroups()[1] == "extra-setter-scopes" && a.GetVerb() == "impersonate" && a.GetResource() == "userextras" && a.GetSubresource() == "scopes" { if len(user.GetGroups()) > 1 && user.GetGroups()[1] == "extra-setter-scopes" && a.GetVerb() == "impersonate" && a.GetResource() == "userextras" && a.GetSubresource() == "scopes" {
return true, "", nil return authorizer.DecisionAllow, "", nil
} }
if len(user.GetGroups()) > 1 && user.GetGroups()[1] == "extra-setter-particular-scopes" && if len(user.GetGroups()) > 1 && user.GetGroups()[1] == "extra-setter-particular-scopes" &&
a.GetVerb() == "impersonate" && a.GetResource() == "userextras" && a.GetSubresource() == "scopes" && a.GetName() == "scope-a" { a.GetVerb() == "impersonate" && a.GetResource() == "userextras" && a.GetSubresource() == "scopes" && a.GetName() == "scope-a" {
return true, "", nil return authorizer.DecisionAllow, "", nil
} }
if len(user.GetGroups()) > 1 && user.GetGroups()[1] == "extra-setter-project" && a.GetVerb() == "impersonate" && a.GetResource() == "userextras" && a.GetSubresource() == "project" { if len(user.GetGroups()) > 1 && user.GetGroups()[1] == "extra-setter-project" && a.GetVerb() == "impersonate" && a.GetResource() == "userextras" && a.GetSubresource() == "project" {
return true, "", nil return authorizer.DecisionAllow, "", nil
} }
return false, "deny by default", nil return authorizer.DecisionNoOpinion, "deny by default", nil
} }
func TestImpersonationFilter(t *testing.T) { func TestImpersonationFilter(t *testing.T) {
......
...@@ -437,9 +437,9 @@ type mockAuthorizer struct { ...@@ -437,9 +437,9 @@ type mockAuthorizer struct {
lastURI string lastURI string
} }
func (authz *mockAuthorizer) Authorize(a authorizer.Attributes) (authorized bool, reason string, err error) { func (authz *mockAuthorizer) Authorize(a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
authz.lastURI = a.GetPath() authz.lastURI = a.GetPath()
return true, "", nil return authorizer.DecisionAllow, "", nil
} }
type mockAuthenticator struct { type mockAuthenticator struct {
......
...@@ -50,6 +50,7 @@ type WebhookAuthorizer struct { ...@@ -50,6 +50,7 @@ type WebhookAuthorizer struct {
authorizedTTL time.Duration authorizedTTL time.Duration
unauthorizedTTL time.Duration unauthorizedTTL time.Duration
initialBackoff time.Duration initialBackoff time.Duration
decisionOnError authorizer.Decision
} }
// NewFromInterface creates a WebhookAuthorizer using the given subjectAccessReview client // NewFromInterface creates a WebhookAuthorizer using the given subjectAccessReview client
...@@ -93,6 +94,7 @@ func newWithBackoff(subjectAccessReview authorizationclient.SubjectAccessReviewI ...@@ -93,6 +94,7 @@ func newWithBackoff(subjectAccessReview authorizationclient.SubjectAccessReviewI
authorizedTTL: authorizedTTL, authorizedTTL: authorizedTTL,
unauthorizedTTL: unauthorizedTTL, unauthorizedTTL: unauthorizedTTL,
initialBackoff: initialBackoff, initialBackoff: initialBackoff,
decisionOnError: authorizer.DecisionNoOpinion,
}, nil }, nil
} }
...@@ -140,7 +142,10 @@ func newWithBackoff(subjectAccessReview authorizationclient.SubjectAccessReviewI ...@@ -140,7 +142,10 @@ func newWithBackoff(subjectAccessReview authorizationclient.SubjectAccessReviewI
// } // }
// } // }
// //
func (w *WebhookAuthorizer) Authorize(attr authorizer.Attributes) (authorized bool, reason string, err error) { // TODO(mikedanese): We should eventually support failing closed when we
// encounter an error. We are failing open now to preserve backwards compatible
// behavior.
func (w *WebhookAuthorizer) Authorize(attr authorizer.Attributes) (decision authorizer.Decision, reason string, err error) {
r := &authorization.SubjectAccessReview{} r := &authorization.SubjectAccessReview{}
if user := attr.GetUser(); user != nil { if user := attr.GetUser(); user != nil {
r.Spec = authorization.SubjectAccessReviewSpec{ r.Spec = authorization.SubjectAccessReviewSpec{
...@@ -169,7 +174,7 @@ func (w *WebhookAuthorizer) Authorize(attr authorizer.Attributes) (authorized bo ...@@ -169,7 +174,7 @@ func (w *WebhookAuthorizer) Authorize(attr authorizer.Attributes) (authorized bo
} }
key, err := json.Marshal(r.Spec) key, err := json.Marshal(r.Spec)
if err != nil { if err != nil {
return false, "", err return w.decisionOnError, "", err
} }
if entry, ok := w.responseCache.Get(string(key)); ok { if entry, ok := w.responseCache.Get(string(key)); ok {
r.Status = entry.(authorization.SubjectAccessReviewStatus) r.Status = entry.(authorization.SubjectAccessReviewStatus)
...@@ -185,7 +190,7 @@ func (w *WebhookAuthorizer) Authorize(attr authorizer.Attributes) (authorized bo ...@@ -185,7 +190,7 @@ func (w *WebhookAuthorizer) Authorize(attr authorizer.Attributes) (authorized bo
if err != nil { if 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 false, "", err return w.decisionOnError, "", err
} }
r.Status = result.Status r.Status = result.Status
if r.Status.Allowed { if r.Status.Allowed {
...@@ -194,7 +199,17 @@ func (w *WebhookAuthorizer) Authorize(attr authorizer.Attributes) (authorized bo ...@@ -194,7 +199,17 @@ func (w *WebhookAuthorizer) Authorize(attr authorizer.Attributes) (authorized bo
w.responseCache.Add(string(key), r.Status, w.unauthorizedTTL) w.responseCache.Add(string(key), r.Status, w.unauthorizedTTL)
} }
} }
return r.Status.Allowed, r.Status.Reason, nil switch {
case r.Status.Denied && r.Status.Allowed:
return authorizer.DecisionDeny, r.Status.Reason, fmt.Errorf("webhook subject access review returned both allow and deny response")
case r.Status.Denied:
return authorizer.DecisionDeny, r.Status.Reason, nil
case r.Status.Allowed:
return authorizer.DecisionAllow, r.Status.Reason, nil
default:
return authorizer.DecisionNoOpinion, r.Status.Reason, nil
}
} }
//TODO: need to finish the method to get the rules when using webhook mode //TODO: need to finish the method to get the rules when using webhook mode
......
...@@ -396,13 +396,13 @@ func TestTLSConfig(t *testing.T) { ...@@ -396,13 +396,13 @@ 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()
authorized, _, err := wh.Authorize(attr) decision, _, err := wh.Authorize(attr)
if tt.wantAuth { if tt.wantAuth {
if !authorized { if decision != authorizer.DecisionAllow {
t.Errorf("expected successful authorization") t.Errorf("expected successful authorization")
} }
} else { } else {
if authorized { if decision == authorizer.DecisionAllow {
t.Errorf("expected failed authorization") t.Errorf("expected failed authorization")
} }
} }
...@@ -418,7 +418,7 @@ func TestTLSConfig(t *testing.T) { ...@@ -418,7 +418,7 @@ func TestTLSConfig(t *testing.T) {
} }
service.Deny() service.Deny()
if authorized, _, _ := wh.Authorize(attr); authorized { if decision, _, _ := wh.Authorize(attr); decision == authorizer.DecisionAllow {
t.Errorf("%s: incorrectly authorized with DenyAll policy", tt.test) t.Errorf("%s: incorrectly authorized with DenyAll policy", tt.test)
} }
}() }()
...@@ -522,11 +522,11 @@ func TestWebhook(t *testing.T) { ...@@ -522,11 +522,11 @@ func TestWebhook(t *testing.T) {
} }
for i, tt := range tests { for i, tt := range tests {
authorized, _, err := wh.Authorize(tt.attr) decision, _, err := wh.Authorize(tt.attr)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if !authorized { if decision != authorizer.DecisionAllow {
t.Errorf("case %d: authorization failed", i) t.Errorf("case %d: authorization failed", i)
continue continue
} }
...@@ -567,7 +567,7 @@ func testWebhookCacheCases(t *testing.T, serv *mockService, wh *WebhookAuthorize ...@@ -567,7 +567,7 @@ func testWebhookCacheCases(t *testing.T, serv *mockService, wh *WebhookAuthorize
continue continue
} }
if test.expectedAuthorized != authorized { if test.expectedAuthorized != (authorized == authorizer.DecisionAllow) {
t.Errorf("%d: expected authorized=%v, got %v", i, test.expectedAuthorized, authorized) t.Errorf("%d: expected authorized=%v, got %v", i, test.expectedAuthorized, authorized)
} }
......
...@@ -39,12 +39,12 @@ import ( ...@@ -39,12 +39,12 @@ import (
// 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 sarAuthorizer struct{} type sarAuthorizer struct{}
func (sarAuthorizer) Authorize(a authorizer.Attributes) (bool, string, error) { func (sarAuthorizer) Authorize(a authorizer.Attributes) (authorizer.Decision, string, error) {
if a.GetUser().GetName() == "dave" { if a.GetUser().GetName() == "dave" {
return false, "no", errors.New("I'm sorry, Dave") return authorizer.DecisionNoOpinion, "no", errors.New("I'm sorry, Dave")
} }
return true, "you're not dave", nil return authorizer.DecisionAllow, "you're not dave", nil
} }
func alwaysAlice(req *http.Request) (user.Info, bool, error) { func alwaysAlice(req *http.Request) (user.Info, bool, error) {
......
...@@ -535,11 +535,11 @@ func TestAuthModeAlwaysDeny(t *testing.T) { ...@@ -535,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) (bool, string, error) { func (allowAliceAuthorizer) Authorize(a authorizer.Attributes) (authorizer.Decision, string, error) {
if a.GetUser() != nil && a.GetUser().GetName() == "alice" { if a.GetUser() != nil && a.GetUser().GetName() == "alice" {
return true, "", nil return authorizer.DecisionAllow, "", nil
} }
return false, "I can't allow that. Go ask alice.", nil return authorizer.DecisionNoOpinion, "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
...@@ -702,24 +702,24 @@ func TestUnknownUserIsUnauthorized(t *testing.T) { ...@@ -702,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) (bool, string, error) { func (impersonateAuthorizer) Authorize(a authorizer.Attributes) (authorizer.Decision, 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 true, "", nil return authorizer.DecisionAllow, "", nil
} }
if a.GetUser() != nil && a.GetUser().GetName() == "alice" && a.GetVerb() != "impersonate" { if a.GetUser() != nil && a.GetUser().GetName() == "alice" && a.GetVerb() != "impersonate" {
return true, "", nil return authorizer.DecisionAllow, "", 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 true, "", nil return authorizer.DecisionAllow, "", 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 true, "", nil return authorizer.DecisionAllow, "", nil
} }
return false, "I can't allow that. Go ask alice.", nil return authorizer.DecisionNoOpinion, "I can't allow that. Go ask alice.", nil
} }
func TestImpersonateIsForbidden(t *testing.T) { func TestImpersonateIsForbidden(t *testing.T) {
...@@ -861,9 +861,9 @@ type trackingAuthorizer struct { ...@@ -861,9 +861,9 @@ type trackingAuthorizer struct {
requestAttributes []authorizer.Attributes requestAttributes []authorizer.Attributes
} }
func (a *trackingAuthorizer) Authorize(attributes authorizer.Attributes) (bool, string, error) { func (a *trackingAuthorizer) Authorize(attributes authorizer.Attributes) (authorizer.Decision, string, error) {
a.requestAttributes = append(a.requestAttributes, attributes) a.requestAttributes = append(a.requestAttributes, attributes)
return true, "", nil return authorizer.DecisionAllow, "", nil
} }
// TestAuthorizationAttributeDetermination tests that authorization attributes are built correctly // TestAuthorizationAttributeDetermination tests that authorization attributes are built correctly
......
...@@ -46,6 +46,7 @@ import ( ...@@ -46,6 +46,7 @@ import (
"k8s.io/apiserver/pkg/authentication/authenticatorfactory" "k8s.io/apiserver/pkg/authentication/authenticatorfactory"
authenticatorunion "k8s.io/apiserver/pkg/authentication/request/union" authenticatorunion "k8s.io/apiserver/pkg/authentication/request/union"
"k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/authorization/authorizer"
authauthorizer "k8s.io/apiserver/pkg/authorization/authorizer" authauthorizer "k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/authorization/authorizerfactory" "k8s.io/apiserver/pkg/authorization/authorizerfactory"
authorizerunion "k8s.io/apiserver/pkg/authorization/union" authorizerunion "k8s.io/apiserver/pkg/authorization/union"
...@@ -148,8 +149,8 @@ func NewMasterComponents(c *Config) *MasterComponents { ...@@ -148,8 +149,8 @@ func NewMasterComponents(c *Config) *MasterComponents {
// alwaysAllow always allows an action // alwaysAllow always allows an action
type alwaysAllow struct{} type alwaysAllow struct{}
func (alwaysAllow) Authorize(requestAttributes authauthorizer.Attributes) (bool, string, error) { func (alwaysAllow) Authorize(requestAttributes authauthorizer.Attributes) (authorizer.Decision, string, error) {
return true, "always allow", nil return authorizer.DecisionAllow, "always allow", nil
} }
// alwaysEmpty simulates "no authentication" for old tests // alwaysEmpty simulates "no authentication" for old tests
......
...@@ -58,11 +58,11 @@ const ( ...@@ -58,11 +58,11 @@ const (
type allowAliceAuthorizer struct{} type allowAliceAuthorizer struct{}
func (allowAliceAuthorizer) Authorize(a authorizer.Attributes) (bool, string, error) { func (allowAliceAuthorizer) Authorize(a authorizer.Attributes) (authorizer.Decision, string, error) {
if a.GetUser() != nil && a.GetUser().GetName() == "alice" { if a.GetUser() != nil && a.GetUser().GetName() == "alice" {
return true, "", nil return authorizer.DecisionAllow, "", nil
} }
return false, "I can't allow that. Go ask alice.", nil return authorizer.DecisionNoOpinion, "I can't allow that. Go ask alice.", nil
} }
func testPrefix(t *testing.T, prefix string) { func testPrefix(t *testing.T, prefix string) {
......
...@@ -372,7 +372,7 @@ func startServiceAccountTestServer(t *testing.T) (*clientset.Clientset, restclie ...@@ -372,7 +372,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) (bool, string, error) { authorizer := authorizer.AuthorizerFunc(func(attrs authorizer.Attributes) (authorizer.Decision, string, error) {
username := "" username := ""
if user := attrs.GetUser(); user != nil { if user := attrs.GetUser(); user != nil {
username = user.GetName() username = user.GetName()
...@@ -382,7 +382,7 @@ func startServiceAccountTestServer(t *testing.T) (*clientset.Clientset, restclie ...@@ -382,7 +382,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 true, "", nil return authorizer.DecisionAllow, "", nil
} }
// If the user is a service account... // If the user is a service account...
...@@ -392,15 +392,15 @@ func startServiceAccountTestServer(t *testing.T) (*clientset.Clientset, restclie ...@@ -392,15 +392,15 @@ func startServiceAccountTestServer(t *testing.T) (*clientset.Clientset, restclie
switch serviceAccountName { switch serviceAccountName {
case readOnlyServiceAccountName: case readOnlyServiceAccountName:
if attrs.IsReadOnly() { if attrs.IsReadOnly() {
return true, "", nil return authorizer.DecisionAllow, "", nil
} }
case readWriteServiceAccountName: case readWriteServiceAccountName:
return true, "", nil return authorizer.DecisionAllow, "", nil
} }
} }
} }
return false, fmt.Sprintf("User %s is denied (ns=%s, readonly=%v, resource=%s)", username, ns, attrs.IsReadOnly(), attrs.GetResource()), nil return authorizer.DecisionNoOpinion, 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