Browse Source

Add ApiKey expiration time to audit log (#103959)

Follow up to PR: https://github.com/elastic/elasticsearch/pull/103453
Johannes Fredén 1 năm trước cách đây
mục cha
commit
cc9fba36e6

+ 5 - 0
docs/changelog/103959.yaml

@@ -0,0 +1,5 @@
+pr: 103959
+summary: Add `ApiKey` expiration time to audit log
+area: Security
+type: enhancement
+issues: []

+ 3 - 3
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:
 +

+ 4 - 0
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrail.java

@@ -1325,6 +1325,10 @@ public class LoggingAuditTrail implements AuditTrail, ClusterStateListener {
                 // 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 {

+ 12 - 8
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrailTests.java

@@ -627,21 +627,23 @@ public class LoggingAuditTrailTests extends ESTestCase {
         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 class LoggingAuditTrailTests extends ESTestCase {
             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 class LoggingAuditTrailTests extends ESTestCase {
             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);