Skip to content

Commit 4cf4203

Browse files
authored
Merge branch 'main' into file-prefixes
2 parents e7a4218 + 5302589 commit 4cf4203

File tree

9 files changed

+275
-28
lines changed

9 files changed

+275
-28
lines changed

libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/ExternalEntitlement.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
* using this annotation is considered parseable as part of a policy file
2323
* for entitlements.
2424
*/
25-
@Target(ElementType.CONSTRUCTOR)
25+
@Target({ ElementType.CONSTRUCTOR, ElementType.METHOD })
2626
@Retention(RetentionPolicy.RUNTIME)
2727
public @interface ExternalEntitlement {
2828

libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyParser.java

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import java.io.UncheckedIOException;
2828
import java.lang.reflect.Constructor;
2929
import java.lang.reflect.InvocationTargetException;
30+
import java.lang.reflect.Method;
31+
import java.lang.reflect.Modifier;
3032
import java.util.ArrayList;
3133
import java.util.Arrays;
3234
import java.util.List;
@@ -147,6 +149,7 @@ protected Entitlement parseEntitlement(String scopeName, String entitlementType)
147149
}
148150

149151
Constructor<?> entitlementConstructor = null;
152+
Method entitlementMethod = null;
150153
ExternalEntitlement entitlementMetadata = null;
151154
for (var ctor : entitlementClass.getConstructors()) {
152155
var metadata = ctor.getAnnotation(ExternalEntitlement.class);
@@ -161,8 +164,27 @@ protected Entitlement parseEntitlement(String scopeName, String entitlementType)
161164
entitlementConstructor = ctor;
162165
entitlementMetadata = metadata;
163166
}
164-
165167
}
168+
for (var method : entitlementClass.getMethods()) {
169+
var metadata = method.getAnnotation(ExternalEntitlement.class);
170+
if (metadata != null) {
171+
if (Modifier.isStatic(method.getModifiers()) == false) {
172+
throw new IllegalStateException(
173+
"entitlement class [" + entitlementClass.getName() + "] has non-static method annotated with ExternalEntitlement"
174+
);
175+
}
176+
if (entitlementMetadata != null) {
177+
throw new IllegalStateException(
178+
"entitlement class ["
179+
+ entitlementClass.getName()
180+
+ "] has more than one constructor and/or method annotated with ExternalEntitlement"
181+
);
182+
}
183+
entitlementMethod = method;
184+
entitlementMetadata = metadata;
185+
}
186+
}
187+
166188
if (entitlementMetadata == null) {
167189
throw newPolicyParserException(scopeName, "unknown entitlement type [" + entitlementType + "]");
168190
}
@@ -171,7 +193,9 @@ protected Entitlement parseEntitlement(String scopeName, String entitlementType)
171193
throw newPolicyParserException("entitlement type [" + entitlementType + "] is allowed only on modules");
172194
}
173195

174-
Class<?>[] parameterTypes = entitlementConstructor.getParameterTypes();
196+
Class<?>[] parameterTypes = entitlementConstructor != null
197+
? entitlementConstructor.getParameterTypes()
198+
: entitlementMethod.getParameterTypes();
175199
String[] parametersNames = entitlementMetadata.parameterNames();
176200

177201
if (parameterTypes.length != 0 || parametersNames.length != 0) {
@@ -204,7 +228,11 @@ protected Entitlement parseEntitlement(String scopeName, String entitlementType)
204228
}
205229

