Skip to content

Commit cbb7c24

Browse files
authored
[Entitlements] Network access checks for miscellanea (elastic#120262)
1 parent 2ee7ca4 commit cbb7c24

File tree

8 files changed

+169
-3
lines changed

8 files changed

+169
-3
lines changed

distribution/tools/server-cli/src/main/java/org/elasticsearch/server/cli/SystemJvmOptions.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,8 @@ private static Stream<String> maybeAttachEntitlementAgent(boolean useEntitlement
171171
throw new IllegalStateException("Failed to list entitlement jars in: " + dir, e);
172172
}
173173
// We instrument classes in these modules to call the bridge. Because the bridge gets patched
174-
// into java.base, we must export the bridge from java.base to these modules.
175-
String modulesContainingEntitlementInstrumentation = "java.logging";
174+
// into java.base, we must export the bridge from java.base to these modules, as a comma-separated list
175+
String modulesContainingEntitlementInstrumentation = "java.logging,java.net.http,java.naming";
176176
return Stream.of(
177177
"-Des.entitlements.enabled=true",
178178
"-XX:+EnableDynamicAgentLoading",

libs/entitlement/bridge/src/main/java/module-info.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,7 @@
1010
// This module-info is used just to satisfy your IDE.
1111
// At build and run time, the bridge is patched into the java.base module.
1212
module org.elasticsearch.entitlement.bridge {
13+
requires java.net.http;
14+
1315
exports org.elasticsearch.entitlement.bridge;
1416
}

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

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@
3030
import java.net.URL;
3131
import java.net.URLStreamHandler;
3232
import java.net.URLStreamHandlerFactory;
33+
import java.net.http.HttpClient;
34+
import java.net.http.HttpRequest;
35+
import java.net.http.HttpResponse;
36+
import java.security.cert.CertStoreParameters;
3337
import java.util.List;
3438

3539
import javax.net.ssl.HostnameVerifier;
@@ -254,4 +258,37 @@ public interface EntitlementChecker {
254258
void check$java_net_Socket$connect(Class<?> callerClass, Socket that, SocketAddress endpoint);
255259

256260
void check$java_net_Socket$connect(Class<?> callerClass, Socket that, SocketAddress endpoint, int backlog);
261+
262+
// Network miscellanea
263+
void check$java_net_URL$openConnection(Class<?> callerClass, java.net.URL that, Proxy proxy);
264+
265+
// HttpClient.Builder is an interface, so we instrument its only (internal) implementation
266+
void check$jdk_internal_net_http_HttpClientBuilderImpl$build(Class<?> callerClass, HttpClient.Builder that);
267+
268+
// HttpClient#send and sendAsync are abstract, so we instrument their internal implementation
269+
void check$jdk_internal_net_http_HttpClientImpl$send(
270+
Class<?> callerClass,
271+
HttpClient that,
272+
HttpRequest request,
273+
HttpResponse.BodyHandler<?> responseBodyHandler
274+
);
275+
276+
void check$jdk_internal_net_http_HttpClientImpl$sendAsync(
277+
Class<?> callerClass,
278+
HttpClient that,
279+
HttpRequest userRequest,
280+
HttpResponse.BodyHandler<?> responseHandler
281+
);
282+
283+
void check$jdk_internal_net_http_HttpClientImpl$sendAsync(
284+
Class<?> callerClass,
285+
HttpClient that,
286+
HttpRequest userRequest,
287+
HttpResponse.BodyHandler<?> responseHandler,
288+
HttpResponse.PushPromiseHandler<?> pushPromiseHandler
289+
);
290+
291+
// We need to check the LDAPCertStore, as this will connect, but this is internal/created via SPI,
292+
// so we instrument the general factory instead and then filter in the check method implementation
293+
void check$java_security_cert_CertStore$$getInstance(Class<?> callerClass, String type, CertStoreParameters params);
257294
}

libs/entitlement/qa/common/src/main/java/module-info.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
// Modules we'll attempt to use in order to exercise entitlements
1616
requires java.logging;
17+
requires java.net.http;
1718

1819
exports org.elasticsearch.entitlement.qa.common;
1920
}

libs/entitlement/qa/common/src/main/java/org/elasticsearch/entitlement/qa/common/NetworkAccessCheckActions.java

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,15 @@
1717
import java.net.Proxy;
1818
import java.net.ServerSocket;
1919
import java.net.Socket;
20+
import java.net.URI;
21+
import java.net.URISyntaxException;
22+
import java.net.http.HttpClient;
23+
import java.net.http.HttpRequest;
24+
import java.net.http.HttpResponse;
25+
import java.security.InvalidAlgorithmParameterException;
26+
import java.security.NoSuchAlgorithmException;
27+
import java.security.cert.CertStore;
28+
import java.util.Arrays;
2029

2130
class NetworkAccessCheckActions {
2231

@@ -59,4 +68,57 @@ static void socketConnect() throws IOException {
5968
socket.connect(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
6069
}
6170
}
71+
72+
@SuppressForbidden(reason = "Testing entitlement check on forbidden action")
73+
static void urlOpenConnectionWithProxy() throws URISyntaxException, IOException {
74+
var url = new URI("http://localhost").toURL();
75+
var urlConnection = url.openConnection(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(0)));
76+
assert urlConnection != null;
77+
}
78+
79+
static void httpClientBuilderBuild() {
80+
try (HttpClient httpClient = HttpClient.newBuilder().build()) {
81+
assert httpClient != null;
82+
}
83+
}
84+
85+
static void httpClientSend() throws InterruptedException {
86+
try (HttpClient httpClient = HttpClient.newBuilder().build()) {
87+
// Shutdown the client, so the send action will shortcut before actually executing any network operation
88+
// (but after it run our check in the prologue)
89+
httpClient.shutdown();
90+
try {
91+
httpClient.send(HttpRequest.newBuilder(URI.create("http://localhost")).build(), HttpResponse.BodyHandlers.discarding());
92+
} catch (IOException e) {
93+
// Expected, since we shut down the client
94+
}
95+
}
96+
}
97+
98+
static void httpClientSendAsync() {
99+
try (HttpClient httpClient = HttpClient.newBuilder().build()) {
100+
// Shutdown the client, so the send action will return before actually executing any network operation
101+
// (but after it run our check in the prologue)
102+
httpClient.shutdown();
103+
var future = httpClient.sendAsync(
104+
HttpRequest.newBuilder(URI.create("http://localhost")).build(),
105+
HttpResponse.BodyHandlers.discarding()
106+
);
107+
assert future.isCompletedExceptionally();
108+
future.exceptionally(ex -> {
109+
assert ex instanceof IOException;
110+
return null;
111+
});
112+
}
113+
}
114+
115+
static void createLDAPCertStore() throws NoSuchAlgorithmException {
116+
try {
117+
// We pass down null params to provoke a InvalidAlgorithmParameterException
118+
CertStore.getInstance("LDAP", null);
119+
} catch (InvalidAlgorithmParameterException ex) {
120+
// Assert we actually hit the class we care about, LDAPCertStore (or its impl)
121+
assert Arrays.stream(ex.getStackTrace()).anyMatch(e -> e.getClassName().endsWith("LDAPCertStore"));
122+
}
123+
}
62124
}

