浏览代码

Settings: Add secure settings validation on startup (#22894)

Secure settings from the elasticsearch keystore were not yet validated.
This changed improves support in Settings so that secure settings more
seamlessly blend in with normal settings, allowing the existing settings
validation to work. Note that the setting names are still not validated
(yet) when using the elasticsearc-keystore tool.
Ryan Ernst 8 年之前
父节点
当前提交
470ad1ae4a

+ 10 - 4
core/src/main/java/org/elasticsearch/common/settings/AbstractScopedSettings.java

@@ -57,7 +57,7 @@ public abstract class AbstractScopedSettings extends AbstractComponent {
     private final Setting.Property scope;
     private static final Pattern KEY_PATTERN = Pattern.compile("^(?:[-\\w]+[.])*[-\\w]+$");
     private static final Pattern GROUP_KEY_PATTERN = Pattern.compile("^(?:[-\\w]+[.])+$");
-    private static final Pattern AFFIX_KEY_PATTERN = Pattern.compile("^(?:[-\\w]+[.])+(?:[*][.])+[-\\w]+$");
+    private static final Pattern AFFIX_KEY_PATTERN = Pattern.compile("^(?:[-\\w]+[.])+[*](?:[.][-\\w]+)+$");
 
     protected AbstractScopedSettings(Settings settings, Set<Setting<?>> settingsSet, Setting.Property scope) {
         super(settings);
@@ -113,7 +113,8 @@ public abstract class AbstractScopedSettings extends AbstractComponent {
         return GROUP_KEY_PATTERN.matcher(key).matches();
     }
 
-    private static boolean isValidAffixKey(String key) {
+    // pkg private for tests
+    static boolean isValidAffixKey(String key) {
         return AFFIX_KEY_PATTERN.matcher(key).matches();
     }
 
@@ -252,7 +253,7 @@ public abstract class AbstractScopedSettings extends AbstractComponent {
      */
     public final void validate(Settings settings) {
         List<RuntimeException> exceptions = new ArrayList<>();
-        for (String key : settings.getAsMap().keySet()) { // settings iterate in deterministic fashion
+        for (String key : settings.keySet()) { // settings iterate in deterministic fashion
             try {
                 validate(key, settings);
             } catch (RuntimeException ex) {
@@ -278,7 +279,12 @@ public abstract class AbstractScopedSettings extends AbstractComponent {
                 }
             }
             CollectionUtil.timSort(scoredKeys, (a,b) -> b.v1().compareTo(a.v1()));
-            String msg = "unknown setting [" + key + "]";
+            String msgPrefix = "unknown setting";
+            SecureSettings secureSettings = settings.getSecureSettings();
+            if (secureSettings != null && settings.getSecureSettings().getSettingNames().contains(key)) {
+                msgPrefix = "unknown secure setting";
+            }
+            String msg = msgPrefix + " [" + key + "]";
             List<String> keys = scoredKeys.stream().map((a) -> a.v2()).collect(Collectors.toList());
             if (keys.isEmpty() == false) {
                 msg += " did you mean " + (keys.size() == 1 ? "[" + keys.get(0) + "]": "any of " + keys.toString()) + "?";

+ 1 - 1
core/src/main/java/org/elasticsearch/common/settings/AddStringKeyStoreCommand.java

@@ -67,7 +67,7 @@ class AddStringKeyStoreCommand extends EnvironmentAwareCommand {
         if (setting == null) {
             throw new UserException(ExitCodes.USAGE, "The setting name can not be null");
         }
-        if (keystore.getSettings().contains(setting) && options.has(forceOption) == false) {
+        if (keystore.getSettingNames().contains(setting) && options.has(forceOption) == false) {
             if (terminal.promptYesNo("Setting " + setting + " already exists. Overwrite?", false) == false) {
                 terminal.println("Exiting without modifying keystore.");
                 return;

+ 2 - 6
core/src/main/java/org/elasticsearch/common/settings/KeyStoreWrapper.java

@@ -226,13 +226,9 @@ public class KeyStoreWrapper implements SecureSettings {
         }
     }
 
-    public Set<String> getSettings() {
-        return settingNames;
-    }
-
     @Override
-    public boolean hasSetting(String setting) {
-        return settingNames.contains(setting);
+    public Set<String> getSettingNames() {
+        return settingNames;
     }
 
     // TODO: make settings accessible only to code that registered the setting

+ 1 - 1
core/src/main/java/org/elasticsearch/common/settings/ListKeyStoreCommand.java

@@ -49,7 +49,7 @@ class ListKeyStoreCommand extends EnvironmentAwareCommand {
 
         keystore.decrypt(new char[0] /* TODO: prompt for password when they are supported */);
 
-        List<String> sortedEntries = new ArrayList<>(keystore.getSettings());
+        List<String> sortedEntries = new ArrayList<>(keystore.getSettingNames());
         Collections.sort(sortedEntries);
         for (String entry : sortedEntries) {
             terminal.println(entry);

+ 1 - 1
core/src/main/java/org/elasticsearch/common/settings/RemoveSettingKeyStoreCommand.java

@@ -56,7 +56,7 @@ class RemoveSettingKeyStoreCommand extends EnvironmentAwareCommand {
         keystore.decrypt(new char[0] /* TODO: prompt for password when they are supported */);
 
         for (String setting : arguments.values(options)) {
-            if (keystore.getSettings().contains(setting) == false) {
+            if (keystore.getSettingNames().contains(setting) == false) {
                 throw new UserException(ExitCodes.CONFIG, "Setting [" + setting + "] does not exist in the keystore.");
             }
             keystore.remove(setting);

+ 2 - 2
core/src/main/java/org/elasticsearch/common/settings/SecureSetting.java

@@ -79,14 +79,14 @@ public abstract class SecureSetting<T> extends Setting<T> {
     @Override
     public boolean exists(Settings settings) {
         final SecureSettings secureSettings = settings.getSecureSettings();
-        return secureSettings != null && secureSettings.hasSetting(getKey());
+        return secureSettings != null && secureSettings.getSettingNames().contains(getKey());
     }
 
     @Override
     public T get(Settings settings) {
         checkDeprecation(settings);
         final SecureSettings secureSettings = settings.getSecureSettings();
-        if (secureSettings == null || secureSettings.hasSetting(getKey()) == false) {
+        if (secureSettings == null || secureSettings.getSettingNames().contains(getKey()) == false) {
             return getFallback(settings);
         }
         try {

+ 3 - 2
core/src/main/java/org/elasticsearch/common/settings/SecureSettings.java

@@ -21,6 +21,7 @@ package org.elasticsearch.common.settings;
 
 import java.io.Closeable;
 import java.security.GeneralSecurityException;
+import java.util.Set;
 
 /**
  * An accessor for settings which are securely stored. See {@link SecureSetting}.
@@ -30,8 +31,8 @@ public interface SecureSettings extends Closeable {
     /** Returns true iff the settings are loaded and retrievable. */
     boolean isLoaded();
 
-    /** Returns true iff the given setting exists in this secure settings. */
-    boolean hasSetting(String setting);
+    /** Returns the names of all secure settings available. */
+    Set<String> getSettingNames();
 
     /** Return a string setting. The {@link SecureString} should be closed once it is used. */
     SecureString getString(String setting) throws GeneralSecurityException;

+ 68 - 23
core/src/main/java/org/elasticsearch/common/settings/Settings.java

@@ -65,8 +65,11 @@ import java.util.TreeMap;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Function;
 import java.util.function.Predicate;
+import java.util.function.UnaryOperator;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import static org.elasticsearch.common.unit.ByteSizeValue.parseBytesSizeValue;
 import static org.elasticsearch.common.unit.SizeValue.parseSizeValue;
@@ -81,10 +84,19 @@ public final class Settings implements ToXContent {
     private static final Pattern ARRAY_PATTERN = Pattern.compile("(.*)\\.\\d+$");
 
     /** The raw settings from the full key to raw string value. */
-    private Map<String, String> settings;
+    private final Map<String, String> settings;
 
     /** The secure settings storage associated with these settings. */
-    private SecureSettings secureSettings;
+    private final SecureSettings secureSettings;
+
+    /** The first level of setting names. This is constructed lazily in {@link #names()}. */
+    private final SetOnce<Set<String>> firstLevelNames = new SetOnce<>();
+
+    /**
+     * Setting names found in this Settings for both string and secure settings.
+     * This is constructed lazily in {@link #keySet()}.
+     */
+    private final SetOnce<Set<String>> keys = new SetOnce<>();
 
     Settings(Map<String, String> settings, SecureSettings secureSettings) {
         // we use a sorted map for consistent serialization when using getAsMap()
@@ -205,16 +217,16 @@ public final class Settings implements ToXContent {
      * A settings that are filtered (and key is removed) with the specified prefix.
      */
     public Settings getByPrefix(String prefix) {
-        return new Settings(new FilteredMap(this.settings, (k) -> k.startsWith(prefix), prefix),
-            secureSettings == null ? null : new PrefixedSecureSettings(secureSettings, prefix));
+        return new Settings(new FilteredMap(this.settings, (k) -> k.startsWith(prefix), prefix), secureSettings == null ? null :
+            new PrefixedSecureSettings(secureSettings, s -> prefix + s, s -> s.startsWith(prefix)));
     }
 
     /**
      * Returns a new settings object that contains all setting of the current one filtered by the given settings key predicate.
-     * Secure settings may not be accessed through a filter.
      */
     public Settings filter(Predicate<String> predicate) {
-        return new Settings(new FilteredMap(this.settings, predicate, null), null);
+        return new Settings(new FilteredMap(this.settings, predicate, null), secureSettings == null ? null :
+            new PrefixedSecureSettings(secureSettings, UnaryOperator.identity(), predicate));
     }
 
     /**
@@ -540,16 +552,24 @@ public final class Settings implements ToXContent {
      * @return  The direct keys of this settings
      */
     public Set<String> names() {
-        Set<String> names = new HashSet<>();
-        for (String key : settings.keySet()) {
-            int i = key.indexOf(".");
-            if (i < 0) {
-                names.add(key);
-            } else {
-                names.add(key.substring(0, i));
+        synchronized (firstLevelNames) {
+            if (firstLevelNames.get() == null) {
+                Stream<String> stream = settings.keySet().stream();
+                if (secureSettings != null) {
+                    stream = Stream.concat(stream, secureSettings.getSettingNames().stream());
+                }
+                Set<String> names = stream.map(k -> {
+                    int i = k.indexOf('.');
+                    if (i < 0) {
+                        return k;
+                    } else {
+                        return k.substring(0, i);
+                    }
+                }).collect(Collectors.toSet());
+                firstLevelNames.set(Collections.unmodifiableSet(names));
             }
         }
-        return names;
+        return firstLevelNames.get();
     }
 
     /**
@@ -626,12 +646,28 @@ public final class Settings implements ToXContent {
      * @return <tt>true</tt> if this settings object contains no settings
      */
     public boolean isEmpty() {
-        return this.settings.isEmpty(); // TODO: account for secure settings
+        return this.settings.isEmpty() && (secureSettings == null || secureSettings.getSettingNames().isEmpty());
     }
 
     /** Returns the number of settings in this settings object. */
     public int size() {
-        return this.settings.size(); // TODO: account for secure settings
+        return keySet().size();
+    }
+
+    /** Returns the fully qualified setting names contained in this settings object. */
+    public Set<String> keySet() {
+        synchronized (keys) {
+            if (keys.get() == null) {
+                if (secureSettings == null) {
+                    keys.set(settings.keySet());
+                } else {
+                    Stream<String> stream = Stream.concat(settings.keySet().stream(), secureSettings.getSettingNames().stream());
+                    // uniquify, since for legacy reasons the same setting name may exist in both
+                    keys.set(Collections.unmodifiableSet(stream.collect(Collectors.toSet())));
+                }
+            }
+        }
+        return keys.get();
     }
 
     /**
@@ -1226,12 +1262,15 @@ public final class Settings implements ToXContent {
     }
 
     private static class PrefixedSecureSettings implements SecureSettings {
-        private SecureSettings delegate;
-        private String prefix;
+        private final SecureSettings delegate;
+        private final UnaryOperator<String> keyTransform;
+        private final Predicate<String> keyPredicate;
+        private final SetOnce<Set<String>> settingNames = new SetOnce<>();
 
-        PrefixedSecureSettings(SecureSettings delegate, String prefix) {
+        PrefixedSecureSettings(SecureSettings delegate, UnaryOperator<String> keyTransform, Predicate<String> keyPredicate) {
             this.delegate = delegate;
-            this.prefix = prefix;
+            this.keyTransform = keyTransform;
+            this.keyPredicate = keyPredicate;
         }
 
         @Override
@@ -1240,13 +1279,19 @@ public final class Settings implements ToXContent {
         }
 
         @Override
-        public boolean hasSetting(String setting) {
-            return delegate.hasSetting(prefix + setting);
+        public Set<String> getSettingNames() {
+            synchronized (settingNames) {
+                if (settingNames.get() == null) {
+                    Set<String> names = delegate.getSettingNames().stream().filter(keyPredicate).collect(Collectors.toSet());
+                    settingNames.set(Collections.unmodifiableSet(names));
+                }
+            }
+            return settingNames.get();
         }
 
         @Override
         public SecureString getString(String setting) throws GeneralSecurityException{
-            return delegate.getString(prefix + setting);
+            return delegate.getString(keyTransform.apply(setting));
         }
 
         @Override

+ 2 - 2
core/src/test/java/org/elasticsearch/common/settings/RemoveSettingKeyStoreCommandTests.java

@@ -68,13 +68,13 @@ public class RemoveSettingKeyStoreCommandTests extends KeyStoreCommandTestCase {
     public void testOne() throws Exception {
         createKeystore("", "foo", "bar");
         execute("foo");
-        assertFalse(loadKeystore("").getSettings().contains("foo"));
+        assertFalse(loadKeystore("").getSettingNames().contains("foo"));
     }
 
     public void testMany() throws Exception {
         createKeystore("", "foo", "1", "bar", "2", "baz", "3");
         execute("foo", "baz");
-        Set<String> settings = loadKeystore("").getSettings();
+        Set<String> settings = loadKeystore("").getSettingNames();
         assertFalse(settings.contains("foo"));
         assertFalse(settings.contains("baz"));
         assertTrue(settings.contains("bar"));

+ 42 - 30
core/src/test/java/org/elasticsearch/common/settings/ScopedSettingsTests.java

@@ -35,6 +35,7 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.IllegalFormatCodePointException;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
@@ -45,6 +46,7 @@ import java.util.function.BiConsumer;
 import java.util.function.Function;
 
 import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.startsWith;
 
 public class ScopedSettingsTests extends ESTestCase {
 
@@ -402,40 +404,39 @@ public class ScopedSettingsTests extends ESTestCase {
             " removed settings";
         settings.validate(Settings.builder().put("index.store.type", "boom"));
         settings.validate(Settings.builder().put("index.store.type", "boom").build());
-        try {
-            settings.validate(Settings.builder().put("index.store.type", "boom", "i.am.not.a.setting", true));
-            fail();
-        } catch (IllegalArgumentException e) {
-            assertEquals("unknown setting [i.am.not.a.setting]" + unknownMsgSuffix, e.getMessage());
-        }
+        IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () ->
+            settings.validate(Settings.builder().put("index.store.type", "boom", "i.am.not.a.setting", true)));
+        assertEquals("unknown setting [i.am.not.a.setting]" + unknownMsgSuffix, e.getMessage());
 
-        try {
-            settings.validate(Settings.builder().put("index.store.type", "boom", "i.am.not.a.setting", true).build());
-            fail();
-        } catch (IllegalArgumentException e) {
-            assertEquals("unknown setting [i.am.not.a.setting]" + unknownMsgSuffix, e.getMessage());
-        }
+        e = expectThrows(IllegalArgumentException.class, () ->
+            settings.validate(Settings.builder().put("index.store.type", "boom", "i.am.not.a.setting", true).build()));
+        assertEquals("unknown setting [i.am.not.a.setting]" + unknownMsgSuffix, e.getMessage());
 
-        try {
-            settings.validate(Settings.builder().put("index.store.type", "boom", "index.number_of_replicas", true).build());
-            fail();
-        } catch (IllegalArgumentException e) {
-            assertEquals("Failed to parse value [true] for setting [index.number_of_replicas]", e.getMessage());
-        }
+        e = expectThrows(IllegalArgumentException.class, () ->
+            settings.validate(Settings.builder().put("index.store.type", "boom", "index.number_of_replicas", true).build()));
+        assertEquals("Failed to parse value [true] for setting [index.number_of_replicas]", e.getMessage());
 
-        try {
-            settings.validate("index.number_of_replicas", Settings.builder().put("index.number_of_replicas", "true").build());
-            fail();
-        } catch (IllegalArgumentException e) {
-            assertEquals("Failed to parse value [true] for setting [index.number_of_replicas]", e.getMessage());
-        }
+        e = expectThrows(IllegalArgumentException.class, () ->
+            settings.validate("index.number_of_replicas", Settings.builder().put("index.number_of_replicas", "true").build()));
+        assertEquals("Failed to parse value [true] for setting [index.number_of_replicas]", e.getMessage());
 
-        try {
-            settings.validate("index.similarity.classic.type", Settings.builder().put("index.similarity.classic.type", "mine").build());
-            fail();
-        } catch (IllegalArgumentException e) {
-            assertEquals("illegal value for [index.similarity.classic] cannot redefine built-in similarity", e.getMessage());
-        }
+        e = expectThrows(IllegalArgumentException.class, () ->
+            settings.validate("index.similarity.classic.type", Settings.builder().put("index.similarity.classic.type", "mine").build()));
+        assertEquals("illegal value for [index.similarity.classic] cannot redefine built-in similarity", e.getMessage());
+    }
+
+    public void testValidateSecureSettings() {
+        MockSecureSettings secureSettings = new MockSecureSettings();
+        secureSettings.setString("some.secure.setting", "secret");
+        Settings settings = Settings.builder().setSecureSettings(secureSettings).build();
+        final ClusterSettings clusterSettings = new ClusterSettings(settings, Collections.emptySet());
+
+        IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> clusterSettings.validate(settings));
+        assertThat(e.getMessage(), startsWith("unknown secure setting [some.secure.setting]"));
+
+        ClusterSettings clusterSettings2 = new ClusterSettings(settings,
+            Collections.singleton(SecureSetting.secureString("some.secure.setting", null, false)));
+        clusterSettings2.validate(settings);
     }
 
 
@@ -497,6 +498,17 @@ public class ScopedSettingsTests extends ESTestCase {
             Settings.EMPTY, Collections.singleton(Setting.boolSetting("index.boo", true, Property.NodeScope)));
     }
 
+    public void testAffixKeyPattern() {
+        assertTrue(AbstractScopedSettings.isValidAffixKey("prefix.*.suffix"));
+        assertTrue(AbstractScopedSettings.isValidAffixKey("prefix.*.split.suffix"));
+        assertTrue(AbstractScopedSettings.isValidAffixKey("split.prefix.*.split.suffix"));
+        assertFalse(AbstractScopedSettings.isValidAffixKey("prefix.*.suffix."));
+        assertFalse(AbstractScopedSettings.isValidAffixKey("prefix.*"));
+        assertFalse(AbstractScopedSettings.isValidAffixKey("*.suffix"));
+        assertFalse(AbstractScopedSettings.isValidAffixKey("*"));
+        assertFalse(AbstractScopedSettings.isValidAffixKey(""));
+    }
+
     public void testLoggingUpdates() {
         final Level level = ESLoggerFactory.getRootLogger().getLevel();
         final Level testLevel = ESLoggerFactory.getLogger("test").getLevel();

+ 6 - 0
core/src/test/java/org/elasticsearch/common/settings/SettingsTests.java

@@ -549,4 +549,10 @@ public class SettingsTests extends ESTestCase {
         }
         expectThrows(NoSuchElementException.class, () -> iterator.next());
     }
+
+    public void testEmpty() {
+        assertTrue(Settings.EMPTY.isEmpty());
+        MockSecureSettings secureSettings = new MockSecureSettings();
+        assertTrue(Settings.builder().setSecureSettings(secureSettings).build().isEmpty());
+    }
 }

+ 49 - 37
plugins/repository-s3/src/main/java/org/elasticsearch/plugin/repository/s3/S3RepositoryPlugin.java

@@ -81,44 +81,56 @@ public class S3RepositoryPlugin extends Plugin implements RepositoryPlugin {
     @Override
     public List<Setting<?>> getSettings() {
         return Arrays.asList(
-        // Register global cloud aws settings: cloud.aws (might have been registered in ec2 plugin)
-        AwsS3Service.KEY_SETTING,
-        AwsS3Service.SECRET_SETTING,
-        AwsS3Service.PROTOCOL_SETTING,
-        AwsS3Service.PROXY_HOST_SETTING,
-        AwsS3Service.PROXY_PORT_SETTING,
-        AwsS3Service.PROXY_USERNAME_SETTING,
-        AwsS3Service.PROXY_PASSWORD_SETTING,
-        AwsS3Service.SIGNER_SETTING,
-        AwsS3Service.READ_TIMEOUT,
 
-        // Register S3 specific settings: cloud.aws.s3
-        AwsS3Service.CLOUD_S3.KEY_SETTING,
-        AwsS3Service.CLOUD_S3.SECRET_SETTING,
-        AwsS3Service.CLOUD_S3.PROTOCOL_SETTING,
-        AwsS3Service.CLOUD_S3.PROXY_HOST_SETTING,
-        AwsS3Service.CLOUD_S3.PROXY_PORT_SETTING,
-        AwsS3Service.CLOUD_S3.PROXY_USERNAME_SETTING,
-        AwsS3Service.CLOUD_S3.PROXY_PASSWORD_SETTING,
-        AwsS3Service.CLOUD_S3.SIGNER_SETTING,
-        AwsS3Service.CLOUD_S3.ENDPOINT_SETTING,
-        AwsS3Service.CLOUD_S3.READ_TIMEOUT,
+            // named s3 client configuration settings
+            S3Repository.ACCESS_KEY_SETTING,
+            S3Repository.SECRET_KEY_SETTING,
+            S3Repository.ENDPOINT_SETTING,
+            S3Repository.PROTOCOL_SETTING,
+            S3Repository.PROXY_HOST_SETTING,
+            S3Repository.PROXY_PORT_SETTING,
+            S3Repository.PROXY_USERNAME_SETTING,
+            S3Repository.PROXY_PASSWORD_SETTING,
+            S3Repository.READ_TIMEOUT_SETTING,
 
-        // Register S3 repositories settings: repositories.s3
-        S3Repository.Repositories.KEY_SETTING,
-        S3Repository.Repositories.SECRET_SETTING,
-        S3Repository.Repositories.BUCKET_SETTING,
-        S3Repository.Repositories.ENDPOINT_SETTING,
-        S3Repository.Repositories.PROTOCOL_SETTING,
-        S3Repository.Repositories.SERVER_SIDE_ENCRYPTION_SETTING,
-        S3Repository.Repositories.BUFFER_SIZE_SETTING,
-        S3Repository.Repositories.MAX_RETRIES_SETTING,
-        S3Repository.Repositories.CHUNK_SIZE_SETTING,
-        S3Repository.Repositories.COMPRESS_SETTING,
-        S3Repository.Repositories.STORAGE_CLASS_SETTING,
-        S3Repository.Repositories.CANNED_ACL_SETTING,
-        S3Repository.Repositories.BASE_PATH_SETTING,
-        S3Repository.Repositories.USE_THROTTLE_RETRIES_SETTING,
-        S3Repository.Repositories.PATH_STYLE_ACCESS_SETTING);
+            // Register global cloud aws settings: cloud.aws (might have been registered in ec2 plugin)
+            AwsS3Service.KEY_SETTING,
+            AwsS3Service.SECRET_SETTING,
+            AwsS3Service.PROTOCOL_SETTING,
+            AwsS3Service.PROXY_HOST_SETTING,
+            AwsS3Service.PROXY_PORT_SETTING,
+            AwsS3Service.PROXY_USERNAME_SETTING,
+            AwsS3Service.PROXY_PASSWORD_SETTING,
+            AwsS3Service.SIGNER_SETTING,
+            AwsS3Service.READ_TIMEOUT,
+
+            // Register S3 specific settings: cloud.aws.s3
+            AwsS3Service.CLOUD_S3.KEY_SETTING,
+            AwsS3Service.CLOUD_S3.SECRET_SETTING,
+            AwsS3Service.CLOUD_S3.PROTOCOL_SETTING,
+            AwsS3Service.CLOUD_S3.PROXY_HOST_SETTING,
+            AwsS3Service.CLOUD_S3.PROXY_PORT_SETTING,
+            AwsS3Service.CLOUD_S3.PROXY_USERNAME_SETTING,
+            AwsS3Service.CLOUD_S3.PROXY_PASSWORD_SETTING,
+            AwsS3Service.CLOUD_S3.SIGNER_SETTING,
+            AwsS3Service.CLOUD_S3.ENDPOINT_SETTING,
+            AwsS3Service.CLOUD_S3.READ_TIMEOUT,
+
+            // Register S3 repositories settings: repositories.s3
+            S3Repository.Repositories.KEY_SETTING,
+            S3Repository.Repositories.SECRET_SETTING,
+            S3Repository.Repositories.BUCKET_SETTING,
+            S3Repository.Repositories.ENDPOINT_SETTING,
+            S3Repository.Repositories.PROTOCOL_SETTING,
+            S3Repository.Repositories.SERVER_SIDE_ENCRYPTION_SETTING,
+            S3Repository.Repositories.BUFFER_SIZE_SETTING,
+            S3Repository.Repositories.MAX_RETRIES_SETTING,
+            S3Repository.Repositories.CHUNK_SIZE_SETTING,
+            S3Repository.Repositories.COMPRESS_SETTING,
+            S3Repository.Repositories.STORAGE_CLASS_SETTING,
+            S3Repository.Repositories.CANNED_ACL_SETTING,
+            S3Repository.Repositories.BASE_PATH_SETTING,
+            S3Repository.Repositories.USE_THROTTLE_RETRIES_SETTING,
+            S3Repository.Repositories.PATH_STYLE_ACCESS_SETTING);
     }
 }

+ 3 - 2
test/framework/src/main/java/org/elasticsearch/common/settings/MockSecureSettings.java

@@ -22,6 +22,7 @@ package org.elasticsearch.common.settings;
 import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * A mock implementation of secure settings for tests to use.
@@ -36,8 +37,8 @@ public class MockSecureSettings implements SecureSettings {
     }
 
     @Override
-    public boolean hasSetting(String setting) {
-        return secureStrings.containsKey(setting);
+    public Set<String> getSettingNames() {
+        return secureStrings.keySet();
     }
 
     @Override