206230
try {
207-
return (Entitlement) entitlementConstructor.newInstance(parameterValues);
231+
if (entitlementConstructor != null) {
232+
return (Entitlement) entitlementConstructor.newInstance(parameterValues);
233+
} else {
234+
return (Entitlement) entitlementMethod.invoke(null, parameterValues);
235+
}
208236
} catch (InvocationTargetException | InstantiationException | IllegalAccessException e) {
209237
if (e.getCause() instanceof PolicyValidationException piae) {
210238
throw newPolicyParserException(startLocation, scopeName, entitlementType, piae);

libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/entitlements/FileEntitlement.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ private static Mode parseMode(String mode) {
3737
}
3838

3939
@ExternalEntitlement(parameterNames = { "path", "mode" }, esModulesOnly = false)
40-
public FileEntitlement(String path, String mode) {
41-
this(path, parseMode(mode));
40+
public static FileEntitlement create(String path, String mode) {
41+
return new FileEntitlement(path, parseMode(mode));
4242
}
4343
}

libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTreeTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,6 @@ public void testForwardSlashes() {
106106

107107
FileEntitlement entitlement(String path, String mode) {
108108
Path p = path(path);
109-
return new FileEntitlement(p.toString(), mode);
109+
return FileEntitlement.create(p.toString(), mode);
110110
}
111111
}

libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserTests.java

Lines changed: 87 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,35 @@ public ManyConstructorsEntitlement(String s) {}
4040
public ManyConstructorsEntitlement(int i) {}
4141
}
4242

43+
public static class ManyMethodsEntitlement implements Entitlement {
44+
@ExternalEntitlement
45+
public static ManyMethodsEntitlement create(String s) {
46+
return new ManyMethodsEntitlement();
47+
}
48+
49+
@ExternalEntitlement
50+
public static ManyMethodsEntitlement create(int i) {
51+
return new ManyMethodsEntitlement();
52+
}
53+
}
54+
55+
public static class ConstructorAndMethodEntitlement implements Entitlement {
56+
@ExternalEntitlement
57+
public static ConstructorAndMethodEntitlement create(String s) {
58+
return new ConstructorAndMethodEntitlement(s);
59+
}
60+
61+
@ExternalEntitlement
62+
public ConstructorAndMethodEntitlement(String s) {}
63+
}
64+
65+
public static class NonStaticMethodEntitlement implements Entitlement {
66+
@ExternalEntitlement
67+
public NonStaticMethodEntitlement create() {
68+
return new NonStaticMethodEntitlement();
69+
}
70+
}
71+
4372
public void testGetEntitlementTypeName() {
4473
assertEquals("create_class_loader", PolicyParser.getEntitlementTypeName(CreateClassLoaderEntitlement.class));
4574

@@ -55,7 +84,7 @@ public void testPolicyBuilder() throws IOException {
5584
.parsePolicy();
5685
Policy expected = new Policy(
5786
"test-policy.yaml",
58-
List.of(new Scope("entitlement-module-name", List.of(new FileEntitlement("test/path/to/file", "read_write"))))
87+
List.of(new Scope("entitlement-module-name", List.of(FileEntitlement.create("test/path/to/file", "read_write"))))
5988
);
6089
assertEquals(expected, parsedPolicy);
6190
}
@@ -65,7 +94,7 @@ public void testPolicyBuilderOnExternalPlugin() throws IOException {
6594
.parsePolicy();
6695
Policy expected = new Policy(
6796
"test-policy.yaml",
68-
List.of(new Scope("entitlement-module-name", List.of(new FileEntitlement("test/path/to/file", "read_write"))))
97+
List.of(new Scope("entitlement-module-name", List.of(FileEntitlement.create("test/path/to/file", "read_write"))))
6998
);
7099
assertEquals(expected, parsedPolicy);
71100
}
@@ -174,4 +203,60 @@ public void testMultipleConstructorsAnnotated() throws IOException {
174203
)
175204
);
176205
}
206+
207+
public void testMultipleMethodsAnnotated() throws IOException {
208+
var parser = new PolicyParser(new ByteArrayInputStream("""
209+
entitlement-module-name:
210+
- many_methods
211+
""".getBytes(StandardCharsets.UTF_8)), "test-policy.yaml", true, Map.of("many_methods", ManyMethodsEntitlement.class));
212+
213+
var e = expectThrows(IllegalStateException.class, parser::parsePolicy);
214+
assertThat(
215+
e.getMessage(),
216+
equalTo(
217+
"entitlement class "
218+
+ "[org.elasticsearch.entitlement.runtime.policy.PolicyParserTests$ManyMethodsEntitlement]"
219+
+ " has more than one constructor and/or method annotated with ExternalEntitlement"
220+
)
221+
);
222+
}
223+
224+
public void testConstructorAndMethodAnnotated() throws IOException {
225+
var parser = new PolicyParser(
226+
new ByteArrayInputStream("""
227+
entitlement-module-name:
228+
- constructor_and_method
229+
""".getBytes(StandardCharsets.UTF_8)),
230+
"test-policy.yaml",
231+
true,
232+
Map.of("constructor_and_method", ConstructorAndMethodEntitlement.class)
233+
);
234+
235+
var e = expectThrows(IllegalStateException.class, parser::parsePolicy);
236+
assertThat(
237+
e.getMessage(),
238+
equalTo(
239+
"entitlement class "
240+
+ "[org.elasticsearch.entitlement.runtime.policy.PolicyParserTests$ConstructorAndMethodEntitlement]"
241+
+ " has more than one constructor and/or method annotated with ExternalEntitlement"
242+
)
243+
);
244+
}
245+
246+
public void testNonStaticMethodAnnotated() throws IOException {
247+
var parser = new PolicyParser(new ByteArrayInputStream("""
248+
entitlement-module-name:
249+
- non_static
250+
""".getBytes(StandardCharsets.UTF_8)), "test-policy.yaml", true, Map.of("non_static", NonStaticMethodEntitlement.class));
251+
252+
var e = expectThrows(IllegalStateException.class, parser::parsePolicy);
253+
assertThat(
254+
e.getMessage(),
255+
equalTo(
256+
"entitlement class "
257+
+ "[org.elasticsearch.entitlement.runtime.policy.PolicyParserTests$NonStaticMethodEntitlement]"
258+
+ " has non-static method annotated with ExternalEntitlement"
259+
)
260+
);
261+
}
177262
}

