-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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
Add logging implementation for AuditManager and audit more endpoints #15480
Conversation
server/src/test/java/org/apache/druid/server/audit/SQLAuditManagerTest.java
Fixed
Show fixed
Hide fixed
try { | ||
Response response = | ||
dataSourcesResource.markAsUnusedAllSegmentsOrKillUnusedSegmentsInInterval("datasource", "true", "???"); | ||
dataSourcesResource.markAsUnusedAllSegmentsOrKillUnusedSegmentsInInterval("datasource", "true", "???", request); |
Check notice
Code scanning / CodeQL
Deprecated method or constructor invocation Note test
DataSourcesResource.markAsUnusedAllSegmentsOrKillUnusedSegmentsInInterval
...java/org/apache/druid/security/authentication/CoordinatorBasicAuthenticatorResourceTest.java
Fixed
Show fixed
Hide fixed
...java/org/apache/druid/security/authentication/CoordinatorBasicAuthenticatorResourceTest.java
Fixed
Show fixed
Hide fixed
indexing-service/src/main/java/org/apache/druid/indexing/overlord/http/OverlordResource.java
Outdated
Show resolved
Hide resolved
@@ -900,7 +900,7 @@ public void testTaskPostDeniesDatasourceReadUser() | |||
Task task = NoopTask.forDatasource(Datasources.WIKIPEDIA); | |||
expectedException.expect(ForbiddenException.class); | |||
expectedException.expect(ForbiddenException.class); | |||
overlordResource.taskPost(task, req); | |||
overlordResource.taskPost(task, "", "", req); |
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.
maybe add a test with non-empty author to see that audit event does go through?
processing/src/test/java/org/apache/druid/audit/NoopAuditManager.java
Outdated
Show resolved
Hide resolved
server/src/main/java/org/apache/druid/guice/SQLMetadataStorageDruidModule.java
Outdated
Show resolved
Hide resolved
server/src/main/java/org/apache/druid/server/audit/AuditSerdeHelper.java
Show resolved
Hide resolved
server/src/main/java/org/apache/druid/server/audit/SQLAuditManager.java
Outdated
Show resolved
Hide resolved
auditManager.doAudit( | ||
AuditEvent.builder() | ||
.key(dataSourceName) | ||
.type("segments.markUnused") |
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.
can the type name be standardized? E.g. serviceName.api-path
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.
Yeah, that makes sense to me.
So would something like the following work?
coordinator.markSegmentsUnused
coordinator.basicAuth.createUser
overlord.postTask
overlord.killSegments
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.
Added a field request
in AuditEntry
to contain the details of path
, uri
, method
and service
.
@@ -225,18 +222,29 @@ public Response taskPost(final Task task, @Context final HttpServletRequest req) | |||
taskQueue -> { | |||
try { | |||
taskQueue.add(task); | |||
|
|||
auditManager.doAudit( |
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.
can we filter task submissions that were initiated by the system itself? E.g. compaction duty, MSQ controller task, etc?
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, this is already happening inside the respective audit impls, such as SQLAuditManager
and LoggingAuditManager
. We check if an audited event was started by an internal service (by checking the author field) and then audit that event only if system event auditing is enabled via config (false by default).
This is better than checking at each call site if the request was initiated by an internal service.
<T> void doAudit(String key, String type, AuditInfo auditInfo, T payload, ConfigSerde<T> configSerde); | ||
default boolean isSystemRequest(AuditInfo auditInfo) | ||
{ | ||
return AUTHOR_DRUID_SYSTEM.equals(auditInfo.getAuthor()); |
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.
would this be valid for any auth extension?
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, because we would explicitly set this as the header value when using internal clients.
Currently, we are setting it only in OverlordClientImpl.runTask
but we can choose do set it in other audited endpoints too, or better yet just always set it by default in any service client. What do you think?
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.
So its possible for a user to bypass audit system if they set this header?
final MapBinder<String, AuditManager> auditManagerBinder | ||
= PolyBind.optionBinder(binder, Key.get(AuditManager.class)); | ||
auditManagerBinder | ||
.addBinding("log") | ||
.to(LoggingAuditManager.class) | ||
.in(LazySingleton.class); |
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.
seems off that the log choice is added in this module. should this be moved to some other module?
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.
Sure, let me put this in some other module.
@@ -96,7 +97,8 @@ public ListenableFuture<Void> runTask(final String taskId, final Object taskObje | |||
return FutureUtils.transform( | |||
client.asyncRequest( | |||
new RequestBuilder(HttpMethod.POST, "/druid/indexer/v1/task") | |||
.jsonContent(jsonMapper, taskObject), | |||
.jsonContent(jsonMapper, taskObject) | |||
.header(AuditManager.X_DRUID_AUTHOR, AuditManager.AUTHOR_DRUID_SYSTEM), |
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.
is this needed? can we not rely on authenticated identity instead?
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 seemed much easier to implement.
I encountered some issues with the other approach:
- The authenticated identity would depend on both auth impl and configuration. I couldn't find a definite way to determine the internal service identity. Basic auth creates the
druid_system
user but I don't see it being used in internal communication. (On second thought, let me see if I can pull the identity from the escalated client that is being injected) - When there is no auth, all requests get the identity
allowAll
. - When auth is enabled, a user could still use the internal druid service username/password to invoke an API and bypass the audit (This is technically still possible by setting the author header to this specific value but that is less likely to happen. This is one of the reasons I chose to include a config for auditing system requests too).
Please let me know your thoughts on this.
server/src/main/java/org/apache/druid/server/audit/AuditManagerConfig.java
Show resolved
Hide resolved
"KillCompactionConfig", | ||
"KillCompactionConfig", |
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.
identity and author are KillCompactionConfig?
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.
Thanks for catching this. Since we are revisiting auditing in this PR, I guess it would be better to use druid_system
or something as the author instead.
Thanks for the review, @abhishekagarwal87 ! |
@kfaraz the master build started failing for some middle-manager integration tests after merging your PR, could you take a look? |
Description
This PR adds an implementation of
AuditManager
that simply logs the audit events instead of persisting them to metadata store.Sample audit log
Setup
Authentication: none
druid.audit.manager.type=log
Changes
log
implementation forAuditManager
alongwithSQLAuditManager
LoggingAuditManager
simply logs the audit event. Thus, it returns empty for allfetchAuditHistory
callsdruid.audit.manager.type
which can take valueslog
,sql
(default)druid.audit.manager.logLevel
which can take valuesDEBUG
,INFO
,WARN
. This gets activated only iftype
islog
.ConfigSerde
fromAuditManager
as audit is not just limited to configsAuditSerdeHelper
for a single implementation of serialization/deserialization of audit payload that can be used by differentAuditManager
implementationsHow is audit performed
All existing and new audited REST API endpoints accept two headers
X-Druid-Author
andX-Druid-Comment
which are being used to populate the audit info. The web-console currently passesconsole
as the value forX-Druid-Author
.An alternative to this can be to extract the authentication result set as an attribute in the
HttpServletRequest
and invokeauthenticationResult.getIdentity()
.New audited endpoints
type=ingestion.batch
type=markSegmentsAsUnused
type=killSegments
type=basicAuth.createUser
type=basicAuth.deleteUser
type=basicAuth.updateUserCreds
Release note
druid.audit.manager.type
which can take valueslog
,sql
(default). This allows audited events to either be logged or persisted in metadata store (default behaviour).druid.audit.manager.logLevel
which allows users to set the log level of audit events and can take valuesDEBUG
,INFO
(default),WARN
.This PR has: