From fe17af99a86584be4bf16a402115a590737f1e7e Mon Sep 17 00:00:00 2001 From: Ryan Ernst Date: Wed, 26 Feb 2025 12:41:44 -0800 Subject: [PATCH] Add ignore_url for setting based files entitlement (#123541) File entitlements which read a setting may actually contain urls. This commit adds an optional `ignore_url` property for the entitlement to skip any values which are urls. --- .../policy/entitlements/FilesEntitlement.java | 49 +++++++++++++------ .../entitlements/FilesEntitlementTests.java | 39 +++++++++++++-- 2 files changed, 67 insertions(+), 21 deletions(-) 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 609e0ed66cfa0..c47b0b93c5471 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 @@ -9,6 +9,7 @@ package org.elasticsearch.entitlement.runtime.policy.entitlements; +import org.elasticsearch.core.Booleans; import org.elasticsearch.entitlement.runtime.policy.ExternalEntitlement; import org.elasticsearch.entitlement.runtime.policy.PathLookup; import org.elasticsearch.entitlement.runtime.policy.PolicyValidationException; @@ -17,6 +18,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.stream.Stream; @@ -85,12 +87,12 @@ static FileData ofRelativePath(Path relativePath, BaseDir baseDir, Mode mode) { return new RelativePathFileData(relativePath, baseDir, mode, null); } - static FileData ofPathSetting(String setting, Mode mode) { - return new PathSettingFileData(setting, mode, null); + static FileData ofPathSetting(String setting, Mode mode, boolean ignoreUrl) { + return new PathSettingFileData(setting, mode, ignoreUrl, null); } - static FileData ofRelativePathSetting(String setting, BaseDir baseDir, Mode mode) { - return new RelativePathSettingFileData(setting, baseDir, mode, null); + static FileData ofRelativePathSetting(String setting, BaseDir baseDir, Mode mode, boolean ignoreUrl) { + return new RelativePathSettingFileData(setting, baseDir, mode, ignoreUrl, null); } /** @@ -207,10 +209,10 @@ public FileData withPlatform(Platform platform) { } } - private record PathSettingFileData(String setting, Mode mode, Platform platform) implements FileData { + private record PathSettingFileData(String setting, Mode mode, boolean ignoreUrl, Platform platform) implements FileData { @Override public Stream resolvePaths(PathLookup pathLookup) { - return resolvePathSettings(pathLookup, setting); + return resolvePathSettings(pathLookup, setting, ignoreUrl); } @Override @@ -218,17 +220,17 @@ public FileData withPlatform(Platform platform) { if (platform == platform()) { return this; } - return new PathSettingFileData(setting, mode, platform); + return new PathSettingFileData(setting, mode, ignoreUrl, platform); } } - private record RelativePathSettingFileData(String setting, BaseDir baseDir, Mode mode, Platform platform) + private record RelativePathSettingFileData(String setting, BaseDir baseDir, Mode mode, boolean ignoreUrl, Platform platform) implements FileData, RelativeFileData { @Override public Stream resolveRelativePaths(PathLookup pathLookup) { - return resolvePathSettings(pathLookup, setting); + return resolvePathSettings(pathLookup, setting, ignoreUrl); } @Override @@ -236,16 +238,22 @@ public FileData withPlatform(Platform platform) { if (platform == platform()) { return this; } - return new RelativePathSettingFileData(setting, baseDir, mode, platform); + return new RelativePathSettingFileData(setting, baseDir, mode, ignoreUrl, platform); } } - private static Stream resolvePathSettings(PathLookup pathLookup, String setting) { + private static Stream resolvePathSettings(PathLookup pathLookup, String setting, boolean ignoreUrl) { + Stream result; if (setting.contains("*")) { - return pathLookup.settingGlobResolver().apply(setting).map(Path::of); + result = pathLookup.settingGlobResolver().apply(setting); + } else { + String path = pathLookup.settingResolver().apply(setting); + result = path == null ? Stream.of() : Stream.of(path); + } + if (ignoreUrl) { + result = result.filter(s -> s.toLowerCase(Locale.ROOT).startsWith("https://") == false); } - String path = pathLookup.settingResolver().apply(setting); - return path == null ? Stream.of() : Stream.of(Path.of(path)); + return result.map(Path::of); } private static Mode parseMode(String mode) { @@ -298,6 +306,7 @@ public static FilesEntitlement build(List paths) { String relativePathSetting = file.remove("relative_path_setting"); String modeAsString = file.remove("mode"); String platformAsString = file.remove("platform"); + String ignoreUrlAsString = file.remove("ignore_url"); if (file.isEmpty() == false) { throw new PolicyValidationException("unknown key(s) [" + file + "] in a listed file for files entitlement"); @@ -324,6 +333,14 @@ public static FilesEntitlement build(List paths) { baseDir = parseBaseDir(relativeTo); } + boolean ignoreUrl = false; + if (ignoreUrlAsString != null) { + if (relativePathAsString != null || pathAsString != null) { + throw new PolicyValidationException("'ignore_url' may only be used with `path_setting` or `relative_path_setting`"); + } + ignoreUrl = Booleans.parseBoolean(ignoreUrlAsString); + } + final FileData fileData; if (relativePathAsString != null) { if (baseDir == null) { @@ -342,12 +359,12 @@ public static FilesEntitlement build(List paths) { } fileData = FileData.ofPath(path, mode); } else if (pathSetting != null) { - fileData = FileData.ofPathSetting(pathSetting, mode); + fileData = FileData.ofPathSetting(pathSetting, mode, ignoreUrl); } else if (relativePathSetting != null) { if (baseDir == null) { throw new PolicyValidationException("files entitlement with a 'relative_path_setting' must specify 'relative_to'"); } - fileData = FileData.ofRelativePathSetting(relativePathSetting, baseDir, mode); + fileData = FileData.ofRelativePathSetting(relativePathSetting, baseDir, mode, ignoreUrl); } else { throw new AssertionError("File entry validation error"); } 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 e0c08d18b8c15..a453d6cf54992 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 @@ -20,6 +20,7 @@ import java.util.List; import java.util.Map; +import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.BaseDir.CONFIG; 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; @@ -94,22 +95,50 @@ public void testFileDataRelativeWithEmptyDirectory() { 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))); + assertThat(filesData, contains(FileData.ofPathSetting("foo.bar", READ, false))); - var fileData = FileData.ofPathSetting("foo.bar", READ); + var fileData = FileData.ofPathSetting("foo.bar", READ, false); // empty settings assertThat(fileData.resolvePaths(TEST_PATH_LOOKUP).toList(), empty()); - fileData = FileData.ofPathSetting("foo.bar", READ); + fileData = FileData.ofPathSetting("foo.bar", READ, false); 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); + fileData = FileData.ofPathSetting("foo.*.bar", READ, false); 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); + fileData = FileData.ofPathSetting("foo.*.bar", READ, false); 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"))); } + + public void testPathSettingIgnoreUrl() { + var fileData = FileData.ofPathSetting("foo.*.bar", READ, true); + settings = Settings.builder().put("foo.nonurl.bar", "/setting/path").put("foo.url.bar", "https://mysite").build(); + assertThat(fileData.resolvePaths(TEST_PATH_LOOKUP).toList(), contains(Path.of("/setting/path"))); + } + + public void testRelativePathSettingIgnoreUrl() { + var fileData = FileData.ofRelativePathSetting("foo.*.bar", CONFIG, READ, true); + settings = Settings.builder().put("foo.nonurl.bar", "path").put("foo.url.bar", "https://mysite").build(); + assertThat(fileData.resolvePaths(TEST_PATH_LOOKUP).toList(), contains(Path.of("/config/path"))); + } + + public void testIgnoreUrlValidation() { + var e = expectThrows( + PolicyValidationException.class, + () -> FilesEntitlement.build(List.of(Map.of("path", "/foo", "mode", "read", "ignore_url", "true"))) + ); + assertThat(e.getMessage(), is("'ignore_url' may only be used with `path_setting` or `relative_path_setting`")); + + e = expectThrows( + PolicyValidationException.class, + () -> FilesEntitlement.build( + List.of(Map.of("relative_path", "foo", "relative_to", "config", "mode", "read", "ignore_url", "true")) + ) + ); + assertThat(e.getMessage(), is("'ignore_url' may only be used with `path_setting` or `relative_path_setting`")); + } }