mirror of https://github.com/k3s-io/k3s
Log rbac info into advanced audit event
parent
ebae09e741
commit
e87c2c9f27
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue