浏览代码

Add ApiKey expiration time to audit log (#103959)

Follow up to PR: https://github.com/elastic/elasticsearch/pull/103453
Johannes Fredén 1 年之前
父节点
当前提交
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":
 "applications":[],"run_as":[]},{"cluster":["all"],"indices":[{"names":
 ["index-b*"],"privileges":["all"]}],"applications":[],"run_as":[]}],
 ["index-b*"],"privileges":["all"]}],"applications":[],"run_as":[]}],
 "metadata":{"application":"my-application","environment":{"level": 1,
 "metadata":{"application":"my-application","environment":{"level": 1,
-"tags":["dev","staging"]}}}}}
+"tags":["dev","staging"]}},"expiration":"10d"}}}
 ====
 ====
 
 
 [[event-change-apikeys]]
 [[event-change-apikeys]]
@@ -281,7 +281,7 @@ event action.
 "applications":[],"run_as":[]},{"cluster":["all"],"indices":[{"names":
 "applications":[],"run_as":[]},{"cluster":["all"],"indices":[{"names":
 ["index-b*"],"privileges":["all"]}],"applications":[],"run_as":[]}],
 ["index-b*"],"privileges":["all"]}],"applications":[],"run_as":[]}],
 "metadata":{"application":"my-application","environment":{"level":1,
 "metadata":{"application":"my-application","environment":{"level":1,
-"tags":["dev","staging"]}}}}}
+"tags":["dev","staging"]}},"expiration":"10d"}}}
 ====
 ====
 
 
 [[event-delete-privileges]]
 [[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.
 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
 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:
 `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
                 // because it replaces any metadata previously associated with the API key
                 builder.field("metadata", baseUpdateApiKeyRequest.getMetadata());
                 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 {
         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();
         CapturingLogger.output(logger.getName(), Level.INFO).clear();
 
 
         final String keyId = randomAlphaOfLength(10);
         final String keyId = randomAlphaOfLength(10);
+        final TimeValue newExpiration = randomFrom(ApiKeyTests.randomFutureExpirationTime(), null);
         final var updateApiKeyRequest = new UpdateApiKeyRequest(
         final var updateApiKeyRequest = new UpdateApiKeyRequest(
             keyId,
             keyId,
             randomBoolean() ? null : keyRoleDescriptors,
             randomBoolean() ? null : keyRoleDescriptors,
             metadataWithSerialization.metadata(),
             metadataWithSerialization.metadata(),
-            ApiKeyTests.randomFutureExpirationTime()
+            newExpiration
         );
         );
         auditTrail.accessGranted(requestId, authentication, UpdateApiKeyAction.NAME, updateApiKeyRequest, authorizationInfo);
         auditTrail.accessGranted(requestId, authentication, UpdateApiKeyAction.NAME, updateApiKeyRequest, authorizationInfo);
         final var expectedUpdateKeyAuditEventString = String.format(
         final var expectedUpdateKeyAuditEventString = String.format(
             Locale.ROOT,
             Locale.ROOT,
             """
             """
-                "change":{"apikey":{"id":"%s","type":"rest"%s%s}}\
+                "change":{"apikey":{"id":"%s","type":"rest"%s%s,"expiration":%s}}\
                 """,
                 """,
             keyId,
             keyId,
             updateApiKeyRequest.getRoleDescriptors() == null ? "" : "," + roleDescriptorsStringBuilder,
             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);
         output = CapturingLogger.output(logger.getName(), Level.INFO);
         assertThat(output.size(), is(2));
         assertThat(output.size(), is(2));
@@ -664,13 +666,13 @@ public class LoggingAuditTrailTests extends ESTestCase {
             keyIds,
             keyIds,
             randomBoolean() ? null : keyRoleDescriptors,
             randomBoolean() ? null : keyRoleDescriptors,
             metadataWithSerialization.metadata(),
             metadataWithSerialization.metadata(),
-            ApiKeyTests.randomFutureExpirationTime()
+            null
         );
         );
         auditTrail.accessGranted(requestId, authentication, BulkUpdateApiKeyAction.NAME, bulkUpdateApiKeyRequest, authorizationInfo);
         auditTrail.accessGranted(requestId, authentication, BulkUpdateApiKeyAction.NAME, bulkUpdateApiKeyRequest, authorizationInfo);
         final var expectedBulkUpdateKeyAuditEventString = String.format(
         final var expectedBulkUpdateKeyAuditEventString = String.format(
             Locale.ROOT,
             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.getIds().stream().map(s -> Strings.format("\"%s\"", s)).collect(Collectors.joining(",")),
             bulkUpdateApiKeyRequest.getRoleDescriptors() == null ? "" : "," + roleDescriptorsStringBuilder,
             bulkUpdateApiKeyRequest.getRoleDescriptors() == null ? "" : "," + roleDescriptorsStringBuilder,
@@ -875,22 +877,24 @@ public class LoggingAuditTrailTests extends ESTestCase {
             updateMetadataWithSerialization = randomApiKeyMetadataWithSerialization();
             updateMetadataWithSerialization = randomApiKeyMetadataWithSerialization();
         }
         }
 
 
+        final TimeValue newExpiration = randomFrom(ApiKeyTests.randomFutureExpirationTime(), null);
         final var updateRequest = new UpdateCrossClusterApiKeyRequest(
         final var updateRequest = new UpdateCrossClusterApiKeyRequest(
             createRequest.getId(),
             createRequest.getId(),
             updateAccess,
             updateAccess,
             updateMetadataWithSerialization.metadata(),
             updateMetadataWithSerialization.metadata(),
-            ApiKeyTests.randomFutureExpirationTime()
+            newExpiration
         );
         );
         auditTrail.accessGranted(requestId, authentication, UpdateCrossClusterApiKeyAction.NAME, updateRequest, authorizationInfo);
         auditTrail.accessGranted(requestId, authentication, UpdateCrossClusterApiKeyAction.NAME, updateRequest, authorizationInfo);
 
 
         final String expectedUpdateAuditEventString = String.format(
         final String expectedUpdateAuditEventString = String.format(
             Locale.ROOT,
             Locale.ROOT,
             """
             """
-                "change":{"apikey":{"id":"%s","type":"cross_cluster"%s%s}}\
+                "change":{"apikey":{"id":"%s","type":"cross_cluster"%s%s,"expiration":%s}}\
                 """,
                 """,
             createRequest.getId(),
             createRequest.getId(),
             updateAccess == null ? "" : ",\"role_descriptors\":" + accessWithSerialization.serialization(),
             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);
         output = CapturingLogger.output(logger.getName(), Level.INFO);