Skip to content

Commit 796dfe0

Browse files
authored
Merge branch 'main' into rest_handlers_services
2 parents 59b76f6 + 24fe761 commit 796dfe0

File tree

42 files changed

+1644
-187
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1644
-187
lines changed

libs/entitlement/asm-provider/src/main/java/org/elasticsearch/entitlement/instrumentation/impl/InstrumenterImpl.java

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,19 @@ public static InstrumenterImpl create(Class<?> registryClass, Map<MethodKey, Ins
6060
return new InstrumenterImpl(handleClass, getCheckerClassMethodDescriptor, checkMethods);
6161
}
6262

63+
private static boolean isJvmConstant(Object value) {
64+
return value == null
65+
|| value instanceof String
66+
|| value instanceof Integer
67+
|| value instanceof Long
68+
|| value instanceof Float
69+
|| value instanceof Double
70+
|| value instanceof Character
71+
|| value instanceof Short
72+
|| value instanceof Byte
73+
|| value instanceof Boolean;
74+
}
75+
6376
private enum VerificationPhase {
6477
BEFORE_INSTRUMENTATION,
6578
AFTER_INSTRUMENTATION
@@ -291,8 +304,15 @@ public void visitCode() {
291304
catchNotEntitledAndReturnEarly();
292305
}
293306
case DeniedEntitlementStrategy.DefaultValueDeniedEntitlementStrategy defaultValue -> {
294-
// For default value strategy we want to catch not entitled and return the default value
295-
catchNotEntitledAndReturnValue(defaultValue.getDefaultValue());
307+
// For default value strategy we want to catch not entitled and return a default value;
308+
// null, String, and boxed primitives are embedded as JVM constants, all other reference
309+
// types are retrieved at runtime via defaultValue$
310+
Object value = defaultValue.getDefaultValue();
311+
if (isJvmConstant(value)) {
312+
catchNotEntitledAndReturnValue(value);
313+
} else {
314+
catchNotEntitledAndReturnReferenceDefault();
315+
}
296316
}
297317
case DeniedEntitlementStrategy.MethodArgumentValueDeniedEntitlementStrategy methodArgValue -> {
298318
// For method argument value strategy we want to catch not entitled and return the method argument at the given index
@@ -426,6 +446,24 @@ private void invokeInstrumentationMethod() {
426446
);
427447
}
428448

449+
private void catchNotEntitledAndReturnReferenceDefault() {
450+
wrapInstrumentationInTryCatch(() -> {
451+
mv.visitInsn(Opcodes.POP);
452+
pushEntitlementChecker();
453+
mv.visitLdcInsn(instrumentationId);
454+
mv.visitMethodInsn(
455+
INVOKEINTERFACE,
456+
Type.getReturnType(registryClassMethodDescriptor).getInternalName(),
457+
"defaultValue$",
458+
"(Ljava/lang/String;)Ljava/lang/Object;",
459+
true
460+
);
461+
Type returnType = Type.getReturnType(instrumentedMethodDescriptor);
462+
mv.visitTypeInsn(Opcodes.CHECKCAST, returnType.getInternalName());
463+
mv.visitInsn(Opcodes.ARETURN);
464+
});
465+
}
466+
429467
private void catchNotEntitledAndReturnValue(Object defaultValue) {
430468
wrapInstrumentationInTryCatch(() -> { returnConstantValue(defaultValue); });
431469
}
@@ -503,7 +541,9 @@ private void returnConstantValue(Object constant) {
503541
|| constant instanceof Boolean) {
504542
mv.visitInsn(Opcodes.IRETURN);
505543
} else {
506-
throw new IllegalStateException("unexpected check method constant [" + constant + "]");
544+
throw new IllegalStateException(
545+
"unsupported default return value [" + constant + "] of type [" + constant.getClass().getName() + "]"
546+
);
507547
}
508548
}
509549
}

