Log rbac info into advanced audit event

pull/8/head
Cao Shufeng 2018-01-22 15:19:15 +08:00
parent ebae09e741
commit e87c2c9f27
5 changed files with 92 additions and 3 deletions

View File

@ -62,7 +62,7 @@ type authorizingVisitor struct {
func (v *authorizingVisitor) visit(source fmt.Stringer, rule *rbac.PolicyRule, err error) bool {
if rule != nil && RuleAllows(v.requestAttributes, rule) {
v.allowed = true
v.reason = fmt.Sprintf("allowed by %s", source.String())
v.reason = fmt.Sprintf("RBAC: allowed by %s", source.String())
return false
}
if err != nil {
@ -120,7 +120,7 @@ func (r *RBACAuthorizer) Authorize(requestAttributes authorizer.Attributes) (aut
reason := ""
if len(ruleCheckingVisitor.errors) > 0 {
reason = fmt.Sprintf("%v", utilerrors.NewAggregate(ruleCheckingVisitor.errors))
reason = fmt.Sprintf("RBAC: %v", utilerrors.NewAggregate(ruleCheckingVisitor.errors))
}
return authorizer.DecisionNoOpinion, reason, nil
}

View File

@ -20,6 +20,7 @@ go_test(
embed = [":go_default_library"],
deps = [
"//vendor/github.com/pborman/uuid:go_default_library",
"//vendor/github.com/stretchr/testify/assert:go_default_library",
"//vendor/k8s.io/api/authentication/v1:go_default_library",
"//vendor/k8s.io/api/batch/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",

View File

@ -736,7 +736,8 @@ func TestAudit(t *testing.T) {
}
type fakeRequestContextMapper struct {
user *user.DefaultInfo
user *user.DefaultInfo
audit *auditinternal.Event
}
func (m *fakeRequestContextMapper) Get(req *http.Request) (request.Context, bool) {
@ -744,6 +745,9 @@ func (m *fakeRequestContextMapper) Get(req *http.Request) (request.Context, bool
if m.user != nil {
ctx = request.WithUser(ctx, m.user)
}
if m.audit != nil {
ctx = request.WithAuditEvent(ctx, m.audit)
}
resolver := newTestRequestInfoResolver()
info, err := resolver.NewRequestInfo(req)

View File

@ -23,11 +23,23 @@ import (
"github.com/golang/glog"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/audit"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
"k8s.io/apiserver/pkg/endpoints/request"
)
const (
// Annotation key names set in advanced audit
decisionAnnotationKey = "authorization.k8s.io/decision"
reasonAnnotationKey = "authorization.k8s.io/reason"
// Annotation values set in advanced audit
decisionAllow = "allow"
decisionForbid = "forbid"
reasonError = "internal error"
)
// WithAuthorizationCheck passes all authorized requests on to handler, and returns a forbidden error otherwise.
func WithAuthorization(handler http.Handler, requestContextMapper request.RequestContextMapper, a authorizer.Authorizer, s runtime.NegotiatedSerializer) http.Handler {
if a == nil {
@ -40,6 +52,7 @@ func WithAuthorization(handler http.Handler, requestContextMapper request.Reques
responsewriters.InternalError(w, req, errors.New("no context found for request"))
return
}
ae := request.AuditEventFrom(ctx)
attributes, err := GetAuthorizerAttributes(ctx)
if err != nil {
@ -49,15 +62,20 @@ func WithAuthorization(handler http.Handler, requestContextMapper request.Reques
authorized, reason, err := a.Authorize(attributes)
// an authorizer like RBAC could encounter evaluation errors and still allow the request, so authorizer decision is checked before error here.
if authorized == authorizer.DecisionAllow {
audit.LogAnnotation(ae, decisionAnnotationKey, decisionAllow)
audit.LogAnnotation(ae, reasonAnnotationKey, reason)
handler.ServeHTTP(w, req)
return
}
if err != nil {
audit.LogAnnotation(ae, reasonAnnotationKey, reasonError)
responsewriters.InternalError(w, req, err)
return
}
glog.V(4).Infof("Forbidden: %#v, Reason: %q", req.RequestURI, reason)
audit.LogAnnotation(ae, decisionAnnotationKey, decisionForbid)
audit.LogAnnotation(ae, reasonAnnotationKey, reason)
responsewriters.Forbidden(ctx, attributes, w, req, reason, s)
})
}

View File

@ -23,7 +23,11 @@ import (
"reflect"
"testing"
"github.com/stretchr/testify/assert"
batch "k8s.io/api/batch/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
auditinternal "k8s.io/apiserver/pkg/apis/audit"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
"k8s.io/apiserver/pkg/endpoints/request"
@ -127,3 +131,65 @@ func TestGetAuthorizerAttributes(t *testing.T) {
}
}
}
type fakeAuthorizer struct {
decision authorizer.Decision
reason string
err error
}
func (f fakeAuthorizer) Authorize(a authorizer.Attributes) (authorizer.Decision, string, error) {
return f.decision, f.reason, f.err
}
func TestAuditAnnotation(t *testing.T) {
testcases := map[string]struct {
authorizer fakeAuthorizer
decisionAnnotation string
reasonAnnotation string
}{
"decision allow": {
fakeAuthorizer{
authorizer.DecisionAllow,
"RBAC: allowed to patch pod",
nil,
},
"allow",
"RBAC: allowed to patch pod",
},
"decision forbid": {
fakeAuthorizer{
authorizer.DecisionDeny,
"RBAC: not allowed to patch pod",
nil,
},
"forbid",
"RBAC: not allowed to patch pod",
},
"error": {
fakeAuthorizer{
authorizer.DecisionNoOpinion,
"",
errors.New("can't parse user info"),
},
"",
reasonError,
},
}
scheme := runtime.NewScheme()
negotiatedSerializer := serializer.DirectCodecFactory{CodecFactory: serializer.NewCodecFactory(scheme)}
for k, tc := range testcases {
audit := &auditinternal.Event{Level: auditinternal.LevelMetadata}
handler := WithAuthorization(&fakeHTTPHandler{}, &fakeRequestContextMapper{
audit: audit,
}, tc.authorizer, negotiatedSerializer)
req, _ := http.NewRequest("GET", "/api/v1/namespaces/default/pods", nil)
req.RemoteAddr = "127.0.0.1"
handler.ServeHTTP(httptest.NewRecorder(), req)
assert.Equal(t, tc.decisionAnnotation, audit.Annotations[decisionAnnotationKey], k+": unexpected decision annotation")
assert.Equal(t, tc.reasonAnnotation, audit.Annotations[reasonAnnotationKey], k+": unexpected reason annotation")
}
}