Merge branch 'master' into feature/http_client
Original commit: elastic/x-pack-elasticsearch@f8d810f0e8
This commit is contained in:
commit
82a19cda0e
|
@ -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";
|
||||
};
|
|
@ -149,9 +149,7 @@ public class GraphExploreRequest extends ActionRequest<GraphExploreRequest> impl
|
|||
indicesOptions = IndicesOptions.readIndicesOptions(in);
|
||||
types = in.readStringArray();
|
||||
routing = in.readOptionalString();
|
||||
if (in.readBoolean()) {
|
||||
timeout = TimeValue.readTimeValue(in);
|
||||
}
|
||||
timeout = in.readOptionalWriteable(TimeValue::new);
|
||||
sampleSize = in.readInt();
|
||||
sampleDiversityField = in.readOptionalString();
|
||||
maxDocsPerDiversityValue = in.readInt();
|
||||
|
@ -177,7 +175,7 @@ public class GraphExploreRequest extends ActionRequest<GraphExploreRequest> impl
|
|||
indicesOptions.writeIndicesOptions(out);
|
||||
out.writeStringArray(types);
|
||||
out.writeOptionalString(routing);
|
||||
out.writeOptionalStreamable(timeout);
|
||||
out.writeOptionalWriteable(timeout);
|
||||
|
||||
out.writeInt(sampleSize);
|
||||
out.writeOptionalString(sampleDiversityField);
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
package org.elasticsearch.shield.authc;
|
||||
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.common.collect.Iterators;
|
||||
import org.elasticsearch.common.component.AbstractLifecycleComponent;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.settings.Setting;
|
||||
|
@ -101,6 +102,10 @@ public class Realms extends AbstractLifecycleComponent<Realms> implements Iterab
|
|||
|
||||
@Override
|
||||
public Iterator<Realm> iterator() {
|
||||
if (shieldLicenseState.authenticationAndAuthorizationEnabled() == false) {
|
||||
return Collections.emptyIterator();
|
||||
}
|
||||
|
||||
EnabledRealmType enabledRealmType = shieldLicenseState.enabledRealmType();
|
||||
switch (enabledRealmType) {
|
||||
case ALL:
|
||||
|
|
|
@ -35,6 +35,7 @@ import org.elasticsearch.index.cache.bitset.BitsetFilterCache;
|
|||
import org.elasticsearch.index.engine.EngineException;
|
||||
import org.elasticsearch.index.mapper.DocumentMapper;
|
||||
import org.elasticsearch.index.mapper.MapperService;
|
||||
import org.elasticsearch.index.mapper.internal.FieldNamesFieldMapper;
|
||||
import org.elasticsearch.index.mapper.internal.ParentFieldMapper;
|
||||
import org.elasticsearch.index.query.ParsedQuery;
|
||||
import org.elasticsearch.index.query.QueryBuilder;
|
||||
|
@ -90,6 +91,7 @@ public class ShieldIndexSearcherWrapper extends IndexSearcherWrapper {
|
|||
|
||||
Set<String> allowedMetaFields = new HashSet<>();
|
||||
allowedMetaFields.addAll(Arrays.asList(MapperService.getAllMetaFields()));
|
||||
allowedMetaFields.add(FieldNamesFieldMapper.NAME); // TODO: add _field_names to MapperService#META_FIELDS?
|
||||
allowedMetaFields.add("_source"); // TODO: add _source to MapperService#META_FIELDS?
|
||||
allowedMetaFields.add("_version"); // TODO: add _version to MapperService#META_FIELDS?
|
||||
allowedMetaFields.remove("_all"); // The _all field contains actual data and we can't include that by default.
|
||||
|
|
|
@ -34,6 +34,7 @@ import java.util.Collections;
|
|||
|
||||
import static org.elasticsearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE;
|
||||
import static org.elasticsearch.index.query.QueryBuilders.constantScoreQuery;
|
||||
import static org.elasticsearch.index.query.QueryBuilders.existsQuery;
|
||||
import static org.elasticsearch.index.query.QueryBuilders.hasChildQuery;
|
||||
import static org.elasticsearch.index.query.QueryBuilders.matchQuery;
|
||||
import static org.elasticsearch.index.query.QueryBuilders.termQuery;
|
||||
|
@ -482,8 +483,6 @@ public class FieldLevelSecurityTests extends ShieldIntegTestCase {
|
|||
assertThat(response.getResponses()[0].getResponse().getSource().get("field2").toString(), equalTo("value2"));
|
||||
}
|
||||
|
||||
// norelease - we need to fix the issue so that only fields a user can see are returned
|
||||
@AwaitsFix(bugUrl = "https://github.com/elastic/x-plugins/issues/2120")
|
||||
public void testFieldStatsApi() throws Exception {
|
||||
assertAcked(client().admin().indices().prepareCreate("test")
|
||||
.addMapping("type1", "field1", "type=text", "field2", "type=text", "field3", "type=text")
|
||||
|
@ -883,28 +882,28 @@ public class FieldLevelSecurityTests extends ShieldIntegTestCase {
|
|||
.prepareSearch("test")
|
||||
.addSort("field1", SortOrder.ASC)
|
||||
.get();
|
||||
assertThat((Long) response.getHits().getAt(0).sortValues()[0], equalTo(1L));
|
||||
assertThat(response.getHits().getAt(0).sortValues()[0], equalTo(1L));
|
||||
|
||||
// user2 is not granted to use field1, so the default missing sort value is included
|
||||
response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user2", USERS_PASSWD)))
|
||||
.prepareSearch("test")
|
||||
.addSort("field1", SortOrder.ASC)
|
||||
.get();
|
||||
assertThat((Long) response.getHits().getAt(0).sortValues()[0], equalTo(Long.MAX_VALUE));
|
||||
assertThat(response.getHits().getAt(0).sortValues()[0], equalTo(Long.MAX_VALUE));
|
||||
|
||||
// user1 is not granted to use field2, so the default missing sort value is included
|
||||
response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user1", USERS_PASSWD)))
|
||||
.prepareSearch("test")
|
||||
.addSort("field2", SortOrder.ASC)
|
||||
.get();
|
||||
assertThat((Long) response.getHits().getAt(0).sortValues()[0], equalTo(Long.MAX_VALUE));
|
||||
assertThat(response.getHits().getAt(0).sortValues()[0], equalTo(Long.MAX_VALUE));
|
||||
|
||||
// user2 is granted to use field2, so it is included in the sort_values
|
||||
response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user2", USERS_PASSWD)))
|
||||
.prepareSearch("test")
|
||||
.addSort("field2", SortOrder.ASC)
|
||||
.get();
|
||||
assertThat((Long) response.getHits().getAt(0).sortValues()[0], equalTo(2L));
|
||||
assertThat(response.getHits().getAt(0).sortValues()[0], equalTo(2L));
|
||||
}
|
||||
|
||||
public void testAggs() throws Exception {
|
||||
|
@ -1225,4 +1224,64 @@ public class FieldLevelSecurityTests extends ShieldIntegTestCase {
|
|||
assertThat(response.getHits().getAt(0).sourceAsMap().get("field2").toString(), equalTo("value2"));
|
||||
}
|
||||
|
||||
public void testExistQuery() {
|
||||
assertAcked(client().admin().indices().prepareCreate("test")
|
||||
.addMapping("type1", "field1", "type=text", "field2", "type=text", "field3", "type=text")
|
||||
);
|
||||
client().prepareIndex("test", "type1", "1").setSource("field1", "value1", "field2", "value2", "field3", "value3")
|
||||
.setRefreshPolicy(IMMEDIATE)
|
||||
.get();
|
||||
|
||||
// user1 has access to field1, so the query should match with the document:
|
||||
SearchResponse response = client()
|
||||
.filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user1", USERS_PASSWD)))
|
||||
.prepareSearch("test")
|
||||
.setQuery(existsQuery("field1"))
|
||||
.get();
|
||||
assertHitCount(response, 1);
|
||||
// user1 has no access to field2, so the query should not match with the document:
|
||||
response = client()
|
||||
.filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user1", USERS_PASSWD)))
|
||||
.prepareSearch("test")
|
||||
.setQuery(existsQuery("field2"))
|
||||
.get();
|
||||
assertHitCount(response, 0);
|
||||
// user2 has no access to field1, so the query should not match with the document:
|
||||
response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user2", USERS_PASSWD)))
|
||||
.prepareSearch("test")
|
||||
.setQuery(existsQuery("field1"))
|
||||
.get();
|
||||
assertHitCount(response, 0);
|
||||
// user2 has access to field2, so the query should match with the document:
|
||||
response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user2", USERS_PASSWD)))
|
||||
.prepareSearch("test")
|
||||
.setQuery(existsQuery("field2"))
|
||||
.get();
|
||||
assertHitCount(response, 1);
|
||||
// user3 has access to field1 and field2, so the query should match with the document:
|
||||
response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user3", USERS_PASSWD)))
|
||||
.prepareSearch("test")
|
||||
.setQuery(existsQuery("field1"))
|
||||
.get();
|
||||
assertHitCount(response, 1);
|
||||
// user3 has access to field1 and field2, so the query should match with the document:
|
||||
response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user3", USERS_PASSWD)))
|
||||
.prepareSearch("test")
|
||||
.setQuery(existsQuery("field2"))
|
||||
.get();
|
||||
assertHitCount(response, 1);
|
||||
// user4 has access to no fields, so the query should not match with the document:
|
||||
response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user4", USERS_PASSWD)))
|
||||
.prepareSearch("test")
|
||||
.setQuery(existsQuery("field1"))
|
||||
.get();
|
||||
assertHitCount(response, 0);
|
||||
// user4 has access to no fields, so the query should not match with the document:
|
||||
response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user4", USERS_PASSWD)))
|
||||
.prepareSearch("test")
|
||||
.setQuery(existsQuery("field2"))
|
||||
.get();
|
||||
assertHitCount(response, 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import org.elasticsearch.xpack.watcher.support.xcontent.XContentSource;
|
|||
import org.junit.Before;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -92,7 +93,7 @@ public class SecurityFeatureSetTests extends ESTestCase {
|
|||
realmUsage.put("key3", i % 2 == 0);
|
||||
when(realm.usageStats()).thenReturn(realmUsage);
|
||||
}
|
||||
when(realms.iterator()).thenReturn(realmsList.iterator());
|
||||
when(realms.iterator()).thenReturn(available ? realmsList.iterator() : Collections.<Realm>emptyIterator());
|
||||
|
||||
SecurityFeatureSet featureSet = new SecurityFeatureSet(settings.build(), licenseState, realms, namedWriteableRegistry);
|
||||
XPackFeatureSet.Usage usage = featureSet.usage();
|
||||
|
@ -102,12 +103,14 @@ public class SecurityFeatureSetTests extends ESTestCase {
|
|||
assertThat(usage.available(), is(available));
|
||||
XContentSource source = new XContentSource(usage);
|
||||
|
||||
if (enabled) {
|
||||
if (enabled && available) {
|
||||
for (int i = 0; i < 5; i++) {
|
||||
assertThat(source.getValue("enabled_realms." + i + ".key1"), is("value" + i));
|
||||
assertThat(source.getValue("enabled_realms." + i + ".key2"), is(i));
|
||||
assertThat(source.getValue("enabled_realms." + i + ".key3"), is(i % 2 == 0));
|
||||
}
|
||||
} else if (enabled) {
|
||||
assertThat(source.getValue("enabled_realms"), is(notNullValue()));
|
||||
} else {
|
||||
assertThat(source.getValue("enabled_realms"), is(nullValue()));
|
||||
}
|
||||
|
|
|
@ -37,6 +37,6 @@ public class VersionCompatibilityTests extends ESTestCase {
|
|||
*
|
||||
*/
|
||||
assertThat("Remove workaround in LicenseService class when es core supports merging cluster level custom metadata",
|
||||
Version.CURRENT.equals(Version.V_5_0_0), is(true));
|
||||
Version.CURRENT.equals(Version.V_5_0_0_alpha4), is(true));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -94,6 +94,7 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
|
|||
Settings settings = Settings.builder().put("path.home", createTempDir()).build();
|
||||
SecurityLicenseState shieldLicenseState = mock(SecurityLicenseState.class);
|
||||
when(shieldLicenseState.enabledRealmType()).thenReturn(EnabledRealmType.ALL);
|
||||
when(shieldLicenseState.authenticationAndAuthorizationEnabled()).thenReturn(true);
|
||||
realms = new Realms(Settings.EMPTY, new Environment(settings), Collections.<String, Realm.Factory>emptyMap(), shieldLicenseState,
|
||||
mock(ReservedRealm.class)) {
|
||||
|
||||
|
|
|
@ -52,6 +52,7 @@ public class RealmsTests extends ESTestCase {
|
|||
}
|
||||
shieldLicenseState = mock(SecurityLicenseState.class);
|
||||
reservedRealm = mock(ReservedRealm.class);
|
||||
when(shieldLicenseState.authenticationAndAuthorizationEnabled()).thenReturn(true);
|
||||
when(shieldLicenseState.enabledRealmType()).thenReturn(EnabledRealmType.ALL);
|
||||
}
|
||||
|
||||
|
@ -338,6 +339,21 @@ public class RealmsTests extends ESTestCase {
|
|||
assertThat(count, equalTo(orderToIndex.size()));
|
||||
}
|
||||
|
||||
public void testAuthcAuthzDisabled() {
|
||||
Settings settings = Settings.builder()
|
||||
.put("path.home", createTempDir())
|
||||
.put("xpack.security.authc.realms.realm_1.type", FileRealm.TYPE)
|
||||
.put("xpack.security.authc.realms.realm_1.order", 0)
|
||||
.build();
|
||||
Environment env = new Environment(settings);
|
||||
Realms realms = new Realms(settings, env, factories, shieldLicenseState, reservedRealm).start();
|
||||
|
||||
assertThat(realms.iterator().hasNext(), is(true));
|
||||
|
||||
when(shieldLicenseState.authenticationAndAuthorizationEnabled()).thenReturn(false);
|
||||
assertThat(realms.iterator().hasNext(), is(false));
|
||||
}
|
||||
|
||||
static class DummyRealm extends Realm {
|
||||
|
||||
public DummyRealm(String type, RealmConfig config) {
|
||||
|
|
|
@ -133,7 +133,7 @@ public class ShieldIndexSearcherWrapperUnitTests extends ESTestCase {
|
|||
|
||||
FieldSubsetReader.FieldSubsetDirectoryReader result =
|
||||
(FieldSubsetReader.FieldSubsetDirectoryReader) shieldIndexSearcherWrapper.wrap(esIn);
|
||||
assertThat(result.getFieldNames().size(), equalTo(11));
|
||||
assertThat(result.getFieldNames().size(), equalTo(12));
|
||||
assertThat(result.getFieldNames().contains("_uid"), is(true));
|
||||
assertThat(result.getFieldNames().contains("_id"), is(true));
|
||||
assertThat(result.getFieldNames().contains("_version"), is(true));
|
||||
|
@ -145,6 +145,7 @@ public class ShieldIndexSearcherWrapperUnitTests extends ESTestCase {
|
|||
assertThat(result.getFieldNames().contains("_ttl"), is(true));
|
||||
assertThat(result.getFieldNames().contains("_size"), is(true));
|
||||
assertThat(result.getFieldNames().contains("_index"), is(true));
|
||||
assertThat(result.getFieldNames().contains("_field_names"), is(true));
|
||||
// _all contains actual user data and therefor can't be included by default
|
||||
assertThat(result.getFieldNames().contains("_all"), is(false));
|
||||
}
|
||||
|
@ -468,6 +469,16 @@ public class ShieldIndexSearcherWrapperUnitTests extends ESTestCase {
|
|||
public Weight createWeight(IndexSearcher searcher, boolean needsScores) throws IOException {
|
||||
return new CreateScorerOnceWeight(query.createWeight(searcher, needsScores));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return sameClassAs(obj) && query.equals(((CreateScorerOnceQuery) obj).query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 31 * classHash() + query.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
public void doTestIndexSearcherWrapper(boolean sparse, boolean deletions) throws IOException {
|
||||
|
|
|
@ -20,6 +20,7 @@ cluster:monitor/nodes/stats
|
|||
cluster:monitor/state
|
||||
cluster:monitor/stats
|
||||
cluster:monitor/task
|
||||
cluster:monitor/task/get
|
||||
cluster:monitor/tasks/lists
|
||||
cluster:monitor/xpack/watcher/stats
|
||||
indices:admin/aliases
|
||||
|
|
|
@ -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";
|
||||
};
|
|
@ -114,6 +114,7 @@ public class PutWatchRequest extends MasterNodeRequest<PutWatchRequest> {
|
|||
super.readFrom(in);
|
||||
id = in.readString();
|
||||
source = in.readBytesReference();
|
||||
active = in.readBoolean();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -121,6 +122,7 @@ public class PutWatchRequest extends MasterNodeRequest<PutWatchRequest> {
|
|||
super.writeTo(out);
|
||||
out.writeString(id);
|
||||
out.writeBytesReference(source);
|
||||
out.writeBoolean(active);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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.watcher.transport.action.put;
|
||||
|
||||
import org.elasticsearch.common.bytes.BytesArray;
|
||||
import org.elasticsearch.common.io.stream.BytesStreamOutput;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.xpack.watcher.transport.actions.put.PutWatchRequest;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
public class PutWatchSerializationTests extends ESTestCase {
|
||||
|
||||
// https://github.com/elastic/x-plugins/issues/2490
|
||||
public void testPutWatchSerialization() throws Exception {
|
||||
PutWatchRequest request = new PutWatchRequest();
|
||||
request.setId(randomAsciiOfLength(10));
|
||||
request.setActive(randomBoolean());
|
||||
request.setSource(new BytesArray(randomAsciiOfLength(20)));
|
||||
|
||||
BytesStreamOutput streamOutput = new BytesStreamOutput();
|
||||
request.writeTo(streamOutput);
|
||||
|
||||
PutWatchRequest readRequest = new PutWatchRequest();
|
||||
readRequest.readFrom(streamOutput.bytes().streamInput());
|
||||
assertThat(readRequest.isActive(), is(request.isActive()));
|
||||
assertThat(readRequest.getId(), is(request.getId()));
|
||||
assertThat(readRequest.getSource(), is(request.getSource()));
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue