Cleanup plugin security
* plugin authors can use full policy syntax, including codebase substitution properties like core syntax. * simplify test logic. * move out test-framework permissions to separate file. Closes #14311
This commit is contained in:
parent
ad1e3ab925
commit
eec3c2a97c
|
@ -236,7 +236,7 @@
|
||||||
<includes>
|
<includes>
|
||||||
<include>org/elasticsearch/test/**/*</include>
|
<include>org/elasticsearch/test/**/*</include>
|
||||||
<include>org/elasticsearch/bootstrap/BootstrapForTesting.class</include>
|
<include>org/elasticsearch/bootstrap/BootstrapForTesting.class</include>
|
||||||
<include>org/elasticsearch/bootstrap/MockPluginPolicy.class</include>
|
<include>org/elasticsearch/bootstrap/BootstrapForTesting$*.class</include>
|
||||||
<include>org/elasticsearch/common/cli/CliToolTestCase.class</include>
|
<include>org/elasticsearch/common/cli/CliToolTestCase.class</include>
|
||||||
<include>org/elasticsearch/common/cli/CliToolTestCase$*.class</include>
|
<include>org/elasticsearch/common/cli/CliToolTestCase$*.class</include>
|
||||||
</includes>
|
</includes>
|
||||||
|
@ -265,7 +265,7 @@
|
||||||
<include>rest-api-spec/**/*</include>
|
<include>rest-api-spec/**/*</include>
|
||||||
<include>org/elasticsearch/test/**/*</include>
|
<include>org/elasticsearch/test/**/*</include>
|
||||||
<include>org/elasticsearch/bootstrap/BootstrapForTesting.class</include>
|
<include>org/elasticsearch/bootstrap/BootstrapForTesting.class</include>
|
||||||
<include>org/elasticsearch/bootstrap/MockPluginPolicy.class</include>
|
<include>org/elasticsearch/bootstrap/BootstrapForTesting$*.class</include>
|
||||||
<include>org/elasticsearch/common/cli/CliToolTestCase.class</include>
|
<include>org/elasticsearch/common/cli/CliToolTestCase.class</include>
|
||||||
<include>org/elasticsearch/common/cli/CliToolTestCase$*.class</include>
|
<include>org/elasticsearch/common/cli/CliToolTestCase$*.class</include>
|
||||||
<include>org/elasticsearch/cluster/MockInternalClusterInfoService.class</include>
|
<include>org/elasticsearch/cluster/MockInternalClusterInfoService.class</include>
|
||||||
|
|
|
@ -21,14 +21,12 @@ package org.elasticsearch.bootstrap;
|
||||||
|
|
||||||
import org.elasticsearch.common.SuppressForbidden;
|
import org.elasticsearch.common.SuppressForbidden;
|
||||||
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.security.CodeSource;
|
import java.security.CodeSource;
|
||||||
import java.security.Permission;
|
import java.security.Permission;
|
||||||
import java.security.PermissionCollection;
|
import java.security.PermissionCollection;
|
||||||
import java.security.Policy;
|
import java.security.Policy;
|
||||||
import java.security.ProtectionDomain;
|
import java.security.ProtectionDomain;
|
||||||
import java.security.URIParameter;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/** custom policy for union of static and dynamic permissions */
|
/** custom policy for union of static and dynamic permissions */
|
||||||
|
@ -42,13 +40,11 @@ final class ESPolicy extends Policy {
|
||||||
final Policy template;
|
final Policy template;
|
||||||
final Policy untrusted;
|
final Policy untrusted;
|
||||||
final PermissionCollection dynamic;
|
final PermissionCollection dynamic;
|
||||||
final Map<String,PermissionCollection> plugins;
|
final Map<String,Policy> plugins;
|
||||||
|
|
||||||
public ESPolicy(PermissionCollection dynamic, Map<String,PermissionCollection> plugins) throws Exception {
|
public ESPolicy(PermissionCollection dynamic, Map<String,Policy> plugins) {
|
||||||
URI policyUri = getClass().getResource(POLICY_RESOURCE).toURI();
|
this.template = Security.readPolicy(getClass().getResource(POLICY_RESOURCE), JarHell.parseClassPath());
|
||||||
URI untrustedUri = getClass().getResource(UNTRUSTED_RESOURCE).toURI();
|
this.untrusted = Security.readPolicy(getClass().getResource(UNTRUSTED_RESOURCE), new URL[0]);
|
||||||
this.template = Policy.getInstance("JavaPolicy", new URIParameter(policyUri));
|
|
||||||
this.untrusted = Policy.getInstance("JavaPolicy", new URIParameter(untrustedUri));
|
|
||||||
this.dynamic = dynamic;
|
this.dynamic = dynamic;
|
||||||
this.plugins = plugins;
|
this.plugins = plugins;
|
||||||
}
|
}
|
||||||
|
@ -69,9 +65,10 @@ final class ESPolicy extends Policy {
|
||||||
if (BootstrapInfo.UNTRUSTED_CODEBASE.equals(location.getFile())) {
|
if (BootstrapInfo.UNTRUSTED_CODEBASE.equals(location.getFile())) {
|
||||||
return untrusted.implies(domain, permission);
|
return untrusted.implies(domain, permission);
|
||||||
}
|
}
|
||||||
// check for an additional plugin permission
|
// check for an additional plugin permission: plugin policy is
|
||||||
PermissionCollection plugin = plugins.get(location.getFile());
|
// only consulted for its codesources.
|
||||||
if (plugin != null && plugin.implies(permission)) {
|
Policy plugin = plugins.get(location.getFile());
|
||||||
|
if (plugin != null && plugin.implies(domain, permission)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,10 +20,12 @@
|
||||||
package org.elasticsearch.bootstrap;
|
package org.elasticsearch.bootstrap;
|
||||||
|
|
||||||
import org.elasticsearch.common.SuppressForbidden;
|
import org.elasticsearch.common.SuppressForbidden;
|
||||||
|
import org.elasticsearch.common.io.PathUtils;
|
||||||
import org.elasticsearch.env.Environment;
|
import org.elasticsearch.env.Environment;
|
||||||
import org.elasticsearch.plugins.PluginInfo;
|
import org.elasticsearch.plugins.PluginInfo;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.nio.file.AccessMode;
|
import java.nio.file.AccessMode;
|
||||||
import java.nio.file.DirectoryStream;
|
import java.nio.file.DirectoryStream;
|
||||||
|
@ -32,15 +34,14 @@ import java.nio.file.Files;
|
||||||
import java.nio.file.NotDirectoryException;
|
import java.nio.file.NotDirectoryException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.PermissionCollection;
|
|
||||||
import java.security.Permissions;
|
import java.security.Permissions;
|
||||||
import java.security.Policy;
|
import java.security.Policy;
|
||||||
import java.security.URIParameter;
|
import java.security.URIParameter;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.IdentityHashMap;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes SecurityManager with necessary permissions.
|
* Initializes SecurityManager with necessary permissions.
|
||||||
|
@ -99,8 +100,6 @@ final class Security {
|
||||||
* Can only happen once!
|
* Can only happen once!
|
||||||
*/
|
*/
|
||||||
static void configure(Environment environment) throws Exception {
|
static void configure(Environment environment) throws Exception {
|
||||||
// set properties for jar locations
|
|
||||||
setCodebaseProperties();
|
|
||||||
|
|
||||||
// enable security policy: union of template and environment-based paths, and possibly plugin permissions
|
// enable security policy: union of template and environment-based paths, and possibly plugin permissions
|
||||||
Policy.setPolicy(new ESPolicy(createPermissions(environment), getPluginPermissions(environment)));
|
Policy.setPolicy(new ESPolicy(createPermissions(environment), getPluginPermissions(environment)));
|
||||||
|
@ -121,70 +120,34 @@ final class Security {
|
||||||
selfTest();
|
selfTest();
|
||||||
}
|
}
|
||||||
|
|
||||||
// mapping of jars to codebase properties
|
|
||||||
// note that this is only read once, when policy is parsed.
|
|
||||||
private static final Map<Pattern,String> SPECIAL_JARS;
|
|
||||||
static {
|
|
||||||
Map<Pattern,String> m = new IdentityHashMap<>();
|
|
||||||
m.put(Pattern.compile(".*lucene-core-.*\\.jar$"), "es.security.jar.lucene.core");
|
|
||||||
m.put(Pattern.compile(".*lucene-test-framework-.*\\.jar$"), "es.security.jar.lucene.testframework");
|
|
||||||
m.put(Pattern.compile(".*randomizedtesting-runner-.*\\.jar$"), "es.security.jar.randomizedtesting.runner");
|
|
||||||
m.put(Pattern.compile(".*junit4-ant-.*\\.jar$"), "es.security.jar.randomizedtesting.junit4");
|
|
||||||
m.put(Pattern.compile(".*securemock-.*\\.jar$"), "es.security.jar.elasticsearch.securemock");
|
|
||||||
SPECIAL_JARS = Collections.unmodifiableMap(m);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets properties (codebase URLs) for policy files.
|
|
||||||
* JAR locations are not fixed so we have to find the locations of
|
|
||||||
* the ones we want.
|
|
||||||
*/
|
|
||||||
@SuppressForbidden(reason = "proper use of URL")
|
|
||||||
static void setCodebaseProperties() {
|
|
||||||
for (URL url : JarHell.parseClassPath()) {
|
|
||||||
for (Map.Entry<Pattern,String> e : SPECIAL_JARS.entrySet()) {
|
|
||||||
if (e.getKey().matcher(url.getPath()).matches()) {
|
|
||||||
String prop = e.getValue();
|
|
||||||
if (System.getProperty(prop) != null) {
|
|
||||||
throw new IllegalStateException("property: " + prop + " is unexpectedly set: " + System.getProperty(prop));
|
|
||||||
}
|
|
||||||
System.setProperty(prop, url.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (String prop : SPECIAL_JARS.values()) {
|
|
||||||
if (System.getProperty(prop) == null) {
|
|
||||||
System.setProperty(prop, "file:/dev/null"); // no chance to be interpreted as "all"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets properties (codebase URLs) for policy files.
|
* Sets properties (codebase URLs) for policy files.
|
||||||
* we look for matching plugins and set URLs to fit
|
* we look for matching plugins and set URLs to fit
|
||||||
*/
|
*/
|
||||||
@SuppressForbidden(reason = "proper use of URL")
|
@SuppressForbidden(reason = "proper use of URL")
|
||||||
static Map<String,PermissionCollection> getPluginPermissions(Environment environment) throws IOException, NoSuchAlgorithmException {
|
static Map<String,Policy> getPluginPermissions(Environment environment) throws IOException, NoSuchAlgorithmException {
|
||||||
Map<String,PermissionCollection> map = new HashMap<>();
|
Map<String,Policy> map = new HashMap<>();
|
||||||
if (Files.exists(environment.pluginsFile())) {
|
if (Files.exists(environment.pluginsFile())) {
|
||||||
try (DirectoryStream<Path> stream = Files.newDirectoryStream(environment.pluginsFile())) {
|
try (DirectoryStream<Path> stream = Files.newDirectoryStream(environment.pluginsFile())) {
|
||||||
for (Path plugin : stream) {
|
for (Path plugin : stream) {
|
||||||
Path policyFile = plugin.resolve(PluginInfo.ES_PLUGIN_POLICY);
|
Path policyFile = plugin.resolve(PluginInfo.ES_PLUGIN_POLICY);
|
||||||
if (Files.exists(policyFile)) {
|
if (Files.exists(policyFile)) {
|
||||||
// parse the plugin's policy file into a set of permissions
|
// first get a list of URLs for the plugins' jars:
|
||||||
Policy policy = Policy.getInstance("JavaPolicy", new URIParameter(policyFile.toUri()));
|
List<URL> codebases = new ArrayList<>();
|
||||||
PermissionCollection permissions = policy.getPermissions(Security.class.getProtectionDomain());
|
|
||||||
// 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");
|
|
||||||
}
|
|
||||||
// grant the permissions to each jar in the plugin
|
|
||||||
try (DirectoryStream<Path> jarStream = Files.newDirectoryStream(plugin, "*.jar")) {
|
try (DirectoryStream<Path> jarStream = Files.newDirectoryStream(plugin, "*.jar")) {
|
||||||
for (Path jar : jarStream) {
|
for (Path jar : jarStream) {
|
||||||
if (map.put(jar.toUri().toURL().getFile(), permissions) != null) {
|
codebases.add(jar.toUri().toURL());
|
||||||
// just be paranoid ok?
|
}
|
||||||
throw new IllegalStateException("per-plugin permissions already granted for jar file: " + jar);
|
}
|
||||||
}
|
|
||||||
|
// parse the plugin's policy file into a set of permissions
|
||||||
|
Policy policy = readPolicy(policyFile.toUri().toURL(), codebases.toArray(new URL[codebases.size()]));
|
||||||
|
|
||||||
|
// consult this policy for each of the plugin's jars:
|
||||||
|
for (URL url : codebases) {
|
||||||
|
if (map.put(url.getFile(), policy) != null) {
|
||||||
|
// just be paranoid ok?
|
||||||
|
throw new IllegalStateException("per-plugin permissions already granted for jar file: " + url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -194,6 +157,35 @@ final class Security {
|
||||||
return Collections.unmodifiableMap(map);
|
return Collections.unmodifiableMap(map);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads and returns the specified {@code policyFile}.
|
||||||
|
* <p>
|
||||||
|
* Resources (e.g. jar files and directories) listed in {@code codebases} location
|
||||||
|
* will be provided to the policy file via a system property of the short name:
|
||||||
|
* e.g. <code>${codebase.joda-convert-1.2.jar}</code> would map to full URL.
|
||||||
|
*/
|
||||||
|
@SuppressForbidden(reason = "accesses fully qualified URLs to configure security")
|
||||||
|
static Policy readPolicy(URL policyFile, URL codebases[]) {
|
||||||
|
try {
|
||||||
|
try {
|
||||||
|
// set codebase properties
|
||||||
|
for (URL url : codebases) {
|
||||||
|
String shortName = PathUtils.get(url.toURI()).getFileName().toString();
|
||||||
|
System.setProperty("codebase." + shortName, url.toString());
|
||||||
|
}
|
||||||
|
return Policy.getInstance("JavaPolicy", new URIParameter(policyFile.toURI()));
|
||||||
|
} finally {
|
||||||
|
// clear codebase properties
|
||||||
|
for (URL url : codebases) {
|
||||||
|
String shortName = PathUtils.get(url.toURI()).getFileName().toString();
|
||||||
|
System.clearProperty("codebase." + shortName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (NoSuchAlgorithmException | URISyntaxException e) {
|
||||||
|
throw new IllegalArgumentException("unable to parse policy file `" + policyFile + "`", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** returns dynamic Permissions to configured paths */
|
/** returns dynamic Permissions to configured paths */
|
||||||
static Permissions createPermissions(Environment environment) throws IOException {
|
static Permissions createPermissions(Environment environment) throws IOException {
|
||||||
Permissions policy = new Permissions();
|
Permissions policy = new Permissions();
|
||||||
|
|
|
@ -31,46 +31,12 @@ grant codeBase "file:${{java.ext.dirs}}/*" {
|
||||||
//// Very special jar permissions:
|
//// Very special jar permissions:
|
||||||
//// These are dangerous permissions that we don't want to grant to everything.
|
//// These are dangerous permissions that we don't want to grant to everything.
|
||||||
|
|
||||||
grant codeBase "${es.security.jar.lucene.core}" {
|
grant codeBase "${codebase.lucene-core-5.4.0-snapshot-1708254.jar}" {
|
||||||
// needed to allow MMapDirectory's "unmap hack"
|
// needed to allow MMapDirectory's "unmap hack"
|
||||||
permission java.lang.RuntimePermission "accessClassInPackage.sun.misc";
|
permission java.lang.RuntimePermission "accessClassInPackage.sun.misc";
|
||||||
permission java.lang.reflect.ReflectPermission "suppressAccessChecks";
|
permission java.lang.reflect.ReflectPermission "suppressAccessChecks";
|
||||||
};
|
};
|
||||||
|
|
||||||
//// test framework permissions.
|
|
||||||
//// These are mock objects and test management that we allow test framework libs
|
|
||||||
//// to provide on our behalf. But tests themselves cannot do this stuff!
|
|
||||||
|
|
||||||
grant codeBase "${es.security.jar.elasticsearch.securemock}" {
|
|
||||||
// needed to access ReflectionFactory (see below)
|
|
||||||
permission java.lang.RuntimePermission "accessClassInPackage.sun.reflect";
|
|
||||||
// needed to support creation of mocks
|
|
||||||
permission java.lang.RuntimePermission "reflectionFactoryAccess";
|
|
||||||
// needed for spy interception, etc
|
|
||||||
permission java.lang.reflect.ReflectPermission "suppressAccessChecks";
|
|
||||||
};
|
|
||||||
|
|
||||||
grant codeBase "${es.security.jar.lucene.testframework}" {
|
|
||||||
// needed by RamUsageTester
|
|
||||||
permission java.lang.reflect.ReflectPermission "suppressAccessChecks";
|
|
||||||
};
|
|
||||||
|
|
||||||
grant codeBase "${es.security.jar.randomizedtesting.runner}" {
|
|
||||||
// optionally needed for access to private test methods (e.g. beforeClass)
|
|
||||||
permission java.lang.reflect.ReflectPermission "suppressAccessChecks";
|
|
||||||
|
|
||||||
// needed for top threads handling
|
|
||||||
permission java.lang.RuntimePermission "modifyThreadGroup";
|
|
||||||
};
|
|
||||||
|
|
||||||
grant codeBase "${es.security.jar.randomizedtesting.junit4}" {
|
|
||||||
// needed for gson serialization
|
|
||||||
permission java.lang.reflect.ReflectPermission "suppressAccessChecks";
|
|
||||||
|
|
||||||
// needed for stream redirection
|
|
||||||
permission java.lang.RuntimePermission "setIO";
|
|
||||||
};
|
|
||||||
|
|
||||||
//// Everything else:
|
//// Everything else:
|
||||||
|
|
||||||
grant {
|
grant {
|
||||||
|
@ -126,10 +92,4 @@ grant {
|
||||||
|
|
||||||
// needed by JDKESLoggerTests
|
// needed by JDKESLoggerTests
|
||||||
permission java.util.logging.LoggingPermission "control";
|
permission java.util.logging.LoggingPermission "control";
|
||||||
|
|
||||||
// needed to install SSLFactories, advanced SSL configuration, etc.
|
|
||||||
permission java.lang.RuntimePermission "setFactory";
|
|
||||||
|
|
||||||
// needed to allow installation of bouncycastle crypto provider
|
|
||||||
permission java.security.SecurityPermission "putProviderProperty.BC";
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Elasticsearch under one or more contributor
|
||||||
|
* license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright
|
||||||
|
* ownership. Elasticsearch licenses this file to you under
|
||||||
|
* the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
* not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//// additional test framework permissions.
|
||||||
|
//// These are mock objects and test management that we allow test framework libs
|
||||||
|
//// to provide on our behalf. But tests themselves cannot do this stuff!
|
||||||
|
|
||||||
|
grant codeBase "${codebase.securemock-1.1.jar}" {
|
||||||
|
// needed to access ReflectionFactory (see below)
|
||||||
|
permission java.lang.RuntimePermission "accessClassInPackage.sun.reflect";
|
||||||
|
// needed to support creation of mocks
|
||||||
|
permission java.lang.RuntimePermission "reflectionFactoryAccess";
|
||||||
|
// needed for spy interception, etc
|
||||||
|
permission java.lang.reflect.ReflectPermission "suppressAccessChecks";
|
||||||
|
};
|
||||||
|
|
||||||
|
grant codeBase "${codebase.lucene-test-framework-5.4.0-snapshot-1708254.jar}" {
|
||||||
|
// needed by RamUsageTester
|
||||||
|
permission java.lang.reflect.ReflectPermission "suppressAccessChecks";
|
||||||
|
};
|
||||||
|
|
||||||
|
grant codeBase "${codebase.randomizedtesting-runner-2.1.17.jar}" {
|
||||||
|
// optionally needed for access to private test methods (e.g. beforeClass)
|
||||||
|
permission java.lang.reflect.ReflectPermission "suppressAccessChecks";
|
||||||
|
|
||||||
|
// needed for top threads handling
|
||||||
|
permission java.lang.RuntimePermission "modifyThreadGroup";
|
||||||
|
};
|
||||||
|
|
||||||
|
grant codeBase "${codebase.junit4-ant-2.1.17.jar}" {
|
||||||
|
// needed for gson serialization
|
||||||
|
permission java.lang.reflect.ReflectPermission "suppressAccessChecks";
|
||||||
|
|
||||||
|
// needed for stream redirection
|
||||||
|
permission java.lang.RuntimePermission "setIO";
|
||||||
|
};
|
|
@ -19,28 +19,37 @@
|
||||||
|
|
||||||
package org.elasticsearch.bootstrap;
|
package org.elasticsearch.bootstrap;
|
||||||
|
|
||||||
|
import com.carrotsearch.randomizedtesting.RandomizedRunner;
|
||||||
|
|
||||||
|
import org.apache.lucene.util.LuceneTestCase;
|
||||||
import org.apache.lucene.util.TestSecurityManager;
|
import org.apache.lucene.util.TestSecurityManager;
|
||||||
import org.elasticsearch.bootstrap.Bootstrap;
|
import org.elasticsearch.bootstrap.Bootstrap;
|
||||||
import org.elasticsearch.bootstrap.ESPolicy;
|
import org.elasticsearch.bootstrap.ESPolicy;
|
||||||
import org.elasticsearch.bootstrap.Security;
|
import org.elasticsearch.bootstrap.Security;
|
||||||
import org.elasticsearch.common.Strings;
|
import org.elasticsearch.common.Strings;
|
||||||
|
import org.elasticsearch.common.SuppressForbidden;
|
||||||
import org.elasticsearch.common.io.PathUtils;
|
import org.elasticsearch.common.io.PathUtils;
|
||||||
import org.elasticsearch.plugins.PluginInfo;
|
import org.elasticsearch.plugins.PluginInfo;
|
||||||
|
import org.junit.Assert;
|
||||||
|
|
||||||
import java.io.FilePermission;
|
import java.io.FilePermission;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.security.Permission;
|
import java.security.Permission;
|
||||||
import java.security.PermissionCollection;
|
|
||||||
import java.security.Permissions;
|
import java.security.Permissions;
|
||||||
import java.security.Policy;
|
import java.security.Policy;
|
||||||
import java.security.URIParameter;
|
import java.security.ProtectionDomain;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import static com.carrotsearch.randomizedtesting.RandomizedTest.systemPropertyAsBoolean;
|
import static com.carrotsearch.randomizedtesting.RandomizedTest.systemPropertyAsBoolean;
|
||||||
|
|
||||||
|
@ -83,7 +92,6 @@ public class BootstrapForTesting {
|
||||||
// install security manager if requested
|
// install security manager if requested
|
||||||
if (systemPropertyAsBoolean("tests.security.manager", true)) {
|
if (systemPropertyAsBoolean("tests.security.manager", true)) {
|
||||||
try {
|
try {
|
||||||
Security.setCodebaseProperties();
|
|
||||||
// initialize paths the same exact way as bootstrap
|
// initialize paths the same exact way as bootstrap
|
||||||
Permissions perms = new Permissions();
|
Permissions perms = new Permissions();
|
||||||
// add permissions to everything in classpath
|
// add permissions to everything in classpath
|
||||||
|
@ -121,30 +129,16 @@ public class BootstrapForTesting {
|
||||||
perms.add(new RuntimePermission("setIO"));
|
perms.add(new RuntimePermission("setIO"));
|
||||||
}
|
}
|
||||||
|
|
||||||
final Policy policy;
|
// read test-framework permissions
|
||||||
// if its a plugin with special permissions, we use a wrapper policy impl to try
|
final Policy testFramework = Security.readPolicy(Bootstrap.class.getResource("test-framework.policy"), JarHell.parseClassPath());
|
||||||
// to simulate what happens with a real distribution
|
final Policy esPolicy = new ESPolicy(perms, getPluginPermissions());
|
||||||
List<URL> pluginPolicies = Collections.list(BootstrapForTesting.class.getClassLoader().getResources(PluginInfo.ES_PLUGIN_POLICY));
|
Policy.setPolicy(new Policy() {
|
||||||
if (!pluginPolicies.isEmpty()) {
|
@Override
|
||||||
Permissions extra = new Permissions();
|
public boolean implies(ProtectionDomain domain, Permission permission) {
|
||||||
for (URL url : pluginPolicies) {
|
// implements union
|
||||||
URI uri = url.toURI();
|
return esPolicy.implies(domain, permission) || testFramework.implies(domain, permission);
|
||||||
Policy pluginPolicy = Policy.getInstance("JavaPolicy", new URIParameter(uri));
|
|
||||||
PermissionCollection permissions = pluginPolicy.getPermissions(BootstrapForTesting.class.getProtectionDomain());
|
|
||||||
// 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");
|
|
||||||
}
|
|
||||||
for (Permission permission : Collections.list(permissions.elements())) {
|
|
||||||
extra.add(permission);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// TODO: try to get rid of this class now that the world is simpler?
|
});
|
||||||
policy = new MockPluginPolicy(perms, extra);
|
|
||||||
} else {
|
|
||||||
policy = new ESPolicy(perms, Collections.emptyMap());
|
|
||||||
}
|
|
||||||
Policy.setPolicy(policy);
|
|
||||||
System.setSecurityManager(new TestSecurityManager());
|
System.setSecurityManager(new TestSecurityManager());
|
||||||
Security.selfTest();
|
Security.selfTest();
|
||||||
|
|
||||||
|
@ -168,6 +162,58 @@ public class BootstrapForTesting {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* we dont know which codesources belong to which plugin, so just remove the permission from key codebases
|
||||||
|
* like core, test-framework, etc. this way tests fail if accesscontroller blocks are missing.
|
||||||
|
*/
|
||||||
|
@SuppressForbidden(reason = "accesses fully qualified URLs to configure security")
|
||||||
|
static Map<String,Policy> getPluginPermissions() throws Exception {
|
||||||
|
List<URL> pluginPolicies = Collections.list(BootstrapForTesting.class.getClassLoader().getResources(PluginInfo.ES_PLUGIN_POLICY));
|
||||||
|
if (pluginPolicies.isEmpty()) {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// compute classpath minus obvious places, all other jars will get the permission.
|
||||||
|
Set<URL> codebases = new HashSet<>(Arrays.asList(JarHell.parseClassPath()));
|
||||||
|
Set<URL> excluded = new HashSet<>(Arrays.asList(
|
||||||
|
// es core
|
||||||
|
Bootstrap.class.getProtectionDomain().getCodeSource().getLocation(),
|
||||||
|
// es test framework
|
||||||
|
BootstrapForTesting.class.getProtectionDomain().getCodeSource().getLocation(),
|
||||||
|
// lucene test framework
|
||||||
|
LuceneTestCase.class.getProtectionDomain().getCodeSource().getLocation(),
|
||||||
|
// randomized runner
|
||||||
|
RandomizedRunner.class.getProtectionDomain().getCodeSource().getLocation(),
|
||||||
|
// junit library
|
||||||
|
Assert.class.getProtectionDomain().getCodeSource().getLocation()
|
||||||
|
));
|
||||||
|
codebases.removeAll(excluded);
|
||||||
|
|
||||||
|
// parse each policy file, with codebase substitution from the classpath
|
||||||
|
final List<Policy> policies = new ArrayList<>();
|
||||||
|
for (URL policyFile : pluginPolicies) {
|
||||||
|
policies.add(Security.readPolicy(policyFile, codebases.toArray(new URL[codebases.size()])));
|
||||||
|
}
|
||||||
|
|
||||||
|
// consult each policy file for those codebases
|
||||||
|
Map<String,Policy> map = new HashMap<>();
|
||||||
|
for (URL url : codebases) {
|
||||||
|
map.put(url.getFile(), new Policy() {
|
||||||
|
@Override
|
||||||
|
public boolean implies(ProtectionDomain domain, Permission permission) {
|
||||||
|
// implements union
|
||||||
|
for (Policy p : policies) {
|
||||||
|
if (p.implies(domain, permission)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Collections.unmodifiableMap(map);
|
||||||
|
}
|
||||||
|
|
||||||
// does nothing, just easy way to make sure the class is loaded.
|
// does nothing, just easy way to make sure the class is loaded.
|
||||||
public static void ensureInitialized() {}
|
public static void ensureInitialized() {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,101 +0,0 @@
|
||||||
/*
|
|
||||||
* Licensed to Elasticsearch under one or more contributor
|
|
||||||
* license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright
|
|
||||||
* ownership. Elasticsearch licenses this file to you under
|
|
||||||
* the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
* not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.elasticsearch.bootstrap;
|
|
||||||
|
|
||||||
import com.carrotsearch.randomizedtesting.RandomizedRunner;
|
|
||||||
|
|
||||||
import org.apache.lucene.util.LuceneTestCase;
|
|
||||||
import org.elasticsearch.common.logging.Loggers;
|
|
||||||
import org.junit.Assert;
|
|
||||||
|
|
||||||
import java.net.URL;
|
|
||||||
import java.security.CodeSource;
|
|
||||||
import java.security.Permission;
|
|
||||||
import java.security.PermissionCollection;
|
|
||||||
import java.security.Policy;
|
|
||||||
import java.security.ProtectionDomain;
|
|
||||||
import java.security.cert.Certificate;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simulates in unit tests per-plugin permissions.
|
|
||||||
* Unit tests for plugins do not have a proper plugin structure,
|
|
||||||
* so we don't know which codebases to apply the permission to.
|
|
||||||
* <p>
|
|
||||||
* As an approximation, we just exclude es/test/framework classes,
|
|
||||||
* because they will be present in stacks and fail tests for the
|
|
||||||
* simple case where an AccessController block is missing, because
|
|
||||||
* java security checks every codebase in the stacktrace, and we
|
|
||||||
* are sure to pollute it.
|
|
||||||
*/
|
|
||||||
final class MockPluginPolicy extends Policy {
|
|
||||||
final ESPolicy standardPolicy;
|
|
||||||
final PermissionCollection extraPermissions;
|
|
||||||
final Set<CodeSource> excludedSources;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new MockPluginPolicy with dynamic {@code permissions} and
|
|
||||||
* adding the extra plugin permissions from {@code insecurePluginProp} to
|
|
||||||
* all code except test classes.
|
|
||||||
*/
|
|
||||||
MockPluginPolicy(PermissionCollection standard, PermissionCollection extra) throws Exception {
|
|
||||||
// the hack begins!
|
|
||||||
|
|
||||||
this.standardPolicy = new ESPolicy(standard, Collections.emptyMap());
|
|
||||||
this.extraPermissions = extra;
|
|
||||||
|
|
||||||
excludedSources = new HashSet<CodeSource>();
|
|
||||||
// exclude some obvious places
|
|
||||||
// es core
|
|
||||||
excludedSources.add(Bootstrap.class.getProtectionDomain().getCodeSource());
|
|
||||||
// es test framework
|
|
||||||
excludedSources.add(getClass().getProtectionDomain().getCodeSource());
|
|
||||||
// lucene test framework
|
|
||||||
excludedSources.add(LuceneTestCase.class.getProtectionDomain().getCodeSource());
|
|
||||||
// test runner
|
|
||||||
excludedSources.add(RandomizedRunner.class.getProtectionDomain().getCodeSource());
|
|
||||||
// junit library
|
|
||||||
excludedSources.add(Assert.class.getProtectionDomain().getCodeSource());
|
|
||||||
// scripts
|
|
||||||
excludedSources.add(new CodeSource(new URL("file:" + BootstrapInfo.UNTRUSTED_CODEBASE), (Certificate[])null));
|
|
||||||
|
|
||||||
Loggers.getLogger(getClass()).debug("Apply extra permissions [{}] excluding codebases [{}]", extraPermissions, excludedSources);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (standardPolicy.implies(domain, permission)) {
|
|
||||||
return true;
|
|
||||||
} else if (excludedSources.contains(codeSource) == false &&
|
|
||||||
codeSource.toString().contains("test-classes") == false) {
|
|
||||||
return extraPermissions.implies(permission);
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
5
pom.xml
5
pom.xml
|
@ -45,10 +45,15 @@
|
||||||
<doclint.options>-Xdoclint:-missing</doclint.options>
|
<doclint.options>-Xdoclint:-missing</doclint.options>
|
||||||
|
|
||||||
<!-- libraries -->
|
<!-- libraries -->
|
||||||
|
|
||||||
|
<!-- NOTE: when changing these versions, also update security.policy and test-framework.policy to match -->
|
||||||
|
<!-- (we don't do any automatic property substitution on those resources, it could get confusing... -->
|
||||||
<lucene.version>5.4.0</lucene.version>
|
<lucene.version>5.4.0</lucene.version>
|
||||||
<lucene.snapshot.revision>1708254</lucene.snapshot.revision>
|
<lucene.snapshot.revision>1708254</lucene.snapshot.revision>
|
||||||
<lucene.maven.version>5.4.0-snapshot-${lucene.snapshot.revision}</lucene.maven.version>
|
<lucene.maven.version>5.4.0-snapshot-${lucene.snapshot.revision}</lucene.maven.version>
|
||||||
<testframework.version>2.1.17</testframework.version>
|
<testframework.version>2.1.17</testframework.version>
|
||||||
|
<securemock.version>1.1</securemock.version>
|
||||||
|
|
||||||
<jackson.version>2.6.2</jackson.version>
|
<jackson.version>2.6.2</jackson.version>
|
||||||
<slf4j.version>1.6.2</slf4j.version>
|
<slf4j.version>1.6.2</slf4j.version>
|
||||||
<log4j.version>1.2.17</log4j.version>
|
<log4j.version>1.2.17</log4j.version>
|
||||||
|
|
Loading…
Reference in New Issue