1
0
Эх сурвалжийг харах

Decouple API key version from node version (#104156)

This commit decouples the version stored in API key documents from the
node version, as part of the broader effort to stop relying on node
version for product logic. The actual data format does not change - the
version is stored as an integer before and after this commit, it's just
determined by a manually set `ApiKey.Version` class rather than being
derived from the node version.
Athena Brown 1 жил өмнө
parent
commit
32deb7fa46

+ 1 - 1
x-pack/plugin/core/src/internalClusterTest/java/org/elasticsearch/nodesinfo/ComponentVersionsNodesInfoIT.java

@@ -29,7 +29,7 @@ public class ComponentVersionsNodesInfoIT extends ESIntegTestCase {
         assertThat(response.getNodesMap().get(server1NodeId), notNullValue());
         assertThat(
             response.getNodesMap().get(server1NodeId).getComponentVersions().keySet(),
-            containsInAnyOrder("transform_config_version", "ml_config_version")
+            containsInAnyOrder("transform_config_version", "ml_config_version", "api_key_version")
         );
     }
 }

+ 2 - 1
x-pack/plugin/core/src/main/java/module-info.java

@@ -232,7 +232,8 @@ module org.elasticsearch.xcore {
     provides org.elasticsearch.action.admin.cluster.node.info.ComponentVersionNumber
         with
             org.elasticsearch.xpack.core.ml.MlConfigVersionComponent,
-            org.elasticsearch.xpack.core.transform.TransformConfigVersionComponent;
+            org.elasticsearch.xpack.core.transform.TransformConfigVersionComponent,
+            org.elasticsearch.xpack.core.security.action.apikey.ApiKey.VersionComponent;
 
     provides org.elasticsearch.features.FeatureSpecification with org.elasticsearch.xpack.core.XPackFeatures;
 }

+ 24 - 0
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/apikey/ApiKey.java

@@ -7,6 +7,8 @@
 
 package org.elasticsearch.xpack.core.security.action.apikey;
 
+import org.elasticsearch.action.admin.cluster.node.info.ComponentVersionNumber;
+import org.elasticsearch.common.VersionId;
 import org.elasticsearch.common.xcontent.XContentParserUtils;
 import org.elasticsearch.core.Assertions;
 import org.elasticsearch.core.Nullable;
@@ -81,6 +83,28 @@ public final class ApiKey implements ToXContentObject {
         }
     }
 
+    public record Version(int version) implements VersionId<Version> {
+        @Override
+        public int id() {
+            return version;
+        }
+    }
+
+    public static class VersionComponent implements ComponentVersionNumber {
+
+        @Override
+        public String componentId() {
+            return "api_key_version";
+        }
+
+        @Override
+        public VersionId<?> versionNumber() {
+            return CURRENT_API_KEY_VERSION;
+        }
+    }
+
+    public static final ApiKey.Version CURRENT_API_KEY_VERSION = new ApiKey.Version(8_13_00_99);
+
     private final String name;
     private final String id;
     private final Type type;

+ 1 - 0
x-pack/plugin/core/src/main/resources/META-INF/services/org.elasticsearch.action.admin.cluster.node.info.ComponentVersionNumber

@@ -1,2 +1,3 @@
 org.elasticsearch.xpack.core.ml.MlConfigVersionComponent
 org.elasticsearch.xpack.core.transform.TransformConfigVersionComponent
+org.elasticsearch.xpack.core.security.action.apikey.ApiKey$VersionComponent

+ 9 - 11
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ApiKeyService.java

@@ -14,7 +14,6 @@ import org.elasticsearch.ElasticsearchSecurityException;
 import org.elasticsearch.ExceptionsHelper;
 import org.elasticsearch.ResourceNotFoundException;
 import org.elasticsearch.TransportVersion;
-import org.elasticsearch.Version;
 import org.elasticsearch.action.ActionListener;
 import org.elasticsearch.action.ActionRunnable;
 import org.elasticsearch.action.DocWriteRequest;
