diff --git a/docs/changelog/103959.yaml b/docs/changelog/103959.yaml new file mode 100644 index 0000000000000..4c8b4413b95f8 --- /dev/null +++ b/docs/changelog/103959.yaml @@ -0,0 +1,5 @@ +pr: 103959 +summary: Add `ApiKey` expiration time to audit log +area: Security +type: enhancement +issues: [] diff --git a/docs/reference/security/auditing/event-types.asciidoc b/docs/reference/security/auditing/event-types.asciidoc index 9539ea38b5a6b..a856336dba8d0 100644 --- a/docs/reference/security/auditing/event-types.asciidoc +++ b/docs/reference/security/auditing/event-types.asciidoc @@ -255,7 +255,7 @@ event action. "applications":[],"run_as":[]},{"cluster":["all"],"indices":[{"names": ["index-b*"],"privileges":["all"]}],"applications":[],"run_as":[]}], "metadata":{"application":"my-application","environment":{"level": 1, -"tags":["dev","staging"]}}}}} +"tags":["dev","staging"]}},"expiration":"10d"}}} ==== [[event-change-apikeys]] @@ -281,7 +281,7 @@ event action. "applications":[],"run_as":[]},{"cluster":["all"],"indices":[{"names": ["index-b*"],"privileges":["all"]}],"applications":[],"run_as":[]}], "metadata":{"application":"my-application","environment":{"level":1, -"tags":["dev","staging"]}}}}} +"tags":["dev","staging"]}},"expiration":"10d"}}} ==== [[event-delete-privileges]] @@ -797,7 +797,7 @@ The `role_descriptors` objects have the same schema as the `role_descriptor` object that is part of the above `role` config object. The object for an API key update will differ in that it will not include -a `name` or `expiration`. +a `name`. `grant` :: An object like: + diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrail.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrail.java index e2b9c36c1d0ee..87c372f561757 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrail.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrail.java @@ -1325,6 +1325,10 @@ private static void withBaseUpdateApiKeyFields(final XContentBuilder builder, fi // because it replaces any metadata previously associated with the API key builder.field("metadata", baseUpdateApiKeyRequest.getMetadata()); } + builder.field( + "expiration", + baseUpdateApiKeyRequest.getExpiration() != null ? baseUpdateApiKeyRequest.getExpiration().toString() : null + ); } private static void withRoleDescriptor(XContentBuilder builder, RoleDescriptor roleDescriptor) throws IOException { diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrailTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrailTests.java index 9c48354b951d8..2438e625259d1 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrailTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrailTests.java @@ -627,21 +627,23 @@ public void testSecurityConfigChangeEventFormattingForRoles() throws IOException CapturingLogger.output(logger.getName(), Level.INFO).clear(); final String keyId = randomAlphaOfLength(10); + final TimeValue newExpiration = randomFrom(ApiKeyTests.randomFutureExpirationTime(), null); final var updateApiKeyRequest = new UpdateApiKeyRequest( keyId, randomBoolean() ? null : keyRoleDescriptors, metadataWithSerialization.metadata(), - ApiKeyTests.randomFutureExpirationTime() + newExpiration ); auditTrail.accessGranted(requestId, authentication, UpdateApiKeyAction.NAME, updateApiKeyRequest, authorizationInfo); final var expectedUpdateKeyAuditEventString = String.format( Locale.ROOT, """ - "change":{"apikey":{"id":"%s","type":"rest"%s%s}}\ + "change":{"apikey":{"id":"%s","type":"rest"%s%s,"expiration":%s}}\ """, keyId, updateApiKeyRequest.getRoleDescriptors() == null ? "" : "," + roleDescriptorsStringBuilder, - updateApiKeyRequest.getMetadata() == null ? "" : Strings.format(",\"metadata\":%s", metadataWithSerialization.serialization()) + updateApiKeyRequest.getMetadata() == null ? "" : Strings.format(",\"metadata\":%s", metadataWithSerialization.serialization()), + updateApiKeyRequest.getExpiration() == null ? null : Strings.format("\"%s\"", newExpiration) ); output = CapturingLogger.output(logger.getName(), Level.INFO); assertThat(output.size(), is(2)); @@ -664,13 +666,13 @@ public void testSecurityConfigChangeEventFormattingForRoles() throws IOException keyIds, randomBoolean() ? null : keyRoleDescriptors, metadataWithSerialization.metadata(), - ApiKeyTests.randomFutureExpirationTime() + null ); auditTrail.accessGranted(requestId, authentication, BulkUpdateApiKeyAction.NAME, bulkUpdateApiKeyRequest, authorizationInfo); final var expectedBulkUpdateKeyAuditEventString = String.format( Locale.ROOT, """ - "change":{"apikeys":{"ids":[%s],"type":"rest"%s%s}}\ + "change":{"apikeys":{"ids":[%s],"type":"rest"%s%s,"expiration":null}}\ """, bulkUpdateApiKeyRequest.getIds().stream().map(s -> Strings.format("\"%s\"", s)).collect(Collectors.joining(",")), bulkUpdateApiKeyRequest.getRoleDescriptors() == null ? "" : "," + roleDescriptorsStringBuilder, @@ -875,22 +877,24 @@ public void testSecurityConfigChangeEventForCrossClusterApiKeys() throws IOExcep updateMetadataWithSerialization = randomApiKeyMetadataWithSerialization(); } + final TimeValue newExpiration = randomFrom(ApiKeyTests.randomFutureExpirationTime(), null); final var updateRequest = new UpdateCrossClusterApiKeyRequest( createRequest.getId(), updateAccess, updateMetadataWithSerialization.metadata(), - ApiKeyTests.randomFutureExpirationTime() + newExpiration ); auditTrail.accessGranted(requestId, authentication, UpdateCrossClusterApiKeyAction.NAME, updateRequest, authorizationInfo); final String expectedUpdateAuditEventString = String.format( Locale.ROOT, """ - "change":{"apikey":{"id":"%s","type":"cross_cluster"%s%s}}\ + "change":{"apikey":{"id":"%s","type":"cross_cluster"%s%s,"expiration":%s}}\ """, createRequest.getId(), updateAccess == null ? "" : ",\"role_descriptors\":" + accessWithSerialization.serialization(), - updateRequest.getMetadata() == null ? "" : Strings.format(",\"metadata\":%s", updateMetadataWithSerialization.serialization()) + updateRequest.getMetadata() == null ? "" : Strings.format(",\"metadata\":%s", updateMetadataWithSerialization.serialization()), + newExpiration == null ? null : String.format(Locale.ROOT, "\"%s\"", newExpiration) ); output = CapturingLogger.output(logger.getName(), Level.INFO);