228 lines
9.1 KiB
Java
Raw Normal View History

2015-05-04 15:38:46 -04:00
/*
* 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;
2015-05-05 00:33:29 -04:00
import org.elasticsearch.common.SuppressForbidden;
2015-12-18 20:16:15 -05:00
import java.io.FilePermission;
import java.io.IOException;
import java.net.SocketPermission;
import java.net.URL;
import java.security.CodeSource;
2015-05-04 15:38:46 -04:00
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Permissions;
2015-05-04 15:38:46 -04:00
import java.security.Policy;
import java.security.ProtectionDomain;
import java.util.Collections;
Decentralize plugin security * Add ability for plugins to declare additional permissions with a custom plugin-security.policy file and corresponding AccessController logic. See the plugin author's guide for more information. * Add warning messages to users for extra plugin permissions in bin/plugin. * When bin/plugin is run interactively (stdin is a controlling terminal and -b/--batch not supplied), require user confirmation. * Improve unit test and IDE support for plugins with additional permissions by exposing plugin's metadata as a maven test resource. Closes #14108 Squashed commit of the following: commit cf8ace65a7397aaccd356bf55f95d6fbb8bb571c Author: Robert Muir <rmuir@apache.org> Date: Wed Oct 14 13:36:05 2015 -0400 fix new unit test from master merge commit 9be3c5aa38f2d9ae50f3d54924a30ad9cddeeb65 Merge: 2f168b8 7368231 Author: Robert Muir <rmuir@apache.org> Date: Wed Oct 14 12:58:31 2015 -0400 Merge branch 'master' into off_my_back commit 2f168b8038e32672f01ad0279fb5db77ba902ae8 Author: Robert Muir <rmuir@apache.org> Date: Wed Oct 14 12:56:04 2015 -0400 improve plugin author documentation commit 6e6c2bfda68a418d92733ac22a58eec35508b2d0 Author: Robert Muir <rmuir@apache.org> Date: Wed Oct 14 12:52:14 2015 -0400 move security confirmation after 'plugin already installed' check, to prevent user from answering unnecessary questions. commit 08233a2972554afef2a6a7521990283102e20d92 Author: Robert Muir <rmuir@apache.org> Date: Wed Oct 14 05:36:42 2015 -0400 Add documentation and pluginmanager support commit 05dad86c51488ba43ccbd749f0164f3fbd3aee62 Author: Robert Muir <rmuir@apache.org> Date: Wed Oct 14 02:22:24 2015 -0400 Decentralize plugin permissions (modulo docs and pluginmanager work)
2015-10-14 14:46:45 -04:00
import java.util.Map;
import java.util.function.Predicate;
2015-05-04 15:38:46 -04:00
/** custom policy for union of static and dynamic permissions */
final class ESPolicy extends Policy {
Add primitive to shrink an index into a single shard (#18270) This adds a low level primitive operations to shrink an existing index into a new index with a single shard. This primitive expects all shards of the source index to allocated on a single node. Once the target index is initializing on the shrink node it takes a snapshot of the source index shards and copies all files into the target indices data folder. An [optimization](https://issues.apache.org/jira/browse/LUCENE-7300) coming in Lucene 6.1 will also allow for optional constant time copy if hard-links are supported by the filesystem. All mappings are merged into the new indexes metadata once the snapshots have been taken on the merge node. To shrink an existing index all shards must be moved to a single node (one instance of each shard) and the index must be read-only: ```BASH $ curl -XPUT 'http://localhost:9200/logs/_settings' -d '{ "settings" : { "index.routing.allocation.require._name" : "shrink_node_name", "index.blocks.write" : true } } ``` once all shards are started on the shrink node. the new index can be created via: ```BASH $ curl -XPUT 'http://localhost:9200/logs/_shrink/logs_single_shard' -d '{ "settings" : { "index.codec" : "best_compression", "index.number_of_replicas" : 1 } }' ``` This API will perform all needed check before the new index is created and selects the shrink node based on the allocation of the source index. This call returns immediately, to monitor shrink progress the recovery API should be used since all copy operations are reflected in the recovery API with byte copy progress etc. The shrink operation does not modify the source index, if a shrink operation should be canceled or if the shrink failed, the target index can simply be deleted and all resources are released.
2016-05-31 10:41:44 +02:00
2015-05-04 15:38:46 -04:00
/** template policy file, the one used in tests */
static final String POLICY_RESOURCE = "security.policy";
/** limited policy for scripts */
static final String UNTRUSTED_RESOURCE = "untrusted.policy";
Add primitive to shrink an index into a single shard (#18270) This adds a low level primitive operations to shrink an existing index into a new index with a single shard. This primitive expects all shards of the source index to allocated on a single node. Once the target index is initializing on the shrink node it takes a snapshot of the source index shards and copies all files into the target indices data folder. An [optimization](https://issues.apache.org/jira/browse/LUCENE-7300) coming in Lucene 6.1 will also allow for optional constant time copy if hard-links are supported by the filesystem. All mappings are merged into the new indexes metadata once the snapshots have been taken on the merge node. To shrink an existing index all shards must be moved to a single node (one instance of each shard) and the index must be read-only: ```BASH $ curl -XPUT 'http://localhost:9200/logs/_settings' -d '{ "settings" : { "index.routing.allocation.require._name" : "shrink_node_name", "index.blocks.write" : true } } ``` once all shards are started on the shrink node. the new index can be created via: ```BASH $ curl -XPUT 'http://localhost:9200/logs/_shrink/logs_single_shard' -d '{ "settings" : { "index.codec" : "best_compression", "index.number_of_replicas" : 1 } }' ``` This API will perform all needed check before the new index is created and selects the shrink node based on the allocation of the source index. This call returns immediately, to monitor shrink progress the recovery API should be used since all copy operations are reflected in the recovery API with byte copy progress etc. The shrink operation does not modify the source index, if a shrink operation should be canceled or if the shrink failed, the target index can simply be deleted and all resources are released.
2016-05-31 10:41:44 +02:00
2015-05-04 15:38:46 -04:00
final Policy template;
final Policy untrusted;
final Policy system;
2015-05-04 15:38:46 -04:00
final PermissionCollection dynamic;
final PermissionCollection dataPathPermission;
final Map<String,Policy> plugins;
2015-05-04 15:38:46 -04:00
ESPolicy(Map<String, URL> codebases, PermissionCollection dynamic, Map<String,Policy> plugins, boolean filterBadDefaults,
PermissionCollection dataPathPermission) {
this.template = Security.readPolicy(getClass().getResource(POLICY_RESOURCE), codebases);
this.dataPathPermission = dataPathPermission;
this.untrusted = Security.readPolicy(getClass().getResource(UNTRUSTED_RESOURCE), Collections.emptyMap());
if (filterBadDefaults) {
this.system = new SystemPolicy(Policy.getPolicy());
} else {
this.system = Policy.getPolicy();
}
2015-05-04 15:38:46 -04:00
this.dynamic = dynamic;
Decentralize plugin security * Add ability for plugins to declare additional permissions with a custom plugin-security.policy file and corresponding AccessController logic. See the plugin author's guide for more information. * Add warning messages to users for extra plugin permissions in bin/plugin. * When bin/plugin is run interactively (stdin is a controlling terminal and -b/--batch not supplied), require user confirmation. * Improve unit test and IDE support for plugins with additional permissions by exposing plugin's metadata as a maven test resource. Closes #14108 Squashed commit of the following: commit cf8ace65a7397aaccd356bf55f95d6fbb8bb571c Author: Robert Muir <rmuir@apache.org> Date: Wed Oct 14 13:36:05 2015 -0400 fix new unit test from master merge commit 9be3c5aa38f2d9ae50f3d54924a30ad9cddeeb65 Merge: 2f168b8 7368231 Author: Robert Muir <rmuir@apache.org> Date: Wed Oct 14 12:58:31 2015 -0400 Merge branch 'master' into off_my_back commit 2f168b8038e32672f01ad0279fb5db77ba902ae8 Author: Robert Muir <rmuir@apache.org> Date: Wed Oct 14 12:56:04 2015 -0400 improve plugin author documentation commit 6e6c2bfda68a418d92733ac22a58eec35508b2d0 Author: Robert Muir <rmuir@apache.org> Date: Wed Oct 14 12:52:14 2015 -0400 move security confirmation after 'plugin already installed' check, to prevent user from answering unnecessary questions. commit 08233a2972554afef2a6a7521990283102e20d92 Author: Robert Muir <rmuir@apache.org> Date: Wed Oct 14 05:36:42 2015 -0400 Add documentation and pluginmanager support commit 05dad86c51488ba43ccbd749f0164f3fbd3aee62 Author: Robert Muir <rmuir@apache.org> Date: Wed Oct 14 02:22:24 2015 -0400 Decentralize plugin permissions (modulo docs and pluginmanager work)
2015-10-14 14:46:45 -04:00
this.plugins = plugins;
2015-05-04 15:38:46 -04:00
}
@Override @SuppressForbidden(reason = "fast equals check is desired")
Add primitive to shrink an index into a single shard (#18270) This adds a low level primitive operations to shrink an existing index into a new index with a single shard. This primitive expects all shards of the source index to allocated on a single node. Once the target index is initializing on the shrink node it takes a snapshot of the source index shards and copies all files into the target indices data folder. An [optimization](https://issues.apache.org/jira/browse/LUCENE-7300) coming in Lucene 6.1 will also allow for optional constant time copy if hard-links are supported by the filesystem. All mappings are merged into the new indexes metadata once the snapshots have been taken on the merge node. To shrink an existing index all shards must be moved to a single node (one instance of each shard) and the index must be read-only: ```BASH $ curl -XPUT 'http://localhost:9200/logs/_settings' -d '{ "settings" : { "index.routing.allocation.require._name" : "shrink_node_name", "index.blocks.write" : true } } ``` once all shards are started on the shrink node. the new index can be created via: ```BASH $ curl -XPUT 'http://localhost:9200/logs/_shrink/logs_single_shard' -d '{ "settings" : { "index.codec" : "best_compression", "index.number_of_replicas" : 1 } }' ``` This API will perform all needed check before the new index is created and selects the shrink node based on the allocation of the source index. This call returns immediately, to monitor shrink progress the recovery API should be used since all copy operations are reflected in the recovery API with byte copy progress etc. The shrink operation does not modify the source index, if a shrink operation should be canceled or if the shrink failed, the target index can simply be deleted and all resources are released.
2016-05-31 10:41:44 +02:00
public boolean implies(ProtectionDomain domain, Permission permission) {
CodeSource codeSource = domain.getCodeSource();
// codesource can be null when reducing privileges via doPrivileged()
if (codeSource == null) {
return false;
}
URL location = codeSource.getLocation();
// location can be null... ??? nobody knows
// https://bugs.openjdk.java.net/browse/JDK-8129972
if (location != null) {
// run scripts with limited permissions
if (BootstrapInfo.UNTRUSTED_CODEBASE.equals(location.getFile())) {
return untrusted.implies(domain, permission);
}
// check for an additional plugin permission: plugin policy is
// only consulted for its codesources.
Policy plugin = plugins.get(location.getFile());
if (plugin != null && plugin.implies(domain, permission)) {
Decentralize plugin security * Add ability for plugins to declare additional permissions with a custom plugin-security.policy file and corresponding AccessController logic. See the plugin author's guide for more information. * Add warning messages to users for extra plugin permissions in bin/plugin. * When bin/plugin is run interactively (stdin is a controlling terminal and -b/--batch not supplied), require user confirmation. * Improve unit test and IDE support for plugins with additional permissions by exposing plugin's metadata as a maven test resource. Closes #14108 Squashed commit of the following: commit cf8ace65a7397aaccd356bf55f95d6fbb8bb571c Author: Robert Muir <rmuir@apache.org> Date: Wed Oct 14 13:36:05 2015 -0400 fix new unit test from master merge commit 9be3c5aa38f2d9ae50f3d54924a30ad9cddeeb65 Merge: 2f168b8 7368231 Author: Robert Muir <rmuir@apache.org> Date: Wed Oct 14 12:58:31 2015 -0400 Merge branch 'master' into off_my_back commit 2f168b8038e32672f01ad0279fb5db77ba902ae8 Author: Robert Muir <rmuir@apache.org> Date: Wed Oct 14 12:56:04 2015 -0400 improve plugin author documentation commit 6e6c2bfda68a418d92733ac22a58eec35508b2d0 Author: Robert Muir <rmuir@apache.org> Date: Wed Oct 14 12:52:14 2015 -0400 move security confirmation after 'plugin already installed' check, to prevent user from answering unnecessary questions. commit 08233a2972554afef2a6a7521990283102e20d92 Author: Robert Muir <rmuir@apache.org> Date: Wed Oct 14 05:36:42 2015 -0400 Add documentation and pluginmanager support commit 05dad86c51488ba43ccbd749f0164f3fbd3aee62 Author: Robert Muir <rmuir@apache.org> Date: Wed Oct 14 02:22:24 2015 -0400 Decentralize plugin permissions (modulo docs and pluginmanager work)
2015-10-14 14:46:45 -04:00
return true;
}
2015-05-05 00:33:29 -04:00
}
2015-12-18 20:16:15 -05:00
// Special handling for broken Hadoop code: "let me execute or my classes will not load"
// yeah right, REMOVE THIS when hadoop is fixed
if (permission instanceof FilePermission && "<<ALL FILES>>".equals(permission.getName())) {
for (StackTraceElement element : Thread.currentThread().getStackTrace()) {
if ("org.apache.hadoop.util.Shell".equals(element.getClassName()) &&
"runCommand".equals(element.getMethodName())) {
// we found the horrible method: the hack begins!
// force the hadoop code to back down, by throwing an exception that it catches.
rethrow(new IOException("no hadoop, you cannot do this."));
}
}
}
// The FilePermission to check access to the path.data is the hottest permission check in
// Elasticsearch, so we check it first.
if (permission instanceof FilePermission && dataPathPermission.implies(permission)) {
return true;
}
// otherwise defer to template + dynamic file permissions
return template.implies(domain, permission) || dynamic.implies(permission) || system.implies(domain, permission);
2015-05-04 15:38:46 -04:00
}
2015-12-18 20:16:15 -05:00
/**
* Classy puzzler to rethrow any checked exception as an unchecked one.
*/
private static class Rethrower<T extends Throwable> {
private void rethrow(Throwable t) throws T {
throw (T) t;
}
}
/**
* Rethrows <code>t</code> (identical object).
*/
private void rethrow(Throwable t) {
new Rethrower<Error>().rethrow(t);
}
@Override
public PermissionCollection getPermissions(CodeSource codesource) {
// code should not rely on this method, or at least use it correctly:
// https://bugs.openjdk.java.net/browse/JDK-8014008
// return them a new empty permissions object so jvisualvm etc work
for (StackTraceElement element : Thread.currentThread().getStackTrace()) {
if ("sun.rmi.server.LoaderHandler".equals(element.getClassName()) &&
"loadClass".equals(element.getMethodName())) {
return new Permissions();
}
}
// return UNSUPPORTED_EMPTY_COLLECTION since it is safe.
return super.getPermissions(codesource);
}
// TODO: remove this hack when insecure defaults are removed from java
/**
* Wraps a bad default permission, applying a pre-implies to any permissions before checking if the wrapped bad default permission
* implies a permission.
*/
private static class BadDefaultPermission extends Permission {
private final Permission badDefaultPermission;
private final Predicate<Permission> preImplies;
/**
* Construct an instance with a pre-implies check to apply to desired permissions.
*
* @param badDefaultPermission the bad default permission to wrap
* @param preImplies a test that is applied to a desired permission before checking if the bad default permission that
* this instance wraps implies the desired permission
*/
BadDefaultPermission(final Permission badDefaultPermission, final Predicate<Permission> preImplies) {
super(badDefaultPermission.getName());
this.badDefaultPermission = badDefaultPermission;
this.preImplies = preImplies;
}
@Override
public final boolean implies(Permission permission) {
return preImplies.test(permission) && badDefaultPermission.implies(permission);
}
@Override
public final boolean equals(Object obj) {
return badDefaultPermission.equals(obj);
}
@Override
public int hashCode() {
return badDefaultPermission.hashCode();
}
@Override
public String getActions() {
return badDefaultPermission.getActions();
}
}
// 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...
private static final Permission BAD_DEFAULT_NUMBER_ONE = new BadDefaultPermission(new RuntimePermission("stopThread"), p -> true);
// 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"
private static final Permission BAD_DEFAULT_NUMBER_TWO =
new BadDefaultPermission(
new SocketPermission("localhost:0", "listen"),
// we apply this pre-implies test because some SocketPermission#implies calls do expensive reverse-DNS resolves
p -> p instanceof SocketPermission && p.getActions().contains("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.implies(permission) || BAD_DEFAULT_NUMBER_TWO.implies(permission)) {
return false;
}
return delegate.implies(domain, permission);
}
}
2015-05-04 15:38:46 -04:00
}