libs/entitlement/qa/common/src/main/java/org/elasticsearch/entitlement/qa/common/RestEntitlementsCheckAction.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,13 @@ static CheckAction alwaysDenied(CheckedRunnable<Exception> action) {
157157
entry("socket_bind", forPlugins(NetworkAccessCheckActions::socketBind)),
158158
entry("socket_connect", forPlugins(NetworkAccessCheckActions::socketConnect)),
159159
entry("server_socket_bind", forPlugins(NetworkAccessCheckActions::serverSocketBind)),
160-
entry("server_socket_accept", forPlugins(NetworkAccessCheckActions::serverSocketAccept))
160+
entry("server_socket_accept", forPlugins(NetworkAccessCheckActions::serverSocketAccept)),
161+
162+
entry("url_open_connection_proxy", forPlugins(NetworkAccessCheckActions::urlOpenConnectionWithProxy)),
163+
entry("http_client_builder_build", forPlugins(NetworkAccessCheckActions::httpClientBuilderBuild)),
164+
entry("http_client_send", forPlugins(NetworkAccessCheckActions::httpClientSend)),
165+
entry("http_client_send_async", forPlugins(NetworkAccessCheckActions::httpClientSendAsync)),
166+
entry("create_ldap_cert_store", forPlugins(NetworkAccessCheckActions::createLDAPCertStore))
161167
)
162168
.filter(entry -> entry.getValue().fromJavaVersion() == null || Runtime.version().feature() >= entry.getValue().fromJavaVersion())
163169
.collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue));

