Skip to content

Add "request.id" to file audit logs #35536

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

Merged
merged 7 commits into from
Nov 27, 2018
Merged

Conversation

tvernum
Copy link
Contributor

@tvernum tvernum commented Nov 14, 2018

This generates a synthesized "id" for each incoming request that is
included in the audit logs (file only).
This id can be used to correlate events for the same request (e.g.
authentication success with access granted).

This request.id is specific to the audit logs and is not used for any
other purpose

This generates a synthesized "id" for each incoming request that is
included in the audit logs (file only).
This id can be used to correlate events for the same request (e.g.
authentication success with access granted).

This request.id is specific to the audit logs and is not used for any
other purpose
Time based uuids are good for lucene ids, but are not great for
audit request.id
For a request.id we want a generation system where consequative ids
arevisually distinct, so that it is easy to spot correlations.
@elasticmachine
Copy link
Collaborator

Pinging @elastic/es-security

@jaymode jaymode self-requested a review November 16, 2018 16:06
@@ -70,7 +70,7 @@ public void intercept(IndicesAliasesRequest request, Authentication authenticati
permissionsMap.computeIfAbsent(alias, userPermissions.indices()::allowedActionsMatcher);
if (Operations.subsetOf(aliasPermissions, indexPermissions) == false) {
// TODO we've already audited a access granted event so this is going to look ugly
auditTrailService.accessDenied(authentication, action, request, userPermissions.names());
auditTrailService.accessDenied(null, authentication, action, request, userPermissions.names());
Copy link
Contributor

Choose a reason for hiding this comment

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

I prefer us to be explicit here: threadContext.getTransient(AUDIT_REQUEST_ID).
It is a case of tamper request , if audit request Id is missing.

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 think, in this case, it's better to be null, because it's already a denial.
I'd rather fail on the denied rather than fail on a tampered request.

Copy link
Contributor

Choose a reason for hiding this comment

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

I've expressed myself poorly. I agree failing with access_denied is better and we should not change this.

I meant that since these request interceptors are called after AuthorizationService#authorize

we can be confident that they are assigned an audit request id already. If they don't have it, then it must be a case of tamper request. Does this sound reasonable?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, it's reasonable, but the question is what should we do if there is not request.id?
We don't want to fail because then we'd lose the accessDenied audit, so I think we're better off logging it with no request.id
It certainly implies something is wrong, but I don't think there's any reasonable action to take about it.

@@ -59,7 +59,7 @@ public void intercept(ResizeRequest request, Authentication authentication, Role
userPermissions.indices().allowedActionsMatcher(request.getTargetIndexRequest().index());
if (Operations.subsetOf(targetIndexPermissions, sourceIndexPermissions) == false) {
// TODO we've already audited a access granted event so this is going to look ugly
auditTrailService.accessDenied(authentication, action, request, userPermissions.names());
auditTrailService.accessDenied(null, authentication, action, request, userPermissions.names());
Copy link
Contributor

Choose a reason for hiding this comment

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

idem:

I prefer us to be explicit here: threadContext.getTransient(AUDIT_REQUEST_ID).
It is a case of tamper request , if audit request Id is missing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As above - but let's chat if you disagree.

Copy link
Contributor

Choose a reason for hiding this comment

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

I've clarified my thoughts in the previous comment. Judging from the code flow, I believe we can be explicit about the id and not pass null.


public static String getOrGenerateRequestId(ThreadContext threadContext) {
String requestId = extractRequestId(threadContext);
if (Strings.isEmpty(requestId)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we can get rid of this.

Copy link
Contributor

@albertzaharovits albertzaharovits Nov 19, 2018

Choose a reason for hiding this comment

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

As in: be explicit when we generate these.
I know it would be a bummer if we hit an assertion about an event not assigned to a request, but I think there's value if we catch such an error, because such an event might reveal hidden bugs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's not really feasible to do that unfortunately.
When we process a transport action, we simply don't/can't know whether that action was spawned from an existing rest/transport request or whether it's standalone.

Well, I guess we could refactor a lot of stuff to make that explicit, but it's not right now.
Maybe, between system context, and withOrigin we could pull something together to know when a transport action is isolated from any originating request, but it's going to be fragile.

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree.

@@ -659,6 +679,16 @@ LogEntryBuilder withRequestBody(RestRequest request) {
return this;
}

LogEntryBuilder withRequestId(String requestId, ThreadContext threadContext) {
if (Strings.isNullOrEmpty(requestId)) {
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 again a place where I think we should not generate if missing. Missing is an indication that we don't know how this request came to be which is a problem in itself.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We don't generate if missing - we just pull it from the thread context if it was passed in as null.
We can remove that (by making all callers do the extraction explicitly) if we want.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ahhhh, yes, it makes sense now. I was mistaken in my belief that they're also generated here. All precautionary recommendations in the other comments assumed they're also generated here.

All good, sorry.

@@ -145,11 +146,13 @@ Authenticator createAuthenticator(String action, TransportMessage message, User
private AuthenticationResult authenticationResult = null;

Authenticator(RestRequest request, ActionListener<Authentication> listener) {
this(new AuditableRestRequest(auditTrail, failureHandler, threadContext, request), null, listener);
this(new AuditableRestRequest(auditTrail, failureHandler, threadContext, request,
AuditUtil.getOrGenerateRequestId(threadContext)), null, listener);
Copy link
Contributor

Choose a reason for hiding this comment

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

How do you feel if we move AuditUtil.getOrGenerateRequestId(threadContext) inside the AuditableRestRequest and AuditableTransportRequest ? This is oboviously equivalent, but I think the requestId fits better at the Request abstraction level, rather than Authenticator . The consistent abstraction argument, I believe, is more important when dealing with the threadContext since it is important when (on what thread) we pin down the request id. But, again, this is just a abstraction consistency type of argument.

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 think you're right.
I think, originally the generate... method didn't exist, so I the generation was pushed up tothe Authenticator, but now that it's on AuditUtil it should be generated in the request.

if (auditId == null) {
if (isInternalUser(authentication.getUser()) == false) {
logger.warn("Authorizing action without an existing request-id");
assert false : "Audit request.id should be generated during authentication";
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this is a case for tampered request and authz failed ?

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 is one of those cases where I'm worried about breaking something important to support a feature that is only relevant to some people.
On balance, I think you're correct here, but I still have some reservations.

Copy link
Contributor

Choose a reason for hiding this comment

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

Okay, I agree. Let's keep it as is.

@@ -94,7 +94,7 @@ static void ensureAuthenticatedUserIsSame(Authentication original, Authenticatio

final boolean sameUser = samePrincipal && sameRealmType;
if (sameUser == false) {
auditTrailService.accessDenied(current, action, request, roleNames);
auditTrailService.accessDenied(null, current, action, request, roleNames);
Copy link
Contributor

Choose a reason for hiding this comment

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

Here should also be a request id present, otherwise tampered request?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As above, it's already a denied, which I think is the better failure than complaining about request ids.

@albertzaharovits
Copy link
Contributor

I have left a bunch of comments, all about being more strict about when we generate the request id and not simply generating a new one for a request without it. I like though that you've chosen to pass the request_id as an argument instead of using the threadContext which feels like global variables type of cheating.

@albertzaharovits
Copy link
Contributor

I've been mistaken about where you generated the ids, I though you generated them on each event if the parameter is null and if they're not in the context. Instead they are not generated at all, they are pulled from the context.

Copy link
Contributor

@albertzaharovits albertzaharovits left a comment

Choose a reason for hiding this comment

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

LGTM

@tvernum
Copy link
Contributor Author

tvernum commented Nov 22, 2018

I actually changed a bunch of things based on your comments, but I had some unrelated test failures so it didn't get pushed.

- Explicitly generate request.id when we know we should
- Explicitly extract request.id instead of doing this in the audit
  trail
- Reject authz of external requests without a request.id
@@ -59,7 +61,7 @@ public void intercept(ResizeRequest request, Authentication authentication, Role
userPermissions.indices().allowedActionsMatcher(request.getTargetIndexRequest().index());
if (Operations.subsetOf(targetIndexPermissions, sourceIndexPermissions) == false) {
// TODO we've already audited a access granted event so this is going to look ugly
auditTrailService.accessDenied(null, authentication, action, request, userPermissions.names());
auditTrailService.accessDenied(extractRequestId(threadContext), authentication, action, request, userPermissions.names());
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

Copy link
Contributor

@albertzaharovits albertzaharovits left a comment

Choose a reason for hiding this comment

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

LGTM Thank you!

@@ -211,7 +210,7 @@ public void authenticationSuccess(String requestId, String realm, User user, Res
.with(EVENT_ACTION_FIELD_NAME, "authentication_success")
.with(REALM_FIELD_NAME, realm)
.withRestUri(request)
.withRequestId(requestId, threadContext)
.withRequestId(requestId)
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

@tvernum
Copy link
Contributor Author

tvernum commented Nov 23, 2018

I plan to merge this as as (pending any additional review comments) but I also want to investigate passing the request.id between nodes so that it's possible to correlate when 1 request causes actions to run on different (search being the obvious example).

I don't expect any issues, but I'd like to keep it to its on PR.

@tvernum
Copy link
Contributor Author

tvernum commented Nov 23, 2018

Scrap the comment above. It was a 2 line change to make it a header rather than transient, so I've added it into this PR.

@tvernum
Copy link
Contributor Author

tvernum commented Nov 26, 2018

@elasticmachine test this please

@tvernum
Copy link
Contributor Author

tvernum commented Nov 26, 2018

@elasticmachine: run gradle build tests 1

Copy link
Contributor

@albertzaharovits albertzaharovits left a comment

Choose a reason for hiding this comment

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

👍

@tvernum tvernum merged commit 5b427d4 into elastic:master Nov 27, 2018
tvernum added a commit to tvernum/elasticsearch that referenced this pull request Nov 29, 2018
This generates a synthesized "id" for each incoming request that is
included in the audit logs (file only).
This id can be used to correlate events for the same request (e.g.
authentication success with access granted).

This request.id is specific to the audit logs and is not used for any
other purpose

The request.id is consistent across nodes if a single request requires
execution on multiple nodes (e.g. search acros multiple shards).
tvernum added a commit that referenced this pull request Nov 29, 2018
This generates a synthesized "id" for each incoming request that is
included in the audit logs (file only).
This id can be used to correlate events for the same request (e.g.
authentication success with access granted).

This request.id is specific to the audit logs and is not used for any
other purpose

The request.id is consistent across nodes if a single request requires
execution on multiple nodes (e.g. search across multiple shards).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants