Handle system policy correctly.

Just suck in the system policy, so its compatible with any version of java.
It means it also respects configuration (e.g. for monitoring agents)

Closes #14704
This commit is contained in:
Robert Muir 2015-11-12 18:05:59 -05:00
parent 85ce81c34e
commit 720ebe347d
6 changed files with 90 additions and 23 deletions

View File

@ -182,11 +182,18 @@ final class Bootstrap {
* option for elasticsearch.yml etc to turn off our security manager completely,
* for example if you want to have your own configuration or just disable.
*/
// TODO: remove this: http://www.openbsd.org/papers/hackfest2015-pledge/mgp00005.jpg
static final String SECURITY_SETTING = "security.manager.enabled";
/**
* option for elasticsearch.yml to fully respect the system policy, including bad defaults
* from java.
*/
// TODO: remove this hack when insecure defaults are removed from java
static final String SECURITY_FILTER_BAD_DEFAULTS_SETTING = "security.manager.filter_bad_defaults";
private void setupSecurity(Settings settings, Environment environment) throws Exception {
if (settings.getAsBoolean(SECURITY_SETTING, true)) {
Security.configure(environment);
Security.configure(environment, settings.getAsBoolean(SECURITY_FILTER_BAD_DEFAULTS_SETTING, true));
}
}

View File

@ -21,6 +21,7 @@ package org.elasticsearch.bootstrap;
import org.elasticsearch.common.SuppressForbidden;
import java.net.SocketPermission;
import java.net.URL;
import java.security.CodeSource;
import java.security.Permission;
@ -40,12 +41,18 @@ final class ESPolicy extends Policy {
final Policy template;
final Policy untrusted;
final Policy system;
final PermissionCollection dynamic;
final Map<String,Policy> plugins;
public ESPolicy(PermissionCollection dynamic, Map<String,Policy> plugins) {
public ESPolicy(PermissionCollection dynamic, Map<String,Policy> plugins, boolean filterBadDefaults) {
this.template = Security.readPolicy(getClass().getResource(POLICY_RESOURCE), JarHell.parseClassPath());
this.untrusted = Security.readPolicy(getClass().getResource(UNTRUSTED_RESOURCE), new URL[0]);
if (filterBadDefaults) {
this.system = new SystemPolicy(Policy.getPolicy());
} else {
this.system = Policy.getPolicy();
}
this.dynamic = dynamic;
this.plugins = plugins;
}
@ -75,7 +82,7 @@ final class ESPolicy extends Policy {
}
// otherwise defer to template + dynamic file permissions
return template.implies(domain, permission) || dynamic.implies(permission);
return template.implies(domain, permission) || dynamic.implies(permission) || system.implies(domain, permission);
}
@Override
@ -92,4 +99,39 @@ final class ESPolicy extends Policy {
// return UNSUPPORTED_EMPTY_COLLECTION since it is safe.
return super.getPermissions(codesource);
}
// TODO: remove this hack when insecure defaults are removed from java
// 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...
static final Permission BAD_DEFAULT_NUMBER_ONE = new RuntimePermission("stopThread");
// 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"
static final Permission BAD_DEFAULT_NUMBER_TWO = new SocketPermission("localhost:0", "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.equals(permission) || BAD_DEFAULT_NUMBER_TWO.equals(permission)) {
return false;
}
return delegate.implies(domain, permission);
}
}
}

View File

