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:
Robert Muir 2015-10-27 20:06:13 -04:00
parent ad1e3ab925
commit eec3c2a97c
8 changed files with 191 additions and 240 deletions

View File

@ -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>

View File

@ -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;
} }
} }

View File

@ -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();

View File

@ -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";
}; };

View File

@ -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";
};

View File

@ -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() {}
} }

View File

@ -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;
}
}
}

View File

@ -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>