simplify security rules

This commit is contained in:
Robert Muir 2015-05-02 14:42:06 -04:00
parent 8c0d03c3ee
commit 86fc8ceac7
2 changed files with 79 additions and 96 deletions

View File

@ -19,18 +19,19 @@
package org.elasticsearch.bootstrap; package org.elasticsearch.bootstrap;
import com.google.common.io.ByteStreams;
import org.apache.lucene.util.StringHelper; import org.apache.lucene.util.StringHelper;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.env.Environment; import org.elasticsearch.env.Environment;
import java.io.*; import java.io.*;
import java.nio.charset.StandardCharsets; import java.net.URI;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path; import java.nio.file.Path;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.Policy;
import java.security.ProtectionDomain;
import java.security.URIParameter;
/** /**
* Initializes securitymanager with necessary permissions. * Initializes securitymanager with necessary permissions.
@ -47,84 +48,74 @@ class Security {
* Initializes securitymanager for the environment * Initializes securitymanager for the environment
* Can only happen once! * Can only happen once!
*/ */
static void configure(Environment environment) throws IOException { static void configure(Environment environment) throws Exception {
ESLogger log = Loggers.getLogger(Security.class); // init lucene random seed. it will use /dev/urandom where available:
// init lucene random seed. it will use /dev/urandom where available.
StringHelper.randomId(); StringHelper.randomId();
InputStream config = Security.class.getResourceAsStream(POLICY_RESOURCE);
if (config == null) { // enable security policy: union of template and environment-based paths.
throw new NoSuchFileException(POLICY_RESOURCE); URI template = Security.class.getResource(POLICY_RESOURCE).toURI();
} Policy.setPolicy(new ESPolicy(template, createPermissions(environment)));
Path newConfig = processTemplate(config, environment);
System.setProperty("java.security.policy", newConfig.toString()); // enable security manager
System.setSecurityManager(new SecurityManager()); System.setSecurityManager(new SecurityManager());
try {
// don't hide securityexception here, it means java.io.tmpdir is not accessible! // do some basic tests
Files.delete(newConfig); selfTest();
} catch (SecurityException broken) {
log.error("unable to properly access temporary files, run with -Djava.security.debug=policy for more information");
throw broken;
} catch (IOException ignore) {
// e.g. virus scanner on windows
}
} }
// package-private for testing
static Path processTemplate(InputStream template, Environment environment) throws IOException {
Path processed = Files.createTempFile(null, null);
try (OutputStream output = new BufferedOutputStream(Files.newOutputStream(processed))) {
// copy the template as-is.
try (InputStream in = new BufferedInputStream(template)) {
ByteStreams.copy(in, output);
}
// all policy files are UTF-8: /** returns dynamic Permissions to configured paths */
// https://docs.oracle.com/javase/7/docs/technotes/guides/security/PolicyFiles.html static Permissions createPermissions(Environment environment) throws IOException {
try (Writer writer = new OutputStreamWriter(output, StandardCharsets.UTF_8)) { // TODO: improve test infra so we can reduce permissions where read/write
writer.write(System.lineSeparator()); // is not really needed...
writer.write("grant {"); Permissions policy = new Permissions();
writer.write(System.lineSeparator()); addPath(policy, environment.homeFile(), "read,readlink,write,delete");
addPath(policy, environment.configFile(), "read,readlink,write,delete");
// add permissions for all configured paths. addPath(policy, environment.logsFile(), "read,readlink,write,delete");
// TODO: improve test infra so we can reduce permissions where read/write addPath(policy, environment.pluginsFile(), "read,readlink,write,delete");
// is not really needed... for (Path path : environment.dataFiles()) {
addPath(writer, environment.homeFile(), "read,readlink,write,delete"); addPath(policy, path, "read,readlink,write,delete");
addPath(writer, environment.configFile(), "read,readlink,write,delete");
addPath(writer, environment.logsFile(), "read,readlink,write,delete");
addPath(writer, environment.pluginsFile(), "read,readlink,write,delete");
// generate explicit perms for actual temp dir:
// (in case there is java.io.tmpdir sheistiness on windows)
addPath(writer, processed.getParent(), "read,readlink,write,delete");
for (Path path : environment.dataFiles()) {
addPath(writer, path, "read,readlink,write,delete");
}
for (Path path : environment.dataWithClusterFiles()) {
addPath(writer, path, "read,readlink,write,delete");
}
writer.write("};");
writer.write(System.lineSeparator());
}
} }
return processed; for (Path path : environment.dataWithClusterFiles()) {
addPath(policy, path, "read,readlink,write,delete");
}
return policy;
} }
static void addPath(Writer writer, Path path, String permissions) throws IOException { /** Add access to path (and all files underneath it */
static void addPath(Permissions policy, Path path, String permissions) throws IOException {
// paths may not exist yet // paths may not exist yet
Files.createDirectories(path); Files.createDirectories(path);
// add each path twice: once for itself, again for files underneath it // add each path twice: once for itself, again for files underneath it
writer.write("permission java.io.FilePermission \"" + encode(path) + "\", \"" + permissions + "\";"); policy.add(new FilePermission(path.toString(), permissions));
writer.write(System.lineSeparator()); policy.add(new FilePermission(path.toString() + path.getFileSystem().getSeparator() + "-", permissions));
writer.write("permission java.io.FilePermission \"" + encode(path) + "${/}-\", \"" + permissions + "\";");
writer.write(System.lineSeparator());
} }
// Any backslashes in paths must be escaped, because it is the escape character when parsing. /** Simple checks that everything is ok */
// See "Note Regarding File Path Specifications on Windows Systems". static void selfTest() {
// https://docs.oracle.com/javase/7/docs/technotes/guides/security/PolicyFiles.html // check we can manipulate temporary files
static String encode(Path path) { try {
return path.toString().replace("\\", "\\\\"); Files.delete(Files.createTempFile(null, null));
} catch (IOException ignored) {
// potentially virus scanner
} catch (SecurityException problem) {
throw new SecurityException("Security misconfiguration: cannot access java.io.tmpdir", problem);
}
}
/** custom policy for union of static and dynamic permissions */
static class ESPolicy extends Policy {
final Policy template;
final PermissionCollection dynamic;
ESPolicy(URI template, PermissionCollection dynamic) throws Exception {
this.template = Policy.getInstance("JavaPolicy", new URIParameter(template));
this.dynamic = dynamic;
}
@Override
public boolean implies(ProtectionDomain domain, Permission permission) {
return template.implies(domain, permission) || dynamic.implies(permission);
}
} }
} }

