From 270cb9f349aad7a3b51edb26cf43984b9bb18372 Mon Sep 17 00:00:00 2001 From: Robert Muir Date: Wed, 22 Apr 2015 03:04:50 -0400 Subject: [PATCH] enable securitymanager --- config/elasticsearch.yml | 9 + .../tests.policy => config/security.policy | 17 +- pom.xml | 2 +- .../elasticsearch/bootstrap/Bootstrap.java | 13 +- .../org/elasticsearch/bootstrap/Security.java | 160 ++++++++++++++++++ .../elasticsearch/env/NodeEnvironment.java | 4 +- 6 files changed, 194 insertions(+), 11 deletions(-) rename dev-tools/tests.policy => config/security.policy (93%) create mode 100644 src/main/java/org/elasticsearch/bootstrap/Security.java diff --git a/config/elasticsearch.yml b/config/elasticsearch.yml index f1359cd58d0..8842b93e35c 100644 --- a/config/elasticsearch.yml +++ b/config/elasticsearch.yml @@ -231,6 +231,15 @@ # #http.enabled: false +################################### Security ################################## + +# SecurityManager runs elasticsearch with a lower set of priviledges. +# For more information, see +# . + +# Disable security completely: +# +# security.enabled: false ################################### Gateway ################################### diff --git a/dev-tools/tests.policy b/config/security.policy similarity index 93% rename from dev-tools/tests.policy rename to config/security.policy index 724f001e422..44b89c47c58 100644 --- a/dev-tools/tests.policy +++ b/config/security.policy @@ -17,21 +17,26 @@ * under the License. */ -// Policy file to prevent tests from writing outside the test sandbox directory -// PLEASE NOTE: You may need to enable other permissions when new tests are added, -// everything not allowed here is forbidden! +// Default security policy file. +// On startup, BootStrap reads environment and adds additional permissions +// for configured paths to these. grant { - // contain read access to only what we need: + // system jar resources + permission java.io.FilePermission "${java.home}${/}-", "read"; + + // temporary files + permission java.io.FilePermission "${java.io.tmpdir}", "read,write"; + permission java.io.FilePermission "${java.io.tmpdir}${/}-", "read,write,delete"; + + // paths used for running tests // project base directory permission java.io.FilePermission "${project.basedir}${/}target${/}-", "read"; // read permission for lib sigar permission java.io.FilePermission "${project.basedir}${/}lib/sigar{/}-", "read"; // mvn custom ./m2/repository for dependency jars permission java.io.FilePermission "${m2.repository}${/}-", "read"; - // system jar resources - permission java.io.FilePermission "${java.home}${/}-", "read"; // per-jvm directory permission java.io.FilePermission "${junit4.childvm.cwd}${/}temp", "read,write"; permission java.io.FilePermission "${junit4.childvm.cwd}${/}temp${/}-", "read,write,delete"; diff --git a/pom.xml b/pom.xml index 772d7ef6578..66df6cbd5d6 100644 --- a/pom.xml +++ b/pom.xml @@ -628,7 +628,7 @@ ${tests.compatibility} true - ${basedir}/dev-tools/tests.policy + ${basedir}/config/security.policy diff --git a/src/main/java/org/elasticsearch/bootstrap/Bootstrap.java b/src/main/java/org/elasticsearch/bootstrap/Bootstrap.java index 9020d115a8b..72c13efa0f4 100644 --- a/src/main/java/org/elasticsearch/bootstrap/Bootstrap.java +++ b/src/main/java/org/elasticsearch/bootstrap/Bootstrap.java @@ -26,7 +26,6 @@ import org.elasticsearch.common.SuppressForbidden; import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.inject.CreationException; import org.elasticsearch.common.inject.spi.Message; -import org.elasticsearch.common.io.FileSystemUtils; import org.elasticsearch.common.io.PathUtils; import org.elasticsearch.common.jna.Natives; import org.elasticsearch.common.logging.ESLogger; @@ -40,7 +39,6 @@ import org.elasticsearch.node.Node; import org.elasticsearch.node.NodeBuilder; import org.elasticsearch.node.internal.InternalSettingsPreparer; -import java.nio.file.Paths; import java.util.Locale; import java.util.Set; import java.util.concurrent.CountDownLatch; @@ -61,6 +59,7 @@ public class Bootstrap { private static Bootstrap bootstrap; private void setup(boolean addShutdownHook, Tuple tuple) throws Exception { + setupSecurity(tuple.v1(), tuple.v2()); if (tuple.v1().getAsBoolean("bootstrap.mlockall", false)) { Natives.tryMlockall(); } @@ -92,6 +91,16 @@ public class Bootstrap { }); } } + + private void setupSecurity(Settings settings, Environment environment) throws Exception { + ESLogger logger = Loggers.getLogger(Bootstrap.class); + if (settings.getAsBoolean("security.enabled", true)) { + Security.configure(environment); + logger.info("security enabled"); + } else { + logger.info("security disabled"); + } + } @SuppressForbidden(reason = "Exception#printStackTrace()") private static void setupLogging(Tuple tuple) { diff --git a/src/main/java/org/elasticsearch/bootstrap/Security.java b/src/main/java/org/elasticsearch/bootstrap/Security.java new file mode 100644 index 00000000000..35de7edbe9f --- /dev/null +++ b/src/main/java/org/elasticsearch/bootstrap/Security.java @@ -0,0 +1,160 @@ +/* + * 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 org.apache.lucene.util.Constants; +import org.elasticsearch.common.io.PathUtils; +import org.elasticsearch.common.logging.Loggers; +import org.elasticsearch.env.Environment; +import org.elasticsearch.env.NodeEnvironment; + +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileStore; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashSet; +import java.util.Set; + +/** + * Initializes securitymanager with necessary permissions. + *

