Skip to content

Commit e166e3d

Browse files
committed
WIP: test entitlement bootstrap and initialization classes
1 parent 0289a2a commit e166e3d

File tree

3 files changed

+168
-0
lines changed

3 files changed

+168
-0
lines changed

test/framework/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ dependencies {
1616
api project(':libs:ssl-config')
1717
api project(":server")
1818
api project(":libs:cli")
19+
api project(":libs:entitlement:bridge")
1920
api "com.carrotsearch.randomizedtesting:randomizedtesting-runner:${versions.randomizedrunner}"
2021
api "junit:junit:${versions.junit}"
2122
api "org.hamcrest:hamcrest:${versions.hamcrest}"
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
package org.elasticsearch.bootstrap;
11+
12+
import com.sun.tools.attach.AgentInitializationException;
13+
import com.sun.tools.attach.AgentLoadException;
14+
import com.sun.tools.attach.AttachNotSupportedException;
15+
import com.sun.tools.attach.VirtualMachine;
16+
17+
import org.elasticsearch.core.SuppressForbidden;
18+
import org.elasticsearch.entitlement.bootstrap.EntitlementBootstrap;
19+
import org.elasticsearch.logging.LogManager;
20+
import org.elasticsearch.logging.Logger;
21+
22+
import java.io.IOException;
23+
24+
class TestEntitlementBootstrap {
25+
26+
private static final Logger logger = LogManager.getLogger(TestEntitlementBootstrap.class);
27+
28+
/**
29+
* Activates entitlement checking in tests.
30+
*/
31+
public static void bootstrap() {
32+
logger.debug("Loading entitlement agent");
33+
loadAgent(EntitlementBootstrap.findAgentJar());
34+
}
35+
36+
@SuppressForbidden(reason = "The VirtualMachine API is the only way to attach a java agent dynamically")
37+
private static void loadAgent(String agentPath) {
38+
try {
39+
VirtualMachine vm = VirtualMachine.attach(Long.toString(ProcessHandle.current().pid()));
40+
try {
41+
vm.loadAgent(agentPath, TestEntitlementInitialization.class.getName());
42+
} finally {
43+
vm.detach();
44+
}
45+
} catch (AttachNotSupportedException | IOException | AgentLoadException | AgentInitializationException e) {
46+
throw new IllegalStateException("Unable to attach entitlement agent", e);
47+
}
48+
}
49+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
package org.elasticsearch.bootstrap;
11+
12+
import org.elasticsearch.core.Booleans;
13+
import org.elasticsearch.entitlement.bridge.EntitlementChecker;
14+
import org.elasticsearch.entitlement.initialization.DynamicInstrumentation;
15+
import org.elasticsearch.entitlement.initialization.EntitlementCheckerUtils;
16+
import org.elasticsearch.entitlement.initialization.FilesEntitlementsValidation;
17+
import org.elasticsearch.entitlement.initialization.HardcodedEntitlements;
18+
import org.elasticsearch.entitlement.runtime.api.ElasticsearchEntitlementChecker;
19+
import org.elasticsearch.entitlement.runtime.policy.PathLookup;
20+
import org.elasticsearch.entitlement.runtime.policy.Policy;
21+
import org.elasticsearch.entitlement.runtime.policy.PolicyManager;
22+
23+
import java.lang.instrument.Instrumentation;
24+
import java.lang.reflect.Constructor;
25+
import java.lang.reflect.InvocationTargetException;
26+
import java.util.Map;
27+
import java.util.Set;
28+
29+
/**
30+
* Test-only version of {@code EntitlementInitialization}
31+
*/
32+
public class TestEntitlementInitialization {
33+
34+
private static final Module ENTITLEMENTS_MODULE = PolicyManager.class.getModule();
35+
36+
private static ElasticsearchEntitlementChecker manager;
37+
38+
// Note: referenced by bridge reflectively
39+
public static EntitlementChecker checker() {
40+
return manager;
41+
}
42+
43+
public static void initialize(Instrumentation inst) throws Exception {
44+
manager = initChecker();
45+
46+
var verifyBytecode = Booleans.parseBoolean(System.getProperty("es.entitlements.verify_bytecode", "false"));
47+
if (verifyBytecode) {
48+
ensureClassesSensitiveToVerificationAreInitialized();
49+
}
50+
51+
DynamicInstrumentation.initialize(
52+
inst,
53+
EntitlementCheckerUtils.getVersionSpecificCheckerClass(EntitlementChecker.class, Runtime.version().feature()),
54+
verifyBytecode
55+
);
56+
}
57+
58+
private static PolicyManager createPolicyManager() {
59+
60+
// TODO: parse policies. Locate them using help from TestBuildInfo
61+
Map<String, Policy> pluginPolicies = Map.of();
62+
63+
// TODO: create here the test pathLookup
64+
PathLookup pathLookup = null;
65+
66+
FilesEntitlementsValidation.validate(pluginPolicies, pathLookup);
67+
68+
return new PolicyManager(
69+
HardcodedEntitlements.serverPolicy(null, null),
70+
HardcodedEntitlements.agentEntitlements(),
71+
pluginPolicies,
72+
null, // TestScopeResolver.createScopeResolver
73+
Map.of(), // TODO: a map that always return nulls? Replace with functor
74+
ENTITLEMENTS_MODULE, // TODO: this will need to change -- encapsulate it when we extract isTriviallyAllowed
75+
pathLookup,
76+
Set.of()
77+
);
78+
}
79+
80+
/**
81+
* If bytecode verification is enabled, ensure these classes get loaded before transforming/retransforming them.
82+
* For these classes, the order in which we transform and verify them matters. Verification during class transformation is at least an
83+
* unforeseen (if not unsupported) scenario: we are loading a class, and while we are still loading it (during transformation) we try
84+
* to verify it. This in turn leads to more classes loading (for verification purposes), which could turn into those classes to be
85+
* transformed and undergo verification. In order to avoid circularity errors as much as possible, we force a partial order.
86+
*/
87+
private static void ensureClassesSensitiveToVerificationAreInitialized() {
88+
var classesToInitialize = Set.of("sun.net.www.protocol.http.HttpURLConnection");
89+
for (String className : classesToInitialize) {
90+
try {
91+
Class.forName(className);
92+
} catch (ClassNotFoundException unexpected) {
93+
throw new AssertionError(unexpected);
94+
}
95+
}
96+
}
97+
98+
private static ElasticsearchEntitlementChecker initChecker() {
99+
final PolicyManager policyManager = createPolicyManager();
100+
101+
final Class<?> clazz = EntitlementCheckerUtils.getVersionSpecificCheckerClass(
102+
ElasticsearchEntitlementChecker.class,
103+
Runtime.version().feature()
104+
);
105+
106+
Constructor<?> constructor;
107+
try {
108+
constructor = clazz.getConstructor(PolicyManager.class);
109+
} catch (NoSuchMethodException e) {
110+
throw new AssertionError("entitlement impl is missing no arg constructor", e);
111+
}
112+
try {
113+
return (ElasticsearchEntitlementChecker) constructor.newInstance(policyManager);
114+
} catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
115+
throw new AssertionError(e);
116+
}
117+
}
118+
}

0 commit comments

Comments
 (0)