diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/bootstrap/EntitlementBootstrap.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/bootstrap/EntitlementBootstrap.java index 364c81bf2d263..17885fb103077 100644 --- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/bootstrap/EntitlementBootstrap.java +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/bootstrap/EntitlementBootstrap.java @@ -28,6 +28,7 @@ import java.nio.file.Path; import java.util.Map; import java.util.function.Function; +import java.util.stream.Stream; import static java.util.Objects.requireNonNull; @@ -36,19 +37,24 @@ public class EntitlementBootstrap { public record BootstrapArgs( Map pluginPolicies, Function, String> pluginResolver, + Function settingResolver, + Function> settingGlobResolver, Path[] dataDirs, Path configDir, - Path tempDir, - Path logsDir + Path logsDir, + Path tempDir ) { public BootstrapArgs { requireNonNull(pluginPolicies); requireNonNull(pluginResolver); + requireNonNull(settingResolver); + requireNonNull(settingGlobResolver); requireNonNull(dataDirs); if (dataDirs.length == 0) { throw new IllegalArgumentException("must provide at least one data directory"); } requireNonNull(configDir); + requireNonNull(logsDir); requireNonNull(tempDir); } } @@ -73,16 +79,27 @@ public static BootstrapArgs bootstrapArgs() { public static void bootstrap( Map pluginPolicies, Function, String> pluginResolver, + Function settingResolver, + Function> settingGlobResolver, Path[] dataDirs, Path configDir, - Path tempDir, - Path logsDir + Path logsDir, + Path tempDir ) { logger.debug("Loading entitlement agent"); if (EntitlementBootstrap.bootstrapArgs != null) { throw new IllegalStateException("plugin data is already set"); } - EntitlementBootstrap.bootstrapArgs = new BootstrapArgs(pluginPolicies, pluginResolver, dataDirs, configDir, tempDir, logsDir); + EntitlementBootstrap.bootstrapArgs = new BootstrapArgs( + pluginPolicies, + pluginResolver, + settingResolver, + settingGlobResolver, + dataDirs, + configDir, + logsDir, + tempDir + ); exportInitializationToAgent(); loadAgent(findAgentJar()); selfTest(); diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/EntitlementInitialization.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/EntitlementInitialization.java index aee731cdd7622..0e501b7107a71 100644 --- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/EntitlementInitialization.java +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/EntitlementInitialization.java @@ -135,8 +135,14 @@ private static Class[] findClassesToRetransform(Class[] loadedClasses, Set private static PolicyManager createPolicyManager() { EntitlementBootstrap.BootstrapArgs bootstrapArgs = EntitlementBootstrap.bootstrapArgs(); Map pluginPolicies = bootstrapArgs.pluginPolicies(); - var pathLookup = new PathLookup(getUserHome(), bootstrapArgs.configDir(), bootstrapArgs.dataDirs(), bootstrapArgs.tempDir()); - Path logsDir = EntitlementBootstrap.bootstrapArgs().logsDir(); + var pathLookup = new PathLookup( + getUserHome(), + bootstrapArgs.configDir(), + bootstrapArgs.dataDirs(), + bootstrapArgs.tempDir(), + bootstrapArgs.settingResolver(), + bootstrapArgs.settingGlobResolver() + ); List serverScopes = new ArrayList<>(); Collections.addAll( diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTree.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTree.java index 46ee46c7b30c5..98076af51ae60 100644 --- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTree.java +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTree.java @@ -42,8 +42,9 @@ private FileAccessTree(FilesEntitlement filesEntitlement, PathLookup pathLookup) } // everything has access to the temp dir - readPaths.add(pathLookup.tempDir().toString()); - writePaths.add(pathLookup.tempDir().toString()); + String tempDir = normalizePath(pathLookup.tempDir()); + readPaths.add(tempDir); + writePaths.add(tempDir); readPaths.sort(String::compareTo); writePaths.sort(String::compareTo); diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PathLookup.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PathLookup.java index 5790e7245aad9..6f6220e21546e 100644 --- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PathLookup.java +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PathLookup.java @@ -10,5 +10,14 @@ package org.elasticsearch.entitlement.runtime.policy; import java.nio.file.Path; +import java.util.function.Function; +import java.util.stream.Stream; -public record PathLookup(Path homeDir, Path configDir, Path[] dataDirs, Path tempDir) {} +public record PathLookup( + Path homeDir, + Path configDir, + Path[] dataDirs, + Path tempDir, + Function settingResolver, + Function> settingGlobResolver +) {} diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/entitlements/FilesEntitlement.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/entitlements/FilesEntitlement.java index 3859de2dc2f9e..21b54ba51ca87 100644 --- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/entitlements/FilesEntitlement.java +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/entitlements/FilesEntitlement.java @@ -15,7 +15,6 @@ import java.nio.file.Path; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -53,33 +52,81 @@ static FileData ofPath(Path path, Mode mode) { static FileData ofRelativePath(Path relativePath, BaseDir baseDir, Mode mode) { return new RelativePathFileData(relativePath, baseDir, mode); } - } - private record AbsolutePathFileData(Path path, Mode mode) implements FileData { - @Override - public Stream resolvePaths(PathLookup pathLookup) { - return Stream.of(path); + static FileData ofPathSetting(String setting, Mode mode) { + return new PathSettingFileData(setting, mode); + } + + static FileData ofRelativePathSetting(String setting, BaseDir baseDir, Mode mode) { + return new RelativePathSettingFileData(setting, baseDir, mode); } } - private record RelativePathFileData(Path relativePath, BaseDir baseDir, Mode mode) implements FileData { + private sealed interface RelativeFileData extends FileData { + BaseDir baseDir(); + + Stream resolveRelativePaths(PathLookup pathLookup); @Override - public Stream resolvePaths(PathLookup pathLookup) { + default Stream resolvePaths(PathLookup pathLookup) { Objects.requireNonNull(pathLookup); - switch (baseDir) { + var relativePaths = resolveRelativePaths(pathLookup); + switch (baseDir()) { case CONFIG: - return Stream.of(pathLookup.configDir().resolve(relativePath)); + return relativePaths.map(relativePath -> pathLookup.configDir().resolve(relativePath)); case DATA: - return Arrays.stream(pathLookup.dataDirs()).map(d -> d.resolve(relativePath)); + // multiple data dirs are a pain...we need the combination of relative paths and data dirs + List paths = new ArrayList<>(); + for (var relativePath : relativePaths.toList()) { + for (var dataDir : pathLookup.dataDirs()) { + paths.add(dataDir.resolve(relativePath)); + } + } + return paths.stream(); case HOME: - return Stream.of(pathLookup.homeDir().resolve(relativePath)); + return relativePaths.map(relativePath -> pathLookup.homeDir().resolve(relativePath)); default: throw new IllegalArgumentException(); } } } + private record AbsolutePathFileData(Path path, Mode mode) implements FileData { + @Override + public Stream resolvePaths(PathLookup pathLookup) { + return Stream.of(path); + } + } + + private record RelativePathFileData(Path relativePath, BaseDir baseDir, Mode mode) implements FileData, RelativeFileData { + @Override + public Stream resolveRelativePaths(PathLookup pathLookup) { + return Stream.of(relativePath); + } + } + + private record PathSettingFileData(String setting, Mode mode) implements FileData { + @Override + public Stream resolvePaths(PathLookup pathLookup) { + return resolvePathSettings(pathLookup, setting); + } + } + + private record RelativePathSettingFileData(String setting, BaseDir baseDir, Mode mode) implements FileData, RelativeFileData { + @Override + public Stream resolveRelativePaths(PathLookup pathLookup) { + return resolvePathSettings(pathLookup, setting); + } + } + + private static Stream resolvePathSettings(PathLookup pathLookup, String setting) { + if (setting.contains("*")) { + return pathLookup.settingGlobResolver().apply(setting).map(Path::of); + } + String path = pathLookup.settingResolver().apply(setting); + return path == null ? Stream.of() : Stream.of(Path.of(path)); + } + private static Mode parseMode(String mode) { if (mode.equals("read")) { return Mode.READ; @@ -113,37 +160,56 @@ public static FilesEntitlement build(List paths) { String pathAsString = file.remove("path"); String relativePathAsString = file.remove("relative_path"); String relativeTo = file.remove("relative_to"); - String mode = file.remove("mode"); + String pathSetting = file.remove("path_setting"); + String relativePathSetting = file.remove("relative_path_setting"); + String modeAsString = file.remove("mode"); if (file.isEmpty() == false) { throw new PolicyValidationException("unknown key(s) [" + file + "] in a listed file for files entitlement"); } - if (mode == null) { + int foundKeys = (pathAsString != null ? 1 : 0) + (relativePathAsString != null ? 1 : 0) + (pathSetting != null ? 1 : 0) + + (relativePathSetting != null ? 1 : 0); + if (foundKeys != 1) { + throw new PolicyValidationException( + "a files entitlement entry must contain one of " + "[path, relative_path, path_setting, relative_path_setting]" + ); + } + + if (modeAsString == null) { throw new PolicyValidationException("files entitlement must contain 'mode' for every listed file"); } - if (pathAsString != null && relativePathAsString != null) { - throw new PolicyValidationException("a files entitlement entry cannot contain both 'path' and 'relative_path'"); + Mode mode = parseMode(modeAsString); + + BaseDir baseDir = null; + if (relativeTo != null) { + baseDir = parseBaseDir(relativeTo); } if (relativePathAsString != null) { - if (relativeTo == null) { + if (baseDir == null) { throw new PolicyValidationException("files entitlement with a 'relative_path' must specify 'relative_to'"); } - final BaseDir baseDir = parseBaseDir(relativeTo); Path relativePath = Path.of(relativePathAsString); if (relativePath.isAbsolute()) { throw new PolicyValidationException("'relative_path' [" + relativePathAsString + "] must be relative"); } - filesData.add(FileData.ofRelativePath(relativePath, baseDir, parseMode(mode))); + filesData.add(FileData.ofRelativePath(relativePath, baseDir, mode)); } else if (pathAsString != null) { Path path = Path.of(pathAsString); if (path.isAbsolute() == false) { throw new PolicyValidationException("'path' [" + pathAsString + "] must be absolute"); } - filesData.add(FileData.ofPath(path, parseMode(mode))); + filesData.add(FileData.ofPath(path, mode)); + } else if (pathSetting != null) { + filesData.add(FileData.ofPathSetting(pathSetting, mode)); + } else if (relativePathSetting != null) { + if (baseDir == null) { + throw new PolicyValidationException("files entitlement with a 'relative_path_setting' must specify 'relative_to'"); + } + filesData.add(FileData.ofRelativePathSetting(relativePathSetting, baseDir, mode)); } else { - throw new PolicyValidationException("files entitlement must contain either 'path' or 'relative_path' for every entry"); + throw new AssertionError("File entry validation error"); } } return new FilesEntitlement(filesData); diff --git a/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTreeTests.java b/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTreeTests.java index 37b2bfb19f819..218fc0c956723 100644 --- a/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTreeTests.java +++ b/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTreeTests.java @@ -9,6 +9,7 @@ package org.elasticsearch.entitlement.runtime.policy; +import org.elasticsearch.common.settings.Settings; import org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement; import org.elasticsearch.test.ESTestCase; import org.junit.BeforeClass; @@ -25,10 +26,12 @@ public class FileAccessTreeTests extends ESTestCase { static Path root; + static Settings settings; @BeforeClass public static void setupRoot() { root = createTempDir(); + settings = Settings.EMPTY; } private static Path path(String s) { @@ -39,7 +42,9 @@ private static Path path(String s) { Path.of("/home"), Path.of("/config"), new Path[] { Path.of("/data1"), Path.of("/data2") }, - Path.of("/tmp") + Path.of("/tmp"), + setting -> settings.get(setting), + glob -> settings.getGlobValues(glob) ); public void testEmpty() { @@ -163,13 +168,9 @@ public void testForwardSlashes() { } public void testTempDirAccess() { - Path tempDir = createTempDir(); - var tree = FileAccessTree.of( - FilesEntitlement.EMPTY, - new PathLookup(Path.of("/home"), Path.of("/config"), new Path[] { Path.of("/data1"), Path.of("/data2") }, tempDir) - ); - assertThat(tree.canRead(tempDir), is(true)); - assertThat(tree.canWrite(tempDir), is(true)); + var tree = FileAccessTree.of(FilesEntitlement.EMPTY, TEST_PATH_LOOKUP); + assertThat(tree.canRead(TEST_PATH_LOOKUP.tempDir()), is(true)); + assertThat(tree.canWrite(TEST_PATH_LOOKUP.tempDir()), is(true)); } FileAccessTree accessTree(FilesEntitlement entitlement) { diff --git a/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyManagerTests.java b/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyManagerTests.java index d63a9f4bcadf9..2b5700d03e714 100644 --- a/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyManagerTests.java +++ b/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyManagerTests.java @@ -9,6 +9,7 @@ package org.elasticsearch.entitlement.runtime.policy; +import org.elasticsearch.common.settings.Settings; import org.elasticsearch.entitlement.runtime.policy.PolicyManager.ModuleEntitlements; import org.elasticsearch.entitlement.runtime.policy.agent.TestAgent; import org.elasticsearch.entitlement.runtime.policy.agent.inner.TestInnerAgent; @@ -68,7 +69,9 @@ public static void beforeClass() { TEST_BASE_DIR.resolve("/user/home"), TEST_BASE_DIR.resolve("/config"), new Path[] { TEST_BASE_DIR.resolve("/data1/"), TEST_BASE_DIR.resolve("/data2") }, - TEST_BASE_DIR.resolve("/temp") + TEST_BASE_DIR.resolve("/temp"), + Settings.EMPTY::get, + Settings.EMPTY::getGlobValues ); } catch (Exception e) { throw new IllegalStateException(e); diff --git a/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserFailureTests.java b/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserFailureTests.java index f64fe33158dd8..5bf582b894c38 100644 --- a/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserFailureTests.java +++ b/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserFailureTests.java @@ -74,7 +74,8 @@ public void testEntitlementMutuallyExclusiveParameters() { """.getBytes(StandardCharsets.UTF_8)), "test-failure-policy.yaml", false).parsePolicy()); assertEquals( "[2:5] policy parsing error for [test-failure-policy.yaml] in scope [entitlement-module-name] " - + "for entitlement type [files]: a files entitlement entry cannot contain both 'path' and 'relative_path'", + + "for entitlement type [files]: a files entitlement entry must contain one of " + + "[path, relative_path, path_setting, relative_path_setting]", ppe.getMessage() ); } @@ -87,7 +88,8 @@ public void testEntitlementAtLeastOneParameter() { """.getBytes(StandardCharsets.UTF_8)), "test-failure-policy.yaml", false).parsePolicy()); assertEquals( "[2:5] policy parsing error for [test-failure-policy.yaml] in scope [entitlement-module-name] " - + "for entitlement type [files]: files entitlement must contain either 'path' or 'relative_path' for every entry", + + "for entitlement type [files]: a files entitlement entry must contain one of " + + "[path, relative_path, path_setting, relative_path_setting]", ppe.getMessage() ); } diff --git a/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserTests.java b/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserTests.java index 9af743f817153..45c523c58b7ea 100644 --- a/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserTests.java +++ b/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserTests.java @@ -182,6 +182,11 @@ public void testParseFiles() throws IOException { mode: "read" - path: '%s' mode: "read_write" + - path_setting: foo.bar + mode: read + - relative_path_setting: foo.bar + relative_to: config + mode: read """, relativePathToFile, relativePathToDir, TEST_ABSOLUTE_PATH_TO_FILE).getBytes(StandardCharsets.UTF_8)), "test-policy.yaml", false @@ -196,7 +201,9 @@ public void testParseFiles() throws IOException { List.of( Map.of("relative_path", relativePathToFile, "mode", "read_write", "relative_to", "data"), Map.of("relative_path", relativePathToDir, "mode", "read", "relative_to", "config"), - Map.of("path", TEST_ABSOLUTE_PATH_TO_FILE, "mode", "read_write") + Map.of("path", TEST_ABSOLUTE_PATH_TO_FILE, "mode", "read_write"), + Map.of("path_setting", "foo.bar", "mode", "read"), + Map.of("relative_path_setting", "foo.bar", "relative_to", "config", "mode", "read") ) ) ) diff --git a/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/entitlements/FilesEntitlementTests.java b/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/entitlements/FilesEntitlementTests.java index 511299ba73c6e..b871e8ccf3e6f 100644 --- a/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/entitlements/FilesEntitlementTests.java +++ b/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/entitlements/FilesEntitlementTests.java @@ -9,20 +9,42 @@ package org.elasticsearch.entitlement.runtime.policy.entitlements; +import org.elasticsearch.common.settings.Settings; import org.elasticsearch.entitlement.runtime.policy.PathLookup; import org.elasticsearch.entitlement.runtime.policy.PolicyValidationException; +import org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.FileData; import org.elasticsearch.test.ESTestCase; +import org.junit.BeforeClass; import java.nio.file.Path; import java.util.List; import java.util.Map; +import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.Mode.READ; import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.Mode.READ_WRITE; import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.is; public class FilesEntitlementTests extends ESTestCase { + static Settings settings; + + @BeforeClass + public static void setupRoot() { + settings = Settings.EMPTY; + } + + private static final PathLookup TEST_PATH_LOOKUP = new PathLookup( + Path.of("home"), + Path.of("/config"), + new Path[] { Path.of("/data1"), Path.of("/data2") }, + Path.of("/tmp"), + setting -> settings.get(setting), + glob -> settings.getGlobValues(glob) + ); + public void testEmptyBuild() { PolicyValidationException pve = expectThrows(PolicyValidationException.class, () -> FilesEntitlement.build(List.of())); assertEquals("must specify at least one path", pve.getMessage()); @@ -39,10 +61,30 @@ public void testInvalidRelativeDirectory() { } public void testFileDataRelativeWithEmptyDirectory() { - var fileData = FilesEntitlement.FileData.ofRelativePath(Path.of(""), FilesEntitlement.BaseDir.DATA, READ_WRITE); - var dataDirs = fileData.resolvePaths( - new PathLookup(Path.of("/home"), Path.of("/config"), new Path[] { Path.of("/data1/"), Path.of("/data2") }, Path.of("/temp")) - ); + var fileData = FileData.ofRelativePath(Path.of(""), FilesEntitlement.BaseDir.DATA, READ_WRITE); + var dataDirs = fileData.resolvePaths(TEST_PATH_LOOKUP); assertThat(dataDirs.toList(), contains(Path.of("/data1/"), Path.of("/data2"))); } + + public void testPathSettingResolve() { + var entitlement = FilesEntitlement.build(List.of(Map.of("path_setting", "foo.bar", "mode", "read"))); + var filesData = entitlement.filesData(); + assertThat(filesData, contains(FileData.ofPathSetting("foo.bar", READ))); + + var fileData = FileData.ofPathSetting("foo.bar", READ); + // empty settings + assertThat(fileData.resolvePaths(TEST_PATH_LOOKUP).toList(), empty()); + + fileData = FileData.ofPathSetting("foo.bar", READ); + settings = Settings.builder().put("foo.bar", "/setting/path").build(); + assertThat(fileData.resolvePaths(TEST_PATH_LOOKUP).toList(), contains(Path.of("/setting/path"))); + + fileData = FileData.ofPathSetting("foo.*.bar", READ); + settings = Settings.builder().put("foo.baz.bar", "/setting/path").build(); + assertThat(fileData.resolvePaths(TEST_PATH_LOOKUP).toList(), contains(Path.of("/setting/path"))); + + fileData = FileData.ofPathSetting("foo.*.bar", READ); + settings = Settings.builder().put("foo.baz.bar", "/setting/path").put("foo.baz2.bar", "/other/path").build(); + assertThat(fileData.resolvePaths(TEST_PATH_LOOKUP).toList(), containsInAnyOrder(Path.of("/setting/path"), Path.of("/other/path"))); + } } diff --git a/server/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java b/server/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java index 6e07c7012cc06..429d2584b0fae 100644 --- a/server/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java +++ b/server/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java @@ -245,10 +245,12 @@ private static void initPhase2(Bootstrap bootstrap) throws IOException { EntitlementBootstrap.bootstrap( pluginPolicies, pluginsResolver::resolveClassToPluginName, + nodeEnv.settings()::get, + nodeEnv.settings()::getGlobValues, nodeEnv.dataDirs(), nodeEnv.configDir(), - nodeEnv.tmpDir(), - nodeEnv.logsDir() + nodeEnv.logsDir(), + nodeEnv.tmpDir() ); } else { assert RuntimeVersionFeature.isSecurityManagerAvailable(); diff --git a/server/src/main/java/org/elasticsearch/common/settings/Settings.java b/server/src/main/java/org/elasticsearch/common/settings/Settings.java index 09773ba1de35e..05d4212de0b0c 100644 --- a/server/src/main/java/org/elasticsearch/common/settings/Settings.java +++ b/server/src/main/java/org/elasticsearch/common/settings/Settings.java @@ -290,6 +290,24 @@ public String get(String setting, String defaultValue) { return retVal == null ? defaultValue : retVal; } + /** + * Returns the values for settings that match the given glob pattern. + * A single glob is supported. + * + * @param settingGlob setting name containing a glob + * @return zero or more values for any settings in this settings object that match the glob pattern + */ + public Stream getGlobValues(String settingGlob) { + int globIndex = settingGlob.indexOf(".*."); + if (globIndex == -1) { + throw new IllegalArgumentException("Pattern [" + settingGlob + "] does not contain a glob [*]"); + } + String prefix = settingGlob.substring(0, globIndex + 1); + String suffix = settingGlob.substring(globIndex + 2); + Settings subSettings = getByPrefix(prefix); + return subSettings.names().stream().map(k -> k + suffix).map(subSettings::get).filter(Objects::nonNull); + } + /** * Returns the setting value (as float) associated with the setting key. If it does not exists, * returns the default value provided. diff --git a/server/src/test/java/org/elasticsearch/common/settings/SettingsTests.java b/server/src/test/java/org/elasticsearch/common/settings/SettingsTests.java index d0f17f6a495d6..0d5dbdec8360f 100644 --- a/server/src/test/java/org/elasticsearch/common/settings/SettingsTests.java +++ b/server/src/test/java/org/elasticsearch/common/settings/SettingsTests.java @@ -703,4 +703,10 @@ public void testProcessSetting() throws IOException { {"ant.bee":{"cat.dog":{"ewe":"value3"},"cat":"value2"},"ant":"value1"}""", Strings.toString(builder)); } + public void testGlobValues() throws IOException { + Settings test = Settings.builder().put("foo.x.bar", "1").put("foo.y.bar", "2").build(); + var values = test.getGlobValues("foo.*.bar").toList(); + assertThat(values, containsInAnyOrder("1", "2")); + } + }