Commit 9d29ce13 authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #49280 from CaoShuFeng/RequestReceived

Automatic merge from submit-queue Provide a way to omit Event stages in audit policy This provide a way to omit some stages for each audit policy rule. For example: ``` apiVersion: audit.k8s.io/v1beta1 kind: Policy - level: Metadata resources: - group: "rbac.authorization.k8s.io" resources: ["roles"] omitStages: - "RequestReceived" ``` RequestReceived stage will not be emitted to audit backends with previous config. **Release note**: ``` None ``` #
parents ffed1d34 b50acbdf
...@@ -207,6 +207,10 @@ type PolicyRule struct { ...@@ -207,6 +207,10 @@ type PolicyRule struct {
// "/healthz*" - Log all health checks // "/healthz*" - Log all health checks
// +optional // +optional
NonResourceURLs []string NonResourceURLs []string
// OmitStages specify events generated in which stages will not be emitted to backend.
// An empty list means no restrictions will apply.
OmitStages []Stage
} }
// GroupResources represents resource kinds in an API group. // GroupResources represents resource kinds in an API group.
......
...@@ -205,5 +205,9 @@ message PolicyRule { ...@@ -205,5 +205,9 @@ message PolicyRule {
// "/healthz*" - Log all health checks // "/healthz*" - Log all health checks
// +optional // +optional
repeated string nonResourceURLs = 7; repeated string nonResourceURLs = 7;
// OmitStages specify events generated in which stages will not be emitted to backend.
// An empty list means no restrictions will apply.
repeated string omitStages = 8;
} }
...@@ -208,6 +208,10 @@ type PolicyRule struct { ...@@ -208,6 +208,10 @@ type PolicyRule struct {
// "/healthz*" - Log all health checks // "/healthz*" - Log all health checks
// +optional // +optional
NonResourceURLs []string `json:"nonResourceURLs,omitempty" protobuf:"bytes,7,rep,name=nonResourceURLs"` NonResourceURLs []string `json:"nonResourceURLs,omitempty" protobuf:"bytes,7,rep,name=nonResourceURLs"`
// OmitStages specify events generated in which stages will not be emitted to backend.
// An empty list means no restrictions will apply.
OmitStages []Stage `json:"omitStages,omitempty" protobuf:"bytes,8,rep,name=omitStages"`
} }
// GroupResources represents resource kinds in an API group. // GroupResources represents resource kinds in an API group.
......
...@@ -264,6 +264,7 @@ func autoConvert_v1alpha1_PolicyRule_To_audit_PolicyRule(in *PolicyRule, out *au ...@@ -264,6 +264,7 @@ func autoConvert_v1alpha1_PolicyRule_To_audit_PolicyRule(in *PolicyRule, out *au
out.Resources = *(*[]audit.GroupResources)(unsafe.Pointer(&in.Resources)) out.Resources = *(*[]audit.GroupResources)(unsafe.Pointer(&in.Resources))
out.Namespaces = *(*[]string)(unsafe.Pointer(&in.Namespaces)) out.Namespaces = *(*[]string)(unsafe.Pointer(&in.Namespaces))
out.NonResourceURLs = *(*[]string)(unsafe.Pointer(&in.NonResourceURLs)) out.NonResourceURLs = *(*[]string)(unsafe.Pointer(&in.NonResourceURLs))
out.OmitStages = *(*[]audit.Stage)(unsafe.Pointer(&in.OmitStages))
return nil return nil
} }
...@@ -280,6 +281,7 @@ func autoConvert_audit_PolicyRule_To_v1alpha1_PolicyRule(in *audit.PolicyRule, o ...@@ -280,6 +281,7 @@ func autoConvert_audit_PolicyRule_To_v1alpha1_PolicyRule(in *audit.PolicyRule, o
out.Resources = *(*[]GroupResources)(unsafe.Pointer(&in.Resources)) out.Resources = *(*[]GroupResources)(unsafe.Pointer(&in.Resources))
out.Namespaces = *(*[]string)(unsafe.Pointer(&in.Namespaces)) out.Namespaces = *(*[]string)(unsafe.Pointer(&in.Namespaces))
out.NonResourceURLs = *(*[]string)(unsafe.Pointer(&in.NonResourceURLs)) out.NonResourceURLs = *(*[]string)(unsafe.Pointer(&in.NonResourceURLs))
out.OmitStages = *(*[]Stage)(unsafe.Pointer(&in.OmitStages))
return nil return nil
} }
......
...@@ -327,6 +327,11 @@ func (in *PolicyRule) DeepCopyInto(out *PolicyRule) { ...@@ -327,6 +327,11 @@ func (in *PolicyRule) DeepCopyInto(out *PolicyRule) {
*out = make([]string, len(*in)) *out = make([]string, len(*in))
copy(*out, *in) copy(*out, *in)
} }
if in.OmitStages != nil {
in, out := &in.OmitStages, &out.OmitStages
*out = make([]Stage, len(*in))
copy(*out, *in)
}
return return
} }
......
...@@ -212,5 +212,9 @@ message PolicyRule { ...@@ -212,5 +212,9 @@ message PolicyRule {
// "/healthz*" - Log all health checks // "/healthz*" - Log all health checks
// +optional // +optional
repeated string nonResourceURLs = 7; repeated string nonResourceURLs = 7;
// OmitStages specify events generated in which stages will not be emitted to backend.
// An empty list means no restrictions will apply.
repeated string omitStages = 8;
} }
...@@ -201,6 +201,10 @@ type PolicyRule struct { ...@@ -201,6 +201,10 @@ type PolicyRule struct {
// "/healthz*" - Log all health checks // "/healthz*" - Log all health checks
// +optional // +optional
NonResourceURLs []string `json:"nonResourceURLs,omitempty" protobuf:"bytes,7,rep,name=nonResourceURLs"` NonResourceURLs []string `json:"nonResourceURLs,omitempty" protobuf:"bytes,7,rep,name=nonResourceURLs"`
// OmitStages specify events generated in which stages will not be emitted to backend.
// An empty list means no restrictions will apply.
OmitStages []Stage `json:"omitStages,omitempty" protobuf:"bytes,8,rep,name=omitStages"`
} }
// GroupResources represents resource kinds in an API group. // GroupResources represents resource kinds in an API group.
......
...@@ -239,6 +239,7 @@ func autoConvert_v1beta1_PolicyRule_To_audit_PolicyRule(in *PolicyRule, out *aud ...@@ -239,6 +239,7 @@ func autoConvert_v1beta1_PolicyRule_To_audit_PolicyRule(in *PolicyRule, out *aud
out.Resources = *(*[]audit.GroupResources)(unsafe.Pointer(&in.Resources)) out.Resources = *(*[]audit.GroupResources)(unsafe.Pointer(&in.Resources))
out.Namespaces = *(*[]string)(unsafe.Pointer(&in.Namespaces)) out.Namespaces = *(*[]string)(unsafe.Pointer(&in.Namespaces))
out.NonResourceURLs = *(*[]string)(unsafe.Pointer(&in.NonResourceURLs)) out.NonResourceURLs = *(*[]string)(unsafe.Pointer(&in.NonResourceURLs))
out.OmitStages = *(*[]audit.Stage)(unsafe.Pointer(&in.OmitStages))
return nil return nil
} }
...@@ -255,6 +256,7 @@ func autoConvert_audit_PolicyRule_To_v1beta1_PolicyRule(in *audit.PolicyRule, ou ...@@ -255,6 +256,7 @@ func autoConvert_audit_PolicyRule_To_v1beta1_PolicyRule(in *audit.PolicyRule, ou
out.Resources = *(*[]GroupResources)(unsafe.Pointer(&in.Resources)) out.Resources = *(*[]GroupResources)(unsafe.Pointer(&in.Resources))
out.Namespaces = *(*[]string)(unsafe.Pointer(&in.Namespaces)) out.Namespaces = *(*[]string)(unsafe.Pointer(&in.Namespaces))
out.NonResourceURLs = *(*[]string)(unsafe.Pointer(&in.NonResourceURLs)) out.NonResourceURLs = *(*[]string)(unsafe.Pointer(&in.NonResourceURLs))
out.OmitStages = *(*[]Stage)(unsafe.Pointer(&in.OmitStages))
return nil return nil
} }
......
...@@ -327,6 +327,11 @@ func (in *PolicyRule) DeepCopyInto(out *PolicyRule) { ...@@ -327,6 +327,11 @@ func (in *PolicyRule) DeepCopyInto(out *PolicyRule) {
*out = make([]string, len(*in)) *out = make([]string, len(*in))
copy(*out, *in) copy(*out, *in)
} }
if in.OmitStages != nil {
in, out := &in.OmitStages, &out.OmitStages
*out = make([]Stage, len(*in))
copy(*out, *in)
}
return return
} }
......
...@@ -38,6 +38,7 @@ func validatePolicyRule(rule audit.PolicyRule, fldPath *field.Path) field.ErrorL ...@@ -38,6 +38,7 @@ func validatePolicyRule(rule audit.PolicyRule, fldPath *field.Path) field.ErrorL
allErrs = append(allErrs, validateLevel(rule.Level, fldPath.Child("level"))...) allErrs = append(allErrs, validateLevel(rule.Level, fldPath.Child("level"))...)
allErrs = append(allErrs, validateNonResourceURLs(rule.NonResourceURLs, fldPath.Child("nonResourceURLs"))...) allErrs = append(allErrs, validateNonResourceURLs(rule.NonResourceURLs, fldPath.Child("nonResourceURLs"))...)
allErrs = append(allErrs, validateResources(rule.Resources, fldPath.Child("resources"))...) allErrs = append(allErrs, validateResources(rule.Resources, fldPath.Child("resources"))...)
allErrs = append(allErrs, validateOmitStages(rule.OmitStages, fldPath.Child("omitStages"))...)
if len(rule.NonResourceURLs) > 0 { if len(rule.NonResourceURLs) > 0 {
if len(rule.Resources) > 0 || len(rule.Namespaces) > 0 { if len(rule.Resources) > 0 || len(rule.Namespaces) > 0 {
...@@ -55,6 +56,13 @@ var validLevels = []string{ ...@@ -55,6 +56,13 @@ var validLevels = []string{
string(audit.LevelRequestResponse), string(audit.LevelRequestResponse),
} }
var validOmitStages = []string{
string(audit.StageRequestReceived),
string(audit.StageResponseStarted),
string(audit.StageResponseComplete),
string(audit.StagePanic),
}
func validateLevel(level audit.Level, fldPath *field.Path) field.ErrorList { func validateLevel(level audit.Level, fldPath *field.Path) field.ErrorList {
switch level { switch level {
case audit.LevelNone, audit.LevelMetadata, audit.LevelRequest, audit.LevelRequestResponse: case audit.LevelNone, audit.LevelMetadata, audit.LevelRequest, audit.LevelRequestResponse:
...@@ -104,3 +112,20 @@ func validateResources(groupResources []audit.GroupResources, fldPath *field.Pat ...@@ -104,3 +112,20 @@ func validateResources(groupResources []audit.GroupResources, fldPath *field.Pat
} }
return allErrs return allErrs
} }
func validateOmitStages(omitStages []audit.Stage, fldPath *field.Path) field.ErrorList {
var allErrs field.ErrorList
for i, stage := range omitStages {
valid := false
for _, validOmitStage := range validOmitStages {
if string(stage) == validOmitStage {
valid = true
break
}
}
if !valid {
allErrs = append(allErrs, field.Invalid(fldPath.Index(i), string(stage), "allowed stages are "+strings.Join(validOmitStages, ",")))
}
}
return allErrs
}
...@@ -43,6 +43,11 @@ func TestValidatePolicy(t *testing.T) { ...@@ -43,6 +43,11 @@ func TestValidatePolicy(t *testing.T) {
"/metrics", "/metrics",
"*", "*",
}, },
}, { // Omit RequestReceived stage
Level: audit.LevelMetadata,
OmitStages: []audit.Stage{
audit.Stage("RequestReceived"),
},
}, },
} }
successCases := []audit.Policy{} successCases := []audit.Policy{}
...@@ -108,6 +113,12 @@ func TestValidatePolicy(t *testing.T) { ...@@ -108,6 +113,12 @@ func TestValidatePolicy(t *testing.T) {
Resources: []audit.GroupResources{{ResourceNames: []string{"leader"}}}, Resources: []audit.GroupResources{{ResourceNames: []string{"leader"}}},
Namespaces: []string{"kube-system"}, Namespaces: []string{"kube-system"},
}, },
{ // invalid omitStages
Level: audit.LevelMetadata,
OmitStages: []audit.Stage{
audit.Stage("foo"),
},
},
} }
errorCases := []audit.Policy{} errorCases := []audit.Policy{}
for _, rule := range invalidRules { for _, rule := range invalidRules {
......
...@@ -325,6 +325,11 @@ func (in *PolicyRule) DeepCopyInto(out *PolicyRule) { ...@@ -325,6 +325,11 @@ func (in *PolicyRule) DeepCopyInto(out *PolicyRule) {
*out = make([]string, len(*in)) *out = make([]string, len(*in))
copy(*out, *in) copy(*out, *in)
} }
if in.OmitStages != nil {
in, out := &in.OmitStages, &out.OmitStages
*out = make([]Stage, len(*in))
copy(*out, *in)
}
return return
} }
......
...@@ -31,7 +31,7 @@ const ( ...@@ -31,7 +31,7 @@ const (
// Checker exposes methods for checking the policy rules. // Checker exposes methods for checking the policy rules.
type Checker interface { type Checker interface {
// Check the audit level for a request with the given authorizer attributes. // Check the audit level for a request with the given authorizer attributes.
Level(authorizer.Attributes) audit.Level LevelAndStages(authorizer.Attributes) (audit.Level, []audit.Stage)
} }
// NewChecker creates a new policy checker. // NewChecker creates a new policy checker.
...@@ -40,21 +40,21 @@ func NewChecker(policy *audit.Policy) Checker { ...@@ -40,21 +40,21 @@ func NewChecker(policy *audit.Policy) Checker {
} }
// FakeChecker creates a checker that returns a constant level for all requests (for testing). // FakeChecker creates a checker that returns a constant level for all requests (for testing).
func FakeChecker(level audit.Level) Checker { func FakeChecker(level audit.Level, stage []audit.Stage) Checker {
return &fakeChecker{level} return &fakeChecker{level, stage}
} }
type policyChecker struct { type policyChecker struct {
audit.Policy audit.Policy
} }
func (p *policyChecker) Level(attrs authorizer.Attributes) audit.Level { func (p *policyChecker) LevelAndStages(attrs authorizer.Attributes) (audit.Level, []audit.Stage) {
for _, rule := range p.Rules { for _, rule := range p.Rules {
if ruleMatches(&rule, attrs) { if ruleMatches(&rule, attrs) {
return rule.Level return rule.Level, rule.OmitStages
} }
} }
return DefaultAuditLevel return DefaultAuditLevel, nil
} }
// Check whether the rule matches the request attrs. // Check whether the rule matches the request attrs.
...@@ -181,8 +181,9 @@ func hasString(slice []string, value string) bool { ...@@ -181,8 +181,9 @@ func hasString(slice []string, value string) bool {
type fakeChecker struct { type fakeChecker struct {
level audit.Level level audit.Level
stage []audit.Stage
} }
func (f *fakeChecker) Level(_ authorizer.Attributes) audit.Level { func (f *fakeChecker) LevelAndStages(_ authorizer.Attributes) (audit.Level, []audit.Stage) {
return f.level return f.level, f.stage
} }
...@@ -136,59 +136,80 @@ func TestChecker(t *testing.T) { ...@@ -136,59 +136,80 @@ func TestChecker(t *testing.T) {
ResourceNames: []string{"edit"}, ResourceNames: []string{"edit"},
}}, }},
}, },
"omit RequestReceived": {
Level: audit.LevelRequest,
OmitStages: []audit.Stage{
audit.StageRequestReceived,
},
},
"only audit panic": {
Level: audit.LevelRequest,
OmitStages: []audit.Stage{
audit.StageRequestReceived,
audit.StageResponseStarted,
audit.StageResponseComplete,
},
},
} }
test := func(req string, expected audit.Level, ruleNames ...string) { test := func(req string, expLevel audit.Level, expOmitStages []audit.Stage, ruleNames ...string) {
policy := audit.Policy{} policy := audit.Policy{}
for _, rule := range ruleNames { for _, rule := range ruleNames {
require.Contains(t, rules, rule) require.Contains(t, rules, rule)
policy.Rules = append(policy.Rules, rules[rule]) policy.Rules = append(policy.Rules, rules[rule])
} }
require.Contains(t, attrs, req) require.Contains(t, attrs, req)
actual := NewChecker(&policy).Level(attrs[req]) actualLevel, actualOmitStages := NewChecker(&policy).LevelAndStages(attrs[req])
assert.Equal(t, expected, actual, "request:%s rules:%s", req, strings.Join(ruleNames, ",")) assert.Equal(t, expLevel, actualLevel, "request:%s rules:%s", req, strings.Join(ruleNames, ","))
assert.Equal(t, expOmitStages, actualOmitStages, "request:%s rules:%s", req, strings.Join(ruleNames, ","))
} }
test("namespaced", audit.LevelMetadata, "default") test("namespaced", audit.LevelMetadata, nil, "default")
test("namespaced", audit.LevelNone, "create") test("namespaced", audit.LevelNone, nil, "create")
test("namespaced", audit.LevelMetadata, "tims") test("namespaced", audit.LevelMetadata, nil, "tims")
test("namespaced", audit.LevelMetadata, "humans") test("namespaced", audit.LevelMetadata, nil, "humans")
test("namespaced", audit.LevelNone, "serviceAccounts") test("namespaced", audit.LevelNone, nil, "serviceAccounts")
test("namespaced", audit.LevelRequestResponse, "getPods") test("namespaced", audit.LevelRequestResponse, nil, "getPods")
test("namespaced", audit.LevelNone, "getClusterRoles") test("namespaced", audit.LevelNone, nil, "getClusterRoles")
test("namespaced", audit.LevelNone, "getLogs") test("namespaced", audit.LevelNone, nil, "getLogs")
test("namespaced", audit.LevelNone, "getMetrics") test("namespaced", audit.LevelNone, nil, "getMetrics")
test("namespaced", audit.LevelMetadata, "getMetrics", "serviceAccounts", "default") test("namespaced", audit.LevelMetadata, nil, "getMetrics", "serviceAccounts", "default")
test("namespaced", audit.LevelRequestResponse, "getMetrics", "getPods", "default") test("namespaced", audit.LevelRequestResponse, nil, "getMetrics", "getPods", "default")
test("namespaced", audit.LevelRequestResponse, "getPodLogs", "getPods") test("namespaced", audit.LevelRequestResponse, nil, "getPodLogs", "getPods")
test("namespaced", audit.LevelRequest, []audit.Stage{audit.StageRequestReceived}, "omit RequestReceived", "getPods", "default")
test("namespaced", audit.LevelRequest, []audit.Stage{audit.StageRequestReceived, audit.StageResponseStarted, audit.StageResponseComplete}, "only audit panic", "getPods", "default")
test("cluster", audit.LevelMetadata, "default") test("cluster", audit.LevelMetadata, nil, "default")
test("cluster", audit.LevelNone, "create") test("cluster", audit.LevelNone, nil, "create")
test("cluster", audit.LevelMetadata, "tims") test("cluster", audit.LevelMetadata, nil, "tims")
test("cluster", audit.LevelMetadata, "humans") test("cluster", audit.LevelMetadata, nil, "humans")
test("cluster", audit.LevelNone, "serviceAccounts") test("cluster", audit.LevelNone, nil, "serviceAccounts")
test("cluster", audit.LevelNone, "getPods") test("cluster", audit.LevelNone, nil, "getPods")
test("cluster", audit.LevelRequestResponse, "getClusterRoles") test("cluster", audit.LevelRequestResponse, nil, "getClusterRoles")
test("cluster", audit.LevelRequest, "clusterRoleEdit", "getClusterRoles") test("cluster", audit.LevelRequest, nil, "clusterRoleEdit", "getClusterRoles")
test("cluster", audit.LevelNone, "getLogs") test("cluster", audit.LevelNone, nil, "getLogs")
test("cluster", audit.LevelNone, "getMetrics") test("cluster", audit.LevelNone, nil, "getMetrics")
test("cluster", audit.LevelMetadata, "getMetrics", "serviceAccounts", "default") test("cluster", audit.LevelMetadata, nil, "getMetrics", "serviceAccounts", "default")
test("cluster", audit.LevelRequestResponse, "getMetrics", "getClusterRoles", "default") test("cluster", audit.LevelRequestResponse, nil, "getMetrics", "getClusterRoles", "default")
test("cluster", audit.LevelNone, "getPodLogs", "getPods") test("cluster", audit.LevelNone, nil, "getPodLogs", "getPods")
test("cluster", audit.LevelRequest, []audit.Stage{audit.StageRequestReceived}, "omit RequestReceived", "getPods", "default")
test("cluster", audit.LevelRequest, []audit.Stage{audit.StageRequestReceived, audit.StageResponseStarted, audit.StageResponseComplete}, "only audit panic", "getPods", "default")
test("nonResource", audit.LevelMetadata, "default") test("nonResource", audit.LevelMetadata, nil, "default")
test("nonResource", audit.LevelNone, "create") test("nonResource", audit.LevelNone, nil, "create")
test("nonResource", audit.LevelMetadata, "tims") test("nonResource", audit.LevelMetadata, nil, "tims")
test("nonResource", audit.LevelMetadata, "humans") test("nonResource", audit.LevelMetadata, nil, "humans")
test("nonResource", audit.LevelNone, "serviceAccounts") test("nonResource", audit.LevelNone, nil, "serviceAccounts")
test("nonResource", audit.LevelNone, "getPods") test("nonResource", audit.LevelNone, nil, "getPods")
test("nonResource", audit.LevelNone, "getClusterRoles") test("nonResource", audit.LevelNone, nil, "getClusterRoles")
test("nonResource", audit.LevelRequestResponse, "getLogs") test("nonResource", audit.LevelRequestResponse, nil, "getLogs")
test("nonResource", audit.LevelNone, "getMetrics") test("nonResource", audit.LevelNone, nil, "getMetrics")
test("nonResource", audit.LevelMetadata, "getMetrics", "serviceAccounts", "default") test("nonResource", audit.LevelMetadata, nil, "getMetrics", "serviceAccounts", "default")
test("nonResource", audit.LevelRequestResponse, "getLogs", "getClusterRoles", "default") test("nonResource", audit.LevelRequestResponse, nil, "getLogs", "getClusterRoles", "default")
test("nonResource", audit.LevelNone, "getPodLogs", "getPods") test("nonResource", audit.LevelNone, nil, "getPodLogs", "getPods")
test("nonResource", audit.LevelRequest, []audit.Stage{audit.StageRequestReceived}, "omit RequestReceived", "getPods", "default")
test("nonResource", audit.LevelRequest, []audit.Stage{audit.StageRequestReceived, audit.StageResponseStarted, audit.StageResponseComplete}, "only audit panic", "getPods", "default")
test("subresource", audit.LevelRequest, "getPodLogs", "getPods") test("subresource", audit.LevelRequest, nil, "getPodLogs", "getPods")
test("subresource", audit.LevelRequest, "getPods", "getPodLogs") test("subresource", audit.LevelRequest, nil, "getPods", "getPodLogs")
} }
...@@ -331,7 +331,7 @@ func handleInternal(storage map[string]rest.Storage, admissionControl admission. ...@@ -331,7 +331,7 @@ func handleInternal(storage map[string]rest.Storage, admissionControl admission.
} }
} }
handler := genericapifilters.WithAudit(mux, requestContextMapper, auditSink, auditpolicy.FakeChecker(auditinternal.LevelRequestResponse), func(r *http.Request, requestInfo *request.RequestInfo) bool { handler := genericapifilters.WithAudit(mux, requestContextMapper, auditSink, auditpolicy.FakeChecker(auditinternal.LevelRequestResponse, nil), func(r *http.Request, requestInfo *request.RequestInfo) bool {
// simplified long-running check // simplified long-running check
return requestInfo.Verb == "watch" || requestInfo.Verb == "proxy" return requestInfo.Verb == "watch" || requestInfo.Verb == "proxy"
}) })
......
...@@ -42,7 +42,7 @@ func WithAudit(handler http.Handler, requestContextMapper request.RequestContext ...@@ -42,7 +42,7 @@ func WithAudit(handler http.Handler, requestContextMapper request.RequestContext
return handler return handler
} }
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
ctx, ev, err := createAuditEventAndAttachToContext(requestContextMapper, req, policy) ctx, ev, omitStages, err := createAuditEventAndAttachToContext(requestContextMapper, req, policy)
if err != nil { if err != nil {
utilruntime.HandleError(fmt.Errorf("failed to create audit event: %v", err)) utilruntime.HandleError(fmt.Errorf("failed to create audit event: %v", err))
responsewriters.InternalError(w, req, errors.New("failed to create audit event")) responsewriters.InternalError(w, req, errors.New("failed to create audit event"))
...@@ -54,7 +54,7 @@ func WithAudit(handler http.Handler, requestContextMapper request.RequestContext ...@@ -54,7 +54,7 @@ func WithAudit(handler http.Handler, requestContextMapper request.RequestContext
} }
ev.Stage = auditinternal.StageRequestReceived ev.Stage = auditinternal.StageRequestReceived
processAuditEvent(sink, ev) processAuditEvent(sink, ev, omitStages)
// intercept the status code // intercept the status code
var longRunningSink audit.Sink var longRunningSink audit.Sink
...@@ -64,7 +64,7 @@ func WithAudit(handler http.Handler, requestContextMapper request.RequestContext ...@@ -64,7 +64,7 @@ func WithAudit(handler http.Handler, requestContextMapper request.RequestContext
longRunningSink = sink longRunningSink = sink
} }
} }
respWriter := decorateResponseWriter(w, ev, longRunningSink) respWriter := decorateResponseWriter(w, ev, longRunningSink, omitStages)
// send audit event when we leave this func, either via a panic or cleanly. In the case of long // send audit event when we leave this func, either via a panic or cleanly. In the case of long
// running requests, this will be the second audit event. // running requests, this will be the second audit event.
...@@ -78,7 +78,7 @@ func WithAudit(handler http.Handler, requestContextMapper request.RequestContext ...@@ -78,7 +78,7 @@ func WithAudit(handler http.Handler, requestContextMapper request.RequestContext
Reason: metav1.StatusReasonInternalError, Reason: metav1.StatusReasonInternalError,
Message: fmt.Sprintf("APIServer panic'd: %v", r), Message: fmt.Sprintf("APIServer panic'd: %v", r),
} }
processAuditEvent(sink, ev) processAuditEvent(sink, ev, omitStages)
return return
} }
...@@ -92,14 +92,14 @@ func WithAudit(handler http.Handler, requestContextMapper request.RequestContext ...@@ -92,14 +92,14 @@ func WithAudit(handler http.Handler, requestContextMapper request.RequestContext
if ev.ResponseStatus == nil && longRunningSink != nil { if ev.ResponseStatus == nil && longRunningSink != nil {
ev.ResponseStatus = fakedSuccessStatus ev.ResponseStatus = fakedSuccessStatus
ev.Stage = auditinternal.StageResponseStarted ev.Stage = auditinternal.StageResponseStarted
processAuditEvent(longRunningSink, ev) processAuditEvent(longRunningSink, ev, omitStages)
} }
ev.Stage = auditinternal.StageResponseComplete ev.Stage = auditinternal.StageResponseComplete
if ev.ResponseStatus == nil { if ev.ResponseStatus == nil {
ev.ResponseStatus = fakedSuccessStatus ev.ResponseStatus = fakedSuccessStatus
} }
processAuditEvent(sink, ev) processAuditEvent(sink, ev, omitStages)
}() }()
handler.ServeHTTP(respWriter, req) handler.ServeHTTP(respWriter, req)
}) })
...@@ -110,47 +110,53 @@ func WithAudit(handler http.Handler, requestContextMapper request.RequestContext ...@@ -110,47 +110,53 @@ func WithAudit(handler http.Handler, requestContextMapper request.RequestContext
// - context with audit event attached to it // - context with audit event attached to it
// - created audit event // - created audit event
// - error if anything bad happened // - error if anything bad happened
func createAuditEventAndAttachToContext(requestContextMapper request.RequestContextMapper, req *http.Request, policy policy.Checker) (request.Context, *auditinternal.Event, error) { func createAuditEventAndAttachToContext(requestContextMapper request.RequestContextMapper, req *http.Request, policy policy.Checker) (request.Context, *auditinternal.Event, []auditinternal.Stage, error) {
ctx, ok := requestContextMapper.Get(req) ctx, ok := requestContextMapper.Get(req)
if !ok { if !ok {
return nil, nil, fmt.Errorf("no context found for request") return nil, nil, nil, fmt.Errorf("no context found for request")
} }
attribs, err := GetAuthorizerAttributes(ctx) attribs, err := GetAuthorizerAttributes(ctx)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("failed to GetAuthorizerAttributes: %v", err) return nil, nil, nil, fmt.Errorf("failed to GetAuthorizerAttributes: %v", err)
} }
level := policy.Level(attribs) level, omitStages := policy.LevelAndStages(attribs)
audit.ObservePolicyLevel(level) audit.ObservePolicyLevel(level)
if level == auditinternal.LevelNone { if level == auditinternal.LevelNone {
// Don't audit. // Don't audit.
return nil, nil, nil return nil, nil, nil, nil
} }
ev, err := audit.NewEventFromRequest(req, level, attribs) ev, err := audit.NewEventFromRequest(req, level, attribs)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("failed to complete audit event from request: %v", err) return nil, nil, nil, fmt.Errorf("failed to complete audit event from request: %v", err)
} }
ctx = request.WithAuditEvent(ctx, ev) ctx = request.WithAuditEvent(ctx, ev)
if err := requestContextMapper.Update(req, ctx); err != nil { if err := requestContextMapper.Update(req, ctx); err != nil {
return nil, nil, fmt.Errorf("failed to attach audit event to context: %v", err) return nil, nil, nil, fmt.Errorf("failed to attach audit event to context: %v", err)
} }
return ctx, ev, nil return ctx, ev, omitStages, nil
} }
func processAuditEvent(sink audit.Sink, ev *auditinternal.Event) { func processAuditEvent(sink audit.Sink, ev *auditinternal.Event, omitStages []auditinternal.Stage) {
for _, stage := range omitStages {
if ev.Stage == stage {
return
}
}
audit.ObserveEvent() audit.ObserveEvent()
sink.ProcessEvents(ev) sink.ProcessEvents(ev)
} }
func decorateResponseWriter(responseWriter http.ResponseWriter, ev *auditinternal.Event, sink audit.Sink) http.ResponseWriter { func decorateResponseWriter(responseWriter http.ResponseWriter, ev *auditinternal.Event, sink audit.Sink, omitStages []auditinternal.Stage) http.ResponseWriter {
delegate := &auditResponseWriter{ delegate := &auditResponseWriter{
ResponseWriter: responseWriter, ResponseWriter: responseWriter,
event: ev, event: ev,
sink: sink, sink: sink,
omitStages: omitStages,
} }
// check if the ResponseWriter we're wrapping is the fancy one we need // check if the ResponseWriter we're wrapping is the fancy one we need
...@@ -170,9 +176,10 @@ var _ http.ResponseWriter = &auditResponseWriter{} ...@@ -170,9 +176,10 @@ var _ http.ResponseWriter = &auditResponseWriter{}
// create immediately an event (for long running requests). // create immediately an event (for long running requests).
type auditResponseWriter struct { type auditResponseWriter struct {
http.ResponseWriter http.ResponseWriter
event *auditinternal.Event event *auditinternal.Event
once sync.Once once sync.Once
sink audit.Sink sink audit.Sink
omitStages []auditinternal.Stage
} }
func (a *auditResponseWriter) setHttpHeader() { func (a *auditResponseWriter) setHttpHeader() {
...@@ -188,7 +195,7 @@ func (a *auditResponseWriter) processCode(code int) { ...@@ -188,7 +195,7 @@ func (a *auditResponseWriter) processCode(code int) {
a.event.Stage = auditinternal.StageResponseStarted a.event.Stage = auditinternal.StageResponseStarted
if a.sink != nil { if a.sink != nil {
processAuditEvent(a.sink, a.event) processAuditEvent(a.sink, a.event, a.omitStages)
} }
}) })
} }
......
...@@ -38,7 +38,7 @@ func WithFailedAuthenticationAudit(failedHandler http.Handler, requestContextMap ...@@ -38,7 +38,7 @@ func WithFailedAuthenticationAudit(failedHandler http.Handler, requestContextMap
return failedHandler return failedHandler
} }
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
_, ev, err := createAuditEventAndAttachToContext(requestContextMapper, req, policy) _, ev, omitStages, err := createAuditEventAndAttachToContext(requestContextMapper, req, policy)
if err != nil { if err != nil {
utilruntime.HandleError(fmt.Errorf("failed to create audit event: %v", err)) utilruntime.HandleError(fmt.Errorf("failed to create audit event: %v", err))
responsewriters.InternalError(w, req, errors.New("failed to create audit event")) responsewriters.InternalError(w, req, errors.New("failed to create audit event"))
...@@ -53,7 +53,7 @@ func WithFailedAuthenticationAudit(failedHandler http.Handler, requestContextMap ...@@ -53,7 +53,7 @@ func WithFailedAuthenticationAudit(failedHandler http.Handler, requestContextMap
ev.ResponseStatus.Message = getAuthMethods(req) ev.ResponseStatus.Message = getAuthMethods(req)
ev.Stage = auditinternal.StageResponseStarted ev.Stage = auditinternal.StageResponseStarted
rw := decorateResponseWriter(w, ev, sink) rw := decorateResponseWriter(w, ev, sink, omitStages)
failedHandler.ServeHTTP(rw, req) failedHandler.ServeHTTP(rw, req)
}) })
} }
......
...@@ -30,7 +30,7 @@ import ( ...@@ -30,7 +30,7 @@ import (
func TestFailedAuthnAudit(t *testing.T) { func TestFailedAuthnAudit(t *testing.T) {
sink := &fakeAuditSink{} sink := &fakeAuditSink{}
policyChecker := policy.FakeChecker(auditinternal.LevelRequestResponse) policyChecker := policy.FakeChecker(auditinternal.LevelRequestResponse, nil)
handler := WithFailedAuthenticationAudit( handler := WithFailedAuthenticationAudit(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusUnauthorized) w.WriteHeader(http.StatusUnauthorized)
...@@ -61,7 +61,7 @@ func TestFailedAuthnAudit(t *testing.T) { ...@@ -61,7 +61,7 @@ func TestFailedAuthnAudit(t *testing.T) {
func TestFailedMultipleAuthnAudit(t *testing.T) { func TestFailedMultipleAuthnAudit(t *testing.T) {
sink := &fakeAuditSink{} sink := &fakeAuditSink{}
policyChecker := policy.FakeChecker(auditinternal.LevelRequestResponse) policyChecker := policy.FakeChecker(auditinternal.LevelRequestResponse, nil)
handler := WithFailedAuthenticationAudit( handler := WithFailedAuthenticationAudit(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusUnauthorized) w.WriteHeader(http.StatusUnauthorized)
...@@ -93,7 +93,7 @@ func TestFailedMultipleAuthnAudit(t *testing.T) { ...@@ -93,7 +93,7 @@ func TestFailedMultipleAuthnAudit(t *testing.T) {
func TestFailedAuthnAuditWithoutAuthorization(t *testing.T) { func TestFailedAuthnAuditWithoutAuthorization(t *testing.T) {
sink := &fakeAuditSink{} sink := &fakeAuditSink{}
policyChecker := policy.FakeChecker(auditinternal.LevelRequestResponse) policyChecker := policy.FakeChecker(auditinternal.LevelRequestResponse, nil)
handler := WithFailedAuthenticationAudit( handler := WithFailedAuthenticationAudit(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusUnauthorized) w.WriteHeader(http.StatusUnauthorized)
...@@ -120,3 +120,20 @@ func TestFailedAuthnAuditWithoutAuthorization(t *testing.T) { ...@@ -120,3 +120,20 @@ func TestFailedAuthnAuditWithoutAuthorization(t *testing.T) {
t.Errorf("Unexpected user, expected /api/v1/namespaces/default/pods, got %s", ev.RequestURI) t.Errorf("Unexpected user, expected /api/v1/namespaces/default/pods, got %s", ev.RequestURI)
} }
} }
func TestFailedAuthnAuditOmitted(t *testing.T) {
sink := &fakeAuditSink{}
policyChecker := policy.FakeChecker(auditinternal.LevelRequestResponse, []auditinternal.Stage{auditinternal.StageResponseStarted})
handler := WithFailedAuthenticationAudit(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusUnauthorized)
}),
&fakeRequestContextMapper{}, sink, policyChecker)
req, _ := http.NewRequest("GET", "/api/v1/namespaces/default/pods", nil)
req.RemoteAddr = "127.0.0.1"
handler.ServeHTTP(httptest.NewRecorder(), req)
if len(sink.events) != 0 {
t.Fatalf("Unexpected number of audit events generated, expected 0, got: %d", len(sink.events))
}
}
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