diff --git a/qa/evil-tests/src/test/java/org/elasticsearch/bootstrap/ESPolicyUnitTests.java b/qa/evil-tests/src/test/java/org/elasticsearch/bootstrap/ESPolicyUnitTests.java deleted file mode 100644 index 34d15fa4aebba..0000000000000 --- a/qa/evil-tests/src/test/java/org/elasticsearch/bootstrap/ESPolicyUnitTests.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -package org.elasticsearch.bootstrap; - -import org.elasticsearch.core.SuppressForbidden; -import org.elasticsearch.jdk.RuntimeVersionFeature; -import org.elasticsearch.test.ESTestCase; -import org.junit.BeforeClass; - -import java.io.FilePermission; -import java.net.SocketPermission; -import java.net.URL; -import java.security.AllPermission; -import java.security.CodeSource; -import java.security.Permission; -import java.security.PermissionCollection; -import java.security.Permissions; -import java.security.Policy; -import java.security.ProtectionDomain; -import java.security.cert.Certificate; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import static java.util.Map.entry; -import static org.elasticsearch.bootstrap.ESPolicy.POLICY_RESOURCE; - -/** - * Unit tests for ESPolicy: these cannot run with security manager, - * we don't allow messing with the policy - */ -public class ESPolicyUnitTests extends ESTestCase { - - static final Map TEST_CODEBASES = BootstrapForTesting.getCodebases(); - static Policy DEFAULT_POLICY; - - @BeforeClass - public static void setupPolicy() { - assumeTrue("test requires security manager to be supported", RuntimeVersionFeature.isSecurityManagerAvailable()); - assumeTrue("test cannot run with security manager", System.getSecurityManager() == null); - DEFAULT_POLICY = PolicyUtil.readPolicy(ESPolicy.class.getResource(POLICY_RESOURCE), TEST_CODEBASES); - } - - /** - * Test policy with null codesource. - *

- * This can happen when restricting privileges with doPrivileged, - * even though ProtectionDomain's ctor javadocs might make you think - * that the policy won't be consulted. - */ - @SuppressForbidden(reason = "to create FilePermission object") - public void testNullCodeSource() throws Exception { - // create a policy with AllPermission - Permission all = new AllPermission(); - PermissionCollection allCollection = all.newPermissionCollection(); - allCollection.add(all); - ESPolicy policy = new ESPolicy(DEFAULT_POLICY, allCollection, Map.of(), true, List.of(), Map.of()); - // restrict ourselves to NoPermission - PermissionCollection noPermissions = new Permissions(); - assertFalse(policy.implies(new ProtectionDomain(null, noPermissions), new FilePermission("foo", "read"))); - } - - /** - * As of JDK 9, {@link CodeSource#getLocation} is documented to potentially return {@code null} - */ - @SuppressForbidden(reason = "to create FilePermission object") - public void testNullLocation() throws Exception { - PermissionCollection noPermissions = new Permissions(); - ESPolicy policy = new ESPolicy(DEFAULT_POLICY, noPermissions, Map.of(), true, List.of(), Map.of()); - assertFalse( - policy.implies( - new ProtectionDomain(new CodeSource(null, (Certificate[]) null), noPermissions), - new FilePermission("foo", "read") - ) - ); - } - - public void testListen() { - final PermissionCollection noPermissions = new Permissions(); - final ESPolicy policy = new ESPolicy(DEFAULT_POLICY, noPermissions, Map.of(), true, List.of(), Map.of()); - assertFalse( - policy.implies( - new ProtectionDomain(ESPolicyUnitTests.class.getProtectionDomain().getCodeSource(), noPermissions), - new SocketPermission("localhost:" + randomFrom(0, randomIntBetween(49152, 65535)), "listen") - ) - ); - } - - @SuppressForbidden(reason = "to create FilePermission object") - public void testDataPathPermissionIsChecked() { - final ESPolicy policy = new ESPolicy( - DEFAULT_POLICY, - new Permissions(), - Map.of(), - true, - List.of(new FilePermission("/home/elasticsearch/data/-", "read")), - Map.of() - ); - assertTrue( - policy.implies( - new ProtectionDomain(new CodeSource(null, (Certificate[]) null), new Permissions()), - new FilePermission("/home/elasticsearch/data/index/file.si", "read") - ) - ); - } - - @SuppressForbidden(reason = "to create FilePermission object") - public void testSecuredAccess() { - String file1 = "/home/elasticsearch/config/pluginFile1.yml"; - URL codebase1 = randomFrom(TEST_CODEBASES.values()); - String file2 = "/home/elasticsearch/config/pluginFile2.yml"; - URL codebase2 = randomValueOtherThan(codebase1, () -> randomFrom(TEST_CODEBASES.values())); - String dir1 = "/home/elasticsearch/config/pluginDir/"; - URL codebase3 = randomValueOtherThanMany(Set.of(codebase1, codebase2)::contains, () -> randomFrom(TEST_CODEBASES.values())); - URL otherCodebase = randomValueOtherThanMany( - Set.of(codebase1, codebase2, codebase3)::contains, - () -> randomFrom(TEST_CODEBASES.values()) - ); - - ESPolicy policy = new ESPolicy( - DEFAULT_POLICY, - new Permissions(), - Map.of(), - true, - List.of(), - Map.ofEntries(entry(file1, Set.of(codebase1)), entry(file2, Set.of(codebase1, codebase2)), entry(dir1 + "*", Set.of(codebase3))) - ); - - ProtectionDomain nullDomain = new ProtectionDomain(new CodeSource(null, (Certificate[]) null), new Permissions()); - ProtectionDomain codebase1Domain = new ProtectionDomain(new CodeSource(codebase1, (Certificate[]) null), new Permissions()); - ProtectionDomain codebase2Domain = new ProtectionDomain(new CodeSource(codebase2, (Certificate[]) null), new Permissions()); - ProtectionDomain codebase3Domain = new ProtectionDomain(new CodeSource(codebase3, (Certificate[]) null), new Permissions()); - ProtectionDomain otherCodebaseDomain = new ProtectionDomain(new CodeSource(otherCodebase, (Certificate[]) null), new Permissions()); - - Set actions = Set.of("read", "write", "read,write", "delete", "read,write,execute,readlink,delete"); - - assertFalse(policy.implies(nullDomain, new FilePermission(file1, randomFrom(actions)))); - assertFalse(policy.implies(otherCodebaseDomain, new FilePermission(file1, randomFrom(actions)))); - assertTrue(policy.implies(codebase1Domain, new FilePermission(file1, randomFrom(actions)))); - assertFalse(policy.implies(codebase2Domain, new FilePermission(file1, randomFrom(actions)))); - assertFalse(policy.implies(codebase3Domain, new FilePermission(file1, randomFrom(actions)))); - - assertFalse(policy.implies(nullDomain, new FilePermission(file2, randomFrom(actions)))); - assertFalse(policy.implies(otherCodebaseDomain, new FilePermission(file2, randomFrom(actions)))); - assertTrue(policy.implies(codebase1Domain, new FilePermission(file2, randomFrom(actions)))); - assertTrue(policy.implies(codebase2Domain, new FilePermission(file2, randomFrom(actions)))); - assertFalse(policy.implies(codebase3Domain, new FilePermission(file2, randomFrom(actions)))); - - String dirFile = dir1 + "file.yml"; - assertFalse(policy.implies(nullDomain, new FilePermission(dirFile, randomFrom(actions)))); - assertFalse(policy.implies(otherCodebaseDomain, new FilePermission(dirFile, randomFrom(actions)))); - assertFalse(policy.implies(codebase1Domain, new FilePermission(dirFile, randomFrom(actions)))); - assertFalse(policy.implies(codebase2Domain, new FilePermission(dirFile, randomFrom(actions)))); - assertTrue(policy.implies(codebase3Domain, new FilePermission(dirFile, randomFrom(actions)))); - } -} diff --git a/qa/evil-tests/src/test/java/org/elasticsearch/bootstrap/PolicyUtilTests.java b/qa/evil-tests/src/test/java/org/elasticsearch/bootstrap/PolicyUtilTests.java deleted file mode 100644 index 5a21cda02e7f3..0000000000000 --- a/qa/evil-tests/src/test/java/org/elasticsearch/bootstrap/PolicyUtilTests.java +++ /dev/null @@ -1,391 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -package org.elasticsearch.bootstrap; - -import org.elasticsearch.core.SuppressForbidden; -import org.elasticsearch.jdk.RuntimeVersionFeature; -import org.elasticsearch.plugins.PluginDescriptor; -import org.elasticsearch.test.ESTestCase; -import org.junit.Before; - -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Path; -import java.security.Permission; -import java.security.Policy; -import java.security.URIParameter; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.emptyIterable; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; -import static org.hamcrest.Matchers.nullValue; -import static org.hamcrest.collection.IsIterableContainingInOrder.contains; -import static org.hamcrest.collection.IsMapContaining.hasKey; - -public class PolicyUtilTests extends ESTestCase { - - @Before - public void assumeSecurityManagerDisabled() { - assumeTrue("test requires security manager to be supported", RuntimeVersionFeature.isSecurityManagerAvailable()); - assumeTrue("test cannot run with security manager enabled", System.getSecurityManager() == null); - } - - URL makeUrl(String s) { - try { - return new URL(s); - } catch (MalformedURLException e) { - throw new RuntimeException(e); - } - } - - Path makeDummyPlugin(String policy, String... files) throws IOException { - Path plugin = createTempDir(); - Files.copy(this.getDataPath(policy), plugin.resolve(PluginDescriptor.ES_PLUGIN_POLICY)); - for (String file : files) { - Files.createFile(plugin.resolve(file)); - } - return plugin; - } - - @SuppressForbidden(reason = "set for test") - void setProperty(String key, String value) { - System.setProperty(key, value); - } - - @SuppressForbidden(reason = "cleanup test") - void clearProperty(String key) { - System.clearProperty(key); - } - - public void testCodebaseJarMap() throws Exception { - Set urls = new LinkedHashSet<>(List.of(makeUrl("file:///foo.jar"), makeUrl("file:///bar.txt"), makeUrl("file:///a/bar.jar"))); - - Map jarMap = PolicyUtil.getCodebaseJarMap(urls); - assertThat(jarMap, hasKey("foo.jar")); - assertThat(jarMap, hasKey("bar.jar")); - // only jars are grabbed - assertThat(jarMap, not(hasKey("bar.txt"))); - - // order matters - assertThat(jarMap.keySet(), contains("foo.jar", "bar.jar")); - } - - public void testPluginPolicyInfoEmpty() throws Exception { - assertThat(PolicyUtil.readPolicyInfo(createTempDir()), is(nullValue())); - } - - public void testPluginPolicyInfoNoJars() throws Exception { - Path noJarsPlugin = makeDummyPlugin("dummy.policy"); - PluginPolicyInfo info = PolicyUtil.readPolicyInfo(noJarsPlugin); - assertThat(info.policy(), is(not(nullValue()))); - assertThat(info.jars(), emptyIterable()); - } - - public void testPluginPolicyInfo() throws Exception { - Path plugin = makeDummyPlugin("dummy.policy", "foo.jar", "foo.txt", "bar.jar"); - PluginPolicyInfo info = PolicyUtil.readPolicyInfo(plugin); - assertThat(info.policy(), is(not(nullValue()))); - assertThat(info.jars(), containsInAnyOrder(plugin.resolve("foo.jar").toUri().toURL(), plugin.resolve("bar.jar").toUri().toURL())); - } - - public void testPolicyMissingCodebaseProperty() throws Exception { - Path plugin = makeDummyPlugin("missing-codebase.policy", "foo.jar"); - URL policyFile = plugin.resolve(PluginDescriptor.ES_PLUGIN_POLICY).toUri().toURL(); - IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> PolicyUtil.readPolicy(policyFile, Map.of())); - assertThat(e.getMessage(), containsString("Unknown codebases [codebase.doesnotexist] in policy file")); - } - - public void testPolicyPermissions() throws Exception { - Path plugin = makeDummyPlugin("global-and-jar.policy", "foo.jar", "bar.jar"); - Path tmpDir = createTempDir(); - try { - URL jarUrl = plugin.resolve("foo.jar").toUri().toURL(); - setProperty("jarUrl", jarUrl.toString()); - URL policyFile = plugin.resolve(PluginDescriptor.ES_PLUGIN_POLICY).toUri().toURL(); - Policy policy = Policy.getInstance("JavaPolicy", new URIParameter(policyFile.toURI())); - - Set globalPermissions = PolicyUtil.getPolicyPermissions(null, policy, tmpDir); - assertThat(globalPermissions, contains(new RuntimePermission("queuePrintJob"))); - - Set jarPermissions = PolicyUtil.getPolicyPermissions(jarUrl, policy, tmpDir); - assertThat(jarPermissions, containsInAnyOrder(new RuntimePermission("getClassLoader"), new RuntimePermission("queuePrintJob"))); - } finally { - clearProperty("jarUrl"); - } - } - - private Path makeSinglePermissionPlugin(String jarUrl, String clazz, String name, String actions) throws IOException { - Path plugin = createTempDir(); - StringBuilder policyString = new StringBuilder("grant"); - if (jarUrl != null) { - Path jar = plugin.resolve(jarUrl); - Files.createFile(jar); - policyString.append(" codeBase \"" + jar.toUri().toURL().toString() + "\""); - } - policyString.append(" {\n permission "); - policyString.append(clazz); - // wildcard - policyString.append(" \"" + name + "\""); - if (actions != null) { - policyString.append(", \"" + actions + "\""); - } - policyString.append(";\n};"); - - logger.info(policyString.toString()); - Files.writeString(plugin.resolve(PluginDescriptor.ES_PLUGIN_POLICY), policyString.toString()); - - return plugin; - } - - interface PolicyParser { - PluginPolicyInfo parse(Path pluginRoot, Path tmpDir) throws IOException; - } - - void assertAllowedPermission(String clazz, String name, String actions, Path tmpDir, PolicyParser parser) throws Exception { - // global policy - Path plugin = makeSinglePermissionPlugin(null, clazz, name, actions); - assertNotNull(parser.parse(plugin, tmpDir)); // no error - - // specific jar policy - plugin = makeSinglePermissionPlugin("foobar.jar", clazz, name, actions); - assertNotNull(parser.parse(plugin, tmpDir)); // no error - } - - void assertAllowedPermissions(List allowedPermissions, PolicyParser parser) throws Exception { - Path tmpDir = createTempDir(); - for (String rawPermission : allowedPermissions) { - String[] elements = rawPermission.split(" "); - assert elements.length <= 3; - assert elements.length >= 2; - - assertAllowedPermission(elements[0], elements[1], elements.length == 3 ? elements[2] : null, tmpDir, parser); - } - } - - void assertIllegalPermission(String clazz, String name, String actions, Path tmpDir, PolicyParser parser) throws Exception { - // global policy - final Path globalPlugin = makeSinglePermissionPlugin(null, clazz, name, actions); - IllegalArgumentException e = expectThrows( - IllegalArgumentException.class, - "Permission (" + clazz + " " + name + (actions == null ? "" : (" " + actions)) + ") should be illegal", - () -> parser.parse(globalPlugin, tmpDir) - ); // no error - assertThat(e.getMessage(), containsString("contains illegal permission")); - assertThat(e.getMessage(), containsString("in global grant")); - - // specific jar policy - final Path jarPlugin = makeSinglePermissionPlugin("foobar.jar", clazz, name, actions); - e = expectThrows(IllegalArgumentException.class, () -> parser.parse(jarPlugin, tmpDir)); // no error - assertThat(e.getMessage(), containsString("contains illegal permission")); - assertThat(e.getMessage(), containsString("for jar")); - } - - void assertIllegalPermissions(List illegalPermissions, PolicyParser parser) throws Exception { - Path tmpDir = createTempDir(); - for (String rawPermission : illegalPermissions) { - String[] elements = rawPermission.split(" "); - assert elements.length <= 3; - assert elements.length >= 2; - - assertIllegalPermission(elements[0], elements[1], elements.length == 3 ? elements[2] : null, tmpDir, parser); - } - } - - static final List PLUGIN_TEST_PERMISSIONS = List.of( - // TODO: move this back to module test permissions, see https://github.com/elastic/elasticsearch/issues/69464 - "java.io.FilePermission /foo/bar read", - - "java.lang.reflect.ReflectPermission suppressAccessChecks", - "java.lang.RuntimePermission getClassLoader", - "java.lang.RuntimePermission setContextClassLoader", - "java.lang.RuntimePermission setFactory", - "java.lang.RuntimePermission loadLibrary.*", - "java.lang.RuntimePermission accessClassInPackage.*", - "java.lang.RuntimePermission accessDeclaredMembers", - "java.net.NetPermission requestPasswordAuthentication", - "java.net.NetPermission getProxySelector", - "java.net.NetPermission getCookieHandler", - "java.net.NetPermission getResponseCache", - "java.net.SocketPermission * accept,connect,listen,resolve", - "java.net.SocketPermission www.elastic.co accept,connect,listen,resolve", - "java.net.URLPermission https://elastic.co", - "java.net.URLPermission http://elastic.co", - "java.security.SecurityPermission createAccessControlContext", - "java.security.SecurityPermission insertProvider", - "java.security.SecurityPermission putProviderProperty.*", - "java.security.SecurityPermission putProviderProperty.foo", - "java.sql.SQLPermission callAbort", - "java.sql.SQLPermission setNetworkTimeout", - "java.util.PropertyPermission * read", - "java.util.PropertyPermission someProperty read", - "java.util.PropertyPermission * write", - "java.util.PropertyPermission foo.bar write", - "javax.management.MBeanPermission * addNotificationListener", - "javax.management.MBeanPermission * getAttribute", - "javax.management.MBeanPermission * getDomains", - "javax.management.MBeanPermission * getMBeanInfo", - "javax.management.MBeanPermission * getObjectInstance", - "javax.management.MBeanPermission * instantiate", - "javax.management.MBeanPermission * invoke", - "javax.management.MBeanPermission * isInstanceOf", - "javax.management.MBeanPermission * queryMBeans", - "javax.management.MBeanPermission * queryNames", - "javax.management.MBeanPermission * registerMBean", - "javax.management.MBeanPermission * removeNotificationListener", - "javax.management.MBeanPermission * setAttribute", - "javax.management.MBeanPermission * unregisterMBean", - "javax.management.MBeanServerPermission *", - "javax.management.MBeanTrustPermission register", - "javax.security.auth.AuthPermission doAs", - "javax.security.auth.AuthPermission doAsPrivileged", - "javax.security.auth.AuthPermission getSubject", - "javax.security.auth.AuthPermission getSubjectFromDomainCombiner", - "javax.security.auth.AuthPermission setReadOnly", - "javax.security.auth.AuthPermission modifyPrincipals", - "javax.security.auth.AuthPermission modifyPublicCredentials", - "javax.security.auth.AuthPermission modifyPrivateCredentials", - "javax.security.auth.AuthPermission refreshCredential", - "javax.security.auth.AuthPermission destroyCredential", - "javax.security.auth.AuthPermission createLoginContext.*", - "javax.security.auth.AuthPermission getLoginConfiguration", - "javax.security.auth.AuthPermission setLoginConfiguration", - "javax.security.auth.AuthPermission createLoginConfiguration.*", - "javax.security.auth.AuthPermission refreshLoginConfiguration", - "javax.security.auth.kerberos.DelegationPermission host/www.elastic.co@ELASTIC.CO krbtgt/ELASTIC.CO@ELASTIC.CO", - "javax.security.auth.kerberos.ServicePermission host/www.elastic.co@ELASTIC.CO accept" - ); - - public void testPluginPolicyAllowedPermissions() throws Exception { - assertAllowedPermissions(PLUGIN_TEST_PERMISSIONS, PolicyUtil::getPluginPolicyInfo); - assertIllegalPermissions(MODULE_TEST_PERMISSIONS, PolicyUtil::getPluginPolicyInfo); - } - - public void testPrivateCredentialPermissionAllowed() throws Exception { - // the test permission list relies on name values not containing spaces, so this - // exists to also check PrivateCredentialPermission which requires a space in the name - String clazz = "javax.security.auth.PrivateCredentialPermission"; - String name = "com.sun.PrivateCredential com.sun.Principal \\\"duke\\\""; - - assertAllowedPermission(clazz, name, "read", createTempDir(), PolicyUtil::getPluginPolicyInfo); - } - - static final List MODULE_TEST_PERMISSIONS = List.of( - "java.io.FilePermission /foo/bar write", - "java.lang.RuntimePermission createClassLoader", - "java.lang.RuntimePermission getFileStoreAttributes", - "java.lang.RuntimePermission accessUserInformation", - "org.elasticsearch.secure_sm.ThreadPermission modifyArbitraryThreadGroup" - ); - - public void testModulePolicyAllowedPermissions() throws Exception { - assertAllowedPermissions(MODULE_TEST_PERMISSIONS, PolicyUtil::getModulePolicyInfo); - } - - static final List ILLEGAL_TEST_PERMISSIONS = List.of( - "java.awt.AWTPermission *", - "java.io.FilePermission /foo/bar execute", - "java.io.FilePermission /foo/bar delete", - "java.io.FilePermission /foo/bar readLink", - "java.io.SerializablePermission enableSubclassImplementation", - "java.io.SerializablePermission enableSubstitution", - "java.lang.management.ManagementPermission control", - "java.lang.management.ManagementPermission monitor", - "java.lang.reflect.ReflectPermission newProxyInPackage.*", - "java.lang.RuntimePermission enableContextClassLoaderOverride", - "java.lang.RuntimePermission closeClassLoader", - "java.lang.RuntimePermission setSecurityManager", - "java.lang.RuntimePermission createSecurityManager", - "java.lang.RuntimePermission getenv.*", - "java.lang.RuntimePermission getenv.FOOBAR", - "java.lang.RuntimePermission shutdownHooks", - "java.lang.RuntimePermission setIO", - "java.lang.RuntimePermission modifyThread", - "java.lang.RuntimePermission stopThread", - "java.lang.RuntimePermission modifyThreadGroup", - "java.lang.RuntimePermission getProtectionDomain", - "java.lang.RuntimePermission readFileDescriptor", - "java.lang.RuntimePermission writeFileDescriptor", - "java.lang.RuntimePermission defineClassInPackage.*", - "java.lang.RuntimePermission defineClassInPackage.foobar", - "java.lang.RuntimePermission queuePrintJob", - "java.lang.RuntimePermission getStackTrace", - "java.lang.RuntimePermission setDefaultUncaughtExceptionHandler", - "java.lang.RuntimePermission preferences", - "java.lang.RuntimePermission usePolicy", - // blanket runtime permission not allowed - "java.lang.RuntimePermission *", - "java.net.NetPermission setDefaultAuthenticator", - "java.net.NetPermission specifyStreamHandler", - "java.net.NetPermission setProxySelector", - "java.net.NetPermission setCookieHandler", - "java.net.NetPermission setResponseCache", - "java.nio.file.LinkPermission hard", - "java.nio.file.LinkPermission symbolic", - "java.security.SecurityPermission getDomainCombiner", - "java.security.SecurityPermission getPolicy", - "java.security.SecurityPermission setPolicy", - "java.security.SecurityPermission getProperty.*", - "java.security.SecurityPermission getProperty.foobar", - "java.security.SecurityPermission setProperty.*", - "java.security.SecurityPermission setProperty.foobar", - "java.security.SecurityPermission removeProvider.*", - "java.security.SecurityPermission removeProvider.foobar", - "java.security.SecurityPermission clearProviderProperties.*", - "java.security.SecurityPermission clearProviderProperties.foobar", - "java.security.SecurityPermission removeProviderProperty.*", - "java.security.SecurityPermission removeProviderProperty.foobar", - "java.security.SecurityPermission insertProvider.*", - "java.security.SecurityPermission insertProvider.foobar", - "java.security.SecurityPermission setSystemScope", - "java.security.SecurityPermission setIdentityPublicKey", - "java.security.SecurityPermission setIdentityInfo", - "java.security.SecurityPermission addIdentityCertificate", - "java.security.SecurityPermission removeIdentityCertificate", - "java.security.SecurityPermission printIdentity", - "java.security.SecurityPermission getSignerPrivateKey", - "java.security.SecurityPermission getSignerKeyPair", - "java.sql.SQLPermission setLog", - "java.sql.SQLPermission setSyncFactory", - "java.sql.SQLPermission deregisterDriver", - "java.util.logging.LoggingPermission control", - "javax.management.MBeanPermission * getClassLoader", - "javax.management.MBeanPermission * getClassLoaderFor", - "javax.management.MBeanPermission * getClassLoaderRepository", - "javax.management.MBeanTrustPermission *", - "javax.management.remote.SubjectDelegationPermission *", - "javax.net.ssl.SSLPermission setHostnameVerifier", - "javax.net.ssl.SSLPermission getSSLSessionContext", - "javax.net.ssl.SSLPermission setDefaultSSLContext", - "javax.sound.sampled.AudioPermission play", - "javax.sound.sampled.AudioPermission record", - "javax.xml.bind.JAXBPermission setDatatypeConverter", - "javax.xml.ws.WebServicePermission publishEndpoint" - ); - - public void testIllegalPermissions() throws Exception { - assertIllegalPermissions(ILLEGAL_TEST_PERMISSIONS, PolicyUtil::getPluginPolicyInfo); - assertIllegalPermissions(ILLEGAL_TEST_PERMISSIONS, PolicyUtil::getModulePolicyInfo); - } - - public void testAllPermission() throws Exception { - // AllPermission has no name element, so doesn't work with the format above - Path tmpDir = createTempDir(); - assertIllegalPermission("java.security.AllPermission", null, null, tmpDir, PolicyUtil::getPluginPolicyInfo); - assertIllegalPermission("java.security.AllPermission", null, null, tmpDir, PolicyUtil::getModulePolicyInfo); - } -} diff --git a/server/src/main/java/org/elasticsearch/bootstrap/ESPolicy.java b/server/src/main/java/org/elasticsearch/bootstrap/ESPolicy.java deleted file mode 100644 index 845303abe6baf..0000000000000 --- a/server/src/main/java/org/elasticsearch/bootstrap/ESPolicy.java +++ /dev/null @@ -1,319 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -package org.elasticsearch.bootstrap; - -import org.elasticsearch.core.Predicates; -import org.elasticsearch.core.SuppressForbidden; - -import java.io.FilePermission; -import java.io.IOException; -import java.net.SocketPermission; -import java.net.URL; -import java.security.AllPermission; -import java.security.CodeSource; -import java.security.Permission; -import java.security.PermissionCollection; -import java.security.Permissions; -import java.security.Policy; -import java.security.ProtectionDomain; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.Predicate; -import java.util.stream.Collectors; - -/** custom policy for union of static and dynamic permissions */ -final class ESPolicy extends Policy { - - /** template policy file, the one used in tests */ - static final String POLICY_RESOURCE = "security.policy"; - /** limited policy for scripts */ - static final String UNTRUSTED_RESOURCE = "untrusted.policy"; - - private static final String ALL_FILE_MASK = "read,readlink,write,delete,execute"; - private static final AllPermission ALL_PERMISSION = new AllPermission(); - - final Policy template; - final Policy untrusted; - final Policy system; - final PermissionCollection dynamic; - final PermissionCollection dataPathPermission; - final Map plugins; - final PermissionCollection allSecuredFiles; - final Map> securedFiles; - - @SuppressForbidden(reason = "Need to access and check file permissions directly") - ESPolicy( - Policy template, - PermissionCollection dynamic, - Map plugins, - boolean filterBadDefaults, - List dataPathPermissions, - Map> securedFiles - ) { - this.template = template; - this.dataPathPermission = createPermission(dataPathPermissions); - this.untrusted = PolicyUtil.readPolicy(getClass().getResource(UNTRUSTED_RESOURCE), Collections.emptyMap()); - if (filterBadDefaults) { - this.system = new SystemPolicy(Policy.getPolicy()); - } else { - this.system = Policy.getPolicy(); - } - this.dynamic = dynamic; - this.plugins = plugins; - - this.securedFiles = securedFiles.entrySet() - .stream() - .collect(Collectors.toUnmodifiableMap(e -> new FilePermission(e.getKey(), ALL_FILE_MASK), e -> Set.copyOf(e.getValue()))); - this.allSecuredFiles = createPermission(this.securedFiles.keySet()); - } - - private static PermissionCollection createPermission(Collection permissions) { - PermissionCollection coll; - var it = permissions.iterator(); - if (it.hasNext() == false) { - coll = new Permissions(); - } else { - Permission p = it.next(); - coll = p.newPermissionCollection(); - coll.add(p); - it.forEachRemaining(coll::add); - } - - coll.setReadOnly(); - return coll; - } - - private static PermissionCollection createPermission(List permissions) { - PermissionCollection coll = null; - for (FilePermission permission : permissions) { - if (coll == null) { - coll = permission.newPermissionCollection(); - } - coll.add(permission); - } - if (coll == null) { - coll = new Permissions(); - } - coll.setReadOnly(); - return coll; - } - - @Override - @SuppressForbidden(reason = "fast equals check is desired") - public boolean implies(ProtectionDomain domain, Permission permission) { - CodeSource codeSource = domain.getCodeSource(); - // codesource can be null when reducing privileges via doPrivileged() - if (codeSource == null) { - return false; - } - - URL location = codeSource.getLocation(); - if (allSecuredFiles.implies(permission)) { - /* - * Check if location can access this secured file - * The permission this is generated from, SecuredFileAccessPermission, doesn't have a mask, - * it just grants all access (and so disallows all access from others) - * It's helpful to use the infrastructure around FilePermission here to do the directory structure check with implies - * so we use ALL_FILE_MASK mask to check if we can do something with this file, whatever the actual operation we're requesting - */ - return canAccessSecuredFile(domain, new FilePermission(permission.getName(), ALL_FILE_MASK)); - } - - if (location != null) { - // run scripts with limited permissions - if (BootstrapInfo.UNTRUSTED_CODEBASE.equals(location.getFile())) { - return untrusted.implies(domain, permission); - } - // check for an additional plugin permission: plugin policy is - // only consulted for its codesources. - Policy plugin = plugins.get(location); - if (plugin != null && plugin.implies(domain, permission)) { - return true; - } - } - - // The FilePermission to check access to the path.data is the hottest permission check in - // Elasticsearch, so we explicitly check it here. - if (dataPathPermission.implies(permission)) { - return true; - } - - // Special handling for broken Hadoop code: "let me execute or my classes will not load" - // yeah right, REMOVE THIS when hadoop is fixed - if (permission instanceof FilePermission && "<>".equals(permission.getName())) { - hadoopHack(); - } - - // otherwise defer to template + dynamic file permissions - return template.implies(domain, permission) || dynamic.implies(permission) || system.implies(domain, permission); - } - - @SuppressForbidden(reason = "We get given an URL by the security infrastructure") - private boolean canAccessSecuredFile(ProtectionDomain domain, FilePermission permission) { - if (domain == null || domain.getCodeSource() == null || domain.getCodeSource().getLocation() == null) { - return false; - } - - // If the domain in question has AllPermission - only true of sources built into the JDK, as we prevent AllPermission from being - // configured in Elasticsearch - then it has access to this file. - - if (system.implies(domain, ALL_PERMISSION)) { - return true; - } - URL location = domain.getCodeSource().getLocation(); - - // check the source - Set accessibleSources = securedFiles.get(permission); - if (accessibleSources != null) { - // simple case - single-file referenced directly - - return accessibleSources.contains(location); - } else { - // there's a directory reference in there somewhere - // do a manual search :( - // there may be several permissions that potentially match, - // grant access if any of them cover this file - return securedFiles.entrySet() - .stream() - .filter(e -> e.getKey().implies(permission)) - .anyMatch(e -> e.getValue().contains(location)); - } - } - - private static void hadoopHack() { - for (StackTraceElement element : Thread.currentThread().getStackTrace()) { - if ("org.apache.hadoop.util.Shell".equals(element.getClassName()) && "runCommand".equals(element.getMethodName())) { - // we found the horrible method: the hack begins! - // force the hadoop code to back down, by throwing an exception that it catches. - rethrow(new IOException("no hadoop, you cannot do this.")); - } - } - } - - /** - * Classy puzzler to rethrow any checked exception as an unchecked one. - */ - private static class Rethrower { - @SuppressWarnings("unchecked") - private void rethrow(Throwable t) throws T { - throw (T) t; - } - } - - /** - * Rethrows t (identical object). - */ - private static void rethrow(Throwable t) { - new Rethrower().rethrow(t); - } - - @Override - public PermissionCollection getPermissions(CodeSource codesource) { - // code should not rely on this method, or at least use it correctly: - // https://bugs.openjdk.java.net/browse/JDK-8014008 - // return them a new empty permissions object so jvisualvm etc work - for (StackTraceElement element : Thread.currentThread().getStackTrace()) { - if ("sun.rmi.server.LoaderHandler".equals(element.getClassName()) && "loadClass".equals(element.getMethodName())) { - return new Permissions(); - } - } - // return UNSUPPORTED_EMPTY_COLLECTION since it is safe. - return super.getPermissions(codesource); - } - - // TODO: remove this hack when insecure defaults are removed from java - - /** - * Wraps a bad default permission, applying a pre-implies to any permissions before checking if the wrapped bad default permission - * implies a permission. - */ - private static class BadDefaultPermission extends Permission { - - private final Permission badDefaultPermission; - private final Predicate preImplies; - - /** - * Construct an instance with a pre-implies check to apply to desired permissions. - * - * @param badDefaultPermission the bad default permission to wrap - * @param preImplies a test that is applied to a desired permission before checking if the bad default permission that - * this instance wraps implies the desired permission - */ - BadDefaultPermission(final Permission badDefaultPermission, final Predicate preImplies) { - super(badDefaultPermission.getName()); - this.badDefaultPermission = badDefaultPermission; - this.preImplies = preImplies; - } - - @Override - public final boolean implies(Permission permission) { - return preImplies.test(permission) && badDefaultPermission.implies(permission); - } - - @Override - public final boolean equals(Object obj) { - return badDefaultPermission.equals(obj); - } - - @Override - public int hashCode() { - return badDefaultPermission.hashCode(); - } - - @Override - public String getActions() { - return badDefaultPermission.getActions(); - } - - } - - // default policy file states: - // "It is strongly recommended that you either remove this permission - // from this policy file or further restrict it to code sources - // that you specify, because Thread.stop() is potentially unsafe." - // not even sure this method still works... - private static final Permission BAD_DEFAULT_NUMBER_ONE = new BadDefaultPermission( - new RuntimePermission("stopThread"), - Predicates.always() - ); - - // default policy file states: - // "allows anyone to listen on dynamic ports" - // specified exactly because that is what we want, and fastest since it won't imply any - // expensive checks for the implicit "resolve" - private static final Permission BAD_DEFAULT_NUMBER_TWO = new BadDefaultPermission( - new SocketPermission("localhost:0", "listen"), - // we apply this pre-implies test because some SocketPermission#implies calls do expensive reverse-DNS resolves - p -> p instanceof SocketPermission && p.getActions().contains("listen") - ); - - /** - * Wraps the Java system policy, filtering out bad default permissions that - * are granted to all domains. Note, before java 8 these were even worse. - */ - static class SystemPolicy extends Policy { - final Policy delegate; - - SystemPolicy(Policy delegate) { - this.delegate = delegate; - } - - @Override - public boolean implies(ProtectionDomain domain, Permission permission) { - if (BAD_DEFAULT_NUMBER_ONE.implies(permission) || BAD_DEFAULT_NUMBER_TWO.implies(permission)) { - return false; - } - return delegate.implies(domain, permission); - } - } -} diff --git a/server/src/main/java/org/elasticsearch/bootstrap/PluginPolicyInfo.java b/server/src/main/java/org/elasticsearch/bootstrap/PluginPolicyInfo.java deleted file mode 100644 index c5fb06a1bcba3..0000000000000 --- a/server/src/main/java/org/elasticsearch/bootstrap/PluginPolicyInfo.java +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -package org.elasticsearch.bootstrap; - -import java.net.URL; -import java.nio.file.Path; -import java.security.Policy; -import java.util.Set; - -public record PluginPolicyInfo(Path file, Set jars, Policy policy) {} diff --git a/server/src/main/java/org/elasticsearch/bootstrap/PolicyUtil.java b/server/src/main/java/org/elasticsearch/bootstrap/PolicyUtil.java deleted file mode 100644 index 78cf0ee93a0e5..0000000000000 --- a/server/src/main/java/org/elasticsearch/bootstrap/PolicyUtil.java +++ /dev/null @@ -1,439 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -package org.elasticsearch.bootstrap; - -import org.elasticsearch.SecuredConfigFileAccessPermission; -import org.elasticsearch.SecuredConfigFileSettingAccessPermission; -import org.elasticsearch.core.IOUtils; -import org.elasticsearch.core.PathUtils; -import org.elasticsearch.core.SuppressForbidden; -import org.elasticsearch.plugins.PluginDescriptor; -import org.elasticsearch.script.ClassPermission; -import org.elasticsearch.secure_sm.ThreadPermission; - -import java.io.FilePermission; -import java.io.IOException; -import java.lang.reflect.ReflectPermission; -import java.net.NetPermission; -import java.net.SocketPermission; -import java.net.URISyntaxException; -import java.net.URL; -import java.net.URLPermission; -import java.nio.file.DirectoryStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.security.CodeSource; -import java.security.NoSuchAlgorithmException; -import java.security.Permission; -import java.security.PermissionCollection; -import java.security.Permissions; -import java.security.Policy; -import java.security.ProtectionDomain; -import java.security.SecurityPermission; -import java.security.URIParameter; -import java.security.UnresolvedPermission; -import java.security.cert.Certificate; -import java.sql.SQLPermission; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.PropertyPermission; -import java.util.Set; -import java.util.function.Predicate; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import javax.management.MBeanPermission; -import javax.management.MBeanServerPermission; -import javax.management.MBeanTrustPermission; -import javax.management.ObjectName; -import javax.security.auth.AuthPermission; -import javax.security.auth.PrivateCredentialPermission; -import javax.security.auth.kerberos.DelegationPermission; -import javax.security.auth.kerberos.ServicePermission; - -import static java.util.Map.entry; - -public class PolicyUtil { - - // this object is checked by reference, so the value in the list does not matter - static final List ALLOW_ALL_NAMES = List.of("ALLOW ALL NAMES SENTINEL"); - - static class PermissionMatcher implements Predicate { - - PermissionCollection namedPermissions; - Map> classPermissions; - - PermissionMatcher(PermissionCollection namedPermissions, Map> classPermissions) { - this.namedPermissions = namedPermissions; - this.classPermissions = classPermissions; - } - - @Override - public boolean test(Permission permission) { - if (namedPermissions.implies(permission)) { - return true; - } - String clazz = permission.getClass().getCanonicalName(); - String name = permission.getName(); - if (permission.getClass().equals(UnresolvedPermission.class)) { - UnresolvedPermission up = (UnresolvedPermission) permission; - clazz = up.getUnresolvedType(); - name = up.getUnresolvedName(); - } - List allowedNames = classPermissions.get(clazz); - return allowedNames != null && (allowedNames == ALLOW_ALL_NAMES || allowedNames.contains(name)); - } - } - - private static final PermissionMatcher ALLOWED_PLUGIN_PERMISSIONS; - private static final PermissionMatcher ALLOWED_MODULE_PERMISSIONS; - static { - List namedPermissions = List.of( - // TODO: remove read permission, see https://github.com/elastic/elasticsearch/issues/69464 - createFilePermission("<>", "read"), - - new ReflectPermission("suppressAccessChecks"), - new RuntimePermission("getClassLoader"), - new RuntimePermission("setContextClassLoader"), - new RuntimePermission("setFactory"), - new RuntimePermission("loadLibrary.*"), - new RuntimePermission("accessClassInPackage.*"), - new RuntimePermission("accessDeclaredMembers"), - new NetPermission("requestPasswordAuthentication"), - new NetPermission("getProxySelector"), - new NetPermission("getCookieHandler"), - new NetPermission("getResponseCache"), - new SocketPermission("*", "accept,connect,listen,resolve"), - new SecurityPermission("createAccessControlContext"), - new SecurityPermission("insertProvider"), - new SecurityPermission("putProviderProperty.*"), - // apache abuses the SecurityPermission class for it's own purposes - new SecurityPermission("org.apache.*"), - // write is needed because of HdfsPlugin - new PropertyPermission("*", "read,write"), - new AuthPermission("doAs"), - new AuthPermission("doAsPrivileged"), - new AuthPermission("getSubject"), - new AuthPermission("getSubjectFromDomainCombiner"), - new AuthPermission("setReadOnly"), - new AuthPermission("modifyPrincipals"), - new AuthPermission("modifyPublicCredentials"), - new AuthPermission("modifyPrivateCredentials"), - new AuthPermission("refreshCredential"), - new AuthPermission("destroyCredential"), - new AuthPermission("createLoginContext.*"), - new AuthPermission("getLoginConfiguration"), - new AuthPermission("setLoginConfiguration"), - new AuthPermission("createLoginConfiguration.*"), - new AuthPermission("refreshLoginConfiguration"), - new MBeanPermission( - "*", - "*", - ObjectName.WILDCARD, - "addNotificationListener,getAttribute,getDomains,getMBeanInfo,getObjectInstance,instantiate,invoke," - + "isInstanceOf,queryMBeans,queryNames,registerMBean,removeNotificationListener,setAttribute,unregisterMBean" - ), - new MBeanServerPermission("*"), - new MBeanTrustPermission("register") - ); - // While it would be ideal to represent all allowed permissions with concrete instances so that we can - // use the builtin implies method to match them against the parsed policy, this does not work in all - // cases for two reasons: - // (1) Some permissions classes do not have a name argument that can represent all possible variants. - // For example, FilePermission has "<< ALL FILES >>" so all paths can be matched, but DelegationPermission - // does not have anything to represent all principals. - // (2) Some permissions classes are in java modules that are not accessible from the classloader used by - // the policy parser. This results in those permissions being in UnresolvedPermission instances. Those - // are normally resolved at runtime when that permission is checked by SecurityManager. But there is - // no general purpose utility to resolve those permissions, so we must be able to match those - // unresolved permissions in the policy by class and name values. - // Given the above, the below map is from permission class to the list of allowed name values. A sentinel value - // is used to mean names are accepted. We do not use this model for all permissions because many permission - // classes have their own meaning for some form of wildcard matching of the name, which we want to delegate - // to those permissions if possible. - Map> classPermissions = Stream.of( - entry(URLPermission.class, ALLOW_ALL_NAMES), - entry(DelegationPermission.class, ALLOW_ALL_NAMES), - entry(ServicePermission.class, ALLOW_ALL_NAMES), - entry(PrivateCredentialPermission.class, ALLOW_ALL_NAMES), - entry(SQLPermission.class, List.of("callAbort", "setNetworkTimeout")), - entry(ClassPermission.class, ALLOW_ALL_NAMES), - entry(SecuredConfigFileAccessPermission.class, ALLOW_ALL_NAMES), - entry(SecuredConfigFileSettingAccessPermission.class, ALLOW_ALL_NAMES) - ).collect(Collectors.toMap(e -> e.getKey().getCanonicalName(), Map.Entry::getValue)); - PermissionCollection pluginPermissionCollection = new Permissions(); - namedPermissions.forEach(pluginPermissionCollection::add); - pluginPermissionCollection.setReadOnly(); - ALLOWED_PLUGIN_PERMISSIONS = new PermissionMatcher(pluginPermissionCollection, classPermissions); - - // Modules are allowed a few extra permissions. While we should strive to keep this list small, modules - // are essentially part of core, so these are permissions we need for various reasons in core functionality, - // but that we do not think plugins in general should need. - List modulePermissions = List.of( - createFilePermission("<>", "read,write"), - new RuntimePermission("createClassLoader"), - new RuntimePermission("getFileStoreAttributes"), - new RuntimePermission("accessUserInformation"), - new AuthPermission("modifyPrivateCredentials"), - new RuntimePermission("accessSystemModules") - ); - PermissionCollection modulePermissionCollection = new Permissions(); - namedPermissions.forEach(modulePermissionCollection::add); - modulePermissions.forEach(modulePermissionCollection::add); - modulePermissionCollection.setReadOnly(); - Map> moduleClassPermissions = new HashMap<>(classPermissions); - moduleClassPermissions.put( - // Not available to the SecurityManager ClassLoader. See classPermissions comment. - ThreadPermission.class.getCanonicalName(), - List.of("modifyArbitraryThreadGroup") - ); - moduleClassPermissions = Collections.unmodifiableMap(moduleClassPermissions); - ALLOWED_MODULE_PERMISSIONS = new PermissionMatcher(modulePermissionCollection, moduleClassPermissions); - } - - @SuppressForbidden(reason = "create permission for test") - private static FilePermission createFilePermission(String path, String actions) { - return new FilePermission(path, actions); - } - - /** - * Return a map from codebase name to codebase url of jar codebases used by ES core. - */ - @SuppressForbidden(reason = "find URL path") - public static Map getCodebaseJarMap(Set urls) { - Map codebases = new LinkedHashMap<>(); // maintain order - for (URL url : urls) { - try { - String fileName = PathUtils.get(url.toURI()).getFileName().toString(); - if (fileName.endsWith(".jar") == false) { - // tests :( - continue; - } - codebases.put(fileName, url); - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } - } - return codebases; - } - - /** - * Reads and returns the specified {@code policyFile}. - *

- * Jar files listed in {@code codebases} location will be provided to the policy file via - * a system property of the short name: e.g. ${codebase.my-dep-1.2.jar} - * would map to full URL. - */ - @SuppressForbidden(reason = "accesses fully qualified URLs to configure security") - public static Policy readPolicy(URL policyFile, Map codebases) { - try { - Properties originalProps = System.getProperties(); - // allow missing while still setting values - Set unknownCodebases = new HashSet<>(); - Map codebaseProperties = new HashMap<>(); - Properties tempProps = new Properties(originalProps) { - @Override - public String getProperty(String key) { - if (key.startsWith("codebase.")) { - String value = codebaseProperties.get(key); - if (value == null) { - unknownCodebases.add(key); - } - return value; - } else { - return super.getProperty(key); - } - } - }; - - try { - System.setProperties(tempProps); - // set codebase properties - for (Map.Entry codebase : codebases.entrySet()) { - String name = codebase.getKey(); - URL url = codebase.getValue(); - - // We attempt to use a versionless identifier for each codebase. This assumes a specific version - // format in the jar filename. While we cannot ensure all jars in all plugins use this format, nonconformity - // only means policy grants would need to include the entire jar filename as they always have before. - String property = "codebase." + name; - String aliasProperty = "codebase." + name.replaceFirst("-\\d+\\.\\d+.*\\.jar", ""); - if (aliasProperty.equals(property) == false) { - - Object previous = codebaseProperties.put(aliasProperty, url.toString()); - if (previous != null) { - throw new IllegalStateException( - "codebase property already set: " + aliasProperty + " -> " + previous + ", cannot set to " + url.toString() - ); - } - } - Object previous = codebaseProperties.put(property, url.toString()); - if (previous != null) { - throw new IllegalStateException( - "codebase property already set: " + property + " -> " + previous + ", cannot set to " + url.toString() - ); - } - } - Policy policy = Policy.getInstance("JavaPolicy", new URIParameter(policyFile.toURI())); - if (unknownCodebases.isEmpty() == false) { - throw new IllegalArgumentException( - "Unknown codebases " - + unknownCodebases - + " in policy file [" - + policyFile - + "]" - + "\nAvailable codebases: \n " - + String.join("\n ", codebaseProperties.keySet().stream().sorted().toList()) - ); - } - return policy; - } finally { - System.setProperties(originalProps); - } - } catch (NoSuchAlgorithmException | URISyntaxException e) { - throw new IllegalArgumentException("unable to parse policy file `" + policyFile + "`", e); - } - } - - // package private for tests - static PluginPolicyInfo readPolicyInfo(Path pluginRoot) throws IOException { - Path policyFile = pluginRoot.resolve(PluginDescriptor.ES_PLUGIN_POLICY); - if (Files.exists(policyFile) == false) { - return null; - } - - // first get a list of URLs for the plugins' jars: - // we resolve symlinks so map is keyed on the normalize codebase name - Set jars = new LinkedHashSet<>(); // order is already lost, but some filesystems have it - try (DirectoryStream jarStream = Files.newDirectoryStream(pluginRoot, "*.jar")) { - for (Path jar : jarStream) { - URL url = jar.toRealPath().toUri().toURL(); - if (jars.add(url) == false) { - throw new IllegalStateException("duplicate module/plugin: " + url); - } - } - } - // also add spi jars - // TODO: move this to a shared function, or fix plugin layout to have jar files in lib directory - Path spiDir = pluginRoot.resolve("spi"); - if (Files.exists(spiDir)) { - try (DirectoryStream jarStream = Files.newDirectoryStream(spiDir, "*.jar")) { - for (Path jar : jarStream) { - URL url = jar.toRealPath().toUri().toURL(); - if (jars.add(url) == false) { - throw new IllegalStateException("duplicate module/plugin: " + url); - } - } - } - } - - // parse the plugin's policy file into a set of permissions - Policy policy = readPolicy(policyFile.toUri().toURL(), getCodebaseJarMap(jars)); - - return new PluginPolicyInfo(policyFile, jars, policy); - } - - private static void validatePolicyPermissionsForJar( - String type, - Path file, - URL jar, - Policy policy, - PermissionMatcher allowedPermissions, - Path tmpDir - ) throws IOException { - Set jarPermissions = getPolicyPermissions(jar, policy, tmpDir); - for (Permission permission : jarPermissions) { - if (allowedPermissions.test(permission) == false) { - String scope = jar == null ? " in global grant" : " for jar " + jar; - throw new IllegalArgumentException(type + " policy [" + file + "] contains illegal permission " + permission + scope); - } - } - } - - private static void validatePolicyPermissions(String type, PluginPolicyInfo info, PermissionMatcher allowedPermissions, Path tmpDir) - throws IOException { - if (info == null) { - return; - } - validatePolicyPermissionsForJar(type, info.file(), null, info.policy(), allowedPermissions, tmpDir); - for (URL jar : info.jars()) { - validatePolicyPermissionsForJar(type, info.file(), jar, info.policy(), allowedPermissions, tmpDir); - } - } - - /** - * Return info about the security policy for a plugin. - */ - public static PluginPolicyInfo getPluginPolicyInfo(Path pluginRoot, Path tmpDir) throws IOException { - PluginPolicyInfo info = readPolicyInfo(pluginRoot); - validatePolicyPermissions("plugin", info, ALLOWED_PLUGIN_PERMISSIONS, tmpDir); - return info; - } - - /** - * Return info about the security policy for a module. - */ - public static PluginPolicyInfo getModulePolicyInfo(Path moduleRoot, Path tmpDir) throws IOException { - PluginPolicyInfo info = readPolicyInfo(moduleRoot); - validatePolicyPermissions("module", info, ALLOWED_MODULE_PERMISSIONS, tmpDir); - return info; - } - - /** - * Return permissions for a policy that apply to a jar. - * - * @param url The url of a jar to find permissions for, or {@code null} for global permissions. - */ - public static Set getPolicyPermissions(URL url, Policy policy, Path tmpDir) throws IOException { - // create a zero byte file for "comparison" - // this is necessary because the default policy impl automatically grants two permissions: - // 1. permission to exitVM (which we ignore) - // 2. read permission to the code itself (e.g. jar file of the code) - - Path emptyPolicyFile = Files.createTempFile(tmpDir, "empty", "tmp"); - final Policy emptyPolicy; - try { - emptyPolicy = Policy.getInstance("JavaPolicy", new URIParameter(emptyPolicyFile.toUri())); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } - IOUtils.rm(emptyPolicyFile); - - final ProtectionDomain protectionDomain; - if (url == null) { - // global, use PolicyUtil since it is part of core ES - protectionDomain = PolicyUtil.class.getProtectionDomain(); - } else { - // we may not have the url loaded, so create a fake protection domain - protectionDomain = new ProtectionDomain(new CodeSource(url, (Certificate[]) null), null); - } - - PermissionCollection permissions = policy.getPermissions(protectionDomain); - // this method is supported with the specific implementation we use, but just check for safety. - if (permissions == Policy.UNSUPPORTED_EMPTY_COLLECTION) { - throw new UnsupportedOperationException("JavaPolicy implementation does not support retrieving permissions"); - } - - Set actualPermissions = new HashSet<>(); - for (Permission permission : Collections.list(permissions.elements())) { - if (emptyPolicy.implies(protectionDomain, permission) == false) { - actualPermissions.add(permission); - } - } - - return actualPermissions; - } -} diff --git a/server/src/test/java/org/elasticsearch/bootstrap/ESPolicyTests.java b/server/src/test/java/org/elasticsearch/bootstrap/ESPolicyTests.java deleted file mode 100644 index 1660eeee837b3..0000000000000 --- a/server/src/test/java/org/elasticsearch/bootstrap/ESPolicyTests.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -package org.elasticsearch.bootstrap; - -import org.elasticsearch.jdk.RuntimeVersionFeature; -import org.elasticsearch.test.ESTestCase; - -import java.security.AccessControlContext; -import java.security.AccessController; -import java.security.PermissionCollection; -import java.security.Permissions; -import java.security.PrivilegedAction; -import java.security.ProtectionDomain; - -/** - * Tests for ESPolicy - */ -public class ESPolicyTests extends ESTestCase { - - /** - * test restricting privileges to no permissions actually works - */ - public void testRestrictPrivileges() { - assumeTrue( - "test requires security manager", - RuntimeVersionFeature.isSecurityManagerAvailable() && System.getSecurityManager() != null - ); - try { - System.getProperty("user.home"); - } catch (SecurityException e) { - fail("this test needs to be fixed: user.home not available by policy"); - } - - PermissionCollection noPermissions = new Permissions(); - AccessControlContext noPermissionsAcc = new AccessControlContext( - new ProtectionDomain[] { new ProtectionDomain(null, noPermissions) } - ); - try { - AccessController.doPrivileged(new PrivilegedAction() { - public Void run() { - System.getProperty("user.home"); - fail("access should have been denied"); - return null; - } - }, noPermissionsAcc); - } catch (SecurityException expected) { - // expected exception - } - } -} diff --git a/test/framework/src/main/java/org/elasticsearch/bootstrap/BootstrapForTesting.java b/test/framework/src/main/java/org/elasticsearch/bootstrap/BootstrapForTesting.java index 09735ad0c3d5c..98cc8ea7f777b 100644 --- a/test/framework/src/main/java/org/elasticsearch/bootstrap/BootstrapForTesting.java +++ b/test/framework/src/main/java/org/elasticsearch/bootstrap/BootstrapForTesting.java @@ -21,10 +21,8 @@ import java.io.IOException; import java.lang.invoke.MethodHandles; -import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Map; import java.util.Objects; /** @@ -88,40 +86,6 @@ public class BootstrapForTesting { IfConfig.logIfNecessary(); } - static Map getCodebases() { - Map codebases = PolicyUtil.getCodebaseJarMap(JarHell.parseClassPath()); - // when testing server, the main elasticsearch code is not yet in a jar, so we need to manually add it - addClassCodebase(codebases, "elasticsearch", "org.elasticsearch.plugins.PluginsService"); - addClassCodebase(codebases, "elasticsearch-plugin-classloader", "org.elasticsearch.plugins.loader.ExtendedPluginsClassLoader"); - addClassCodebase(codebases, "elasticsearch-nio", "org.elasticsearch.nio.ChannelFactory"); - addClassCodebase(codebases, "elasticsearch-secure-sm", "org.elasticsearch.secure_sm.SecureSM"); - addClassCodebase(codebases, "elasticsearch-rest-client", "org.elasticsearch.client.RestClient"); - addClassCodebase(codebases, "elasticsearch-core", "org.elasticsearch.core.Booleans"); - addClassCodebase(codebases, "elasticsearch-cli", "org.elasticsearch.cli.Command"); - addClassCodebase(codebases, "elasticsearch-simdvec", "org.elasticsearch.simdvec.VectorScorerFactory"); - addClassCodebase(codebases, "framework", "org.elasticsearch.test.ESTestCase"); - return codebases; - } - - /** Add the codebase url of the given classname to the codebases map, if the class exists. */ - private static void addClassCodebase(Map codebases, String name, String classname) { - try { - if (codebases.containsKey(name)) { - return; // the codebase already exists, from the classpath - } - Class clazz = BootstrapForTesting.class.getClassLoader().loadClass(classname); - URL location = clazz.getProtectionDomain().getCodeSource().getLocation(); - if (location.toString().endsWith(".jar") == false) { - if (codebases.put(name, location) != null) { - throw new IllegalStateException("Already added " + name + " codebase for testing"); - } - } - } catch (ClassNotFoundException e) { - // no class, fall through to not add. this can happen for any tests that do not include - // the given class. eg only core tests include plugin-classloader - } - } - // does nothing, just easy way to make sure the class is loaded. public static void ensureInitialized() {} }