Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow audit to log authorization failures #51119

Merged
merged 1 commit into from
Sep 3, 2017

Conversation

soltysh
Copy link
Contributor

@soltysh soltysh commented Aug 22, 2017

What this PR does / why we need it:
This PR extends our current audit mechanism allowing to audit failed login attempts.

Release note:

Advanced audit allows logging failed login attempts

@soltysh soltysh added the sig/auth Categorizes an issue or PR as relevant to SIG Auth. label Aug 22, 2017
@k8s-ci-robot k8s-ci-robot added the cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. label Aug 22, 2017
@k8s-github-robot k8s-github-robot added size/L Denotes a PR that changes 100-499 lines, ignoring generated files. release-note Denotes a PR that will be considered when it comes time to generate release notes. labels Aug 22, 2017
@soltysh
Copy link
Contributor Author

soltysh commented Aug 22, 2017

The only remaining part is tests for the new bits.

}

// otherwise, we need to create the event by ourselves and log the auth error
// the majority of this code is copied from upstream WithAudit filter
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/upstream//

ev.ResponseStatus = &metav1.Status{}
ev.ResponseStatus.Code = int32(code)
ev.Stage = auditinternal.StageResponseStarted
processEvent(a.sink, ev)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as we are in another file now, maybe rename this to processAuditEvent

@soltysh soltysh force-pushed the failed_authn_audit branch 2 times, most recently from b20b905 to 2c8c5c0 Compare August 23, 2017 12:40
@soltysh
Copy link
Contributor Author

soltysh commented Aug 23, 2017

@sttts I've added tests and run bazel, ptal

@crassirostris
Copy link

@destijl Could you please take a look?

/cc @tallclair

@k8s-ci-robot k8s-ci-robot requested a review from tallclair August 24, 2017 10:13
@sttts
Copy link
Contributor

sttts commented Aug 24, 2017

lgtm. But two more eyes on it wouldn't harm.

if err != nil {
return basic
}
cred := strings.SplitN(string(str), ":", 2)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to give an unauthenticated attacker complete control over the username field? Potential XSS vector if downstream consumers are displaying the contents?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This piece of code is taken directly from basic auth, so the same problem applies there. If I'm able to address other comment of yours, this should not be a problem anymore.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/cc @liggitt

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any user can fake a user-name and this name is extracted here to be added to the audit event on authn failure. This by itself is expected and intended. But I agree that allowing any kind of binary data might open up an attack vector on buggy consumers. What is best practice here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same vector applies to resource name parsed from the url, right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For resource names at least the charset is restricted.

Copy link
Member

@liggitt liggitt Aug 29, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For resource names at least the charset is restricted.

not by request parsing, the restrictions are per resource

}