libs/entitlement/src/main/java/module-info.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
requires java.instrument;
1414
requires org.elasticsearch.base;
1515
requires jdk.attach;
16+
requires java.net.http;
1617

1718
requires static org.elasticsearch.entitlement.bridge; // At runtime, this will be in java.base
1819

libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/api/ElasticsearchEntitlementChecker.java

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@
3434
import java.net.URL;
3535
import java.net.URLStreamHandler;
3636
import java.net.URLStreamHandlerFactory;
37+
import java.net.http.HttpClient;
38+
import java.net.http.HttpRequest;
39+
import java.net.http.HttpResponse;
40+
import java.security.cert.CertStoreParameters;
3741
import java.util.List;
3842

3943
import javax.net.ssl.HostnameVerifier;
@@ -504,4 +508,57 @@ public ElasticsearchEntitlementChecker(PolicyManager policyManager) {
504508
public void check$java_net_Socket$connect(Class<?> callerClass, Socket that, SocketAddress endpoint, int backlog) {
505509
policyManager.checkNetworkAccess(callerClass, NetworkEntitlement.CONNECT_ACTION);
506510
}
511+
512+
@Override
513+
public void check$java_net_URL$openConnection(Class<?> callerClass, URL that, Proxy proxy) {
514+
if (proxy.type() != Proxy.Type.DIRECT) {
515+
policyManager.checkNetworkAccess(callerClass, NetworkEntitlement.CONNECT_ACTION);
516+
}
517+
}
518+
519+
@Override
520+
public void check$jdk_internal_net_http_HttpClientBuilderImpl$build(Class<?> callerClass, HttpClient.Builder that) {
521+
policyManager.checkNetworkAccess(callerClass, NetworkEntitlement.LISTEN_ACTION);
522+
}
523+
524+
@Override
525+
public void check$jdk_internal_net_http_HttpClientImpl$send(
526+
Class<?> callerClass,
527+
HttpClient that,
528+
HttpRequest request,
529+
HttpResponse.BodyHandler<?> responseBodyHandler
530+
) {
531+
policyManager.checkNetworkAccess(callerClass, NetworkEntitlement.CONNECT_ACTION);
532+
}
533+
534+
@Override
535+
public void check$jdk_internal_net_http_HttpClientImpl$sendAsync(
536+
Class<?> callerClass,
537+
HttpClient that,
538+
HttpRequest userRequest,
539+
HttpResponse.BodyHandler<?> responseHandler
540+
) {
541+
policyManager.checkNetworkAccess(callerClass, NetworkEntitlement.CONNECT_ACTION);
542+
}
543+
544+
@Override
545+
public void check$jdk_internal_net_http_HttpClientImpl$sendAsync(
546+
Class<?> callerClass,
547+
HttpClient that,
548+
HttpRequest userRequest,
549+
HttpResponse.BodyHandler<?> responseHandler,
550+
HttpResponse.PushPromiseHandler<?> pushPromiseHandler
551+
) {
552+
policyManager.checkNetworkAccess(callerClass, NetworkEntitlement.CONNECT_ACTION);
553+
}
554+
555+
@Override
556+
public void check$java_security_cert_CertStore$$getInstance(Class<?> callerClass, String type, CertStoreParameters params) {
557+
// We need to check "just" the LDAPCertStore instantiation: this is the CertStore that will try to perform a network operation
558+
// (connect to an LDAP server). But LDAPCertStore is internal (created via SPI), so we instrument the general factory instead and
559+
// then do the check only for the path that leads to sensitive code (by looking at the `type` parameter).
560+
if ("LDAP".equals(type)) {
561+
policyManager.checkNetworkAccess(callerClass, NetworkEntitlement.CONNECT_ACTION);
562+
}
563+
}
507564
}

0 commit comments

Comments
 (0)