-
Notifications
You must be signed in to change notification settings - Fork 25.2k
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
Conversation
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.
Pinging @elastic/es-security |
@@ -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()); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
Line 174 in e179bd3
for (RequestInterceptor interceptor : requestInterceptors) { |
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?
There was a problem hiding this comment.
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()); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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"; |
There was a problem hiding this comment.
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 ?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
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 |
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
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()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
There was a problem hiding this 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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
I plan to merge this as as (pending any additional review comments) but I also want to investigate passing the I don't expect any issues, but I'd like to keep it to its on PR. |
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. |
@elasticmachine test this please |
@elasticmachine: run gradle build tests 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
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).
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).
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