server/src/main/java/org/elasticsearch/TransportVersion.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,14 @@ public static List<TransportVersion> getAllVersions() {
118118
return VersionsHolder.ALL_VERSIONS;
119119
}
120120

121+
/**
122+
* @return whether this is a known {@link TransportVersion}, i.e. one declared in {@link TransportVersions}. Other versions may exist
123+
* in the wild (they're sent over the wire by numeric ID) but we don't know how to communicate using such versions.
124+
*/
125+
public boolean isKnown() {
126+
return VersionsHolder.ALL_VERSIONS_MAP.containsKey(id);
127+
}
128+
121129
public static TransportVersion fromString(String str) {
122130
return TransportVersion.fromId(Integer.parseInt(str));
123131
}

server/src/main/java/org/elasticsearch/transport/TransportHandshaker.java

Lines changed: 53 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,18 @@
1111

1212
import org.elasticsearch.Build;
1313
import org.elasticsearch.TransportVersion;
14-
import org.elasticsearch.TransportVersions;
1514
import org.elasticsearch.action.ActionListener;
1615
import org.elasticsearch.cluster.node.DiscoveryNode;
1716
import org.elasticsearch.common.bytes.BytesReference;
1817
import org.elasticsearch.common.io.stream.BytesStreamOutput;
1918
import org.elasticsearch.common.io.stream.StreamInput;
2019
import org.elasticsearch.common.io.stream.StreamOutput;
2120
import org.elasticsearch.common.metrics.CounterMetric;
21+
import org.elasticsearch.core.Strings;
2222
import org.elasticsearch.core.TimeValue;
2323
import org.elasticsearch.core.UpdateForV9;
24+
import org.elasticsearch.logging.LogManager;
25+
import org.elasticsearch.logging.Logger;
2426
import org.elasticsearch.threadpool.ThreadPool;
2527