@@ -415,7 +414,6 @@ public class ApiKeyService {
         final Instant expiration = getApiKeyExpiration(created, request.getExpiration());
         final SecureString apiKey = UUIDs.randomBase64UUIDSecureString();
         assert ApiKey.Type.CROSS_CLUSTER != request.getType() || API_KEY_SECRET_LENGTH == apiKey.length();
-        final Version version = clusterService.state().nodes().getMinNodeVersion();
 
         computeHashForApiKey(apiKey, listener.delegateFailure((l, apiKeyHashChars) -> {
             try (
@@ -428,7 +426,7 @@ public class ApiKeyService {
                     expiration,
                     request.getRoleDescriptors(),
                     request.getType(),
-                    version,
+                    ApiKey.CURRENT_API_KEY_VERSION,
                     request.getMetadata()
                 )
             ) {
@@ -712,7 +710,7 @@ public class ApiKeyService {
         Instant expiration,
         List<RoleDescriptor> keyRoleDescriptors,
         ApiKey.Type type,
-        Version version,
+        ApiKey.Version version,
         @Nullable Map<String, Object> metadata
     ) throws IOException {
         final XContentBuilder builder = XContentFactory.jsonBuilder();
@@ -727,7 +725,7 @@ public class ApiKeyService {
         addRoleDescriptors(builder, keyRoleDescriptors);
         addLimitedByRoleDescriptors(builder, userRoleDescriptors);
 
-        builder.field("name", name).field("version", version.id).field("metadata_flattened", metadata);
+        builder.field("name", name).field("version", version.version()).field("metadata_flattened", metadata);
         addCreator(builder, authentication);
 
         return builder.endObject();
@@ -742,7 +740,7 @@ public class ApiKeyService {
     static XContentBuilder maybeBuildUpdatedDocument(
         final String apiKeyId,
         final ApiKeyDoc currentApiKeyDoc,
-        final Version targetDocVersion,
+        final ApiKey.Version targetDocVersion,
         final Authentication authentication,
         final BaseUpdateApiKeyRequest request,
         final Set<RoleDescriptor> userRoleDescriptors,
@@ -779,7 +777,7 @@ public class ApiKeyService {
 
         addLimitedByRoleDescriptors(builder, userRoleDescriptors);
 
-        builder.field("name", currentApiKeyDoc.name).field("version", targetDocVersion.id);
+        builder.field("name", currentApiKeyDoc.name).field("version", targetDocVersion.version());
 
         assert currentApiKeyDoc.metadataFlattened == null
             || MetadataUtils.containsReservedMetadata(
@@ -807,12 +805,12 @@ public class ApiKeyService {
     private static boolean isNoop(
         final String apiKeyId,
         final ApiKeyDoc apiKeyDoc,
-        final Version targetDocVersion,
+        final ApiKey.Version targetDocVersion,
         final Authentication authentication,
         final BaseUpdateApiKeyRequest request,
         final Set<RoleDescriptor> userRoleDescriptors
     ) throws IOException {
-        if (apiKeyDoc.version != targetDocVersion.id) {
+        if (apiKeyDoc.version != targetDocVersion.version()) {
             return false;
         }
 
@@ -1468,8 +1466,8 @@ public class ApiKeyService {
                 currentVersionedDoc.primaryTerm()
             );
         }
-        final var targetDocVersion = clusterService.state().nodes().getMinNodeVersion();
-        final var currentDocVersion = Version.fromId(currentVersionedDoc.doc().version);
+        final var targetDocVersion = ApiKey.CURRENT_API_KEY_VERSION;
+        final var currentDocVersion = new ApiKey.Version(currentVersionedDoc.doc().version);
         assert currentDocVersion.onOrBefore(targetDocVersion) : "current API key doc version must be on or before target version";
         if (logger.isDebugEnabled() && currentDocVersion.before(targetDocVersion)) {
             logger.debug(

+ 10 - 7
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ApiKeyServiceTests.java

@@ -67,7 +67,6 @@ import org.elasticsearch.test.ClusterServiceUtils;
 import org.elasticsearch.test.ESTestCase;
 import org.elasticsearch.test.MockLogAppender;
 import org.elasticsearch.test.TransportVersionUtils;
-import org.elasticsearch.test.VersionUtils;
 import org.elasticsearch.test.XContentTestUtils;
 import org.elasticsearch.threadpool.FixedExecutorBuilder;
 import org.elasticsearch.threadpool.TestThreadPool;
@@ -1161,7 +1160,7 @@ public class ApiKeyServiceTests extends ESTestCase {
             Instant.now().plus(expiry),
             keyRoles,
             type,
-            Version.CURRENT,
+            ApiKey.CURRENT_API_KEY_VERSION,
             metadataMap
         );
         Map<String, Object> keyMap = XContentHelper.convertToMap(BytesReference.bytes(docSource), true, XContentType.JSON).v2();
@@ -2368,7 +2367,7 @@ public class ApiKeyServiceTests extends ESTestCase {
         final long now = randomMillisUpToYear9999();
         when(clock.instant()).thenReturn(Instant.ofEpochMilli(now));
         final Map<String, Object> oldMetadata = ApiKeyTests.randomMetadata();
-        final Version oldVersion = VersionUtils.randomVersion(random());
+        final ApiKey.Version oldVersion = new ApiKey.Version(randomIntBetween(1, ApiKey.CURRENT_API_KEY_VERSION.version()));
         final ApiKeyDoc oldApiKeyDoc = ApiKeyDoc.fromXContent(
             XContentHelper.createParser(
                 XContentParserConfiguration.EMPTY,
@@ -2419,8 +2418,8 @@ public class ApiKeyServiceTests extends ESTestCase {
         final Map<String, Object> newMetadata = changeMetadata
             ? randomValueOtherThanMany(md -> md == null || md.equals(oldMetadata), ApiKeyTests::randomMetadata)
             : (randomBoolean() ? oldMetadata : null);
-        final Version newVersion = changeVersion
-            ? randomValueOtherThan(oldVersion, () -> VersionUtils.randomVersion(random()))
+        final ApiKey.Version newVersion = changeVersion
+            ? randomValueOtherThan(oldVersion, ApiKeyServiceTests::randomApiKeyVersion)
             : oldVersion;
         final Authentication newAuthentication = changeCreator
             ? randomValueOtherThanMany(
@@ -2468,7 +2467,7 @@ public class ApiKeyServiceTests extends ESTestCase {
             assertEquals(oldApiKeyDoc.hash, updatedApiKeyDoc.hash);
             assertEquals(oldApiKeyDoc.creationTime, updatedApiKeyDoc.creationTime);
             assertEquals(oldApiKeyDoc.invalidated, updatedApiKeyDoc.invalidated);
-            assertEquals(newVersion.id, updatedApiKeyDoc.version);
+            assertEquals(newVersion.version(), updatedApiKeyDoc.version);
             final var actualUserRoles = service.parseRoleDescriptorsBytes(
                 "",
                 updatedApiKeyDoc.limitedByRoleDescriptorsBytes,
@@ -2991,7 +2990,7 @@ public class ApiKeyServiceTests extends ESTestCase {
                 Instant.now().plus(Duration.ofSeconds(3600)),
                 keyRoles,
                 ApiKey.Type.REST,
-                Version.CURRENT,
+                ApiKey.CURRENT_API_KEY_VERSION,
                 randomBoolean() ? null : Map.of(randomAlphaOfLengthBetween(3, 8), randomAlphaOfLengthBetween(3, 8))
             );
             final ApiKeyDoc apiKeyDoc = ApiKeyDoc.fromXContent(
@@ -3207,4 +3206,8 @@ public class ApiKeyServiceTests extends ESTestCase {
             mock(Realms.class)
         );
     }
+
+    private static ApiKey.Version randomApiKeyVersion() {
+        return new ApiKey.Version(randomIntBetween(1, ApiKey.CURRENT_API_KEY_VERSION.version()));
+    }
 }