Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ public static void bootstrap(
suppressFailureLogPackages
);
exportInitializationToAgent();
loadAgent(findAgentJar());
loadAgent(findAgentJar(), EntitlementInitialization.class.getName());
}

private static Path getUserHome() {
Expand All @@ -134,11 +134,11 @@ private static Path getUserHome() {
}

@SuppressForbidden(reason = "The VirtualMachine API is the only way to attach a java agent dynamically")
private static void loadAgent(String agentPath) {
static void loadAgent(String agentPath, String entitlementInitializationClassName) {
try {
VirtualMachine vm = VirtualMachine.attach(Long.toString(ProcessHandle.current().pid()));
try {
vm.loadAgent(agentPath, EntitlementInitialization.class.getName());
vm.loadAgent(agentPath, entitlementInitializationClassName);
} finally {
vm.detach();
}
Expand All @@ -154,7 +154,7 @@ private static void exportInitializationToAgent() {
EntitlementInitialization.class.getModule().addExports(initPkg, unnamedModule);
}

public static String findAgentJar() {
static String findAgentJar() {
String propertyName = "es.entitlement.agentJar";
String propertyValue = System.getProperty(propertyName);
if (propertyValue != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ public class EntitlementInitialization {

private static final Module ENTITLEMENTS_MODULE = PolicyManager.class.getModule();

private static ElasticsearchEntitlementChecker manager;
private static ElasticsearchEntitlementChecker checker;

// Note: referenced by bridge reflectively
public static EntitlementChecker checker() {
return manager;
return checker;
}

/**
Expand All @@ -61,18 +61,7 @@ public static EntitlementChecker checker() {
* @param inst the JVM instrumentation class instance
*/
public static void initialize(Instrumentation inst) throws Exception {
manager = initChecker();

var verifyBytecode = Booleans.parseBoolean(System.getProperty("es.entitlements.verify_bytecode", "false"));
if (verifyBytecode) {
ensureClassesSensitiveToVerificationAreInitialized();
}

DynamicInstrumentation.initialize(
inst,
EntitlementCheckerUtils.getVersionSpecificCheckerClass(EntitlementChecker.class, Runtime.version().feature()),
verifyBytecode
);
checker = initChecker(inst, createPolicyManager());
}

private static PolicyManager createPolicyManager() {
Expand Down Expand Up @@ -112,9 +101,7 @@ private static void ensureClassesSensitiveToVerificationAreInitialized() {
}
}

private static ElasticsearchEntitlementChecker initChecker() {
final PolicyManager policyManager = createPolicyManager();

static ElasticsearchEntitlementChecker initChecker(Instrumentation inst, PolicyManager policyManager) throws Exception {
final Class<?> clazz = EntitlementCheckerUtils.getVersionSpecificCheckerClass(
ElasticsearchEntitlementChecker.class,
Runtime.version().feature()
Expand All @@ -126,10 +113,25 @@ private static ElasticsearchEntitlementChecker initChecker() {
} catch (NoSuchMethodException e) {
throw new AssertionError("entitlement impl is missing no arg constructor", e);
}

ElasticsearchEntitlementChecker checker;
try {
return (ElasticsearchEntitlementChecker) constructor.newInstance(policyManager);
checker = (ElasticsearchEntitlementChecker) constructor.newInstance(policyManager);
} catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
throw new AssertionError(e);
}

var verifyBytecode = Booleans.parseBoolean(System.getProperty("es.entitlements.verify_bytecode", "false"));
if (verifyBytecode) {
ensureClassesSensitiveToVerificationAreInitialized();
}

DynamicInstrumentation.initialize(
inst,
EntitlementCheckerUtils.getVersionSpecificCheckerClass(EntitlementChecker.class, Runtime.version().feature()),
verifyBytecode
);

return checker;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,31 @@ public static PluginDescriptor readFromProperties(final Path pluginDir) throws I
return descriptor;
}

/**
* Reads the internal descriptor for a classic plugin.
*
* @param stream the InputStream from which to read the plugin data
* @return the plugin info
* @throws IOException if an I/O exception occurred reading the plugin descriptor
*/
public static PluginDescriptor readInternalDescriptor(InputStream stream) throws IOException {
final Map<String, String> propsMap;
{
final Properties props = new Properties();
props.load(stream);
propsMap = props.stringPropertyNames().stream().collect(Collectors.toMap(Function.identity(), props::getProperty));
}

PluginDescriptor descriptor = readerInternalDescriptor(propsMap, INTERNAL_DESCRIPTOR_FILENAME);
String name = descriptor.getName();

if (propsMap.isEmpty() == false) {
throw new IllegalArgumentException("Unknown properties for plugin [" + name + "] in plugin descriptor: " + propsMap.keySet());
}

return descriptor;
}

private static PluginDescriptor readerInternalDescriptor(Map<String, String> propsMap, String filename) {
String name = readNonEmptyString(propsMap, filename, "name");
String desc = readString(propsMap, name, "description");
Expand Down
1 change: 1 addition & 0 deletions test/framework/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ dependencies {
api project(':libs:ssl-config')
api project(":server")
api project(":libs:cli")
api project(":libs:entitlement:bridge")
api "com.carrotsearch.randomizedtesting:randomizedtesting-runner:${versions.randomizedrunner}"
api "junit:junit:${versions.junit}"
api "org.hamcrest:hamcrest:${versions.hamcrest}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@

import java.util.List;

record TestBuildInfo(String component, List<TestBuildInfoLocation> locations) {}
public record TestBuildInfo(String component, List<TestBuildInfoLocation> locations) {}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import java.util.ArrayList;
import java.util.List;

class TestBuildInfoParser {
public class TestBuildInfoParser {

private static final String PLUGIN_TEST_BUILD_INFO_RESOURCES = "META-INF/plugin-test-build-info.json";
private static final String SERVER_TEST_BUILD_INFO_RESOURCE = "META-INF/server-test-build-info.json";
Expand Down Expand Up @@ -75,7 +75,7 @@ static TestBuildInfo fromXContent(final XContentParser parser) throws IOExceptio
return PARSER.parse(parser, null).build();
}

static List<TestBuildInfo> parseAllPluginTestBuildInfo() throws IOException {
public static List<TestBuildInfo> parseAllPluginTestBuildInfo() throws IOException {
var xContent = XContentFactory.xContent(XContentType.JSON);
List<TestBuildInfo> pluginsTestBuildInfos = new ArrayList<>();
var resources = TestBuildInfoParser.class.getClassLoader().getResources(PLUGIN_TEST_BUILD_INFO_RESOURCES);
Expand All @@ -88,7 +88,7 @@ static List<TestBuildInfo> parseAllPluginTestBuildInfo() throws IOException {
return pluginsTestBuildInfos;
}

static TestBuildInfo parseServerTestBuildInfo() throws IOException {
public static TestBuildInfo parseServerTestBuildInfo() throws IOException {
var xContent = XContentFactory.xContent(XContentType.JSON);
var resource = TestBuildInfoParser.class.getClassLoader().getResource(SERVER_TEST_BUILD_INFO_RESOURCE);
// No test-build-info for server: this might be a non-gradle build. Proceed without TestBuildInfo
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import java.util.Map;
import java.util.function.Function;

record TestScopeResolver(Map<String, PolicyManager.PolicyScope> scopeMap) {
public record TestScopeResolver(Map<String, PolicyManager.PolicyScope> scopeMap) {

private static final Logger logger = LogManager.getLogger(TestScopeResolver.class);

Expand All @@ -38,7 +38,7 @@ PolicyManager.PolicyScope getScope(Class<?> callerClass) {
return scope;
}

static Function<Class<?>, PolicyManager.PolicyScope> createScopeResolver(
public static Function<Class<?>, PolicyManager.PolicyScope> createScopeResolver(
TestBuildInfo serverBuildInfo,
List<TestBuildInfo> pluginsBuildInfo
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

package org.elasticsearch.entitlement.bootstrap;

import org.elasticsearch.entitlement.initialization.TestEntitlementInitialization;
import org.elasticsearch.entitlement.runtime.policy.PathLookup;
import org.elasticsearch.logging.LogManager;
import org.elasticsearch.logging.Logger;

public class TestEntitlementBootstrap {

private static final Logger logger = LogManager.getLogger(TestEntitlementBootstrap.class);
private static BootstrapArgs bootstrapArgs;

/**
* Activates entitlement checking in tests.
* @param bootstrapArgs arguments used for and passed to entitlement initialization
*/
public static void bootstrap(BootstrapArgs bootstrapArgs) {
assert bootstrapArgs != null;
TestEntitlementBootstrap.bootstrapArgs = bootstrapArgs;
logger.debug("Loading entitlement agent");
EntitlementBootstrap.loadAgent(EntitlementBootstrap.findAgentJar(), TestEntitlementInitialization.class.getName());
}

public static BootstrapArgs bootstrapArgs() {
return bootstrapArgs;
}

public record BootstrapArgs(PathLookup pathLookup) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

package org.elasticsearch.entitlement.initialization;

import org.elasticsearch.bootstrap.TestBuildInfo;
import org.elasticsearch.bootstrap.TestBuildInfoParser;
import org.elasticsearch.bootstrap.TestScopeResolver;
import org.elasticsearch.core.Strings;
import org.elasticsearch.core.SuppressForbidden;
import org.elasticsearch.entitlement.bootstrap.TestEntitlementBootstrap;
import org.elasticsearch.entitlement.bridge.EntitlementChecker;
import org.elasticsearch.entitlement.runtime.api.ElasticsearchEntitlementChecker;
import org.elasticsearch.entitlement.runtime.policy.PathLookup;
import org.elasticsearch.entitlement.runtime.policy.Policy;
import org.elasticsearch.entitlement.runtime.policy.PolicyManager;
import org.elasticsearch.entitlement.runtime.policy.PolicyParser;
import org.elasticsearch.plugins.PluginDescriptor;

import java.io.IOException;
import java.io.InputStream;
import java.lang.instrument.Instrumentation;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
* Test-only version of {@code EntitlementInitialization}
*/
public class TestEntitlementInitialization {

private static ElasticsearchEntitlementChecker checker;

// Note: referenced by bridge reflectively
public static EntitlementChecker checker() {
return checker;
}

public static void initialize(Instrumentation inst) throws Exception {
TestEntitlementBootstrap.BootstrapArgs bootstrapArgs = TestEntitlementBootstrap.bootstrapArgs();
checker = EntitlementInitialization.initChecker(inst, createPolicyManager(bootstrapArgs.pathLookup()));
}

private record TestPluginData(String pluginName, boolean isModular, boolean isExternalPlugin) {}

private static Map<String, Policy> parsePluginsPolicies(List<TestPluginData> pluginsData) {
Map<String, Policy> policies = new HashMap<>();
for (var pluginData : pluginsData) {
String pluginName = pluginData.pluginName();
var resourceName = Strings.format("META-INF/es-plugins/%s/entitlement-policy.yaml", pluginName);

var resource = TestEntitlementInitialization.class.getClassLoader().getResource(resourceName);
if (resource != null) {
try (var inputStream = getStream(resource)) {
policies.put(pluginName, new PolicyParser(inputStream, pluginName, pluginData.isExternalPlugin()).parsePolicy());
} catch (IOException e) {
throw new IllegalArgumentException(Strings.format("Cannot read policy for plugin [%s]", pluginName), e);
}
}
}
return policies;
}

private static List<PluginDescriptor> parsePluginsDescriptors(List<String> pluginNames) {
List<PluginDescriptor> descriptors = new ArrayList<>();
for (var pluginName : pluginNames) {
var resourceName = Strings.format("META-INF/es-plugins/%s/plugin-descriptor.properties", pluginName);
var resource = TestEntitlementInitialization.class.getClassLoader().getResource(resourceName);
if (resource != null) {
try (var inputStream = getStream(resource)) {
descriptors.add(PluginDescriptor.readInternalDescriptor(inputStream));
} catch (IOException e) {
throw new IllegalArgumentException(Strings.format("Cannot read descriptor for plugin [%s]", pluginName), e);
}
}
}
return descriptors;
}

@SuppressForbidden(reason = "URLs from class loader")
private static InputStream getStream(URL resource) throws IOException {
return resource.openStream();
}

private static PolicyManager createPolicyManager(PathLookup pathLookup) throws IOException {

var pluginsTestBuildInfo = TestBuildInfoParser.parseAllPluginTestBuildInfo();
var serverTestBuildInfo = TestBuildInfoParser.parseServerTestBuildInfo();
var scopeResolver = TestScopeResolver.createScopeResolver(serverTestBuildInfo, pluginsTestBuildInfo);
List<String> pluginNames = pluginsTestBuildInfo.stream().map(TestBuildInfo::component).toList();

var pluginDescriptors = parsePluginsDescriptors(pluginNames);
var pluginsData = pluginDescriptors.stream()
.map(descriptor -> new TestPluginData(descriptor.getName(), descriptor.isModular(), false))
.toList();
Map<String, Policy> pluginPolicies = parsePluginsPolicies(pluginsData);

FilesEntitlementsValidation.validate(pluginPolicies, pathLookup);

return new PolicyManager(
HardcodedEntitlements.serverPolicy(null, null),
HardcodedEntitlements.agentEntitlements(),
pluginPolicies,
scopeResolver,
Map.of(),
null, // TODO: this will need to change -- encapsulate it when we extract isTriviallyAllowed
pathLookup,
Set.of()
);
}
}