diff --git a/core/src/main/java/org/elasticsearch/common/RandomBasedUUIDGenerator.java b/core/src/main/java/org/elasticsearch/common/RandomBasedUUIDGenerator.java index 9f5e5f34a1b37..970adfc03fb1c 100644 --- a/core/src/main/java/org/elasticsearch/common/RandomBasedUUIDGenerator.java +++ b/core/src/main/java/org/elasticsearch/common/RandomBasedUUIDGenerator.java @@ -19,8 +19,6 @@ package org.elasticsearch.common; - -import java.io.IOException; import java.util.Base64; import java.util.Random; @@ -32,7 +30,7 @@ class RandomBasedUUIDGenerator implements UUIDGenerator { */ @Override public String getBase64UUID() { - return getBase64UUID(SecureRandomHolder.INSTANCE); + return getBase64UUID(Randomness.getSecure()); } /** @@ -49,12 +47,13 @@ public String getBase64UUID(Random random) { * stamp (bits 4 through 7 of the time_hi_and_version field).*/ randomBytes[6] &= 0x0f; /* clear the 4 most significant bits for the version */ randomBytes[6] |= 0x40; /* set the version to 0100 / 0x40 */ - - /* Set the variant: + + /* Set the variant: * The high field of th clock sequence multiplexed with the variant. * We set only the MSB of the variant*/ randomBytes[8] &= 0x3f; /* clear the 2 most significant bits */ randomBytes[8] |= 0x80; /* set the variant (MSB is set)*/ return Base64.getUrlEncoder().withoutPadding().encodeToString(randomBytes); } + } diff --git a/core/src/main/java/org/elasticsearch/common/Randomness.java b/core/src/main/java/org/elasticsearch/common/Randomness.java index ddcd3ea90f7b1..bb700455be5b5 100644 --- a/core/src/main/java/org/elasticsearch/common/Randomness.java +++ b/core/src/main/java/org/elasticsearch/common/Randomness.java @@ -23,6 +23,9 @@ import org.elasticsearch.common.settings.Settings; import java.lang.reflect.Method; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.SecureRandom; import java.util.Collections; import java.util.List; import java.util.Random; @@ -44,6 +47,7 @@ * DiscoveryService#NODE_ID_SEED_SETTING)). */ public final class Randomness { + private static final Method currentMethod; private static final Method getRandomMethod; @@ -72,7 +76,7 @@ private Randomness() {} * @param setting the setting to access the seed * @return a reproducible source of randomness */ - public static Random get(Settings settings, Setting setting) { + public static Random get(final Settings settings, final Setting setting) { if (setting.exists(settings)) { return new Random(setting.get(settings)); } else { @@ -98,7 +102,7 @@ public static Random get(Settings settings, Setting setting) { public static Random get() { if (currentMethod != null && getRandomMethod != null) { try { - Object randomizedContext = currentMethod.invoke(null); + final Object randomizedContext = currentMethod.invoke(null); return (Random) getRandomMethod.invoke(randomizedContext); } catch (ReflectiveOperationException e) { // unexpected, bail @@ -109,13 +113,42 @@ public static Random get() { } } + /** + * Provides a source of randomness that is reproducible when + * running under the Elasticsearch test suite, and otherwise + * produces a non-reproducible source of secure randomness. + * Reproducible sources of randomness are created when the system + * property "tests.seed" is set and the security policy allows + * reading this system property. Otherwise, non-reproducible + * sources of secure randomness are created. + * + * @return a source of randomness + * @throws IllegalStateException if running tests but was not able + * to acquire an instance of Random from + * RandomizedContext or tests are + * running but tests.seed is not set + */ + public static Random getSecure() { + if (currentMethod != null && getRandomMethod != null) { + return get(); + } else { + return getSecureRandomWithoutSeed(); + } + } + @SuppressForbidden(reason = "ThreadLocalRandom is okay when not running tests") private static Random getWithoutSeed() { assert currentMethod == null && getRandomMethod == null : "running under tests but tried to create non-reproducible random"; return ThreadLocalRandom.current(); } - public static void shuffle(List list) { + private static SecureRandom getSecureRandomWithoutSeed() { + assert currentMethod == null && getRandomMethod == null : "running under tests but tried to create non-reproducible random"; + return SecureRandomHolder.INSTANCE; + } + + public static void shuffle(final List list) { Collections.shuffle(list, get()); } + } diff --git a/core/src/test/java/org/elasticsearch/cluster/ClusterChangedEventTests.java b/core/src/test/java/org/elasticsearch/cluster/ClusterChangedEventTests.java index 72748a5998689..7504c778d36e9 100644 --- a/core/src/test/java/org/elasticsearch/cluster/ClusterChangedEventTests.java +++ b/core/src/test/java/org/elasticsearch/cluster/ClusterChangedEventTests.java @@ -34,6 +34,7 @@ import org.elasticsearch.gateway.GatewayService; import org.elasticsearch.index.Index; import org.elasticsearch.test.ESTestCase; +import org.junit.BeforeClass; import java.util.ArrayList; import java.util.Arrays; @@ -54,11 +55,17 @@ public class ClusterChangedEventTests extends ESTestCase { private static final ClusterName TEST_CLUSTER_NAME = new ClusterName("test"); private static final String NODE_ID_PREFIX = "node_"; - private static final String INITIAL_CLUSTER_ID = UUIDs.randomBase64UUID(); - // the initial indices which every cluster state test starts out with - private static final List initialIndices = Arrays.asList(new Index("idx1", UUIDs.randomBase64UUID()), - new Index("idx2", UUIDs.randomBase64UUID()), - new Index("idx3", UUIDs.randomBase64UUID())); + private static String INITIAL_CLUSTER_ID; + private static List initialIndices; + + @BeforeClass + public static void beforeClass() { + INITIAL_CLUSTER_ID = UUIDs.randomBase64UUID(); + // the initial indices which every cluster state test starts out with + initialIndices = Arrays.asList(new Index("idx1", UUIDs.randomBase64UUID()), + new Index("idx2", UUIDs.randomBase64UUID()), + new Index("idx3", UUIDs.randomBase64UUID())); + } /** * Test basic properties of the ClusterChangedEvent class: diff --git a/core/src/test/java/org/elasticsearch/common/UUIDTests.java b/core/src/test/java/org/elasticsearch/common/UUIDTests.java index f82e1a464d91f..d963db2d6f6a0 100644 --- a/core/src/test/java/org/elasticsearch/common/UUIDTests.java +++ b/core/src/test/java/org/elasticsearch/common/UUIDTests.java @@ -20,7 +20,9 @@ import org.elasticsearch.test.ESTestCase; +import java.security.SecureRandom; import java.util.HashSet; +import java.util.Random; import java.util.Set; public class UUIDTests extends ESTestCase { @@ -41,7 +43,18 @@ public void testThreadedTimeUUID() { } public void testThreadedRandomUUID() { - testUUIDThreaded(randomUUIDGen); + // we can not use a reproducible source of randomness for this + // test, the test explicitly relies on each thread having a + // unique source of randomness; thus, we fake what production + // code does when using a RandomBasedUUIDGenerator + testUUIDThreaded(new RandomBasedUUIDGenerator() { + private final SecureRandom sr = SecureRandomHolder.INSTANCE; + + @Override + public String getBase64UUID() { + return getBase64UUID(sr); + } + }); } Set verifyUUIDSet(int count, UUIDGenerator uuidSource) { @@ -98,6 +111,6 @@ public void testUUIDThreaded(UUIDGenerator uuidSource) { for (UUIDGenRunner runner : runners) { globalSet.addAll(runner.uuidSet); } - assertEquals(count*uuids, globalSet.size()); + assertEquals(count * uuids, globalSet.size()); } } diff --git a/modules/reindex/src/test/java/org/elasticsearch/index/reindex/ReindexSameIndexTests.java b/modules/reindex/src/test/java/org/elasticsearch/index/reindex/ReindexSameIndexTests.java index f1218414af7d4..7bdb455cc33e8 100644 --- a/modules/reindex/src/test/java/org/elasticsearch/index/reindex/ReindexSameIndexTests.java +++ b/modules/reindex/src/test/java/org/elasticsearch/index/reindex/ReindexSameIndexTests.java @@ -32,6 +32,7 @@ import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.test.ESTestCase; +import org.junit.BeforeClass; import static org.hamcrest.Matchers.containsString; @@ -39,16 +40,22 @@ * Tests that indexing from an index back into itself fails the request. */ public class ReindexSameIndexTests extends ESTestCase { - private static final ClusterState STATE = ClusterState.builder(new ClusterName("test")).metaData(MetaData.builder() - .put(index("target", "target_alias", "target_multi"), true) - .put(index("target2", "target_multi"), true) - .put(index("foo"), true) - .put(index("bar"), true) - .put(index("baz"), true) - .put(index("source", "source_multi"), true) - .put(index("source2", "source_multi"), true)).build(); + + private static ClusterState STATE; private static final IndexNameExpressionResolver INDEX_NAME_EXPRESSION_RESOLVER = new IndexNameExpressionResolver(Settings.EMPTY); - private static final AutoCreateIndex AUTO_CREATE_INDEX = new AutoCreateIndex(Settings.EMPTY, INDEX_NAME_EXPRESSION_RESOLVER); + private static AutoCreateIndex AUTO_CREATE_INDEX = new AutoCreateIndex(Settings.EMPTY, INDEX_NAME_EXPRESSION_RESOLVER); + + @BeforeClass + public static void beforeClass() { + STATE = ClusterState.builder(new ClusterName("test")).metaData(MetaData.builder() + .put(index("target", "target_alias", "target_multi"), true) + .put(index("target2", "target_multi"), true) + .put(index("foo"), true) + .put(index("bar"), true) + .put(index("baz"), true) + .put(index("source", "source_multi"), true) + .put(index("source2", "source_multi"), true)).build(); + } public void testObviousCases() throws Exception { fails("target", "target");