Merge pull request elastic/elasticsearch#2411 from jimferenczi/extension_security
Add support for a policy file (x-pack-extension-security.policy) in an x-pack extension Original commit: elastic/x-pack-elasticsearch@49caea89ef
This commit is contained in:
commit
b8e76475b1
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Void>) () -> {
|
||||
System.getSecurityManager().checkPrintJobAccess();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
grant {
|
||||
permission java.lang.RuntimePermission "queuePrintJob";
|
||||
};
|
|
@ -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;
|
|||
* <ul>
|
||||
* <li>The property file exists and contains valid metadata. See {@link XPackExtensionInfo#readFromProperties(Path)}</li>
|
||||
* <li>Jar hell does not exist, either between the extension's own jars or with the parent classloader (elasticsearch + x-pack)</li>
|
||||
* <li>If the extension contains extra security permissions, the policy file is validated</li>
|
||||
* </ul>
|
||||
*/
|
||||
class InstallXPackExtensionCommand extends SettingCommand {
|
||||
final class InstallXPackExtensionCommand extends SettingCommand {
|
||||
|
||||
private final OptionSpec<Void> batchOption;
|
||||
private final OptionSpec<String> 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<Path> 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<Policy>) () -> {
|
||||
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<Policy>) () -> {
|
||||
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<Permission> 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<Permission>() {
|
||||
@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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<String, Policy> 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<String, Policy> 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);
|
||||
}
|
||||
}
|
|
@ -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<String, Policy> map = getExtensionsPermissions(extsDirectory);
|
||||
if (map.size() > 0) {
|
||||
SecurityManager sm = System.getSecurityManager();
|
||||
if (sm != null) {
|
||||
sm.checkPermission(new SpecialPermission());
|
||||
}
|
||||
AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
|
||||
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<String, Policy> getExtensionsPermissions(Path extsDirectory) throws IOException {
|
||||
Map<String, Policy> map = new HashMap<>();
|
||||
// collect up lists of extensions
|
||||
List<Path> extensionPaths = new ArrayList<>();
|
||||
if (Files.exists(extsDirectory)) {
|
||||
try (DirectoryStream<Path> 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<URL> codebases = new ArrayList<>();
|
||||
try (DirectoryStream<Path> 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}.
|
||||
* <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[]) 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<Void>) () -> {
|
||||
System.setProperty("codebase." + shortName, url.toString());
|
||||
return null;
|
||||
});
|
||||
}
|
||||
URIParameter uri = new URIParameter(policyFile.toURI());
|
||||
return AccessController.doPrivileged((PrivilegedAction<Policy>) () -> {
|
||||
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<Void>) () -> {
|
||||
System.clearProperty("codebase." + shortName);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (URISyntaxException e) {
|
||||
throw new IllegalArgumentException("unable to parse policy file `" + policyFile + "`", e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<Tuple<XPackExtensionInfo, XPackExtension> > extensions;
|
||||
private final List<Tuple<XPackExtensionInfo, XPackExtension>> 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<Class<? extends XPackExtension>> classpathExtensions) {
|
||||
public XPackExtensionsService(Settings settings, Path extsDirectory,
|
||||
Collection<Class<? extends XPackExtension>> classpathExtensions) {
|
||||
try {
|
||||
XPackExtensionSecurity.configure(extsDirectory);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Unable to configure extension policy", e);
|
||||
}
|
||||
|
||||
this.settings = settings;
|
||||
List<Tuple<XPackExtensionInfo, XPackExtension>> extensionsLoaded = new ArrayList<>();
|
||||
|
||||
// first we load extensions that are on the classpath. this is for tests
|
||||
for (Class<? extends XPackExtension> extClass : classpathExtensions) {
|
||||
XPackExtension ext = loadExtension(extClass, settings);
|
||||
|
@ -123,7 +130,7 @@ public class XPackExtensionsService {
|
|||
return bundles;
|
||||
}
|
||||
|
||||
private List<Tuple<XPackExtensionInfo, XPackExtension> > loadBundles(List<Bundle> bundles) {
|
||||
private List<Tuple<XPackExtensionInfo, XPackExtension>> loadBundles(List<Bundle> bundles) {
|
||||
List<Tuple<XPackExtensionInfo, XPackExtension>> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<Permission> 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())));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
grant {
|
||||
// needed to cause problems
|
||||
permission java.lang.RuntimePermission "getClassLoader";
|
||||
permission java.lang.RuntimePermission "closeClassLoader";
|
||||
};
|
|
@ -0,0 +1,4 @@
|
|||
grant {
|
||||
// needed to waste paper
|
||||
permission java.lang.RuntimePermission "queuePrintJob";
|
||||
};
|
|
@ -0,0 +1,4 @@
|
|||
grant {
|
||||
// an unresolved permission
|
||||
permission org.fake.FakePermission "fakeName";
|
||||
};
|
Loading…
Reference in New Issue