+ * We use a template file (the one we test with), and add additional + * permissions based on the environment (data paths, etc) + */ +class Security { + + /** + * Initializes securitymanager for the environment + * Can only happen once! + */ + static void configure(Environment environment) throws IOException { + Path newConfig = processTemplate(environment.configFile().resolve("security.policy"), environment); + System.setProperty("java.security.policy", newConfig.toString()); + System.setSecurityManager(new SecurityManager()); + try { + Files.delete(newConfig); + } catch (Exception e) { + Loggers.getLogger(Security.class).warn("unable to remove temporary file: " + newConfig, e); + } + } + + // package-private for testing + static Path processTemplate(Path template, Environment environment) throws IOException { + Path processed = Files.createTempFile(null, null); + try (OutputStream output = new BufferedOutputStream(Files.newOutputStream(processed))) { + // copy the template as-is. + Files.copy(template, output); + + // add permissions for all configured paths. + Set paths = new HashSet<>(); + paths.add(environment.homeFile()); + paths.add(environment.configFile()); + paths.add(environment.logsFile()); + paths.add(environment.pluginsFile()); + paths.add(environment.workFile()); + paths.add(environment.workWithClusterFile()); + for (Path path : environment.dataFiles()) { + paths.add(path); + } + for (Path path : environment.dataWithClusterFiles()) { + paths.add(path); + } + output.write(createPermissions(paths)); + } + return processed; + } + + // package private for testing + static byte[] createPermissions(Set paths) throws IOException { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + + // all policy files are UTF-8: + // https://docs.oracle.com/javase/7/docs/technotes/guides/security/PolicyFiles.html + try (Writer writer = new OutputStreamWriter(stream, StandardCharsets.UTF_8)) { + writer.write(System.lineSeparator()); + writer.write("grant {"); + writer.write(System.lineSeparator()); + for (Path path : paths) { + // add each path twice: once for itself, again for files underneath it + addPath(writer, encode(path), "read,readlink,write,delete"); + addRecursivePath(writer, encode(path), "read,readlink,write,delete"); + } + + // on *nix, try to grant read perms to file stores / SSD detection + if (!Constants.WINDOWS) { + Set stores = new HashSet<>(); + for (FileStore store : PathUtils.getDefaultFileSystem().getFileStores()) { + try { + String mount = NodeEnvironment.getMountPoint(store); + // mount point for fstat() calls against it + if (mount.startsWith("/")) { + stores.add(mount); + } + // block device: add it for SSD detection + if (store.name().startsWith("/")) { + stores.add(store.name()); + } + } catch (Throwable t) { + // these are hacks that are not guaranteed + } + } + for (String store : stores) { + addPath(writer, encode(store), "read,readlink"); + } + addRecursivePath(writer, "/sys/block", "read,readlink"); + addRecursivePath(writer, "/sys/devices", "read,readlink"); + addRecursivePath(writer, "/dev", "read,readlink"); + addRecursivePath(writer, "/devices", "read,readlink"); + } + + writer.write("};"); + writer.write(System.lineSeparator()); + } + + return stream.toByteArray(); + } + + static void addPath(Writer writer, String path, String permissions) throws IOException { + writer.write("permission java.io.FilePermission \"" + path + "\", \"" + permissions + "\";"); + writer.write(System.lineSeparator()); + } + + static void addRecursivePath(Writer writer, String path, String permissions) throws IOException { + writer.write("permission java.io.FilePermission \"" + path + "${/}-\", \"" + permissions + "\";"); + writer.write(System.lineSeparator()); + } + + // Any backslashes in paths must be escaped, because it is the escape character when parsing. + // See "Note Regarding File Path Specifications on Windows Systems". + // https://docs.oracle.com/javase/7/docs/technotes/guides/security/PolicyFiles.html + static String encode(Path path) { + return encode(path.toString()); + } + + static String encode(String path) { + return path.replace("\\", "\\\\"); + } +} diff --git a/src/main/java/org/elasticsearch/env/NodeEnvironment.java b/src/main/java/org/elasticsearch/env/NodeEnvironment.java index 9436888e070..aef08e60b38 100644 --- a/src/main/java/org/elasticsearch/env/NodeEnvironment.java +++ b/src/main/java/org/elasticsearch/env/NodeEnvironment.java @@ -295,7 +295,7 @@ public class NodeEnvironment extends AbstractComponent implements Closeable { // NOTE: poached from Lucene's IOUtils: /** Files.getFileStore(Path) useless here! Don't complain, just try it yourself. */ - private static FileStore getFileStore(Path path) throws IOException { + public static FileStore getFileStore(Path path) throws IOException { FileStore store = Files.getFileStore(path); try { @@ -317,7 +317,7 @@ public class NodeEnvironment extends AbstractComponent implements Closeable { // NOTE: poached from Lucene's IOUtils: // these are hacks that are not guaranteed - private static String getMountPoint(FileStore store) { + public static String getMountPoint(FileStore store) { String desc = store.toString(); return desc.substring(0, desc.lastIndexOf('(') - 1); }