libs/entitlement/bridge/src/main/java/org/elasticsearch/entitlement/bridge/InstrumentationRegistry.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,10 @@
1212
public interface InstrumentationRegistry {
1313

1414
void check$(String instrumentationId, Class<?> callingClass, Object... args) throws Exception;
15+
16+
/**
17+
* Returns the configured default reference value for the given instrumentation id.
18+
* Called from bytecode catch handlers when a reference-type default is needed on denial.
19+
*/
20+
Object defaultValue$(String instrumentationId);
1521
}

libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/EntitlementTest.java

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,40 @@ enum ExpectedAccess {
3232
Class<? extends Exception> expectedExceptionIfDenied() default NotEntitledException.class;
3333

3434
/**
35-
* When a denied entitlement strategy returns a default value instead of throwing,
36-
* this specifies the expected string representation of that default value. Test methods
37-
* using this must return a {@code String} representing the actual result. The test
38-
* infrastructure will verify the returned value matches this when running in a denied context.
39-
* An empty string (the default) means no default value check is performed.
35+
* When a denied entitlement strategy returns a non-null default value instead of throwing,
36+
* this specifies the expected string representation of that default value as a single-element
37+
* array. Test methods using this must return the actual result (any type); the infrastructure
38+
* converts it via {@code toString()}. The test infrastructure will verify the returned value
39+
* matches the element when running in a denied context.
40+
* An empty array (the default) means no default value check is performed.
41+
* Mutually exclusive with {@link #isExpectedDefaultNull()} and {@link #isExpectedNoOp()}.
4042
*/
41-
String expectedDefaultIfDenied() default "";
43+
String[] expectedDefaultIfDenied() default {};
44+
45+
/**
46+
* When set, the test infrastructure will verify that the actual result type matches this class.
47+
* This disambiguates cases where different types produce the same {@code toString()} output
48+
* (e.g. empty {@link java.util.List} and empty {@link java.util.Set} both produce {@code "[]"}).
49+
* Only meaningful when {@link #expectedDefaultIfDenied()} is also set.
50+
* {@code void.class} (the default) means no type check is performed.
51+
*/
52+
Class<?> expectedDefaultType() default void.class;
53+
54+
/**
55+
* When a denied entitlement strategy returns {@code null} instead of throwing,
56+
* set this to {@code true}. The test infrastructure will verify that the result
57+
* is actually {@code null} when running in a denied context.
58+
* Mutually exclusive with {@link #expectedDefaultIfDenied()} and {@link #isExpectedNoOp()}.
59+
*/
60+
boolean isExpectedDefaultNull() default false;
61+
62+
/**
63+
* When a denied entitlement strategy silently returns early (no-op) for a void method
64+
* instead of throwing, set this to {@code true}. The test infrastructure will accept
65+
* a successful response as valid denial behavior.
66+
* Mutually exclusive with {@link #expectedDefaultIfDenied()} and {@link #isExpectedDefaultNull()}.
67+
*/
68+
boolean isExpectedNoOp() default false;
4269

4370
int fromJavaVersion() default -1;
4471
}

libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/RestEntitlementsCheckAction.java