@ -109,11 +109,13 @@ final class Security {
/**
* Initializes SecurityManager for the environment
* Can only happen once!
* @param environment configuration for generating dynamic permissions
* @param filterBadDefaults true if we should filter out bad java defaults in the system policy.
*/
static void configure(Environment environment) throws Exception {
static void configure(Environment environment, boolean filterBadDefaults) throws Exception {
// 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), filterBadDefaults));
// enable security manager
System.setSecurityManager(new SecureSM());
@ -192,11 +194,33 @@ final class Security {
/** returns dynamic Permissions to configured paths and bind ports */
static Permissions createPermissions(Environment environment) throws IOException {
Permissions policy = new Permissions();
addClasspathPermissions(policy);
addFilePermissions(policy, environment);
addBindPermissions(policy, environment.settings());
return policy;
}
/** Adds access to classpath jars/classes for jar hell scan, etc */
@SuppressForbidden(reason = "accesses fully qualified URLs to configure security")
static void addClasspathPermissions(Permissions policy) throws IOException {
// add permissions to everything in classpath
// really it should be covered by lib/, but there could be e.g. agents or similar configured)
for (URL url : JarHell.parseClassPath()) {
Path path;
try {
path = PathUtils.get(url.toURI());
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
// resource itself
policy.add(new FilePermission(path.toString(), "read,readlink"));
// classes underneath
if (Files.isDirectory(path)) {
policy.add(new FilePermission(path.toString() + path.getFileSystem().getSeparator() + "-", "read,readlink"));
}
}
}
/**
* Adds access to all configurable paths.
*/
@ -233,7 +257,9 @@ final class Security {
String httpRange = settings.get("http.netty.port",
settings.get("http.port",
NettyHttpServerTransport.DEFAULT_PORT_RANGE));
policy.add(new SocketPermission("localhost:" + httpRange, "listen,resolve"));
// listen is always called with 'localhost' but use wildcard to be sure, no name service is consulted.
// see SocketPermission implies() code
policy.add(new SocketPermission("*:" + httpRange, "listen,resolve"));
// transport is waaaay overengineered
Map<String, Settings> profiles = settings.getGroups("transport.profiles", true);
if (!profiles.containsKey(NettyTransport.DEFAULT_PROFILE)) {
@ -253,7 +279,9 @@ final class Security {
// a profile is only valid if its the default profile, or if it has an actual name and specifies a port
boolean valid = NettyTransport.DEFAULT_PROFILE.equals(name) || (Strings.hasLength(name) && profileSettings.get("port") != null);
if (valid) {
policy.add(new SocketPermission("localhost:" + transportRange, "listen,resolve"));
// listen is always called with 'localhost' but use wildcard to be sure, no name service is consulted.
// see SocketPermission implies() code
policy.add(new SocketPermission("*:" + transportRange, "listen,resolve"));
}
}
}

View File

@ -21,13 +21,6 @@
// On startup, BootStrap reads environment and adds additional permissions
// for configured paths and network binding to these.
//// System code permissions:
//// These permissions apply to the JDK itself:
grant codeBase "file:${{java.ext.dirs}}/*" {
permission java.security.AllPermission;
};
//// SecurityManager impl:
//// Must have all permissions to properly perform access checks

View File

@ -49,7 +49,7 @@ public class ESPolicyUnitTests extends ESTestCase {
Permission all = new AllPermission();
PermissionCollection allCollection = all.newPermissionCollection();
allCollection.add(all);
ESPolicy policy = new ESPolicy(allCollection, Collections.emptyMap());
ESPolicy policy = new ESPolicy(allCollection, Collections.emptyMap(), true);
// restrict ourselves to NoPermission
PermissionCollection noPermissions = new Permissions();
assertFalse(policy.implies(new ProtectionDomain(null, noPermissions), new FilePermission("foo", "read")));
@ -63,7 +63,7 @@ public class ESPolicyUnitTests extends ESTestCase {
public void testNullLocation() throws Exception {
assumeTrue("test cannot run with security manager", System.getSecurityManager() == null);
PermissionCollection noPermissions = new Permissions();
ESPolicy policy = new ESPolicy(noPermissions, Collections.emptyMap());
ESPolicy policy = new ESPolicy(noPermissions, Collections.emptyMap(), true);
assertFalse(policy.implies(new ProtectionDomain(new CodeSource(null, (Certificate[])null), noPermissions), new FilePermission("foo", "read")));
}
}

View File

@ -96,13 +96,10 @@ public class BootstrapForTesting {
try {
// initialize paths the same exact way as bootstrap
Permissions perms = new Permissions();
// add permissions to everything in classpath
Security.addClasspathPermissions(perms);
// crazy jython
for (URL url : JarHell.parseClassPath()) {
Path path = PathUtils.get(url.toURI());
// resource itself
perms.add(new FilePermission(path.toString(), "read,readlink"));
// classes underneath
perms.add(new FilePermission(path.toString() + path.getFileSystem().getSeparator() + "-", "read,readlink"));
// crazy jython...
String filename = path.getFileName().toString();
@ -141,7 +138,7 @@ public class BootstrapForTesting {
// read test-framework permissions
final Policy testFramework = Security.readPolicy(Bootstrap.class.getResource("test-framework.policy"), JarHell.parseClassPath());
final Policy esPolicy = new ESPolicy(perms, getPluginPermissions());
final Policy esPolicy = new ESPolicy(perms, getPluginPermissions(), true);
Policy.setPolicy(new Policy() {
@Override
public boolean implies(ProtectionDomain domain, Permission permission) {