View File

@ -24,12 +24,9 @@ import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment; import org.elasticsearch.env.Environment;
import org.elasticsearch.test.ElasticsearchTestCase; import org.elasticsearch.test.ElasticsearchTestCase;
import java.io.ByteArrayInputStream;
import java.io.FilePermission; import java.io.FilePermission;
import java.nio.file.Path; import java.nio.file.Path;
import java.security.Policy; import java.security.Permissions;
import java.security.ProtectionDomain;
import java.security.URIParameter;
public class SecurityTests extends ElasticsearchTestCase { public class SecurityTests extends ElasticsearchTestCase {
@ -42,17 +39,15 @@ public class SecurityTests extends ElasticsearchTestCase {
settingsBuilder.put("path.home", esHome.toString()); settingsBuilder.put("path.home", esHome.toString());
Settings settings = settingsBuilder.build(); Settings settings = settingsBuilder.build();
Environment environment = new Environment(settings); Environment environment = new Environment(settings);
Path policyFile = Security.processTemplate(new ByteArrayInputStream(new byte[0]), environment); Permissions permissions = Security.createPermissions(environment);
ProtectionDomain domain = getClass().getProtectionDomain();
Policy policy = Policy.getInstance("JavaPolicy", new URIParameter(policyFile.toUri()));
// the fake es home // the fake es home
assertTrue(policy.implies(domain, new FilePermission(esHome.toString(), "read"))); assertTrue(permissions.implies(new FilePermission(esHome.toString(), "read")));
// its parent // its parent
assertFalse(policy.implies(domain, new FilePermission(path.toString(), "read"))); assertFalse(permissions.implies(new FilePermission(path.toString(), "read")));
// some other sibling // some other sibling
assertFalse(policy.implies(domain, new FilePermission(path.resolve("other").toString(), "read"))); assertFalse(permissions.implies(new FilePermission(path.resolve("other").toString(), "read")));
} }
/** test generated permissions for all configured paths */ /** test generated permissions for all configured paths */
@ -67,29 +62,26 @@ public class SecurityTests extends ElasticsearchTestCase {
settingsBuilder.put("path.logs", path.resolve("logs").toString()); settingsBuilder.put("path.logs", path.resolve("logs").toString());
Settings settings = settingsBuilder.build(); Settings settings = settingsBuilder.build();
Environment environment = new Environment(settings); Environment environment = new Environment(settings);
Path policyFile = Security.processTemplate(new ByteArrayInputStream(new byte[0]), environment); Permissions permissions = Security.createPermissions(environment);
ProtectionDomain domain = getClass().getProtectionDomain();
Policy policy = Policy.getInstance("JavaPolicy", new URIParameter(policyFile.toUri()));
// check that all directories got permissions: // check that all directories got permissions:
// homefile: this is needed unless we break out rules for "lib" dir. // homefile: this is needed unless we break out rules for "lib" dir.
// TODO: make read-only // TODO: make read-only
assertTrue(policy.implies(domain, new FilePermission(environment.homeFile().toString(), "read,readlink,write,delete"))); assertTrue(permissions.implies(new FilePermission(environment.homeFile().toString(), "read,readlink,write,delete")));
// config file // config file
// TODO: make read-only // TODO: make read-only
assertTrue(policy.implies(domain, new FilePermission(environment.configFile().toString(), "read,readlink,write,delete"))); assertTrue(permissions.implies(new FilePermission(environment.configFile().toString(), "read,readlink,write,delete")));
// plugins: r/w, TODO: can this be minimized? // plugins: r/w, TODO: can this be minimized?
assertTrue(policy.implies(domain, new FilePermission(environment.pluginsFile().toString(), "read,readlink,write,delete"))); assertTrue(permissions.implies(new FilePermission(environment.pluginsFile().toString(), "read,readlink,write,delete")));
// data paths: r/w // data paths: r/w
for (Path dataPath : environment.dataFiles()) { for (Path dataPath : environment.dataFiles()) {
assertTrue(policy.implies(domain, new FilePermission(dataPath.toString(), "read,readlink,write,delete"))); assertTrue(permissions.implies(new FilePermission(dataPath.toString(), "read,readlink,write,delete")));
} }
for (Path dataPath : environment.dataWithClusterFiles()) { for (Path dataPath : environment.dataWithClusterFiles()) {
assertTrue(policy.implies(domain, new FilePermission(dataPath.toString(), "read,readlink,write,delete"))); assertTrue(permissions.implies(new FilePermission(dataPath.toString(), "read,readlink,write,delete")));
} }
// logs: r/w // logs: r/w
assertTrue(policy.implies(domain, new FilePermission(environment.logsFile().toString(), "read,readlink,write,delete"))); assertTrue(permissions.implies(new FilePermission(environment.logsFile().toString(), "read,readlink,write,delete")));
} }
} }