diff --git a/elasticsearch/qa/shield-example-realm/build.gradle b/elasticsearch/qa/shield-example-realm/build.gradle index f3367b8e542..f7081642bee 100644 --- a/elasticsearch/qa/shield-example-realm/build.gradle +++ b/elasticsearch/qa/shield-example-realm/build.gradle @@ -37,6 +37,7 @@ processResources { task buildZip(type:Zip, dependsOn: [jar]) { from 'build/resources/main/x-pack-extension-descriptor.properties' + from 'build/resources/main/x-pack-extension-security.policy' from project.jar } diff --git a/elasticsearch/qa/shield-example-realm/src/main/java/org/elasticsearch/example/ExampleRealmExtension.java b/elasticsearch/qa/shield-example-realm/src/main/java/org/elasticsearch/example/ExampleRealmExtension.java index 752e0a74b32..308ca7c382e 100644 --- a/elasticsearch/qa/shield-example-realm/src/main/java/org/elasticsearch/example/ExampleRealmExtension.java +++ b/elasticsearch/qa/shield-example-realm/src/main/java/org/elasticsearch/example/ExampleRealmExtension.java @@ -11,6 +11,9 @@ import org.elasticsearch.example.realm.CustomRealmFactory; import org.elasticsearch.shield.authc.AuthenticationModule; import org.elasticsearch.xpack.extensions.XPackExtension; +import java.security.AccessController; +import java.security.PrivilegedAction; + public class ExampleRealmExtension extends XPackExtension { @Override public String name() { @@ -25,5 +28,10 @@ public class ExampleRealmExtension extends XPackExtension { public void onModule(AuthenticationModule authenticationModule) { authenticationModule.addCustomRealm(CustomRealm.TYPE, CustomRealmFactory.class); authenticationModule.setAuthenticationFailureHandler(CustomAuthenticationFailureHandler.class); + // check that the extension's policy works. + AccessController.doPrivileged((PrivilegedAction) () -> { + System.getSecurityManager().checkPrintJobAccess(); + return null; + }); } } diff --git a/elasticsearch/qa/shield-example-realm/src/main/resources/x-pack-extension-security.policy b/elasticsearch/qa/shield-example-realm/src/main/resources/x-pack-extension-security.policy new file mode 100644 index 00000000000..6d05deba55c --- /dev/null +++ b/elasticsearch/qa/shield-example-realm/src/main/resources/x-pack-extension-security.policy @@ -0,0 +1,3 @@ +grant { + permission java.lang.RuntimePermission "queuePrintJob"; +}; \ No newline at end of file diff --git a/elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/extensions/InstallXPackExtensionCommand.java b/elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/extensions/InstallXPackExtensionCommand.java index cf161e65012..df5b59167ea 100644 --- a/elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/extensions/InstallXPackExtensionCommand.java +++ b/elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/extensions/InstallXPackExtensionCommand.java @@ -8,6 +8,7 @@ package org.elasticsearch.xpack.extensions; import joptsimple.OptionSet; import joptsimple.OptionSpec; import org.apache.lucene.util.IOUtils; +import org.elasticsearch.SpecialPermission; import org.elasticsearch.bootstrap.JarHell; import org.elasticsearch.cli.ExitCodes; import org.elasticsearch.cli.SettingCommand; @@ -26,13 +27,26 @@ import java.net.URLDecoder; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; -import java.util.ArrayList; + import java.util.Arrays; -import java.util.List; import java.util.Map; +import java.util.List; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Collections; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; +import java.security.Policy; +import java.security.PermissionCollection; +import java.security.Permission; +import java.security.NoSuchAlgorithmException; +import java.security.Permissions; +import java.security.PrivilegedAction; +import java.security.AccessController; +import java.security.UnresolvedPermission; +import java.security.URIParameter; + import static org.elasticsearch.cli.Terminal.Verbosity.VERBOSE; import static org.elasticsearch.xpack.XPackPlugin.resolveXPackExtensionsFile; @@ -49,18 +63,19 @@ import static org.elasticsearch.xpack.XPackPlugin.resolveXPackExtensionsFile; * */ -class InstallXPackExtensionCommand extends SettingCommand { +final class InstallXPackExtensionCommand extends SettingCommand { private final OptionSpec batchOption; private final OptionSpec arguments; InstallXPackExtensionCommand() { - super("Install a plugin"); + super("Install an extension"); this.batchOption = parser.acceptsAll(Arrays.asList("b", "batch"), "Enable batch mode explicitly, automatic confirmation of security permission"); - this.arguments = parser.nonOptions("plugin id"); + this.arguments = parser.nonOptions("extension id"); } @Override @@ -86,7 +101,7 @@ class InstallXPackExtensionCommand extends SettingCommand { Path extensionZip = download(terminal, extensionId, env.tmpFile()); Path extractedZip = unzip(extensionZip, resolveXPackExtensionsFile(env)); - install(terminal, extractedZip, env); + install(terminal, extractedZip, env, isBatch); } /** Downloads the extension and returns the file it was downloaded to. */ @@ -133,13 +148,21 @@ class InstallXPackExtensionCommand extends SettingCommand { } /** Load information about the extension, and verify it can be installed with no errors. */ - private XPackExtensionInfo verify(Terminal terminal, Path extensionRoot, Environment env) throws Exception { + private XPackExtensionInfo verify(Terminal terminal, Path extensionRoot, Environment env, boolean isBatch) throws Exception { // read and validate the extension descriptor XPackExtensionInfo info = XPackExtensionInfo.readFromProperties(extensionRoot); terminal.println(VERBOSE, info.toString()); // check for jar hell before any copying jarHellCheck(extensionRoot); + + // read optional security policy (extra permissions) + // if it exists, confirm or warn the user + Path policy = extensionRoot.resolve(XPackExtensionInfo.XPACK_EXTENSION_POLICY); + if (Files.exists(policy)) { + readPolicy(policy, terminal, env, isBatch); + } + return info; } @@ -165,11 +188,11 @@ class InstallXPackExtensionCommand extends SettingCommand { /** * Installs the extension from {@code tmpRoot} into the extensions dir. */ - private void install(Terminal terminal, Path tmpRoot, Environment env) throws Exception { + private void install(Terminal terminal, Path tmpRoot, Environment env, boolean isBatch) throws Exception { List deleteOnFailure = new ArrayList<>(); deleteOnFailure.add(tmpRoot); try { - XPackExtensionInfo info = verify(terminal, tmpRoot, env); + XPackExtensionInfo info = verify(terminal, tmpRoot, env, isBatch); final Path destination = resolveXPackExtensionsFile(env).resolve(info.getName()); if (Files.exists(destination)) { throw new UserError(ExitCodes.USAGE, @@ -188,4 +211,150 @@ class InstallXPackExtensionCommand extends SettingCommand { throw installProblem; } } + + /** Format permission type, name, and actions into a string */ + static String formatPermission(Permission permission) { + StringBuilder sb = new StringBuilder(); + + String clazz = null; + if (permission instanceof UnresolvedPermission) { + clazz = ((UnresolvedPermission) permission).getUnresolvedType(); + } else { + clazz = permission.getClass().getName(); + } + sb.append(clazz); + + String name = null; + if (permission instanceof UnresolvedPermission) { + name = ((UnresolvedPermission) permission).getUnresolvedName(); + } else { + name = permission.getName(); + } + if (name != null && name.length() > 0) { + sb.append(' '); + sb.append(name); + } + + String actions = null; + if (permission instanceof UnresolvedPermission) { + actions = ((UnresolvedPermission) permission).getUnresolvedActions(); + } else { + actions = permission.getActions(); + } + if (actions != null && actions.length() > 0) { + sb.append(' '); + sb.append(actions); + } + return sb.toString(); + } + + /** + * Parses extension policy into a set of permissions + */ + static PermissionCollection parsePermissions(Path file, Path tmpDir) throws IOException { + // create a zero byte file for "comparison" + // this is necessary because the default policy impl automatically grants two permissions: + // 1. permission to exitVM (which we ignore) + // 2. read permission to the code itself (e.g. jar file of the code) + + Path emptyPolicyFile = Files.createTempFile(tmpDir, "empty", "tmp"); + final Policy emptyPolicy; + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(new SpecialPermission()); + } + emptyPolicy = + AccessController.doPrivileged((PrivilegedAction) () -> { + try { + return Policy.getInstance("JavaPolicy", new URIParameter(emptyPolicyFile.toUri())); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + }); + IOUtils.rm(emptyPolicyFile); + + // parse the extension's policy file into a set of permissions + final Policy policy = + AccessController.doPrivileged((PrivilegedAction) () -> { + try { + return Policy.getInstance("JavaPolicy", new URIParameter(file.toUri())); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + }); + PermissionCollection permissions = policy.getPermissions(XPackExtensionSecurity.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"); + } + PermissionCollection actualPermissions = new Permissions(); + for (Permission permission : Collections.list(permissions.elements())) { + if (!emptyPolicy.implies(XPackExtensionSecurity.class.getProtectionDomain(), permission)) { + actualPermissions.add(permission); + } + } + actualPermissions.setReadOnly(); + return actualPermissions; + } + + + /** + * Reads extension policy, prints/confirms exceptions + */ + static void readPolicy(Path file, Terminal terminal, Environment environment, boolean batch) throws IOException { + PermissionCollection permissions = parsePermissions(file, environment.tmpFile()); + List requested = Collections.list(permissions.elements()); + if (requested.isEmpty()) { + terminal.println(Terminal.Verbosity.VERBOSE, "extension has a policy file with no additional permissions"); + return; + } + + // sort permissions in a reasonable order + Collections.sort(requested, new Comparator() { + @Override + public int compare(Permission o1, Permission o2) { + int cmp = o1.getClass().getName().compareTo(o2.getClass().getName()); + if (cmp == 0) { + String name1 = o1.getName(); + String name2 = o2.getName(); + if (name1 == null) { + name1 = ""; + } + if (name2 == null) { + name2 = ""; + } + cmp = name1.compareTo(name2); + if (cmp == 0) { + String actions1 = o1.getActions(); + String actions2 = o2.getActions(); + if (actions1 == null) { + actions1 = ""; + } + if (actions2 == null) { + actions2 = ""; + } + cmp = actions1.compareTo(actions2); + } + } + return cmp; + } + }); + + terminal.println(Terminal.Verbosity.NORMAL, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); + terminal.println(Terminal.Verbosity.NORMAL, "@ WARNING: x-pack extension requires additional permissions @"); + terminal.println(Terminal.Verbosity.NORMAL, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); + // print all permissions: + for (Permission permission : requested) { + terminal.println(Terminal.Verbosity.NORMAL, "* " + formatPermission(permission)); + } + terminal.println(Terminal.Verbosity.NORMAL, "See http://docs.oracle.com/javase/8/docs/technotes/guides/security/permissions.html"); + terminal.println(Terminal.Verbosity.NORMAL, "for descriptions of what these permissions allow and the associated risks."); + if (!batch) { + terminal.println(Terminal.Verbosity.NORMAL, ""); + String text = terminal.readText("Continue with installation? [y/N]"); + if (!text.equalsIgnoreCase("y")) { + throw new RuntimeException("installation aborted by user"); + } + } + } } diff --git a/elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/extensions/XPackExtensionInfo.java b/elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/extensions/XPackExtensionInfo.java index d705f8457ae..c2ea2a95316 100644 --- a/elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/extensions/XPackExtensionInfo.java +++ b/elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/extensions/XPackExtensionInfo.java @@ -16,6 +16,7 @@ import java.util.Properties; public class XPackExtensionInfo { public static final String XPACK_EXTENSION_PROPERTIES = "x-pack-extension-descriptor.properties"; + public static final String XPACK_EXTENSION_POLICY = "x-pack-extension-security.policy"; private String name; private String description; diff --git a/elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/extensions/XPackExtensionPolicy.java b/elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/extensions/XPackExtensionPolicy.java new file mode 100644 index 00000000000..5ac71107340 --- /dev/null +++ b/elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/extensions/XPackExtensionPolicy.java @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.xpack.extensions; + +import org.elasticsearch.common.SuppressForbidden; + +import java.net.URL; +import java.security.Policy; +import java.security.ProtectionDomain; +import java.security.CodeSource; +import java.security.Permission; +import java.security.SecurityPermission; +import java.util.Map; + +final class XPackExtensionPolicy extends Policy { + static final Permission SET_POLICY_PERMISSION = new SecurityPermission("setPolicy"); + static final Permission GET_POLICY_PERMISSION = new SecurityPermission("getPolicy"); + static final Permission CREATE_POLICY_PERMISSION = new SecurityPermission("createPolicy.JavaPolicy"); + + // the base policy (es + plugins) + final Policy basePolicy; + // policy extensions + final Map extensions; + // xpack code source location + final URL xpackURL; + + /** + * + * @param basePolicy The base policy + * @param extensions Extra code source extension's policy + */ + public XPackExtensionPolicy(Policy basePolicy, Map extensions) { + this.basePolicy = basePolicy; + this.extensions = extensions; + xpackURL = XPackExtensionPolicy.class.getProtectionDomain().getCodeSource().getLocation(); + } + + private boolean isPolicyPermission(Permission permission) { + return GET_POLICY_PERMISSION.equals(permission) || + CREATE_POLICY_PERMISSION.equals(permission) || + SET_POLICY_PERMISSION.equals(permission); + } + + @Override @SuppressForbidden(reason = "fast equals check is desired") + public boolean implies(ProtectionDomain domain, Permission permission) { + CodeSource codeSource = domain.getCodeSource(); + if (codeSource != null && codeSource.getLocation() != null) { + if (codeSource.getLocation().equals(xpackURL) && + isPolicyPermission(permission)) { + // forbid to get, create and set java policy in xpack codesource + // it is only granted at startup in order to let xpack add the extensions policy + // and make this policy the default. + return false; + } + // check for an additional extension permission: extension policy is + // only consulted for its codesources. + Policy extension = extensions.get(codeSource.getLocation().getFile()); + if (extension != null && extension.implies(domain, permission)) { + return true; + } + } + return basePolicy.implies(domain, permission); + } +} \ No newline at end of file diff --git a/elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/extensions/XPackExtensionSecurity.java b/elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/extensions/XPackExtensionSecurity.java new file mode 100644 index 00000000000..54f744b8551 --- /dev/null +++ b/elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/extensions/XPackExtensionSecurity.java @@ -0,0 +1,144 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.xpack.extensions; + +import org.elasticsearch.SpecialPermission; +import org.elasticsearch.common.SuppressForbidden; +import org.elasticsearch.common.io.PathUtils; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; +import java.util.HashMap; +import java.util.List; +import java.util.ArrayList; +import java.util.Collections; +import java.security.Policy; +import java.security.PrivilegedAction; +import java.security.AccessController; +import java.security.URIParameter; +import java.security.NoSuchAlgorithmException; + +final class XPackExtensionSecurity { + private XPackExtensionSecurity() {} + + /** + * Initializes the XPackExtensionPolicy + * Can only happen once! + * + * @param extsDirectory the directory where the extensions are installed + */ + static void configure(Path extsDirectory) throws IOException { + Map map = getExtensionsPermissions(extsDirectory); + if (map.size() > 0) { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(new SpecialPermission()); + } + AccessController.doPrivileged((PrivilegedAction) () -> { + Policy newPolicy = new XPackExtensionPolicy(Policy.getPolicy(), map); + Policy.setPolicy(newPolicy); + return null; + }); + } + } + + /** + * Sets properties (codebase URLs) for policy files. + * we look for matching extensions and set URLs to fit + */ + @SuppressForbidden(reason = "proper use of URL") + static Map getExtensionsPermissions(Path extsDirectory) throws IOException { + Map map = new HashMap<>(); + // collect up lists of extensions + List extensionPaths = new ArrayList<>(); + if (Files.exists(extsDirectory)) { + try (DirectoryStream stream = Files.newDirectoryStream(extsDirectory)) { + for (Path extension : stream) { + extensionPaths.add(extension); + } + } + } + // now process each one + for (Path extension : extensionPaths) { + Path policyFile = extension.resolve(XPackExtensionInfo.XPACK_EXTENSION_POLICY); + if (Files.exists(policyFile)) { + // first get a list of URLs for the extension's jars: + // we resolve symlinks so map is keyed on the normalize codebase name + List codebases = new ArrayList<>(); + try (DirectoryStream jarStream = Files.newDirectoryStream(extension, "*.jar")) { + for (Path jar : jarStream) { + codebases.add(jar.toRealPath().toUri().toURL()); + } + } + + // parse the extension'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 extension's jars: + for (URL url : codebases) { + if (map.put(url.getFile(), policy) != null) { + // just be paranoid ok? + throw new IllegalStateException("per-extension permissions already granted for jar file: " + url); + } + } + } + } + + return Collections.unmodifiableMap(map); + } + + /** + * Reads and returns the specified {@code policyFile}. + *

+ * 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. ${codebase.joda-convert-1.2.jar} would map to full URL. + */ + @SuppressForbidden(reason = "accesses fully qualified URLs to configure security") + static Policy readPolicy(URL policyFile, URL codebases[]) throws IOException { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(new SpecialPermission()); + } + try { + try { + // set codebase properties + for (URL url : codebases) { + String shortName = PathUtils.get(url.toURI()).getFileName().toString(); + + AccessController.doPrivileged((PrivilegedAction) () -> { + System.setProperty("codebase." + shortName, url.toString()); + return null; + }); + } + URIParameter uri = new URIParameter(policyFile.toURI()); + return AccessController.doPrivileged((PrivilegedAction) () -> { + try { + return Policy.getInstance("JavaPolicy", uri); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + }); + } finally { + // clear codebase properties + for (URL url : codebases) { + String shortName = PathUtils.get(url.toURI()).getFileName().toString(); + AccessController.doPrivileged((PrivilegedAction) () -> { + System.clearProperty("codebase." + shortName); + return null; + }); + } + } + } catch (URISyntaxException e) { + throw new IllegalArgumentException("unable to parse policy file `" + policyFile + "`", e); + } + } +} \ No newline at end of file diff --git a/elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/extensions/XPackExtensionsService.java b/elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/extensions/XPackExtensionsService.java index 42ff95f39f3..e682343838c 100644 --- a/elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/extensions/XPackExtensionsService.java +++ b/elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/extensions/XPackExtensionsService.java @@ -20,10 +20,10 @@ import java.net.URLClassLoader; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; -import java.util.List; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.List; +import java.util.ArrayList; import java.util.Arrays; import static org.elasticsearch.common.io.FileSystemUtils.isAccessibleDirectory; @@ -37,18 +37,25 @@ public class XPackExtensionsService { /** * We keep around a list of extensions */ - private final List > extensions; + private final List> extensions; /** * Constructs a new XPackExtensionsService - * @param settings The settings of the system - * @param extsDirectory The directory extensions exist in, or null if extensions should not be loaded from the filesystem + * + * @param settings The settings of the system + * @param extsDirectory The directory extensions exist in, or null if extensions should not be loaded from the filesystem * @param classpathExtensions Extensions that exist in the classpath which should be loaded */ - public XPackExtensionsService(Settings settings, Path extsDirectory, Collection> classpathExtensions) { + public XPackExtensionsService(Settings settings, Path extsDirectory, + Collection> classpathExtensions) { + try { + XPackExtensionSecurity.configure(extsDirectory); + } catch (Exception e) { + throw new IllegalStateException("Unable to configure extension policy", e); + } + this.settings = settings; List> extensionsLoaded = new ArrayList<>(); - // first we load extensions that are on the classpath. this is for tests for (Class extClass : classpathExtensions) { XPackExtension ext = loadExtension(extClass, settings); @@ -123,7 +130,7 @@ public class XPackExtensionsService { return bundles; } - private List > loadBundles(List bundles) { + private List> loadBundles(List bundles) { List> exts = new ArrayList<>(); for (Bundle bundle : bundles) { @@ -183,4 +190,4 @@ public class XPackExtensionsService { throw new ElasticsearchException("Failed to load extension class [" + extClass.getName() + "]", e); } } -} +} \ No newline at end of file diff --git a/elasticsearch/x-pack/src/main/plugin-metadata/plugin-security.policy b/elasticsearch/x-pack/src/main/plugin-metadata/plugin-security.policy index d3e821f4858..8ea29c8627b 100644 --- a/elasticsearch/x-pack/src/main/plugin-metadata/plugin-security.policy +++ b/elasticsearch/x-pack/src/main/plugin-metadata/plugin-security.policy @@ -16,4 +16,9 @@ grant { // bouncy castle permission java.security.SecurityPermission "putProviderProperty.BC"; + + // needed for x-pack security extension + permission java.security.SecurityPermission "createPolicy.JavaPolicy"; + permission java.security.SecurityPermission "getPolicy"; + permission java.security.SecurityPermission "setPolicy"; }; diff --git a/elasticsearch/x-pack/src/test/java/org/elasticsearch/xpack/extensions/InstallXPackExtensionCommandTests.java b/elasticsearch/x-pack/src/test/java/org/elasticsearch/xpack/extensions/InstallXPackExtensionCommandTests.java index fe04b60844f..04dd9951239 100644 --- a/elasticsearch/x-pack/src/test/java/org/elasticsearch/xpack/extensions/InstallXPackExtensionCommandTests.java +++ b/elasticsearch/x-pack/src/test/java/org/elasticsearch/xpack/extensions/InstallXPackExtensionCommandTests.java @@ -95,7 +95,7 @@ public class InstallXPackExtensionCommandTests extends ESTestCase { return terminal; } - void assertExtension(String name, Path original, Environment env) throws IOException { + void assertExtension(String name, Environment env) throws IOException { Path got = env.pluginsFile().resolve("x-pack").resolve("extensions").resolve(name); assertTrue("dir " + name + " exists", Files.exists(got)); assertTrue("jar was copied", Files.exists(got.resolve("extension.jar"))); @@ -116,7 +116,7 @@ public class InstallXPackExtensionCommandTests extends ESTestCase { Path extDir = createTempDir(); String extZip = createExtension("fake", extDir); installExtension(extZip, home); - assertExtension("fake", extDir, env); + assertExtension("fake", env); } public void testSpaceInUrl() throws Exception { @@ -127,7 +127,7 @@ public class InstallXPackExtensionCommandTests extends ESTestCase { Files.copy(in, extZipWithSpaces, StandardCopyOption.REPLACE_EXISTING); } installExtension(extZipWithSpaces.toUri().toURL().toString(), home); - assertExtension("fake", extDir, env); + assertExtension("fake", env); } public void testMalformedUrlNotMaven() throws Exception { @@ -155,8 +155,8 @@ public class InstallXPackExtensionCommandTests extends ESTestCase { Path extDir2 = createTempDir(); String extZip2 = createExtension("fake2", extDir2); installExtension(extZip2, home); - assertExtension("fake1", extDir1, env); - assertExtension("fake2", extDir2, env); + assertExtension("fake1", env); + assertExtension("fake2", env); } public void testExistingExtension() throws Exception { @@ -175,4 +175,4 @@ public class InstallXPackExtensionCommandTests extends ESTestCase { assertTrue(e.getMessage(), e.getMessage().contains("x-pack-extension-descriptor.properties")); assertInstallCleaned(env); } -} +} \ No newline at end of file diff --git a/elasticsearch/x-pack/src/test/java/org/elasticsearch/xpack/extensions/XPackExtensionSecurityTests.java b/elasticsearch/x-pack/src/test/java/org/elasticsearch/xpack/extensions/XPackExtensionSecurityTests.java new file mode 100644 index 00000000000..d912376aae5 --- /dev/null +++ b/elasticsearch/x-pack/src/test/java/org/elasticsearch/xpack/extensions/XPackExtensionSecurityTests.java @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.xpack.extensions; + +import org.elasticsearch.test.ESTestCase; + +import java.nio.file.Path; +import java.security.Permission; +import java.security.PermissionCollection; +import java.security.Permissions; +import java.util.Collections; +import java.util.List; + +public class XPackExtensionSecurityTests extends ESTestCase { + /** Test that we can parse the set of permissions correctly for a simple policy */ + public void testParsePermissions() throws Exception { + Path scratch = createTempDir(); + Path testFile = this.getDataPath("security/simple-x-pack-extension-security.policy"); + Permissions expected = new Permissions(); + expected.add(new RuntimePermission("queuePrintJob")); + PermissionCollection actual = InstallXPackExtensionCommand.parsePermissions(testFile, scratch); + assertEquals(expected, actual); + } + + /** Test that we can parse the set of permissions correctly for a complex policy */ + public void testParseTwoPermissions() throws Exception { + Path scratch = createTempDir(); + Path testFile = this.getDataPath("security/complex-x-pack-extension-security.policy"); + Permissions expected = new Permissions(); + expected.add(new RuntimePermission("getClassLoader")); + expected.add(new RuntimePermission("closeClassLoader")); + PermissionCollection actual = InstallXPackExtensionCommand.parsePermissions(testFile, scratch); + assertEquals(expected, actual); + } + + /** Test that we can format some simple permissions properly */ + public void testFormatSimplePermission() throws Exception { + assertEquals("java.lang.RuntimePermission queuePrintJob", + InstallXPackExtensionCommand.formatPermission(new RuntimePermission("queuePrintJob"))); + } + + /** Test that we can format an unresolved permission properly */ + public void testFormatUnresolvedPermission() throws Exception { + Path scratch = createTempDir(); + Path testFile = this.getDataPath("security/unresolved-x-pack-extension-security.policy"); + PermissionCollection actual = InstallXPackExtensionCommand.parsePermissions(testFile, scratch); + List permissions = Collections.list(actual.elements()); + assertEquals(1, permissions.size()); + assertEquals("org.fake.FakePermission fakeName", InstallXPackExtensionCommand.formatPermission(permissions.get(0))); + } + + /** no guaranteed equals on these classes, we assert they contain the same set */ + private void assertEquals(PermissionCollection expected, PermissionCollection actual) { + assertEquals(asSet(Collections.list(expected.elements())), asSet(Collections.list(actual.elements()))); + } +} \ No newline at end of file diff --git a/elasticsearch/x-pack/src/test/resources/org/elasticsearch/xpack/extensions/security/complex-x-pack-extension-security.policy b/elasticsearch/x-pack/src/test/resources/org/elasticsearch/xpack/extensions/security/complex-x-pack-extension-security.policy new file mode 100644 index 00000000000..ab52084ffa1 --- /dev/null +++ b/elasticsearch/x-pack/src/test/resources/org/elasticsearch/xpack/extensions/security/complex-x-pack-extension-security.policy @@ -0,0 +1,5 @@ +grant { + // needed to cause problems + permission java.lang.RuntimePermission "getClassLoader"; + permission java.lang.RuntimePermission "closeClassLoader"; +}; diff --git a/elasticsearch/x-pack/src/test/resources/org/elasticsearch/xpack/extensions/security/simple-x-pack-extension-security.policy b/elasticsearch/x-pack/src/test/resources/org/elasticsearch/xpack/extensions/security/simple-x-pack-extension-security.policy new file mode 100644 index 00000000000..3da788e1196 --- /dev/null +++ b/elasticsearch/x-pack/src/test/resources/org/elasticsearch/xpack/extensions/security/simple-x-pack-extension-security.policy @@ -0,0 +1,4 @@ +grant { + // needed to waste paper + permission java.lang.RuntimePermission "queuePrintJob"; +}; diff --git a/elasticsearch/x-pack/src/test/resources/org/elasticsearch/xpack/extensions/security/unresolved-x-pack-extension-security.policy b/elasticsearch/x-pack/src/test/resources/org/elasticsearch/xpack/extensions/security/unresolved-x-pack-extension-security.policy new file mode 100644 index 00000000000..ea785b0d5f6 --- /dev/null +++ b/elasticsearch/x-pack/src/test/resources/org/elasticsearch/xpack/extensions/security/unresolved-x-pack-extension-security.policy @@ -0,0 +1,4 @@ +grant { + // an unresolved permission + permission org.fake.FakePermission "fakeName"; +};