// getUsername returns username or information on the authn method being used.
func getUsername(req *http.Request) string {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AIUI we get an authentication failure, then handover the failed request to this code to try and extract some context from the request. Is there something stopping us logging a more meaningful failure at the actual authentication failure time? Seems like the point where we determine the password/cert/etc is bad is the place where we should create the log.

This hard-codes some assumptions about the auth methods we accept which would seem to better belong in a single library (or libraries, one for each method) so that we can be careful about how we handle this untrusted input and reason about code changes needed to deprecate authentication methods if necessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hrm... I'm not a big fan of it either, but it was the first thing I could think of. I'll dig into the code once more and see how we can do a better job here.


// check basic auth
const basicScheme string = "Basic "
if strings.HasPrefix(auth, basicScheme) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

// other tokens
token := strings.TrimSpace(req.URL.Query().Get("access_token"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this, this method is not supported

if len(cred) < 2 {
return basic
}
return cred[0]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that putting unverified data about users in audit logs is fraught

@soltysh soltysh force-pushed the failed_authn_audit branch from 2c8c5c0 to ca8fb73 Compare August 30, 2017 15:33
}

// if there already exists an audit event in the context we don't need to do anything
if ae := request.AuditEventFrom(ctx); ae != nil {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left this check here, but it shouldn't be needed, since we hook in failed authn specifically.

@soltysh
Copy link
Contributor Author

soltysh commented Aug 30, 2017

@liggitt @sttts @destijl I've addressed your concerns, can I get a quick review on this one?
The only remaining thing I'd like to do here, is to inject auth method name in user.Extras, b/c in majority of cases we're not getting username, but we could at least point to which methods were tried, wdyt?

}

func (m *fakeRequestContextMapper) Get(req *http.Request) (request.Context, bool) {
ctx := request.NewContext()
if m.ctx == nil {
m.ctx = request.NewContext()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this makes fakeRequestContextMapper non-thread safe

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But this is just test, for that particular usecase. I doubt fakeRequestContextMapper will be use anywhere else. Same for the next call, as well.

}

resolver := newTestRequestInfoResolver()
info, err := resolver.NewRequestInfo(req)
if err == nil {
ctx = request.WithRequestInfo(ctx, info)
m.ctx = request.WithRequestInfo(m.ctx, info)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and this makes it not safe to be called multiple times

@@ -63,6 +63,9 @@ func WithAuthentication(handler http.Handler, mapper genericapirequest.RequestCo
if err != nil {
glog.Errorf("Unable to authenticate the request due to an error: %v", err)
}
if auditFailedAuthn != nil {
w = auditFailedAuthn.Decorate(w, user, req)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are you passing user? when err != nil || !ok, user is unusable

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and if you don't need user, then auditFailedAuthn.Decorate has the same args as http.Handler, and you can simple pass a wrapped failed http.Handler in here that logs, then handles

@soltysh soltysh force-pushed the failed_authn_audit branch 2 times, most recently from 8310c21 to c2443ab Compare August 30, 2017 20:37
@@ -48,37 +48,17 @@ func WithAudit(handler http.Handler, requestContextMapper request.RequestContext
return
}

attribs, err := GetAuthorizerAttributes(ctx)
ev, err := createAuditEvent(ctx, policy, requestContextMapper, req, w)
if err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't this still lead to http 500?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ic, createAuditEvent writes the error. I would prefer to see it here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or at least a good comment here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll move the error handling up, it'll be more explicit.

return nil, err
}

ctx = request.WithAuditEvent(ctx, ev)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is unexpected in a create* function as a side-effect.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

either move that our or call the func createAuditEventAndAttachToCtx

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved context extraction also to that method and went with createAuditEventAndAttachToContext.

return
}

ev, err := createAuditEvent(ctx, policy, requestContextMapper, req, w)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so this attaches an event to the context. The WithAudit filter will override that. Correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above.

ev.ResponseStatus.Message = getAuthMethods(req)
ev.Stage = auditinternal.StageResponseStarted

rw := decorateResponseWriter(w, ev, sink)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes the rw.WriteHeader to process an event. The WithAudit also decorates. So if an endpoint handler writes the header, the WithAudit decorator will send an event to the audit backend, then your new outer decorator will the same. Moreover, you ev here is another one than the one of WithAudit. So yours will be pretty empty.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No double decoration will happen. he reason for that is that WithFailedAuthenticationAudit decorates only the failed handler that is passed to WithAuthentication. At that point no request will ever reach regular WithAudit because the processing is passed through a different handler chain.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

got it. Please call the parameter failedHandler here. Then it's clearer.

@soltysh soltysh force-pushed the failed_authn_audit branch from c2443ab to 4e5f11f Compare August 31, 2017 09:15

// WithFailedAuthenticationAudit decorates a http.Handler with a fallback audit,
// logging information only when the original one did was not triggered.
// This needs to be used with WithAuditTriggeredMarker, which wraps the original
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't find "WithAuditTriggeredMarker" in other places.
Maybe this doc string is out of date?

auditinternal "k8s.io/apiserver/pkg/apis/audit"
"k8s.io/apiserver/pkg/audit/policy"
// import to call webhook's init() function to register audit.Event to schema
_ "k8s.io/apiserver/plugin/pkg/audit/webhook"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have tested that it's not necessary to import this in this unit test.

@soltysh soltysh force-pushed the failed_authn_audit branch from 4e5f11f to 6e0c511 Compare August 31, 2017 10:21
@soltysh
Copy link
Contributor Author

soltysh commented Aug 31, 2017

Updated with addressed comments from @CaoShuFeng

@sttts
Copy link
Contributor

sttts commented Aug 31, 2017

lgtm after the failedHandler and @CaoShuFeng's comment are fixed.

@soltysh soltysh force-pushed the failed_authn_audit branch from 6e0c511 to 9fef244 Compare August 31, 2017 10:40
@sttts
Copy link
Contributor

sttts commented Aug 31, 2017

/approve no-issue
/lgtm

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Aug 31, 2017
@k8s-github-robot
Copy link

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: soltysh, sttts

Associated issue requirement bypassed by: sttts

The full list of commands accepted by this bot can be found here.

Needs approval from an approver in each of these OWNERS Files:

You can indicate your approval by writing /approve in a comment
You can cancel your approval by writing /approve cancel in a comment

@k8s-github-robot k8s-github-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Aug 31, 2017
@soltysh soltysh added this to the v1.8 milestone Aug 31, 2017
@soltysh
Copy link
Contributor Author

soltysh commented Aug 31, 2017

/retest

@k8s-ci-robot
Copy link
Contributor

k8s-ci-robot commented Aug 31, 2017

@soltysh: The following test failed, say /retest to rerun them all:

Test name Commit Details Rerun command
pull-kubernetes-bazel 2c8c5c0 link /test pull-kubernetes-bazel

Full PR test history. Your PR dashboard. Please help us cut down on flakes by linking to an open issue when you hit one in your PR.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. I understand the commands that are listed here.

@fejta-bot
Copy link

/retest
This bot automatically retries jobs that failed/flaked on approved PRs (send feedback to @fejta).

Review the full test history for this PR.

@k8s-github-robot
Copy link

Automatic merge from submit-queue (batch tested with PRs 50832, 51119, 51636, 48921, 51712)

@k8s-github-robot k8s-github-robot merged commit f4c6cbd into kubernetes:master Sep 3, 2017

ev.ResponseStatus = &metav1.Status{}
ev.ResponseStatus.Message = getAuthMethods(req)
ev.Stage = auditinternal.StageResponseStarted
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am OK with auditinternal.StageResponseStarted here.
But why StageResponseStarted is better than StageRequestReceived?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. area/audit cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. lgtm "Looks good to me", indicates that a PR is ready to be merged. release-note Denotes a PR that will be considered when it comes time to generate release notes. sig/auth Categorizes an issue or PR as relevant to SIG Auth. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants