Selaa lähdekoodia

Only auto-update license signature if all nodes ready (#30859)

Allows rolling restart from 6.3 to 6.4.

Relates to #30731 and #30251
Yannick Welsch 7 vuotta sitten
vanhempi
commit
3b98c26d03

+ 2 - 1
x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseService.java

@@ -411,7 +411,8 @@ public class LicenseService extends AbstractLifecycleComponent implements Cluste
             // auto-generate license if no licenses ever existed or if the current license is basic and
             // needs extended or if the license signature needs to be updated. this will trigger a subsequent cluster changed event
             if (currentClusterState.getNodes().isLocalNodeElectedMaster() &&
-                    (noLicense || LicenseUtils.licenseNeedsExtended(currentLicense) || LicenseUtils.signatureNeedsUpdate(currentLicense))) {
+                    (noLicense || LicenseUtils.licenseNeedsExtended(currentLicense) ||
+                        LicenseUtils.signatureNeedsUpdate(currentLicense, currentClusterState.nodes()))) {
                 registerOrUpdateSelfGeneratedLicense();
             }
         } else if (logger.isDebugEnabled()) {

+ 23 - 2
x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseUtils.java

@@ -6,8 +6,12 @@
 package org.elasticsearch.license;
 
 import org.elasticsearch.ElasticsearchSecurityException;
+import org.elasticsearch.Version;
+import org.elasticsearch.cluster.node.DiscoveryNodes;
 import org.elasticsearch.rest.RestStatus;
 
+import java.util.stream.StreamSupport;
+
 public class LicenseUtils {
 
     public static final String EXPIRED_FEATURE_METADATA = "es.license.expired.feature";
@@ -42,8 +46,25 @@ public class LicenseUtils {
      * Checks if the signature of a self generated license with older version needs to be
      * recreated with the new key
      */
-    public static boolean signatureNeedsUpdate(License license) {
+    public static boolean signatureNeedsUpdate(License license, DiscoveryNodes currentNodes) {
+        assert License.VERSION_CRYPTO_ALGORITHMS == License.VERSION_CURRENT : "update this method when adding a new version";
+
         return ("basic".equals(license.type()) || "trial".equals(license.type())) &&
-                (license.version() < License.VERSION_CRYPTO_ALGORITHMS);
+                // only upgrade signature when all nodes are ready to deserialize the new signature
+                (license.version() < License.VERSION_CRYPTO_ALGORITHMS &&
+                    compatibleLicenseVersion(currentNodes) == License.VERSION_CRYPTO_ALGORITHMS
+                );
+    }
+
+    public static int compatibleLicenseVersion(DiscoveryNodes currentNodes) {
+        assert License.VERSION_CRYPTO_ALGORITHMS == License.VERSION_CURRENT : "update this method when adding a new version";
+
+        if (StreamSupport.stream(currentNodes.spliterator(), false)
+            .allMatch(node -> node.getVersion().onOrAfter(Version.V_6_4_0))) {
+            // License.VERSION_CRYPTO_ALGORITHMS was introduced in 6.4.0
+            return License.VERSION_CRYPTO_ALGORITHMS;
+        } else {
+            return License.VERSION_START_DATE;
+        }
     }
 }

+ 3 - 2
x-pack/plugin/core/src/main/java/org/elasticsearch/license/SelfGeneratedLicense.java

@@ -5,6 +5,7 @@
  */
 package org.elasticsearch.license;
 
+import org.elasticsearch.cluster.node.DiscoveryNodes;
 import org.elasticsearch.common.bytes.BytesReference;
 import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
 import org.elasticsearch.common.xcontent.NamedXContentRegistry;
@@ -26,8 +27,8 @@ import static org.elasticsearch.license.CryptUtils.decrypt;
 
 class SelfGeneratedLicense {
 
-    public static License create(License.Builder specBuilder) {
-        return create(specBuilder, License.VERSION_CURRENT);
+    public static License create(License.Builder specBuilder, DiscoveryNodes currentNodes) {
+        return create(specBuilder, LicenseUtils.compatibleLicenseVersion(currentNodes));
     }
 
     public static License create(License.Builder specBuilder, int version) {

+ 1 - 1
x-pack/plugin/core/src/main/java/org/elasticsearch/license/StartBasicClusterTask.java

@@ -73,7 +73,7 @@ public class StartBasicClusterTask extends ClusterStateUpdateTask {
                     .issueDate(issueDate)
                     .type("basic")
                     .expiryDate(LicenseService.BASIC_SELF_GENERATED_LICENSE_EXPIRATION_MILLIS);
-            License selfGeneratedLicense = SelfGeneratedLicense.create(specBuilder);
+            License selfGeneratedLicense = SelfGeneratedLicense.create(specBuilder, currentState.nodes());
             if (request.isAcknowledged() == false && currentLicense != null) {
                 Map<String, String[]> ackMessages = LicenseService.getAckMessages(selfGeneratedLicense, currentLicense);
                 if (ackMessages.isEmpty() == false) {

+ 1 - 1
x-pack/plugin/core/src/main/java/org/elasticsearch/license/StartTrialClusterTask.java

@@ -82,7 +82,7 @@ public class StartTrialClusterTask extends ClusterStateUpdateTask {
                     .issueDate(issueDate)
                     .type(request.getType())
                     .expiryDate(expiryDate);
-            License selfGeneratedLicense = SelfGeneratedLicense.create(specBuilder);
+            License selfGeneratedLicense = SelfGeneratedLicense.create(specBuilder, currentState.nodes());
             LicensesMetaData newLicensesMetaData = new LicensesMetaData(selfGeneratedLicense, Version.CURRENT);
             mdBuilder.putCustom(LicensesMetaData.TYPE, newLicensesMetaData);
             return ClusterState.builder(currentState).metaData(mdBuilder).build();

+ 4 - 4
x-pack/plugin/core/src/main/java/org/elasticsearch/license/StartupSelfGeneratedLicenseTask.java

@@ -61,7 +61,7 @@ public class StartupSelfGeneratedLicenseTask extends ClusterStateUpdateTask {
                         "]. Must be trial or basic.");
             }
             return updateWithLicense(currentState, type);
-        } else if (LicenseUtils.signatureNeedsUpdate(currentLicensesMetaData.getLicense())) {
+        } else if (LicenseUtils.signatureNeedsUpdate(currentLicensesMetaData.getLicense(), currentState.nodes())) {
             return updateLicenseSignature(currentState, currentLicensesMetaData);
         } else if (LicenseUtils.licenseNeedsExtended(currentLicensesMetaData.getLicense())) {
             return extendBasic(currentState, currentLicensesMetaData);
@@ -87,7 +87,7 @@ public class StartupSelfGeneratedLicenseTask extends ClusterStateUpdateTask {
                 .issueDate(issueDate)
                 .type(type)
                 .expiryDate(expiryDate);
-        License selfGeneratedLicense = SelfGeneratedLicense.create(specBuilder);
+        License selfGeneratedLicense = SelfGeneratedLicense.create(specBuilder, currentState.nodes());
         Version trialVersion = currentLicenseMetaData.getMostRecentTrialVersion();
         LicensesMetaData newLicenseMetadata = new LicensesMetaData(selfGeneratedLicense, trialVersion);
         mdBuilder.putCustom(LicensesMetaData.TYPE, newLicenseMetadata);
@@ -120,7 +120,7 @@ public class StartupSelfGeneratedLicenseTask extends ClusterStateUpdateTask {
                 .issueDate(currentLicense.issueDate())
                 .type("basic")
                 .expiryDate(LicenseService.BASIC_SELF_GENERATED_LICENSE_EXPIRATION_MILLIS);
-        License selfGeneratedLicense = SelfGeneratedLicense.create(specBuilder);
+        License selfGeneratedLicense = SelfGeneratedLicense.create(specBuilder, currentLicense.version());
         Version trialVersion = currentLicenseMetadata.getMostRecentTrialVersion();
         return new LicensesMetaData(selfGeneratedLicense, trialVersion);
     }
@@ -141,7 +141,7 @@ public class StartupSelfGeneratedLicenseTask extends ClusterStateUpdateTask {
                 .issueDate(issueDate)
                 .type(type)
                 .expiryDate(expiryDate);
-        License selfGeneratedLicense = SelfGeneratedLicense.create(specBuilder);
+        License selfGeneratedLicense = SelfGeneratedLicense.create(specBuilder, currentState.nodes());
         LicensesMetaData licensesMetaData;
         if ("trial".equals(type)) {
             licensesMetaData = new LicensesMetaData(selfGeneratedLicense, Version.CURRENT);

+ 2 - 2
x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseRegistrationTests.java

@@ -104,7 +104,7 @@ public class LicenseRegistrationTests extends AbstractLicenseServiceTestCase {
                 .issueDate(dateMath("now-10h", now))
                 .type("basic")
                 .expiryDate(dateMath("now-2h", now));
-        License license = SelfGeneratedLicense.create(builder);
+        License license = SelfGeneratedLicense.create(builder, License.VERSION_CURRENT);
 
         XPackLicenseState licenseState = new XPackLicenseState(Settings.EMPTY);
         setInitialState(license, licenseState, Settings.EMPTY);
@@ -125,4 +125,4 @@ public class LicenseRegistrationTests extends AbstractLicenseServiceTestCase {
         assertEquals(LicenseService.BASIC_SELF_GENERATED_LICENSE_EXPIRATION_MILLIS, licenseMetaData.getLicense().expiryDate());
         assertEquals(uid, licenseMetaData.getLicense().uid());
     }
-}
+}

+ 1 - 1
x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseSerializationTests.java

@@ -111,7 +111,7 @@ public class LicenseSerializationTests extends ESTestCase {
                 .issueDate(now)
                 .type("basic")
                 .expiryDate(LicenseService.BASIC_SELF_GENERATED_LICENSE_EXPIRATION_MILLIS);
-        License license = SelfGeneratedLicense.create(specBuilder);
+        License license = SelfGeneratedLicense.create(specBuilder, License.VERSION_CURRENT);
         XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON);
         license.toXContent(builder, new ToXContent.MapParams(Collections.singletonMap(License.REST_VIEW_MODE, "true")));
         builder.flush();

+ 1 - 1
x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicensesMetaDataSerializationTests.java

@@ -95,7 +95,7 @@ public class LicensesMetaDataSerializationTests extends ESTestCase {
                 .issueDate(issueDate)
                 .type(randomBoolean() ? "trial" : "basic")
                 .expiryDate(issueDate + TimeValue.timeValueHours(2).getMillis());
-        final License trialLicense = SelfGeneratedLicense.create(specBuilder);
+        final License trialLicense = SelfGeneratedLicense.create(specBuilder, License.VERSION_CURRENT);
         LicensesMetaData licensesMetaData = new LicensesMetaData(trialLicense, Version.CURRENT);
         XContentBuilder builder = XContentFactory.jsonBuilder();
         builder.startObject();

+ 4 - 3
x-pack/plugin/core/src/test/java/org/elasticsearch/license/SelfGeneratedLicenseTests.java

@@ -34,7 +34,7 @@ public class SelfGeneratedLicenseTests extends ESTestCase {
                 .type(randomBoolean() ? "trial" : "basic")
                 .issueDate(issueDate)
                 .expiryDate(issueDate + TimeValue.timeValueHours(2).getMillis());
-        License trialLicense = SelfGeneratedLicense.create(specBuilder);
+        License trialLicense = SelfGeneratedLicense.create(specBuilder, License.VERSION_CURRENT);
         assertThat(SelfGeneratedLicense.verify(trialLicense), equalTo(true));
     }
 
@@ -47,7 +47,7 @@ public class SelfGeneratedLicenseTests extends ESTestCase {
                 .maxNodes(5)
                 .issueDate(issueDate)
                 .expiryDate(issueDate + TimeValue.timeValueHours(2).getMillis());
-        License trialLicense = SelfGeneratedLicense.create(specBuilder);
+        License trialLicense = SelfGeneratedLicense.create(specBuilder, License.VERSION_CURRENT);
         final String originalSignature = trialLicense.signature();
         License tamperedLicense = License.builder().fromLicenseSpec(trialLicense, originalSignature)
                 .expiryDate(System.currentTimeMillis() + TimeValue.timeValueHours(5).getMillis())
@@ -70,7 +70,8 @@ public class SelfGeneratedLicenseTests extends ESTestCase {
                 .issueDate(issueDate)
                 .expiryDate(issueDate + TimeValue.timeValueHours(2).getMillis());
         License pre20TrialLicense = specBuilder.build();
-        License license = SelfGeneratedLicense.create(License.builder().fromPre20LicenseSpec(pre20TrialLicense).type("trial"));
+        License license = SelfGeneratedLicense.create(License.builder().fromPre20LicenseSpec(pre20TrialLicense).type("trial"),
+            License.VERSION_CURRENT);
         assertThat(SelfGeneratedLicense.verify(license), equalTo(true));
     }
 

+ 13 - 4
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java

@@ -7,7 +7,6 @@ package org.elasticsearch.xpack.security;
 
 import org.apache.logging.log4j.Logger;
 import org.apache.lucene.util.SetOnce;
-import org.elasticsearch.ElasticsearchTimeoutException;
 import org.elasticsearch.Version;
 import org.elasticsearch.action.ActionListener;
 import org.elasticsearch.action.ActionRequest;
@@ -17,7 +16,6 @@ import org.elasticsearch.action.support.DestructiveOperations;
 import org.elasticsearch.bootstrap.BootstrapCheck;
 import org.elasticsearch.client.Client;
 import org.elasticsearch.cluster.ClusterState;
-import org.elasticsearch.cluster.health.ClusterHealthStatus;
 import org.elasticsearch.cluster.metadata.IndexMetaData;
 import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
 import org.elasticsearch.cluster.metadata.IndexTemplateMetaData;
@@ -112,7 +110,6 @@ import org.elasticsearch.xpack.core.security.authc.AuthenticationServiceField;
 import org.elasticsearch.xpack.core.security.authc.DefaultAuthenticationFailureHandler;
 import org.elasticsearch.xpack.core.security.authc.Realm;
 import org.elasticsearch.xpack.core.security.authc.RealmSettings;
-import org.elasticsearch.xpack.core.security.authc.TokenMetaData;
 import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken;
 import org.elasticsearch.xpack.core.security.authz.AuthorizationServiceField;
 import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
@@ -934,7 +931,8 @@ public class Security extends Plugin implements ActionPlugin, IngestPlugin, Netw
         if (enabled) {
             return new ValidateTLSOnJoin(XPackSettings.TRANSPORT_SSL_ENABLED.get(settings),
                     DiscoveryModule.DISCOVERY_TYPE_SETTING.get(settings))
-                .andThen(new ValidateUpgradedSecurityIndex());
+                .andThen(new ValidateUpgradedSecurityIndex())
+                .andThen(new ValidateLicenseCanBeDeserialized());
         }
         return null;
     }
@@ -971,6 +969,17 @@ public class Security extends Plugin implements ActionPlugin, IngestPlugin, Netw
         }
     }
 
+    static final class ValidateLicenseCanBeDeserialized implements BiConsumer<DiscoveryNode, ClusterState> {
+        @Override
+        public void accept(DiscoveryNode node, ClusterState state) {
+            License license = LicenseService.getLicense(state.metaData());
+            if (license != null && license.version() >= License.VERSION_CRYPTO_ALGORITHMS && node.getVersion().before(Version.V_6_4_0)) {
+                throw new IllegalStateException("node " + node + " is on version [" + node.getVersion() +
+                    "] that cannot deserialize the license format [" + license.version() + "], upgrade node to at least 6.4.0");
+            }
+        }
+    }
+
     @Override
     public void reloadSPI(ClassLoader loader) {
         securityExtensions.addAll(SecurityExtension.loadExtensions(loader));

+ 15 - 1
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java

@@ -28,6 +28,7 @@ import org.elasticsearch.license.TestUtils;
 import org.elasticsearch.license.XPackLicenseState;
 import org.elasticsearch.plugins.MapperPlugin;
 import org.elasticsearch.test.ESTestCase;
+import org.elasticsearch.test.VersionUtils;
 import org.elasticsearch.threadpool.ThreadPool;
 import org.elasticsearch.watcher.ResourceWatcherService;
 import org.elasticsearch.xpack.core.XPackSettings;
@@ -278,6 +279,19 @@ public class SecurityTests extends ESTestCase {
         }
     }
 
+    public void testJoinValidatorForLicenseDeserialization() throws Exception {
+        DiscoveryNode node = new DiscoveryNode("foo", buildNewFakeTransportAddress(),
+            VersionUtils.randomVersionBetween(random(), null, Version.V_6_3_0));
+        MetaData.Builder builder = MetaData.builder();
+        License license = TestUtils.generateSignedLicense(null,
+            randomIntBetween(License.VERSION_CRYPTO_ALGORITHMS, License.VERSION_CURRENT), -1, TimeValue.timeValueHours(24));
+        TestUtils.putLicense(builder, license);
+        ClusterState state = ClusterState.builder(ClusterName.DEFAULT).metaData(builder.build()).build();
+        IllegalStateException e = expectThrows(IllegalStateException.class,
+            () -> new Security.ValidateLicenseCanBeDeserialized().accept(node, state));
+        assertThat(e.getMessage(), containsString("cannot deserialize the license format"));
+    }
+
     public void testIndexJoinValidator_Old_And_Rolling() throws Exception {
         createComponents(Settings.EMPTY);
         BiConsumer<DiscoveryNode, ClusterState> joinValidator = security.getJoinValidator();
@@ -345,7 +359,7 @@ public class SecurityTests extends ESTestCase {
             .nodes(discoveryNodes).build();
         joinValidator.accept(node, clusterState);
     }
-    
+
     public void testGetFieldFilterSecurityEnabled() throws Exception {
         createComponents(Settings.EMPTY);
         Function<String, Predicate<String>> fieldFilter = security.getFieldFilter();