Merge pull request #13638 from rmuir/more_realistic_unit_tests_for_plugins_like_this
Better simulate problematic plugins permissions in unit tests.
This commit is contained in:
commit
7898ba103e
|
@ -251,6 +251,7 @@
|
||||||
<includes>
|
<includes>
|
||||||
<include>org/elasticsearch/test/**/*</include>
|
<include>org/elasticsearch/test/**/*</include>
|
||||||
<include>org/elasticsearch/bootstrap/BootstrapForTesting.class</include>
|
<include>org/elasticsearch/bootstrap/BootstrapForTesting.class</include>
|
||||||
|
<include>org/elasticsearch/bootstrap/MockPluginPolicy.class</include>
|
||||||
<include>org/elasticsearch/common/cli/CliToolTestCase.class</include>
|
<include>org/elasticsearch/common/cli/CliToolTestCase.class</include>
|
||||||
<include>org/elasticsearch/common/cli/CliToolTestCase$*.class</include>
|
<include>org/elasticsearch/common/cli/CliToolTestCase$*.class</include>
|
||||||
</includes>
|
</includes>
|
||||||
|
@ -279,7 +280,7 @@
|
||||||
<include>rest-api-spec/**/*</include>
|
<include>rest-api-spec/**/*</include>
|
||||||
<include>org/elasticsearch/test/**/*</include>
|
<include>org/elasticsearch/test/**/*</include>
|
||||||
<include>org/elasticsearch/bootstrap/BootstrapForTesting.class</include>
|
<include>org/elasticsearch/bootstrap/BootstrapForTesting.class</include>
|
||||||
<include>org/elasticsearch/bootstrap/XTestSecurityManager*.class</include>
|
<include>org/elasticsearch/bootstrap/MockPluginPolicy.class</include>
|
||||||
<include>org/elasticsearch/common/cli/CliToolTestCase.class</include>
|
<include>org/elasticsearch/common/cli/CliToolTestCase.class</include>
|
||||||
<include>org/elasticsearch/common/cli/CliToolTestCase$*.class</include>
|
<include>org/elasticsearch/common/cli/CliToolTestCase$*.class</include>
|
||||||
<include>org/elasticsearch/cluster/MockInternalClusterInfoService.class</include>
|
<include>org/elasticsearch/cluster/MockInternalClusterInfoService.class</include>
|
||||||
|
|
|
@ -30,13 +30,8 @@ import org.elasticsearch.common.logging.Loggers;
|
||||||
import java.io.FilePermission;
|
import java.io.FilePermission;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.security.CodeSource;
|
|
||||||
import java.security.Permission;
|
|
||||||
import java.security.PermissionCollection;
|
|
||||||
import java.security.Permissions;
|
import java.security.Permissions;
|
||||||
import java.security.Policy;
|
import java.security.Policy;
|
||||||
import java.security.cert.Certificate;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import static com.carrotsearch.randomizedtesting.RandomizedTest.systemPropertyAsBoolean;
|
import static com.carrotsearch.randomizedtesting.RandomizedTest.systemPropertyAsBoolean;
|
||||||
|
@ -119,14 +114,17 @@ public class BootstrapForTesting {
|
||||||
perms.add(new FilePermission(coverageDir.resolve("jacoco-it.exec").toString(), "read,write"));
|
perms.add(new FilePermission(coverageDir.resolve("jacoco-it.exec").toString(), "read,write"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// if its an insecure plugin, its not easy to simulate here, since we don't have a real plugin install.
|
final Policy policy;
|
||||||
// we just do our best so unit testing can work. integration tests for such plugins are essential.
|
// if its an insecure plugin, we use a wrapper policy impl to try
|
||||||
|
// to simulate what happens with a real distribution
|
||||||
String artifact = System.getProperty("tests.artifact");
|
String artifact = System.getProperty("tests.artifact");
|
||||||
String insecurePluginProp = Security.INSECURE_PLUGINS.get(artifact);
|
String insecurePluginProp = Security.INSECURE_PLUGINS.get(artifact);
|
||||||
if (insecurePluginProp != null) {
|
if (insecurePluginProp != null) {
|
||||||
setInsecurePluginPermissions(perms, insecurePluginProp);
|
policy = new MockPluginPolicy(perms, insecurePluginProp);
|
||||||
|
} else {
|
||||||
|
policy = new ESPolicy(perms);
|
||||||
}
|
}
|
||||||
Policy.setPolicy(new ESPolicy(perms));
|
Policy.setPolicy(policy);
|
||||||
System.setSecurityManager(new TestSecurityManager());
|
System.setSecurityManager(new TestSecurityManager());
|
||||||
Security.selfTest();
|
Security.selfTest();
|
||||||
|
|
||||||
|
@ -144,44 +142,6 @@ public class BootstrapForTesting {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* with a real plugin install, we just set a property to plugin/foo*, which matches
|
|
||||||
* plugin code and all dependencies. when running unit tests, things are disorganized,
|
|
||||||
* and might even be on different filesystem roots (windows), so we can't even make
|
|
||||||
* a URL that will match everything. instead, add the extra permissions globally.
|
|
||||||
*/
|
|
||||||
// TODO: maybe wrap with a policy so the extra permissions aren't applied to test classes/framework,
|
|
||||||
// so that stacks are always polluted and tests fail for missing AccessController blocks...
|
|
||||||
static void setInsecurePluginPermissions(Permissions permissions, String insecurePluginProp) throws Exception {
|
|
||||||
// the hack begins!
|
|
||||||
|
|
||||||
// parse whole policy file, with and without the substitution, compute the delta, then add globally.
|
|
||||||
URL bogus = new URL("file:/bogus");
|
|
||||||
ESPolicy policy = new ESPolicy(new Permissions());
|
|
||||||
PermissionCollection small = policy.template.getPermissions(new CodeSource(bogus, (Certificate[])null));
|
|
||||||
System.setProperty(insecurePluginProp, bogus.toString());
|
|
||||||
policy = new ESPolicy(new Permissions());
|
|
||||||
System.clearProperty(insecurePluginProp);
|
|
||||||
PermissionCollection big = policy.template.getPermissions(new CodeSource(bogus, (Certificate[])null));
|
|
||||||
|
|
||||||
PermissionCollection delta = delta(small, big);
|
|
||||||
for (Permission p : Collections.list(delta.elements())) {
|
|
||||||
permissions.add(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// computes delta of small and big, the slow way
|
|
||||||
static PermissionCollection delta(PermissionCollection small, PermissionCollection big) {
|
|
||||||
Permissions extra = new Permissions();
|
|
||||||
for (Permission p : Collections.list(big.elements())) {
|
|
||||||
// check big too, to remove UnresolvedPermissions (acts like NaN)
|
|
||||||
if (big.implies(p) && small.implies(p) == false) {
|
|
||||||
extra.add(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return extra;
|
|
||||||
}
|
|
||||||
|
|
||||||
// does nothing, just easy way to make sure the class is loaded.
|
// does nothing, just easy way to make sure the class is loaded.
|
||||||
public static void ensureInitialized() {}
|
public static void ensureInitialized() {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,125 @@
|
||||||
|
/*
|
||||||
|
* 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 com.carrotsearch.randomizedtesting.RandomizedRunner;
|
||||||
|
|
||||||
|
import org.apache.lucene.util.LuceneTestCase;
|
||||||
|
import org.elasticsearch.common.io.PathUtils;
|
||||||
|
import org.elasticsearch.common.logging.Loggers;
|
||||||
|
import org.junit.Assert;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.security.CodeSource;
|
||||||
|
import java.security.Permission;
|
||||||
|
import java.security.PermissionCollection;
|
||||||
|
import java.security.Permissions;
|
||||||
|
import java.security.Policy;
|
||||||
|
import java.security.ProtectionDomain;
|
||||||
|
import java.security.cert.Certificate;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simulates in unit tests per-plugin permissions.
|
||||||
|
* Unit tests for plugins do not have a proper plugin structure,
|
||||||
|
* so we don't know which codebases to apply the permission to.
|
||||||
|
* <p>
|
||||||
|
* As an approximation, we just exclude es/test/framework classes,
|
||||||
|
* because they will be present in stacks and fail tests for the
|
||||||
|
* simple case where an AccessController block is missing, because
|
||||||
|
* java security checks every codebase in the stacktrace, and we
|
||||||
|
* are sure to pollute it.
|
||||||
|
*/
|
||||||
|
final class MockPluginPolicy extends Policy {
|
||||||
|
final ESPolicy standardPolicy;
|
||||||
|
final PermissionCollection extraPermissions;
|
||||||
|
final Set<CodeSource> extraSources;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new MockPluginPolicy with dynamic {@code permissions} and
|
||||||
|
* adding the extra plugin permissions from {@code insecurePluginProp} to
|
||||||
|
* all code except test classes.
|
||||||
|
*/
|
||||||
|
MockPluginPolicy(Permissions permissions, String insecurePluginProp) throws Exception {
|
||||||
|
// the hack begins!
|
||||||
|
|
||||||
|
// parse whole policy file, with and without the substitution, compute the delta
|
||||||
|
standardPolicy = new ESPolicy(permissions);
|
||||||
|
|
||||||
|
URL bogus = new URL("file:/bogus"); // its "any old codebase" this time: generic permissions
|
||||||
|
PermissionCollection smallPermissions = standardPolicy.template.getPermissions(new CodeSource(bogus, (Certificate[])null));
|
||||||
|
Set<Permission> small = new HashSet<>(Collections.list(smallPermissions.elements()));
|
||||||
|
|
||||||
|
// set the URL for the property substitution, this time it will also have special permissions
|
||||||
|
System.setProperty(insecurePluginProp, bogus.toString());
|
||||||
|
ESPolicy biggerPolicy = new ESPolicy(permissions);
|
||||||
|
System.clearProperty(insecurePluginProp);
|
||||||
|
PermissionCollection bigPermissions = biggerPolicy.template.getPermissions(new CodeSource(bogus, (Certificate[])null));
|
||||||
|
Set<Permission> big = new HashSet<>(Collections.list(bigPermissions.elements()));
|
||||||
|
|
||||||
|
// compute delta to remove all the generic permissions
|
||||||
|
// we want equals() vs implies() for this check, in case we need
|
||||||
|
// to pass along any UnresolvedPermission to the plugin
|
||||||
|
big.removeAll(small);
|
||||||
|
|
||||||
|
// build collection of the special permissions for easy checking
|
||||||
|
extraPermissions = new Permissions();
|
||||||
|
for (Permission p : big) {
|
||||||
|
extraPermissions.add(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
// every element in classpath except test-classes/
|
||||||
|
extraSources = new HashSet<CodeSource>();
|
||||||
|
for (URL location : JarHell.parseClassPath()) {
|
||||||
|
Path path = PathUtils.get(location.toURI());
|
||||||
|
String baseName = path.getFileName().toString();
|
||||||
|
if (baseName.contains("test-classes") == false) {
|
||||||
|
extraSources.add(new CodeSource(location, (Certificate[])null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// exclude some obvious places
|
||||||
|
// es core
|
||||||
|
extraSources.remove(Bootstrap.class.getProtectionDomain().getCodeSource());
|
||||||
|
// es test framework
|
||||||
|
extraSources.remove(getClass().getProtectionDomain().getCodeSource());
|
||||||
|
// lucene test framework
|
||||||
|
extraSources.remove(LuceneTestCase.class.getProtectionDomain().getCodeSource());
|
||||||
|
// test runner
|
||||||
|
extraSources.remove(RandomizedRunner.class.getProtectionDomain().getCodeSource());
|
||||||
|
// junit library
|
||||||
|
extraSources.remove(Assert.class.getProtectionDomain().getCodeSource());
|
||||||
|
|
||||||
|
Loggers.getLogger(getClass()).debug("Apply permissions [{}] to codebases [{}]", extraPermissions, extraSources);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean implies(ProtectionDomain domain, Permission permission) {
|
||||||
|
if (standardPolicy.implies(domain, permission)) {
|
||||||
|
return true;
|
||||||
|
} else if (extraSources.contains(domain.getCodeSource())) {
|
||||||
|
return extraPermissions.implies(permission);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -37,6 +37,7 @@ import org.elasticsearch.common.unit.TimeValue;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.AccessController;
|
import java.security.AccessController;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.security.PrivilegedActionException;
|
||||||
import java.security.PrivilegedExceptionAction;
|
import java.security.PrivilegedExceptionAction;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
@ -59,13 +60,20 @@ public class GceComputeServiceImpl extends AbstractLifecycleComponent<GceCompute
|
||||||
logger.debug("get instances for project [{}], zones [{}]", project, zones);
|
logger.debug("get instances for project [{}], zones [{}]", project, zones);
|
||||||
final List<Instance> instances = zones.stream().map((zoneId) -> {
|
final List<Instance> instances = zones.stream().map((zoneId) -> {
|
||||||
try {
|
try {
|
||||||
|
// hack around code messiness in GCE code
|
||||||
|
// TODO: get this fixed
|
||||||
|
InstanceList instanceList = AccessController.doPrivileged(new PrivilegedExceptionAction<InstanceList>() {
|
||||||
|
@Override
|
||||||
|
public InstanceList run() throws Exception {
|
||||||
Compute.Instances.List list = client().instances().list(project, zoneId);
|
Compute.Instances.List list = client().instances().list(project, zoneId);
|
||||||
InstanceList instanceList = list.execute();
|
return list.execute();
|
||||||
|
}
|
||||||
|
});
|
||||||
if (instanceList.isEmpty()) {
|
if (instanceList.isEmpty()) {
|
||||||
return Collections.EMPTY_LIST;
|
return Collections.EMPTY_LIST;
|
||||||
}
|
}
|
||||||
return instanceList.getItems();
|
return instanceList.getItems();
|
||||||
} catch (IOException e) {
|
} catch (PrivilegedActionException e) {
|
||||||
logger.warn("Problem fetching instance list for zone {}", zoneId);
|
logger.warn("Problem fetching instance list for zone {}", zoneId);
|
||||||
logger.debug("Full exception:", e);
|
logger.debug("Full exception:", e);
|
||||||
return Collections.EMPTY_LIST;
|
return Collections.EMPTY_LIST;
|
||||||
|
|
|
@ -242,8 +242,7 @@ public class GceUnicastHostsProvider extends AbstractComponent implements Unicas
|
||||||
|
|
||||||
}
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
logger.warn("Exception caught during discovery {} : {}", e.getClass().getName(), e.getMessage());
|
logger.warn("Exception caught during discovery: {}", e, e.getMessage());
|
||||||
logger.trace("Exception caught during discovery", e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug("{} node(s) added", cachedDiscoNodes.size());
|
logger.debug("{} node(s) added", cachedDiscoNodes.size());
|
||||||
|
|
Loading…
Reference in New Issue