2628
import java.io.EOFException;
@@ -126,6 +128,8 @@ final class TransportHandshaker {
126128
* [3] Parent task ID should be empty; see org.elasticsearch.tasks.TaskId.writeTo for its structure.
127129
*/
128130

131+
private static final Logger logger = LogManager.getLogger(TransportHandshaker.class);
132+
129133
static final TransportVersion V8_HANDSHAKE_VERSION = TransportVersion.fromId(7_17_00_99);
130134
static final TransportVersion V9_HANDSHAKE_VERSION = TransportVersion.fromId(8_800_00_0);
131135
static final Set<TransportVersion> ALLOWED_HANDSHAKE_VERSIONS = Set.of(V8_HANDSHAKE_VERSION, V9_HANDSHAKE_VERSION);
@@ -159,7 +163,7 @@ void sendHandshake(
159163
ActionListener<TransportVersion> listener
160164
) {
161165
numHandshakes.inc();
162-
final HandshakeResponseHandler handler = new HandshakeResponseHandler(requestId, listener);
166+
final HandshakeResponseHandler handler = new HandshakeResponseHandler(requestId, channel, listener);
163167
pendingHandshakes.put(requestId, handler);
164168
channel.addCloseListener(
165169
ActionListener.running(() -> handler.handleLocalException(new TransportException("handshake failed because connection reset")))
@@ -185,9 +189,9 @@ void sendHandshake(
185189
}
186190

187191
void handleHandshake(TransportChannel channel, long requestId, StreamInput stream) throws IOException {
192+
final HandshakeRequest handshakeRequest;
188193
try {
189-
// Must read the handshake request to exhaust the stream
190-
new HandshakeRequest(stream);
194+
handshakeRequest = new HandshakeRequest(stream);
191195
} catch (Exception e) {
192196
assert ignoreDeserializationErrors : e;
193197
throw e;
@@ -206,9 +210,44 @@ void handleHandshake(TransportChannel channel, long requestId, StreamInput strea
206210
assert ignoreDeserializationErrors : exception;
207211
throw exception;
208212
}
213+
ensureCompatibleVersion(version, handshakeRequest.transportVersion, handshakeRequest.releaseVersion, channel);
209214
channel.sendResponse(new HandshakeResponse(this.version, Build.current().version()));
210215
}
211216

217+
static void ensureCompatibleVersion(
218+
TransportVersion localTransportVersion,
219+
TransportVersion remoteTransportVersion,
220+
String releaseVersion,
221+
Object channel
222+
) {
223+
if (TransportVersion.isCompatible(remoteTransportVersion)) {
224+
if (remoteTransportVersion.onOrAfter(localTransportVersion)) {
225+
// Remote is newer than us, so we will be using our transport protocol and it's up to the other end to decide whether it
226+
// knows how to do that.
227+
return;
228+
}
229+
if (remoteTransportVersion.isKnown()) {
230+
// Remote is older than us, so we will be using its transport protocol, which we can only do if and only if its protocol
231+
// version is known to us.
232+
return;
233+
}
234+
}
235+
236+
final var message = Strings.format(
237+
"""
238+
Rejecting unreadable transport handshake from remote node with version [%s/%s] received on [%s] since this node has \
239+
version [%s/%s] which has an incompatible wire format.""",
240+
releaseVersion,
241+
remoteTransportVersion,
242+
channel,
243+
Build.current().version(),
244+
localTransportVersion
245+
);
246+
logger.warn(message);
247+
throw new IllegalStateException(message);
248+
249+
}
250+
212251
TransportResponseHandler<HandshakeResponse> removeHandlerForHandshake(long requestId) {
213252
return pendingHandshakes.remove(requestId);
214253
}
@@ -224,11 +263,13 @@ long getNumHandshakes() {
224263
private class HandshakeResponseHandler implements TransportResponseHandler<HandshakeResponse> {
225264

226265
private final long requestId;
266+
private final TcpChannel channel;
227267
private final ActionListener<TransportVersion> listener;
228268
private final AtomicBoolean isDone = new AtomicBoolean(false);
229269

230-
private HandshakeResponseHandler(long requestId, ActionListener<TransportVersion> listener) {
270+
private HandshakeResponseHandler(long requestId, TcpChannel channel, ActionListener<TransportVersion> listener) {
231271
this.requestId = requestId;
272+
this.channel = channel;
232273
this.listener = listener;
233274
}
234275

@@ -245,20 +286,13 @@ public Executor executor() {
245286
@Override
246287
public void handleResponse(HandshakeResponse response) {
247288
if (isDone.compareAndSet(false, true)) {
248-
TransportVersion responseVersion = response.transportVersion;
249-
if (TransportVersion.isCompatible(responseVersion) == false) {
250-
listener.onFailure(
251-
new IllegalStateException(
252-
"Received message from unsupported version: ["
253-
+ responseVersion
254-
+ "] minimal compatible version is: ["
255-
+ TransportVersions.MINIMUM_COMPATIBLE
256-
+ "]"
257-
)
258-
);
259-
} else {
260-
listener.onResponse(TransportVersion.min(TransportHandshaker.this.version, response.getTransportVersion()));
261-
}
289+
ActionListener.completeWith(listener, () -> {
290+
ensureCompatibleVersion(version, response.getTransportVersion(), response.getReleaseVersion(), channel);
291+
final var resultVersion = TransportVersion.min(TransportHandshaker.this.version, response.getTransportVersion());
292+
assert TransportVersion.current().before(version) // simulating a newer-version transport service for test purposes
293+
|| resultVersion.isKnown() : "negotiated unknown version " + resultVersion;
294+
return resultVersion;
295+
});
262296
}
263297
}
264298

server/src/test/java/org/elasticsearch/transport/InboundHandlerTests.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,9 @@ public void testLogsSlowInboundProcessing() throws Exception {
290290
);
291291
BytesStreamOutput byteData = new BytesStreamOutput();
292292
TaskId.EMPTY_TASK_ID.writeTo(byteData);
293+
// simulate bytes of a transport handshake: vInt transport version then release version string
293294
TransportVersion.writeVersion(remoteVersion, byteData);
295+
byteData.writeString(randomIdentifier());
294296
final InboundMessage requestMessage = new InboundMessage(
295297
requestHeader,
296298
ReleasableBytesReference.wrap(byteData.bytes()),

0 commit comments

Comments
 (0)