Skip to content

Commit 1848d6b

Browse files
authored
[Entitlements] Network access checks on Sockets (#120093)
1 parent 1448f12 commit 1848d6b

File tree

13 files changed

+356
-31
lines changed

13 files changed

+356
-31
lines changed

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,11 @@
2020
import java.net.InetAddress;
2121
import java.net.MulticastSocket;
2222
import java.net.NetworkInterface;
23+
import java.net.Proxy;
2324
import java.net.ProxySelector;
2425
import java.net.ResponseCache;
26+
import java.net.ServerSocket;
27+
import java.net.Socket;
2528
import java.net.SocketAddress;
2629
import java.net.SocketImplFactory;
2730
import java.net.URL;
@@ -215,4 +218,40 @@ public interface EntitlementChecker {
215218
void check$java_net_MulticastSocket$leaveGroup(Class<?> callerClass, MulticastSocket that, SocketAddress addr, NetworkInterface ni);
216219

217220
void check$java_net_MulticastSocket$send(Class<?> callerClass, MulticastSocket that, DatagramPacket p, byte ttl);
221+
222+
// Binding/connecting ctor
223+
void check$java_net_ServerSocket$(Class<?> callerClass, int port);
224+
225+
void check$java_net_ServerSocket$(Class<?> callerClass, int port, int backlog);
226+
227+
void check$java_net_ServerSocket$(Class<?> callerClass, int port, int backlog, InetAddress bindAddr);
228+
229+
void check$java_net_ServerSocket$accept(Class<?> callerClass, ServerSocket that);
230+
231+
void check$java_net_ServerSocket$implAccept(Class<?> callerClass, ServerSocket that, Socket s);
232+
233+
void check$java_net_ServerSocket$bind(Class<?> callerClass, ServerSocket that, SocketAddress endpoint);
234+
235+
void check$java_net_ServerSocket$bind(Class<?> callerClass, ServerSocket that, SocketAddress endpoint, int backlog);
236+
237+
// Binding/connecting ctors
238+
void check$java_net_Socket$(Class<?> callerClass, Proxy proxy);
239+
240+
void check$java_net_Socket$(Class<?> callerClass, String host, int port);
241+
242+
void check$java_net_Socket$(Class<?> callerClass, InetAddress address, int port);
243+
244+
void check$java_net_Socket$(Class<?> callerClass, String host, int port, InetAddress localAddr, int localPort);
245+
246+
void check$java_net_Socket$(Class<?> callerClass, InetAddress address, int port, InetAddress localAddr, int localPort);
247+
248+
void check$java_net_Socket$(Class<?> callerClass, String host, int port, boolean stream);
249+
250+
void check$java_net_Socket$(Class<?> callerClass, InetAddress host, int port, boolean stream);
251+
252+
void check$java_net_Socket$bind(Class<?> callerClass, Socket that, SocketAddress endpoint);
253+
254+
void check$java_net_Socket$connect(Class<?> callerClass, Socket that, SocketAddress endpoint);
255+
256+
void check$java_net_Socket$connect(Class<?> callerClass, Socket that, SocketAddress endpoint, int backlog);
218257
}

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

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,18 @@
1010
package org.elasticsearch.entitlement.qa.common;
1111

1212
import java.io.IOException;
13+
import java.io.InputStream;
14+
import java.io.OutputStream;
1315
import java.net.DatagramPacket;
1416
import java.net.DatagramSocket;
1517
import java.net.DatagramSocketImpl;
1618
import java.net.InetAddress;
1719
import java.net.NetworkInterface;
20+
import java.net.ServerSocket;
1821
import java.net.Socket;
1922
import java.net.SocketAddress;
2023
import java.net.SocketException;
24+
import java.net.SocketImpl;
2125
import java.security.cert.Certificate;
2226
import java.text.BreakIterator;
2327
import java.text.Collator;
@@ -297,6 +301,81 @@ public Certificate[] getServerCertificates() {
297301
}
298302
}
299303

