Skip to content

Commit

Permalink
[8.x] [Entitlements] Network access checks for miscellanea (#120262) (#…
Browse files Browse the repository at this point in the history
…120353)

* [Entitlements] Network access checks for miscellanea (#120262)

* Move checks that use version-specific API
  • Loading branch information
ldematte authored Jan 17, 2025
1 parent 4aa346d commit fdffdd1
Show file tree
Hide file tree
Showing 11 changed files with 247 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,8 @@ private static Stream<String> maybeAttachEntitlementAgent(boolean useEntitlement
throw new IllegalStateException("Failed to list entitlement jars in: " + dir, e);
}
// We instrument classes in these modules to call the bridge. Because the bridge gets patched
// into java.base, we must export the bridge from java.base to these modules.
String modulesContainingEntitlementInstrumentation = "java.logging";
// into java.base, we must export the bridge from java.base to these modules, as a comma-separated list
String modulesContainingEntitlementInstrumentation = "java.logging,java.net.http,java.naming";
return Stream.of(
"-Des.entitlements.enabled=true",
"-XX:+EnableDynamicAgentLoading",
Expand Down
2 changes: 2 additions & 0 deletions libs/entitlement/bridge/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,7 @@
// This module-info is used just to satisfy your IDE.
// At build and run time, the bridge is patched into the java.base module.
module org.elasticsearch.entitlement.bridge {
requires java.net.http;

exports org.elasticsearch.entitlement.bridge;
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@
import java.net.URL;
import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.security.cert.CertStoreParameters;
import java.util.List;

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

void check$java_net_Socket$connect(Class<?> callerClass, Socket that, SocketAddress endpoint, int backlog);

// Network miscellanea
void check$java_net_URL$openConnection(Class<?> callerClass, java.net.URL that, Proxy proxy);

// HttpClient.Builder is an interface, so we instrument its only (internal) implementation
void check$jdk_internal_net_http_HttpClientBuilderImpl$build(Class<?> callerClass, HttpClient.Builder that);

// HttpClient#send and sendAsync are abstract, so we instrument their internal implementation
void check$jdk_internal_net_http_HttpClientImpl$send(
Class<?> callerClass,
HttpClient that,
HttpRequest request,
HttpResponse.BodyHandler<?> responseBodyHandler
);

void check$jdk_internal_net_http_HttpClientImpl$sendAsync(
Class<?> callerClass,
HttpClient that,
HttpRequest userRequest,
HttpResponse.BodyHandler<?> responseHandler
);

void check$jdk_internal_net_http_HttpClientImpl$sendAsync(
Class<?> callerClass,
HttpClient that,
HttpRequest userRequest,
HttpResponse.BodyHandler<?> responseHandler,
HttpResponse.PushPromiseHandler<?> pushPromiseHandler
);

// We need to check the LDAPCertStore, as this will connect, but this is internal/created via SPI,
// so we instrument the general factory instead and then filter in the check method implementation
void check$java_security_cert_CertStore$$getInstance(Class<?> callerClass, String type, CertStoreParameters params);
}
1 change: 1 addition & 0 deletions libs/entitlement/qa/common/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

// Modules we'll attempt to use in order to exercise entitlements
requires java.logging;
requires java.net.http;

exports org.elasticsearch.entitlement.qa.common;
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@
import java.net.Proxy;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertStore;
import java.util.Arrays;

class NetworkAccessCheckActions {

Expand Down Expand Up @@ -59,4 +65,21 @@ static void socketConnect() throws IOException {
socket.connect(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
}
}

@SuppressForbidden(reason = "Testing entitlement check on forbidden action")
static void urlOpenConnectionWithProxy() throws URISyntaxException, IOException {
var url = new URI("http://localhost").toURL();
var urlConnection = url.openConnection(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(0)));
assert urlConnection != null;
}

static void createLDAPCertStore() throws NoSuchAlgorithmException {
try {
// We pass down null params to provoke a InvalidAlgorithmParameterException
CertStore.getInstance("LDAP", null);
} catch (InvalidAlgorithmParameterException ex) {
// Assert we actually hit the class we care about, LDAPCertStore (or its impl)
assert Arrays.stream(ex.getStackTrace()).anyMatch(e -> e.getClassName().endsWith("LDAPCertStore"));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,13 @@ static CheckAction alwaysDenied(CheckedRunnable<Exception> action) {
entry("socket_bind", forPlugins(NetworkAccessCheckActions::socketBind)),
entry("socket_connect", forPlugins(NetworkAccessCheckActions::socketConnect)),
entry("server_socket_bind", forPlugins(NetworkAccessCheckActions::serverSocketBind)),
entry("server_socket_accept", forPlugins(NetworkAccessCheckActions::serverSocketAccept))
entry("server_socket_accept", forPlugins(NetworkAccessCheckActions::serverSocketAccept)),

entry("url_open_connection_proxy", forPlugins(NetworkAccessCheckActions::urlOpenConnectionWithProxy)),
entry("http_client_builder_build", forPlugins(VersionSpecificNetworkChecks::httpClientBuilderBuild)),
entry("http_client_send", forPlugins(VersionSpecificNetworkChecks::httpClientSend)),
entry("http_client_send_async", forPlugins(VersionSpecificNetworkChecks::httpClientSendAsync)),
entry("create_ldap_cert_store", forPlugins(NetworkAccessCheckActions::createLDAPCertStore))
)
.filter(entry -> entry.getValue().fromJavaVersion() == null || Runtime.version().feature() >= entry.getValue().fromJavaVersion())
.collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,30 @@

package org.elasticsearch.entitlement.qa.common;

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

class VersionSpecificNetworkChecks {
static void createInetAddressResolverProvider() {}

static void httpClientBuilderBuild() {
HttpClient.newBuilder().build();
}

static void httpClientSend() throws InterruptedException {
HttpClient httpClient = HttpClient.newBuilder().build();
try {
httpClient.send(HttpRequest.newBuilder(URI.create("http://localhost")).build(), HttpResponse.BodyHandlers.discarding());
} catch (IOException e) {
// Expected, the send action may fail with these parameters (but after it run the entitlement check in the prologue)
}
}

static void httpClientSendAsync() {
HttpClient httpClient = HttpClient.newBuilder().build();
httpClient.sendAsync(HttpRequest.newBuilder(URI.create("http://localhost")).build(), HttpResponse.BodyHandlers.discarding());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@

package org.elasticsearch.entitlement.qa.common;

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.spi.InetAddressResolver;
import java.net.spi.InetAddressResolverProvider;

Expand All @@ -26,4 +31,22 @@ public String name() {
}
};
}

static void httpClientBuilderBuild() {
HttpClient.newBuilder().build();
}

static void httpClientSend() throws InterruptedException {
HttpClient httpClient = HttpClient.newBuilder().build();
try {
httpClient.send(HttpRequest.newBuilder(URI.create("http://localhost")).build(), HttpResponse.BodyHandlers.discarding());
} catch (IOException e) {
// Expected, the send action may fail with these parameters (but after it run the entitlement check in the prologue)
}
}

static void httpClientSendAsync() {
HttpClient httpClient = HttpClient.newBuilder().build();
httpClient.sendAsync(HttpRequest.newBuilder(URI.create("http://localhost")).build(), HttpResponse.BodyHandlers.discarding());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* 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.qa.common;

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.spi.InetAddressResolver;
import java.net.spi.InetAddressResolverProvider;

class VersionSpecificNetworkChecks {
static void createInetAddressResolverProvider() {
var x = new InetAddressResolverProvider() {
@Override
public InetAddressResolver get(Configuration configuration) {
return null;
}

@Override
public String name() {
return "TEST";
}
};
}

static void httpClientBuilderBuild() {
try (HttpClient httpClient = HttpClient.newBuilder().build()) {
assert httpClient != null;
}
}

static void httpClientSend() throws InterruptedException {
try (HttpClient httpClient = HttpClient.newBuilder().build()) {
// Shutdown the client, so the send action will shortcut before actually executing any network operation
// (but after it run our check in the prologue)
httpClient.shutdown();
try {
httpClient.send(HttpRequest.newBuilder(URI.create("http://localhost")).build(), HttpResponse.BodyHandlers.discarding());
} catch (IOException e) {
// Expected, since we shut down the client
}
}
}

static void httpClientSendAsync() {
try (HttpClient httpClient = HttpClient.newBuilder().build()) {
// Shutdown the client, so the send action will return before actually executing any network operation
// (but after it run our check in the prologue)
httpClient.shutdown();
var future = httpClient.sendAsync(
HttpRequest.newBuilder(URI.create("http://localhost")).build(),
HttpResponse.BodyHandlers.discarding()
);
assert future.isCompletedExceptionally();
future.exceptionally(ex -> {
assert ex instanceof IOException;
return null;
});
}
}
}
1 change: 1 addition & 0 deletions libs/entitlement/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
requires java.instrument;
requires org.elasticsearch.base;
requires jdk.attach;
requires java.net.http;

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
import java.net.URL;
import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.security.cert.CertStoreParameters;
import java.util.List;

import javax.net.ssl.HostnameVerifier;
Expand Down Expand Up @@ -504,4 +508,57 @@ public ElasticsearchEntitlementChecker(PolicyManager policyManager) {
public void check$java_net_Socket$connect(Class<?> callerClass, Socket that, SocketAddress endpoint, int backlog) {
policyManager.checkNetworkAccess(callerClass, NetworkEntitlement.CONNECT_ACTION);
}

@Override
public void check$java_net_URL$openConnection(Class<?> callerClass, URL that, Proxy proxy) {
if (proxy.type() != Proxy.Type.DIRECT) {
policyManager.checkNetworkAccess(callerClass, NetworkEntitlement.CONNECT_ACTION);
}
}

@Override
public void check$jdk_internal_net_http_HttpClientBuilderImpl$build(Class<?> callerClass, HttpClient.Builder that) {
policyManager.checkNetworkAccess(callerClass, NetworkEntitlement.LISTEN_ACTION);
}

@Override
public void check$jdk_internal_net_http_HttpClientImpl$send(
Class<?> callerClass,
HttpClient that,
HttpRequest request,
HttpResponse.BodyHandler<?> responseBodyHandler
) {
policyManager.checkNetworkAccess(callerClass, NetworkEntitlement.CONNECT_ACTION);
}

@Override
public void check$jdk_internal_net_http_HttpClientImpl$sendAsync(
Class<?> callerClass,
HttpClient that,
HttpRequest userRequest,
HttpResponse.BodyHandler<?> responseHandler
) {
policyManager.checkNetworkAccess(callerClass, NetworkEntitlement.CONNECT_ACTION);
}

@Override
public void check$jdk_internal_net_http_HttpClientImpl$sendAsync(
Class<?> callerClass,
HttpClient that,
HttpRequest userRequest,
HttpResponse.BodyHandler<?> responseHandler,
HttpResponse.PushPromiseHandler<?> pushPromiseHandler
) {
policyManager.checkNetworkAccess(callerClass, NetworkEntitlement.CONNECT_ACTION);
}

@Override
public void check$java_security_cert_CertStore$$getInstance(Class<?> callerClass, String type, CertStoreParameters params) {
// We need to check "just" the LDAPCertStore instantiation: this is the CertStore that will try to perform a network operation
// (connect to an LDAP server). But LDAPCertStore is internal (created via SPI), so we instrument the general factory instead and
// then do the check only for the path that leads to sensitive code (by looking at the `type` parameter).
if ("LDAP".equals(type)) {
policyManager.checkNetworkAccess(callerClass, NetworkEntitlement.CONNECT_ACTION);
}
}
}

0 comments on commit fdffdd1

Please sign in to comment.