Check file entitlements on the Lucene FilterFileSystem in tests (#130825)
So far most file entitlement checks have been trivially allowed due to usage of the Lucene FilterFileSystem. This makes sure we properly check for that file system in tests. Fixes #127686 Fixes #127193 Fixes #127192 Fixes #127190 Relates to ES-12210 Relates to ES-12242
This commit is contained in:
parent
517f3bf3c0
commit
3f0b14b444
|
@ -161,7 +161,7 @@ public class EntitlementBootstrap {
|
|||
PathLookup pathLookup,
|
||||
Policy serverPolicyPatch,
|
||||
Function<Class<?>, PolicyManager.PolicyScope> scopeResolver,
|
||||
Map<String, Collection<Path>> pluginSourcePaths
|
||||
Map<String, Collection<Path>> pluginSourcePathsResolver
|
||||
) {
|
||||
FilesEntitlementsValidation.validate(pluginPolicies, pathLookup);
|
||||
|
||||
|
@ -170,7 +170,7 @@ public class EntitlementBootstrap {
|
|||
HardcodedEntitlements.agentEntitlements(),
|
||||
pluginPolicies,
|
||||
scopeResolver,
|
||||
pluginSourcePaths,
|
||||
pluginSourcePathsResolver::get,
|
||||
pathLookup
|
||||
);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
|
||||
package org.elasticsearch.entitlement.runtime.policy;
|
||||
|
||||
import org.elasticsearch.core.PathUtils;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
|
@ -16,6 +18,8 @@ import java.util.stream.Stream;
|
|||
* Resolves paths for known directories checked by entitlements.
|
||||
*/
|
||||
public interface PathLookup {
|
||||
Class<?> DEFAULT_FILESYSTEM_CLASS = PathUtils.getDefaultFileSystem().getClass();
|
||||
|
||||
enum BaseDir {
|
||||
USER_HOME,
|
||||
CONFIG,
|
||||
|
@ -37,4 +41,6 @@ public interface PathLookup {
|
|||
* paths of the given {@code baseDir}.
|
||||
*/
|
||||
Stream<Path> resolveSettingPaths(BaseDir baseDir, String settingName);
|
||||
|
||||
boolean isPathOnDefaultFilesystem(Path path);
|
||||
}
|
||||
|
|
|
@ -75,4 +75,9 @@ public record PathLookupImpl(
|
|||
.toList();
|
||||
return getBaseDirPaths(baseDir).flatMap(path -> relativePaths.stream().map(path::resolve));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPathOnDefaultFilesystem(Path path) {
|
||||
return path.getFileSystem().getClass() == DEFAULT_FILESYSTEM_CLASS;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
|
||||
package org.elasticsearch.entitlement.runtime.policy;
|
||||
|
||||
import org.elasticsearch.core.PathUtils;
|
||||
import org.elasticsearch.core.Strings;
|
||||
import org.elasticsearch.core.SuppressForbidden;
|
||||
import org.elasticsearch.entitlement.instrumentation.InstrumentationService;
|
||||
|
@ -58,7 +57,7 @@ import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.TE
|
|||
*/
|
||||
@SuppressForbidden(reason = "Explicitly checking APIs that are forbidden")
|
||||
public class PolicyCheckerImpl implements PolicyChecker {
|
||||
static final Class<?> DEFAULT_FILESYSTEM_CLASS = PathUtils.getDefaultFileSystem().getClass();
|
||||
|
||||
protected final Set<Package> suppressFailureLogPackages;
|
||||
/**
|
||||
* Frames originating from this module are ignored in the permission logic.
|
||||
|
@ -81,15 +80,14 @@ public class PolicyCheckerImpl implements PolicyChecker {
|
|||
this.pathLookup = pathLookup;
|
||||
}
|
||||
|
||||
private static boolean isPathOnDefaultFilesystem(Path path) {
|
||||
var pathFileSystemClass = path.getFileSystem().getClass();
|
||||
if (path.getFileSystem().getClass() != DEFAULT_FILESYSTEM_CLASS) {
|
||||
private boolean isPathOnDefaultFilesystem(Path path) {
|
||||
if (pathLookup.isPathOnDefaultFilesystem(path) == false) {
|
||||
PolicyManager.generalLogger.trace(
|
||||
() -> Strings.format(
|
||||
"File entitlement trivially allowed: path [%s] is for a different FileSystem class [%s], default is [%s]",
|
||||
path.toString(),
|
||||
pathFileSystemClass.getName(),
|
||||
DEFAULT_FILESYSTEM_CLASS.getName()
|
||||
path.getFileSystem().getClass().getName(),
|
||||
PathLookup.DEFAULT_FILESYSTEM_CLASS.getName()
|
||||
)
|
||||
);
|
||||
return false;
|
||||
|
@ -217,7 +215,7 @@ public class PolicyCheckerImpl implements PolicyChecker {
|
|||
|
||||
@Override
|
||||
public void checkFileRead(Class<?> callerClass, Path path, boolean followLinks) throws NoSuchFileException {
|
||||
if (PolicyCheckerImpl.isPathOnDefaultFilesystem(path) == false) {
|
||||
if (isPathOnDefaultFilesystem(path) == false) {
|
||||
return;
|
||||
}
|
||||
var requestingClass = requestingClass(callerClass);
|
||||
|
@ -265,7 +263,7 @@ public class PolicyCheckerImpl implements PolicyChecker {
|
|||
|
||||
@Override
|
||||
public void checkFileWrite(Class<?> callerClass, Path path) {
|
||||
if (PolicyCheckerImpl.isPathOnDefaultFilesystem(path) == false) {
|
||||
if (isPathOnDefaultFilesystem(path) == false) {
|
||||
return;
|
||||
}
|
||||
var requestingClass = requestingClass(callerClass);
|
||||
|
|
|
@ -22,9 +22,11 @@ import java.nio.file.Path;
|
|||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
|
@ -217,7 +219,7 @@ public class PolicyManager {
|
|||
.filter(m -> SYSTEM_LAYER_MODULES.contains(m) == false)
|
||||
.collect(Collectors.toUnmodifiableSet());
|
||||
|
||||
private final Map<String, Collection<Path>> pluginSourcePaths;
|
||||
private final Function<String, Collection<Path>> pluginSourcePathsResolver;
|
||||
|
||||
/**
|
||||
* Paths that are only allowed for a single module. Used to generate
|
||||
|
@ -231,7 +233,7 @@ public class PolicyManager {
|
|||
List<Entitlement> apmAgentEntitlements,
|
||||
Map<String, Policy> pluginPolicies,
|
||||
Function<Class<?>, PolicyScope> scopeResolver,
|
||||
Map<String, Collection<Path>> pluginSourcePaths,
|
||||
Function<String, Collection<Path>> pluginSourcePathsResolver,
|
||||
PathLookup pathLookup
|
||||
) {
|
||||
this.serverEntitlements = buildScopeEntitlementsMap(requireNonNull(serverPolicy));
|
||||
|
@ -240,7 +242,7 @@ public class PolicyManager {
|
|||
.stream()
|
||||
.collect(toUnmodifiableMap(Map.Entry::getKey, e -> buildScopeEntitlementsMap(e.getValue())));
|
||||
this.scopeResolver = scopeResolver;
|
||||
this.pluginSourcePaths = pluginSourcePaths;
|
||||
this.pluginSourcePathsResolver = pluginSourcePathsResolver;
|
||||
this.pathLookup = requireNonNull(pathLookup);
|
||||
|
||||
List<ExclusiveFileEntitlement> exclusiveFileEntitlements = new ArrayList<>();
|
||||
|
@ -334,7 +336,10 @@ public class PolicyManager {
|
|||
default -> {
|
||||
assert policyScope.kind() == PLUGIN;
|
||||
var pluginEntitlements = pluginsEntitlements.get(componentName);
|
||||
Collection<Path> componentPaths = pluginSourcePaths.getOrDefault(componentName, List.of());
|
||||
Collection<Path> componentPaths = Objects.requireNonNullElse(
|
||||
pluginSourcePathsResolver.apply(componentName),
|
||||
Collections.emptyList()
|
||||
);
|
||||
if (pluginEntitlements == null) {
|
||||
return defaultEntitlements(componentName, componentPaths, moduleName);
|
||||
} else {
|
||||
|
|
|
@ -33,6 +33,7 @@ import java.net.URL;
|
|||
import java.net.URLClassLoader;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
@ -95,7 +96,7 @@ public class PolicyManagerTests extends ESTestCase {
|
|||
List.of(),
|
||||
Map.of("plugin1", new Policy("plugin1", List.of(new Scope("plugin.module1", List.of(new ExitVMEntitlement()))))),
|
||||
c -> policyScope.get(),
|
||||
Map.of("plugin1", plugin1SourcePaths),
|
||||
Map.of("plugin1", plugin1SourcePaths)::get,
|
||||
TEST_PATH_LOOKUP
|
||||
);
|
||||
Collection<Path> thisSourcePaths = policyManager.getComponentPathsFromClass(getClass());
|
||||
|
@ -170,7 +171,7 @@ public class PolicyManagerTests extends ESTestCase {
|
|||
c -> c.getPackageName().startsWith(TEST_AGENTS_PACKAGE_NAME)
|
||||
? PolicyScope.apmAgent("test.agent.module")
|
||||
: PolicyScope.plugin("test", "test.plugin.module"),
|
||||
Map.of(),
|
||||
name -> Collections.emptyList(),
|
||||
TEST_PATH_LOOKUP
|
||||
);
|
||||
ModuleEntitlements agentsEntitlements = policyManager.getEntitlements(TestAgent.class);
|
||||
|
@ -197,7 +198,7 @@ public class PolicyManagerTests extends ESTestCase {
|
|||
List.of(),
|
||||
Map.of(),
|
||||
c -> PolicyScope.plugin("test", moduleName(c)),
|
||||
Map.of(),
|
||||
name -> Collections.emptyList(),
|
||||
TEST_PATH_LOOKUP
|
||||
)
|
||||
);
|
||||
|
@ -213,7 +214,7 @@ public class PolicyManagerTests extends ESTestCase {
|
|||
List.of(new CreateClassLoaderEntitlement(), new CreateClassLoaderEntitlement()),
|
||||
Map.of(),
|
||||
c -> PolicyScope.plugin("test", moduleName(c)),
|
||||
Map.of(),
|
||||
name -> Collections.emptyList(),
|
||||
TEST_PATH_LOOKUP
|
||||
)
|
||||
);
|
||||
|
@ -249,7 +250,7 @@ public class PolicyManagerTests extends ESTestCase {
|
|||
)
|
||||
),
|
||||
c -> PolicyScope.plugin("plugin1", moduleName(c)),
|
||||
Map.of("plugin1", List.of(Path.of("modules", "plugin1"))),
|
||||
Map.of("plugin1", List.of(Path.of("modules", "plugin1")))::get,
|
||||
TEST_PATH_LOOKUP
|
||||
)
|
||||
);
|
||||
|
@ -299,7 +300,7 @@ public class PolicyManagerTests extends ESTestCase {
|
|||
)
|
||||
),
|
||||
c -> PolicyScope.plugin("", moduleName(c)),
|
||||
Map.of("plugin1", List.of(Path.of("modules", "plugin1")), "plugin2", List.of(Path.of("modules", "plugin2"))),
|
||||
Map.of("plugin1", List.of(Path.of("modules", "plugin1")), "plugin2", List.of(Path.of("modules", "plugin2")))::get,
|
||||
TEST_PATH_LOOKUP
|
||||
)
|
||||
);
|
||||
|
@ -350,7 +351,7 @@ public class PolicyManagerTests extends ESTestCase {
|
|||
)
|
||||
),
|
||||
c -> PolicyScope.plugin("", moduleName(c)),
|
||||
Map.of(),
|
||||
name -> Collections.emptyList(),
|
||||
TEST_PATH_LOOKUP
|
||||
)
|
||||
);
|
||||
|
|
|
@ -489,21 +489,6 @@ tests:
|
|||
- class: org.elasticsearch.test.rest.yaml.RcsCcsCommonYamlTestSuiteIT
|
||||
method: test {p0=msearch/20_typed_keys/Multisearch test with typed_keys parameter for sampler and significant terms}
|
||||
issue: https://github.com/elastic/elasticsearch/issues/130472
|
||||
- class: org.elasticsearch.xpack.ssl.SSLErrorMessageFileTests
|
||||
method: testMessageForKeyStoreOutsideConfigDir
|
||||
issue: https://github.com/elastic/elasticsearch/issues/127192
|
||||
- class: org.elasticsearch.xpack.ssl.SSLErrorMessageFileTests
|
||||
method: testMessageForPemKeyOutsideConfigDir
|
||||
issue: https://github.com/elastic/elasticsearch/issues/127192
|
||||
- class: org.elasticsearch.xpack.ssl.SSLErrorMessageFileTests
|
||||
method: testMessageForPemCertificateOutsideConfigDir
|
||||
issue: https://github.com/elastic/elasticsearch/issues/127192
|
||||
- class: org.elasticsearch.xpack.ssl.SSLErrorMessageFileTests
|
||||
method: testMessageForTrustStoreOutsideConfigDir
|
||||
issue: https://github.com/elastic/elasticsearch/issues/127192
|
||||
- class: org.elasticsearch.xpack.ssl.SSLErrorMessageFileTests
|
||||
method: testMessageForCertificateAuthoritiesOutsideConfigDir
|
||||
issue: https://github.com/elastic/elasticsearch/issues/127192
|
||||
- class: org.elasticsearch.index.codec.vectors.cluster.HierarchicalKMeansTests
|
||||
method: testHKmeans
|
||||
issue: https://github.com/elastic/elasticsearch/issues/130497
|
||||
|
|
|
@ -34,7 +34,6 @@ import java.net.URL;
|
|||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -42,7 +41,6 @@ import java.util.Set;
|
|||
import java.util.TreeSet;
|
||||
|
||||
import static java.util.stream.Collectors.toCollection;
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
import static java.util.stream.Collectors.toSet;
|
||||
import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.CONFIG;
|
||||
import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.TEMP;
|
||||
|
@ -128,8 +126,6 @@ public class TestEntitlementBootstrap {
|
|||
} else {
|
||||
classPathEntries = Arrays.stream(classPathProperty.split(separator)).map(PathUtils::get).collect(toCollection(TreeSet::new));
|
||||
}
|
||||
Map<String, Collection<Path>> pluginSourcePaths = pluginNames.stream().collect(toMap(n -> n, n -> classPathEntries));
|
||||
|
||||
FilesEntitlementsValidation.validate(pluginPolicies, pathLookup);
|
||||
|
||||
String testOnlyPathString = System.getenv("es.entitlement.testOnlyPath");
|
||||
|
@ -148,8 +144,8 @@ public class TestEntitlementBootstrap {
|
|||
HardcodedEntitlements.agentEntitlements(),
|
||||
pluginPolicies,
|
||||
scopeResolver,
|
||||
pluginSourcePaths,
|
||||
pathLookup,
|
||||
classPathEntries,
|
||||
testOnlyClassPath
|
||||
);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
|
||||
package org.elasticsearch.entitlement.runtime.policy;
|
||||
|
||||
import org.apache.lucene.tests.mockfile.FilterFileSystem;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
@ -37,4 +39,14 @@ public class TestPathLookup implements PathLookup {
|
|||
return Stream.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPathOnDefaultFilesystem(Path path) {
|
||||
var fileSystem = path.getFileSystem();
|
||||
if (fileSystem.getClass() != DEFAULT_FILESYSTEM_CLASS) {
|
||||
while (fileSystem instanceof FilterFileSystem ffs) {
|
||||
fileSystem = ffs.getDelegate();
|
||||
}
|
||||
}
|
||||
return fileSystem.getClass() == DEFAULT_FILESYSTEM_CLASS;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ public class TestPolicyManager extends PolicyManager {
|
|||
* We need this larger map per class instead.
|
||||
*/
|
||||
final Map<Class<?>, ModuleEntitlements> classEntitlementsMap = new ConcurrentHashMap<>();
|
||||
|
||||
final Collection<Path> classpath;
|
||||
final Collection<URI> testOnlyClasspath;
|
||||
|
||||
public TestPolicyManager(
|
||||
|
@ -46,11 +46,12 @@ public class TestPolicyManager extends PolicyManager {
|
|||
List<Entitlement> apmAgentEntitlements,
|
||||
Map<String, Policy> pluginPolicies,
|
||||
Function<Class<?>, PolicyScope> scopeResolver,
|
||||
Map<String, Collection<Path>> pluginSourcePaths,
|
||||
PathLookup pathLookup,
|
||||
Collection<Path> classpath,
|
||||
Collection<URI> testOnlyClasspath
|
||||
) {
|
||||
super(serverPolicy, apmAgentEntitlements, pluginPolicies, scopeResolver, pluginSourcePaths, pathLookup);
|
||||
super(serverPolicy, apmAgentEntitlements, pluginPolicies, scopeResolver, name -> classpath, pathLookup);
|
||||
this.classpath = classpath;
|
||||
this.testOnlyClasspath = testOnlyClasspath;
|
||||
reset();
|
||||
}
|
||||
|
@ -118,6 +119,11 @@ public class TestPolicyManager extends PolicyManager {
|
|||
return super.isTriviallyAllowed(requestingClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<Path> getComponentPathsFromClass(Class<?> requestingClass) {
|
||||
return classpath; // required to grant read access to the production source and test resources
|
||||
}
|
||||
|
||||
private boolean isEntitlementClass(Class<?> requestingClass) {
|
||||
return requestingClass.getPackageName().startsWith("org.elasticsearch.entitlement")
|
||||
&& (requestingClass.getName().contains("Test") == false);
|
||||
|
@ -180,6 +186,9 @@ public class TestPolicyManager extends PolicyManager {
|
|||
URI needle;
|
||||
try {
|
||||
needle = codeSource.getLocation().toURI();
|
||||
if (needle.getScheme().equals("jrt")) {
|
||||
return false; // won't be on testOnlyClasspath
|
||||
}
|
||||
} catch (URISyntaxException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
|
|
@ -34,8 +34,8 @@ public class TestPolicyManagerTests extends ESTestCase {
|
|||
List.of(),
|
||||
Map.of(),
|
||||
c -> new PolicyScope(PLUGIN, "example-plugin" + scopeCounter.incrementAndGet(), "org.example.module"),
|
||||
Map.of(),
|
||||
new TestPathLookup(Map.of()),
|
||||
List.of(),
|
||||
List.of()
|
||||
);
|
||||
policyManager.setActive(true);
|
||||
|
|
|
@ -16,7 +16,6 @@ import org.elasticsearch.core.Nullable;
|
|||
import org.elasticsearch.core.PathUtils;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.env.TestEnvironment;
|
||||
import org.elasticsearch.jdk.RuntimeVersionFeature;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.xpack.core.ssl.SSLService;
|
||||
import org.junit.Before;
|
||||
|
@ -363,11 +362,6 @@ public class SSLErrorMessageFileTests extends ESTestCase {
|
|||
String configKey,
|
||||
BiConsumer<String, Settings.Builder> configure
|
||||
) throws Exception {
|
||||
assumeTrue(
|
||||
"Requires Security Manager to block access, entitlements are not checked for unit tests",
|
||||
RuntimeVersionFeature.isSecurityManagerAvailable()
|
||||
);
|
||||
|
||||
final String prefix = randomSslPrefix();
|
||||
final Settings.Builder settings = Settings.builder();
|
||||
configure.accept(prefix, settings);
|
||||
|
|
Loading…
Reference in New Issue