304+
private static class DummySocketImpl extends SocketImpl {
305+
@Override
306+
protected void create(boolean stream) {}
307+
308+
@Override
309+
protected void connect(String host, int port) {}
310+
311+
@Override
312+
protected void connect(InetAddress address, int port) {}
313+
314+
@Override
315+
protected void connect(SocketAddress address, int timeout) {}
316+
317+
@Override
318+
protected void bind(InetAddress host, int port) {}
319+
320+
@Override
321+
protected void listen(int backlog) {}
322+
323+
@Override
324+
protected void accept(SocketImpl s) {}
325+
326+
@Override
327+
protected InputStream getInputStream() {
328+
return null;
329+
}
330+
331+
@Override
332+
protected OutputStream getOutputStream() {
333+
return null;
334+
}
335+
336+
@Override
337+
protected int available() {
338+
return 0;
339+
}
340+
341+
@Override
342+
protected void close() {}
343+
344+
@Override
345+
protected void sendUrgentData(int data) {}
346+
347+
@Override
348+
public void setOption(int optID, Object value) {}
349+
350+
@Override
351+
public Object getOption(int optID) {
352+
return null;
353+
}
354+
}
355+
356+
static class DummySocket extends Socket {
357+
DummySocket() throws SocketException {
358+
super(new DummySocketImpl());
359+
}
360+
}
361+
362+
static class DummyServerSocket extends ServerSocket {
363+
DummyServerSocket() {
364+
super(new DummySocketImpl());
365+
}
366+
}
367+
368+
static class DummyBoundServerSocket extends ServerSocket {
369+
DummyBoundServerSocket() {
370+
super(new DummySocketImpl());
371+
}
372+
373+
@Override
374+
public boolean isBound() {
375+
return true;
376+
}
377+
}
378+
300379
static class DummySSLSocketFactory extends SSLSocketFactory {
301380
@Override
302381
public Socket createSocket(String host, int port) {
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
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.entitlement.qa.common;
11+
12+
import org.elasticsearch.core.SuppressForbidden;
13+
14+
import java.io.IOException;
15+
import java.net.InetAddress;
16+
import java.net.InetSocketAddress;
17+
import java.net.Proxy;
18+
import java.net.ServerSocket;
19+
import java.net.Socket;
20+
21+
class NetworkAccessCheckActions {
22+
23+
static void serverSocketAccept() throws IOException {
24+
try (ServerSocket socket = new DummyImplementations.DummyBoundServerSocket()) {
25+
try {
26+
socket.accept();
27+
} catch (IOException e) {
28+
// Our dummy socket cannot accept connections unless we tell the JDK how to create a socket for it.
29+
// But Socket.setSocketImplFactory(); is one of the methods we always forbid, so we cannot use it.
30+
// Still, we can check accept is called (allowed/denied), we don't care if it fails later for this
31+
// known reason.
32+
assert e.getMessage().contains("client socket implementation factory not set");
33+
}
34+
}
35+
}
36+
37+
static void serverSocketBind() throws IOException {
38+
try (ServerSocket socket = new DummyImplementations.DummyServerSocket()) {
39+
socket.bind(null);
40+
}
41+
}
42+
43+
@SuppressForbidden(reason = "Testing entitlement check on forbidden action")
44+
static void createSocketWithProxy() throws IOException {
45+
try (Socket socket = new Socket(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(0)))) {
46+
assert socket.isBound() == false;
47+
}
48+
}
49+
50+
static void socketBind() throws IOException {
51+
try (Socket socket = new DummyImplementations.DummySocket()) {
52+
socket.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
53+
}
54+
}
55+
56+
@SuppressForbidden(reason = "Testing entitlement check on forbidden action")
57+
static void socketConnect() throws IOException {
58+
try (Socket socket = new DummyImplementations.DummySocket()) {
59+
socket.connect(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
60+
}
61+
}
62+
}

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
@@ -149,7 +149,13 @@ static CheckAction alwaysDenied(CheckedRunnable<Exception> action) {
149149
entry("datagram_socket_send", forPlugins(RestEntitlementsCheckAction::sendDatagramSocket)),
150150
entry("datagram_socket_receive", forPlugins(RestEntitlementsCheckAction::receiveDatagramSocket)),
151151
entry("datagram_socket_join_group", forPlugins(RestEntitlementsCheckAction::joinGroupDatagramSocket)),
152-
entry("datagram_socket_leave_group", forPlugins(RestEntitlementsCheckAction::leaveGroupDatagramSocket))
152+
entry("datagram_socket_leave_group", forPlugins(RestEntitlementsCheckAction::leaveGroupDatagramSocket)),
153+
154+
entry("create_socket_with_proxy", forPlugins(NetworkAccessCheckActions::createSocketWithProxy)),
155+
entry("socket_bind", forPlugins(NetworkAccessCheckActions::socketBind)),
156+
entry("socket_connect", forPlugins(NetworkAccessCheckActions::socketConnect)),
157+
entry("server_socket_bind", forPlugins(NetworkAccessCheckActions::serverSocketBind)),
158+
entry("server_socket_accept", forPlugins(NetworkAccessCheckActions::serverSocketAccept))
153159
);
154160

155161
private static void createURLStreamHandlerProvider() {

libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/EntitlementInitialization.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.elasticsearch.entitlement.runtime.policy.CreateClassLoaderEntitlement;
2323
import org.elasticsearch.entitlement.runtime.policy.Entitlement;
2424
import org.elasticsearch.entitlement.runtime.policy.ExitVMEntitlement;
25+
import org.elasticsearch.entitlement.runtime.policy.NetworkEntitlement;
2526
import org.elasticsearch.entitlement.runtime.policy.Policy;
2627
import org.elasticsearch.entitlement.runtime.policy.PolicyManager;
2728
import org.elasticsearch.entitlement.runtime.policy.PolicyParser;
@@ -44,6 +45,9 @@
4445
import java.util.Set;
4546
import java.util.stream.Collectors;
4647

48+
import static org.elasticsearch.entitlement.runtime.policy.NetworkEntitlement.ACCEPT_ACTION;
49+
import static org.elasticsearch.entitlement.runtime.policy.NetworkEntitlement.CONNECT_ACTION;
50+
import static org.elasticsearch.entitlement.runtime.policy.NetworkEntitlement.LISTEN_ACTION;
4751
import static org.elasticsearch.entitlement.runtime.policy.PolicyManager.ALL_UNNAMED;
4852

4953
/**
@@ -97,7 +101,15 @@ private static PolicyManager createPolicyManager() throws IOException {
97101
List.of(
98102
new Scope("org.elasticsearch.base", List.of(new CreateClassLoaderEntitlement())),
99103
new Scope("org.elasticsearch.xcontent", List.of(new CreateClassLoaderEntitlement())),
100-
new Scope("org.elasticsearch.server", List.of(new ExitVMEntitlement(), new CreateClassLoaderEntitlement()))
104+
new Scope(
105+
"org.elasticsearch.server",
106+
List.of(
107+
new ExitVMEntitlement(),
108+
new CreateClassLoaderEntitlement(),
109+
new NetworkEntitlement(LISTEN_ACTION | CONNECT_ACTION | ACCEPT_ACTION)
110+
)
111+
),
112+
new Scope("org.apache.httpcomponents.httpclient", List.of(new NetworkEntitlement(CONNECT_ACTION)))
101113
)
102114
);
103115
// agents run without a module, so this is a special hack for the apm agent

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

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,11 @@
2424
import java.net.InetAddress;
2525
import java.net.MulticastSocket;
2626
import java.net.NetworkInterface;
27+
import java.net.Proxy;
2728
import java.net.ProxySelector;
2829
import java.net.ResponseCache;
30+
import java.net.ServerSocket;
31+
import java.net.Socket;
2932
import java.net.SocketAddress;
3033
import java.net.SocketImplFactory;
3134
import java.net.URL;
@@ -414,4 +417,91 @@ public ElasticsearchEntitlementChecker(PolicyManager policyManager) {
414417
public void check$java_net_MulticastSocket$send(Class<?> callerClass, MulticastSocket that, DatagramPacket p, byte ttl) {
415418
policyManager.checkNetworkAccess(callerClass, NetworkEntitlement.CONNECT_ACTION | NetworkEntitlement.ACCEPT_ACTION);
416419
}
420+
421+
@Override
422+
public void check$java_net_ServerSocket$(Class<?> callerClass, int port) {
423+
policyManager.checkNetworkAccess(callerClass, NetworkEntitlement.LISTEN_ACTION);
424+
}
425+
426+
@Override
427+
public void check$java_net_ServerSocket$(Class<?> callerClass, int port, int backlog) {
428+
policyManager.checkNetworkAccess(callerClass, NetworkEntitlement.LISTEN_ACTION);
429+
}
430+
431+
@Override
432+
public void check$java_net_ServerSocket$(Class<?> callerClass, int port, int backlog, InetAddress bindAddr) {
433+
policyManager.checkNetworkAccess(callerClass, NetworkEntitlement.LISTEN_ACTION);
434+
}
435+
436+
@Override
437+
public void check$java_net_ServerSocket$accept(Class<?> callerClass, ServerSocket that) {
438+
policyManager.checkNetworkAccess(callerClass, NetworkEntitlement.ACCEPT_ACTION);
439+
}
440+
441+
@Override
442+
public void check$java_net_ServerSocket$implAccept(Class<?> callerClass, ServerSocket that, Socket s) {
443+
policyManager.checkNetworkAccess(callerClass, NetworkEntitlement.ACCEPT_ACTION);
444+
}
445+
446+
@Override
447+
public void check$java_net_ServerSocket$bind(Class<?> callerClass, ServerSocket that, SocketAddress endpoint) {
448+
policyManager.checkNetworkAccess(callerClass, NetworkEntitlement.LISTEN_ACTION);
449+
}
450+
451+
@Override
452+
public void check$java_net_ServerSocket$bind(Class<?> callerClass, ServerSocket that, SocketAddress endpoint, int backlog) {
453+
policyManager.checkNetworkAccess(callerClass, NetworkEntitlement.LISTEN_ACTION);
454+
}
455+
456+
@Override
457+
public void check$java_net_Socket$(Class<?> callerClass, Proxy proxy) {
458+
if (proxy.type() == Proxy.Type.SOCKS || proxy.type() == Proxy.Type.HTTP) {
459+
policyManager.checkNetworkAccess(callerClass, NetworkEntitlement.CONNECT_ACTION);
460+
}
461+
}
462+
463+
@Override
464+
public void check$java_net_Socket$(Class<?> callerClass, String host, int port) {
465+
policyManager.checkNetworkAccess(callerClass, NetworkEntitlement.LISTEN_ACTION | NetworkEntitlement.CONNECT_ACTION);
466+
}
467+
468+
@Override
469+
public void check$java_net_Socket$(Class<?> callerClass, InetAddress address, int port) {
470+
policyManager.checkNetworkAccess(callerClass, NetworkEntitlement.LISTEN_ACTION | NetworkEntitlement.CONNECT_ACTION);
471+
}
472+
473+
@Override
474+
public void check$java_net_Socket$(Class<?> callerClass, String host, int port, InetAddress localAddr, int localPort) {
475+
policyManager.checkNetworkAccess(callerClass, NetworkEntitlement.LISTEN_ACTION | NetworkEntitlement.CONNECT_ACTION);
476+
}
477+
478+
@Override
479+
public void check$java_net_Socket$(Class<?> callerClass, InetAddress address, int port, InetAddress localAddr, int localPort) {
480+
policyManager.checkNetworkAccess(callerClass, NetworkEntitlement.LISTEN_ACTION | NetworkEntitlement.CONNECT_ACTION);
481+
}
482+
483+
@Override
484+
public void check$java_net_Socket$(Class<?> callerClass, String host, int port, boolean stream) {
485+
policyManager.checkNetworkAccess(callerClass, NetworkEntitlement.LISTEN_ACTION | NetworkEntitlement.CONNECT_ACTION);
486+
}
487+
488+
@Override
489+
public void check$java_net_Socket$(Class<?> callerClass, InetAddress host, int port, boolean stream) {
490+
policyManager.checkNetworkAccess(callerClass, NetworkEntitlement.LISTEN_ACTION | NetworkEntitlement.CONNECT_ACTION);
491+
}
492+
493+
@Override
494+
public void check$java_net_Socket$bind(Class<?> callerClass, Socket that, SocketAddress endpoint) {
495+
policyManager.checkNetworkAccess(callerClass, NetworkEntitlement.LISTEN_ACTION);
496+
}
497+
498+
@Override
499+
public void check$java_net_Socket$connect(Class<?> callerClass, Socket that, SocketAddress endpoint) {
500+
policyManager.checkNetworkAccess(callerClass, NetworkEntitlement.CONNECT_ACTION);
501+
}
502+
503+
@Override
504+
public void check$java_net_Socket$connect(Class<?> callerClass, Socket that, SocketAddress endpoint, int backlog) {
505+
policyManager.checkNetworkAccess(callerClass, NetworkEntitlement.CONNECT_ACTION);
506+
}
417507
}

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,11 @@ public NetworkEntitlement(List<String> actionsList) {
5858
this.actions = actionsInt;
5959
}
6060

61-
public static Object printActions(int actions) {
61+
public NetworkEntitlement(int actions) {
62+
this.actions = actions;
63+
}
64+
65+
public static String printActions(int actions) {
6266
var joiner = new StringJoiner(",");
6367
for (var entry : ACTION_MAP.entrySet()) {
6468
var action = entry.getValue();

0 commit comments

Comments
 (0)