Lines changed: 56 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,13 @@ public class RestEntitlementsCheckAction extends BaseRestHandler {
4343
private static final Logger logger = LogManager.getLogger(RestEntitlementsCheckAction.class);
4444

4545
record CheckAction(
46-
CheckedFunction<Environment, String, Exception> action,
46+
CheckedFunction<Environment, Object, Exception> action,
4747
EntitlementTest.ExpectedAccess expectedAccess,
4848
Class<? extends Exception> expectedExceptionIfDenied,
49-
String expectedDefaultIfDenied,
49+
String[] expectedDefaultIfDenied,
50+
Class<?> expectedDefaultType,
51+
boolean isExpectedDefaultNull,
52+
boolean isExpectedNoOp,
5053
Integer fromJavaVersion
5154
) {}
5255

@@ -103,15 +106,39 @@ private static void getTestEntries(List<Entry<String, CheckAction>> entries, Cla
103106
if (Modifier.isPrivate(method.getModifiers())) {
104107
throw new AssertionError("Entitlement test method [" + method + "] must not be private");
105108
}
106-
String expectedDefault = testAnnotation.expectedDefaultIfDenied();
107-
if (expectedDefault.isEmpty() == false && method.getReturnType() != String.class) {
108-
throw new AssertionError("Entitlement test method [" + method + "] must return String when expectedDefaultIfDenied is set");
109+
String[] expectedDefault = testAnnotation.expectedDefaultIfDenied();
110+
Class<?> expectedDefaultType = testAnnotation.expectedDefaultType();
111+
boolean isExpectedDefaultNull = testAnnotation.isExpectedDefaultNull();
112+
boolean isExpectedNoOp = testAnnotation.isExpectedNoOp();
113+
boolean hasDefaultValue = expectedDefault.length > 0;
114+
if (hasDefaultValue && expectedDefault.length != 1) {
115+
throw new AssertionError("Entitlement test method [" + method + "] expectedDefaultIfDenied must have exactly one element");
116+
}
117+
if (expectedDefaultType != void.class && hasDefaultValue == false) {
118+
throw new AssertionError(
119+
"Entitlement test method [" + method + "] expectedDefaultType requires expectedDefaultIfDenied to be set"
120+
);
121+
}
122+
int denialStrategyCount = (hasDefaultValue ? 1 : 0) + (isExpectedDefaultNull ? 1 : 0) + (isExpectedNoOp ? 1 : 0);
123+
if (denialStrategyCount > 1) {
124+
throw new AssertionError(
125+
"Entitlement test method ["
126+
+ method
127+
+ "] must set at most one of expectedDefaultIfDenied, isExpectedDefaultNull, or isExpectedNoOp"
128+
);
129+
}
130+
if ((hasDefaultValue || isExpectedDefaultNull) && method.getReturnType() == void.class) {
131+
throw new AssertionError(
132+
"Entitlement test method [" + method + "] must have a return type when a default value is expected"
133+
);
134+
}
135+
if (isExpectedNoOp && method.getReturnType() != void.class) {
136+
throw new AssertionError("Entitlement test method [" + method + "] must be void when isExpectedNoOp is set");
109137
}
110138
final CheckedFunction<Environment, Object, Exception> call = createFunctionForMethod(method);
111-
CheckedFunction<Environment, String, Exception> action = env -> {
139+
CheckedFunction<Environment, Object, Exception> action = env -> {
112140
try {
113-
Object result = call.apply(env);
114-
return result == null ? null : result.toString();
141+
return call.apply(env);
115142
} catch (IllegalAccessException e) {
116143
throw new AssertionError(e);
117144
} catch (InvocationTargetException e) {
@@ -128,6 +155,9 @@ private static void getTestEntries(List<Entry<String, CheckAction>> entries, Cla
128155
testAnnotation.expectedAccess(),
129156
testAnnotation.expectedExceptionIfDenied(),
130157
expectedDefault,
158+
expectedDefaultType,
159+
isExpectedDefaultNull,
160+
isExpectedNoOp,
131161
fromJavaVersion
132162
);
133163
if (filter.test(checkAction)) {
@@ -216,19 +246,32 @@ protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient cli
216246
logger.info("Calling check action [{}]", actionName);
217247
RestResponse response;
218248
try {
219-
String result = checkAction.action().apply(environment);
220-
response = new RestResponse(RestStatus.OK, Strings.format("Succesfully executed action [%s]", actionName));
249+
Object result = checkAction.action().apply(environment);
250+
response = new RestResponse(RestStatus.OK, Strings.format("Successfully executed action [%s]", actionName));
221251
if (result != null) {
222-
response.addHeader("resultValue", result);
252+
response.addHeader("resultValue", result.toString());
253+
response.addHeader("resultType", result.getClass().getName());
254+
} else {
255+
response.addHeader("resultIsNull", "true");
256+
}
257+
if (checkAction.expectedDefaultIfDenied().length == 1) {
258+
response.addHeader("expectedDefaultIfDenied", checkAction.expectedDefaultIfDenied()[0]);
259+
}
260+
if (checkAction.expectedDefaultType() != void.class) {
261+
response.addHeader("expectedDefaultType", checkAction.expectedDefaultType().getName());
262+
}
263+
if (checkAction.isExpectedDefaultNull()) {
264+
response.addHeader("isExpectedDefaultNull", "true");
223265
}
224-
if (checkAction.expectedDefaultIfDenied().isEmpty() == false) {
225-
response.addHeader("expectedDefaultIfDenied", checkAction.expectedDefaultIfDenied());
266+
if (checkAction.isExpectedNoOp()) {
267+
response.addHeader("isExpectedNoOp", "true");
226268
}
227269
} catch (Exception e) {
228270
var statusCode = checkAction.expectedExceptionIfDenied.isInstance(e)
229271
? RestStatus.FORBIDDEN
230272
: RestStatus.INTERNAL_SERVER_ERROR;
231273
response = new RestResponse(channel, statusCode, e);
274+
response.addHeader("actualException", e.getClass().getName());
232275
response.addHeader("expectedException", checkAction.expectedExceptionIfDenied.getName());
233276
if (statusCode == RestStatus.FORBIDDEN && e.getCause() != null) {
234277
response.addHeader("notEntitledCause", String.valueOf(hasCause(e, NOT_ENTITLED_EXCEPTION_NAME)));

libs/entitlement/qa/src/javaRestTest/java/org/elasticsearch/entitlement/qa/AbstractEntitlementsIT.java

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -78,15 +78,32 @@ public void testAction() throws IOException {
7878
} else {
7979
try {
8080
Response result = executeCheck();
81-
// If the call succeeded in a denied context, a default value strategy must be in play.
82-
// Verify the returned default matches the expected value.
83-
String expectedDefault = result.getHeader("expectedDefaultIfDenied");
84-
assertNotNull(
85-
"Action [" + actionName + "] succeeded in denied context but has no expectedDefaultIfDenied",
86-
expectedDefault
87-
);
88-
String actualValue = result.getHeader("resultValue");
89-
assertThat("Action [" + actionName + "] returned unexpected default value", actualValue, equalTo(expectedDefault));
81+
assertThat(result.getStatusLine().getStatusCode(), equalTo(200));
82+
if ("true".equals(result.getHeader("isExpectedNoOp"))) {
83+
// void method with elseReturnEarly — silent success is expected
84+
} else if ("true".equals(result.getHeader("isExpectedDefaultNull"))) {
85+
assertTrue(
86+
"Action [" + actionName + "] expected null default but got a non-null result",
87+
"true".equals(result.getHeader("resultIsNull"))
88+
);
89+
} else if (result.getHeader("expectedDefaultIfDenied") != null) {
90+
String actualValue = result.getHeader("resultValue");
91+
assertThat(
92+
"Action [" + actionName + "] returned unexpected default value",
93+
actualValue,
94+
equalTo(result.getHeader("expectedDefaultIfDenied"))
95+
);
96+
String expectedType = result.getHeader("expectedDefaultType");
97+
if (expectedType != null) {
98+
assertThat(
99+
"Action [" + actionName + "] returned unexpected default type",
100+
result.getHeader("resultType"),
101+
equalTo(expectedType)
102+
);
103+
}
104+
} else {
105+
fail("Action [" + actionName + "] was expected to be denied but succeeded");
106+
}
90107
} catch (ResponseException exception) {
91108
assertThat(exception, statusCodeMatcher(403));
92109
}
@@ -105,6 +122,11 @@ protected boolean matchesSafely(ResponseException item) {
105122
if (resp.getStatusLine().getStatusCode() != statusCode || expectedException == null) {
106123
return false;
107124
}
125+
String actualException = resp.getHeader("actualException");
126+
if (expectedException.equals(actualException) == false) {
127+
mismatchDetail = "expected exception [" + expectedException + "] but got [" + actualException + "]";
128+
return false;
129+
}
108130
String notEntitledCause = resp.getHeader("notEntitledCause");
109131
if ("false".equals(notEntitledCause)) {
110132
mismatchDetail = "expected NotEntitledException in cause chain but it was absent";

libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/registry/InstrumentationRegistryImpl.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,15 @@ public InstrumentationRegistryImpl(PolicyChecker policyChecker) {
4747
}
4848
}
4949

50+
@Override
51+
public Object defaultValue$(String instrumentationId) {
52+
DeniedEntitlementStrategy strategy = implementationIdToStrategy.get(instrumentationId);
53+
if (strategy instanceof DeniedEntitlementStrategy.DefaultValueDeniedEntitlementStrategy<?> defaultValue) {
54+
return defaultValue.getDefaultValue();
55+
}
56+
throw new IllegalStateException("No default value configured for instrumentation id [" + instrumentationId + "]");
57+
}
58+
5059
@Override
5160
public Map<MethodKey, InstrumentationInfo> getInstrumentedMethods() {
5261
return Collections.unmodifiableMap(methodToImplementationInfo);

server/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,7 @@ public void apply(Settings value, Settings current, Settings previous) {
340340
HttpTransportSettings.SETTING_CORS_ALLOW_HEADERS,
341341
HttpTransportSettings.SETTING_HTTP_DETAILED_ERRORS_ENABLED,
342342
HttpTransportSettings.SETTING_HTTP_MAX_CONTENT_LENGTH,
343+
HttpTransportSettings.SETTING_HTTP_MAX_PROTOBUF_CONTENT_LENGTH,
343344
HttpTransportSettings.SETTING_HTTP_MAX_CHUNK_SIZE,
344345
HttpTransportSettings.SETTING_HTTP_MAX_HEADER_SIZE,
345346
HttpTransportSettings.SETTING_HTTP_MAX_WARNING_HEADER_COUNT,

server/src/main/java/org/elasticsearch/http/HttpTransportSettings.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,13 @@ public final class HttpTransportSettings {
9292
ByteSizeValue.ofBytes(Integer.MAX_VALUE),
9393
Property.NodeScope
9494
);
95+
public static final Setting<ByteSizeValue> SETTING_HTTP_MAX_PROTOBUF_CONTENT_LENGTH = Setting.byteSizeSetting(
96+
"http.max_protobuf_content_length",
97+
ByteSizeValue.of(8, ByteSizeUnit.MB),
98+
ByteSizeValue.ZERO,
99+
ByteSizeValue.ofBytes(Integer.MAX_VALUE),
100+
Property.NodeScope
101+
);
95102
public static final Setting<ByteSizeValue> SETTING_HTTP_MAX_CHUNK_SIZE = Setting.byteSizeSetting(
96103
"http.max_chunk_size",
97104
ByteSizeValue.of(8, ByteSizeUnit.KB),

server/src/main/java/org/elasticsearch/index/IndexingPressure.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,18 @@ public void increment(int operations, long bytes) {
303303
totalCoordinatingRequests.getAndIncrement();
304304
}
305305

306+
/**
307+
* Reduces the tracked byte count for this coordinating operation without closing it.
308+
* Use this to lower a reservation once the actual size is known to be smaller than the initially reserved amount.
309+
*/
310+
public void reduceBytes(long bytes) {
311+
assert closed.get() == false;
312+
assert currentOperationsSize >= bytes;
313+
currentOperationsSize -= bytes;
314+
currentCombinedCoordinatingAndPrimaryBytes.getAndAdd(-bytes);
315+
currentCoordinatingBytes.getAndAdd(-bytes);
316+
}
317+
306318
@Override
307319
public void close() {
308320
if (closed.compareAndSet(false, true)) {

0 commit comments

Comments
 (0)