mirror of
https://github.com/elastic/elasticsearch.git
synced 2025-04-25 07:37:19 -04:00
[Entitlements] Network entitlement classes + Datagram socket check functions (#119735)
This commit is contained in:
parent
5123b948a9
commit
1be9253779
11 changed files with 485 additions and 73 deletions
|
@ -13,10 +13,16 @@ import java.io.InputStream;
|
|||
import java.io.PrintStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.ContentHandlerFactory;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.DatagramSocketImplFactory;
|
||||
import java.net.FileNameMap;
|
||||
import java.net.InetAddress;
|
||||
import java.net.MulticastSocket;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.ProxySelector;
|
||||
import java.net.ResponseCache;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.SocketImplFactory;
|
||||
import java.net.URL;
|
||||
import java.net.URLStreamHandler;
|
||||
|
@ -189,4 +195,28 @@ public interface EntitlementChecker {
|
|||
|
||||
// The only implementation of SSLSession#getSessionContext(); unfortunately it's an interface, so we need to check the implementation
|
||||
void check$sun_security_ssl_SSLSessionImpl$getSessionContext(Class<?> callerClass, SSLSession sslSession);
|
||||
|
||||
void check$java_net_DatagramSocket$bind(Class<?> callerClass, DatagramSocket that, SocketAddress addr);
|
||||
|
||||
void check$java_net_DatagramSocket$connect(Class<?> callerClass, DatagramSocket that, InetAddress addr);
|
||||
|
||||
void check$java_net_DatagramSocket$connect(Class<?> callerClass, DatagramSocket that, SocketAddress addr);
|
||||
|
||||
void check$java_net_DatagramSocket$send(Class<?> callerClass, DatagramSocket that, DatagramPacket p);
|
||||
|
||||
void check$java_net_DatagramSocket$receive(Class<?> callerClass, DatagramSocket that, DatagramPacket p);
|
||||
|
||||
void check$java_net_DatagramSocket$joinGroup(Class<?> callerClass, DatagramSocket that, SocketAddress addr, NetworkInterface ni);
|
||||
|
||||
void check$java_net_DatagramSocket$leaveGroup(Class<?> callerClass, DatagramSocket that, SocketAddress addr, NetworkInterface ni);
|
||||
|
||||
void check$java_net_MulticastSocket$joinGroup(Class<?> callerClass, MulticastSocket that, InetAddress addr);
|
||||
|
||||
void check$java_net_MulticastSocket$joinGroup(Class<?> callerClass, MulticastSocket that, SocketAddress addr, NetworkInterface ni);
|
||||
|
||||
void check$java_net_MulticastSocket$leaveGroup(Class<?> callerClass, MulticastSocket that, InetAddress addr);
|
||||
|
||||
void check$java_net_MulticastSocket$leaveGroup(Class<?> callerClass, MulticastSocket that, SocketAddress addr, NetworkInterface ni);
|
||||
|
||||
void check$java_net_MulticastSocket$send(Class<?> callerClass, MulticastSocket that, DatagramPacket p, byte ttl);
|
||||
}
|
||||
|
|
|
@ -9,8 +9,15 @@
|
|||
|
||||
package org.elasticsearch.entitlement.qa.common;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.DatagramSocketImpl;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.SocketException;
|
||||
import java.security.cert.Certificate;
|
||||
import java.text.BreakIterator;
|
||||
import java.text.Collator;
|
||||
|
@ -327,8 +334,77 @@ class DummyImplementations {
|
|||
}
|
||||
}
|
||||
|
||||
static class DummyDatagramSocket extends DatagramSocket {
|
||||
DummyDatagramSocket() throws SocketException {
|
||||
super(new DatagramSocketImpl() {
|
||||
@Override
|
||||
protected void create() throws SocketException {}
|
||||
|
||||
@Override
|
||||
protected void bind(int lport, InetAddress laddr) throws SocketException {}
|
||||
|
||||
@Override
|
||||
protected void send(DatagramPacket p) throws IOException {}
|
||||
|
||||
@Override
|
||||
protected int peek(InetAddress i) throws IOException {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int peekData(DatagramPacket p) throws IOException {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void receive(DatagramPacket p) throws IOException {}
|
||||
|
||||
@Override
|
||||
protected void setTTL(byte ttl) throws IOException {}
|
||||
|
||||
@Override
|
||||
protected byte getTTL() throws IOException {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setTimeToLive(int ttl) throws IOException {}
|
||||
|
||||
@Override
|
||||
protected int getTimeToLive() throws IOException {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void join(InetAddress inetaddr) throws IOException {}
|
||||
|
||||
@Override
|
||||
protected void leave(InetAddress inetaddr) throws IOException {}
|
||||
|
||||
@Override
|
||||
protected void joinGroup(SocketAddress mcastaddr, NetworkInterface netIf) throws IOException {}
|
||||
|
||||
@Override
|
||||
protected void leaveGroup(SocketAddress mcastaddr, NetworkInterface netIf) throws IOException {}
|
||||
|
||||
@Override
|
||||
protected void close() {}
|
||||
|
||||
@Override
|
||||
public void setOption(int optID, Object value) throws SocketException {}
|
||||
|
||||
@Override
|
||||
public Object getOption(int optID) throws SocketException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void connect(InetAddress address, int port) throws SocketException {}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static RuntimeException unexpected() {
|
||||
return new IllegalStateException("This method isn't supposed to be called");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ package org.elasticsearch.entitlement.qa.common;
|
|||
|
||||
import org.elasticsearch.client.internal.node.NodeClient;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.core.CheckedRunnable;
|
||||
import org.elasticsearch.core.SuppressForbidden;
|
||||
import org.elasticsearch.entitlement.qa.common.DummyImplementations.DummyBreakIteratorProvider;
|
||||
import org.elasticsearch.entitlement.qa.common.DummyImplementations.DummyCalendarDataProvider;
|
||||
|
@ -32,14 +33,18 @@ import org.elasticsearch.rest.RestResponse;
|
|||
import org.elasticsearch.rest.RestStatus;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.ProxySelector;
|
||||
import java.net.ResponseCache;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.net.URLConnection;
|
||||
|
@ -71,20 +76,20 @@ public class RestEntitlementsCheckAction extends BaseRestHandler {
|
|||
public static final Thread NO_OP_SHUTDOWN_HOOK = new Thread(() -> {}, "Shutdown hook for testing");
|
||||
private final String prefix;
|
||||
|
||||
record CheckAction(Runnable action, boolean isAlwaysDeniedToPlugins) {
|
||||
record CheckAction(CheckedRunnable<Exception> action, boolean isAlwaysDeniedToPlugins) {
|
||||
/**
|
||||
* These cannot be granted to plugins, so our test plugins cannot test the "allowed" case.
|
||||
* Used both for always-denied entitlements as well as those granted only to the server itself.
|
||||
*/
|
||||
static CheckAction deniedToPlugins(Runnable action) {
|
||||
static CheckAction deniedToPlugins(CheckedRunnable<Exception> action) {
|
||||
return new CheckAction(action, true);
|
||||
}
|
||||
|
||||
static CheckAction forPlugins(Runnable action) {
|
||||
static CheckAction forPlugins(CheckedRunnable<Exception> action) {
|
||||
return new CheckAction(action, false);
|
||||
}
|
||||
|
||||
static CheckAction alwaysDenied(Runnable action) {
|
||||
static CheckAction alwaysDenied(CheckedRunnable<Exception> action) {
|
||||
return new CheckAction(action, true);
|
||||
}
|
||||
}
|
||||
|
@ -142,7 +147,13 @@ public class RestEntitlementsCheckAction extends BaseRestHandler {
|
|||
entry("createURLStreamHandlerProvider", alwaysDenied(RestEntitlementsCheckAction::createURLStreamHandlerProvider)),
|
||||
entry("createURLWithURLStreamHandler", alwaysDenied(RestEntitlementsCheckAction::createURLWithURLStreamHandler)),
|
||||
entry("createURLWithURLStreamHandler2", alwaysDenied(RestEntitlementsCheckAction::createURLWithURLStreamHandler2)),
|
||||
entry("sslSessionImpl_getSessionContext", alwaysDenied(RestEntitlementsCheckAction::sslSessionImplGetSessionContext))
|
||||
entry("sslSessionImpl_getSessionContext", alwaysDenied(RestEntitlementsCheckAction::sslSessionImplGetSessionContext)),
|
||||
entry("datagram_socket_bind", forPlugins(RestEntitlementsCheckAction::bindDatagramSocket)),
|
||||
entry("datagram_socket_connect", forPlugins(RestEntitlementsCheckAction::connectDatagramSocket)),
|
||||
entry("datagram_socket_send", forPlugins(RestEntitlementsCheckAction::sendDatagramSocket)),
|
||||
entry("datagram_socket_receive", forPlugins(RestEntitlementsCheckAction::receiveDatagramSocket)),
|
||||
entry("datagram_socket_join_group", forPlugins(RestEntitlementsCheckAction::joinGroupDatagramSocket)),
|
||||
entry("datagram_socket_leave_group", forPlugins(RestEntitlementsCheckAction::leaveGroupDatagramSocket))
|
||||
);
|
||||
|
||||
private static void createURLStreamHandlerProvider() {
|
||||
|
@ -154,43 +165,33 @@ public class RestEntitlementsCheckAction extends BaseRestHandler {
|
|||
};
|
||||
}
|
||||
|
||||
private static void sslSessionImplGetSessionContext() {
|
||||
private static void sslSessionImplGetSessionContext() throws IOException {
|
||||
SSLSocketFactory factory = HttpsURLConnection.getDefaultSSLSocketFactory();
|
||||
try (SSLSocket socket = (SSLSocket) factory.createSocket()) {
|
||||
SSLSession session = socket.getSession();
|
||||
|
||||
session.getSessionContext();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private static void createURLWithURLStreamHandler() {
|
||||
try {
|
||||
var x = new URL("http", "host", 1234, "file", new URLStreamHandler() {
|
||||
@Override
|
||||
protected URLConnection openConnection(URL u) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
} catch (MalformedURLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
private static void createURLWithURLStreamHandler() throws MalformedURLException {
|
||||
var x = new URL("http", "host", 1234, "file", new URLStreamHandler() {
|
||||
@Override
|
||||
protected URLConnection openConnection(URL u) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private static void createURLWithURLStreamHandler2() {
|
||||
try {
|
||||
var x = new URL(null, "spec", new URLStreamHandler() {
|
||||
@Override
|
||||
protected URLConnection openConnection(URL u) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
} catch (MalformedURLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
private static void createURLWithURLStreamHandler2() throws MalformedURLException {
|
||||
var x = new URL(null, "spec", new URLStreamHandler() {
|
||||
@Override
|
||||
protected URLConnection openConnection(URL u) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void createInetAddressResolverProvider() {
|
||||
|
@ -215,12 +216,8 @@ public class RestEntitlementsCheckAction extends BaseRestHandler {
|
|||
ProxySelector.setDefault(null);
|
||||
}
|
||||
|
||||
private static void setDefaultSSLContext() {
|
||||
try {
|
||||
SSLContext.setDefault(SSLContext.getDefault());
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
private static void setDefaultSSLContext() throws NoSuchAlgorithmException {
|
||||
SSLContext.setDefault(SSLContext.getDefault());
|
||||
}
|
||||
|
||||
private static void setDefaultHostnameVerifier() {
|
||||
|
@ -246,28 +243,18 @@ public class RestEntitlementsCheckAction extends BaseRestHandler {
|
|||
System.exit(123);
|
||||
}
|
||||
|
||||
private static void createClassLoader() {
|
||||
private static void createClassLoader() throws IOException {
|
||||
try (var classLoader = new URLClassLoader("test", new URL[0], RestEntitlementsCheckAction.class.getClassLoader())) {
|
||||
logger.info("Created URLClassLoader [{}]", classLoader.getName());
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void processBuilder_start() {
|
||||
try {
|
||||
new ProcessBuilder("").start();
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
private static void processBuilder_start() throws IOException {
|
||||
new ProcessBuilder("").start();
|
||||
}
|
||||
|
||||
private static void processBuilder_startPipeline() {
|
||||
try {
|
||||
ProcessBuilder.startPipeline(List.of());
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
private static void processBuilder_startPipeline() throws IOException {
|
||||
ProcessBuilder.startPipeline(List.of());
|
||||
}
|
||||
|
||||
private static void setHttpsConnectionProperties() {
|
||||
|
@ -355,12 +342,8 @@ public class RestEntitlementsCheckAction extends BaseRestHandler {
|
|||
|
||||
@SuppressWarnings("deprecation")
|
||||
@SuppressForbidden(reason = "We're required to prevent calls to this forbidden API")
|
||||
private static void datagramSocket$$setDatagramSocketImplFactory() {
|
||||
try {
|
||||
DatagramSocket.setDatagramSocketImplFactory(() -> { throw new IllegalStateException(); });
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
private static void datagramSocket$$setDatagramSocketImplFactory() throws IOException {
|
||||
DatagramSocket.setDatagramSocketImplFactory(() -> { throw new IllegalStateException(); });
|
||||
}
|
||||
|
||||
private static void httpURLConnection$$setFollowRedirects() {
|
||||
|
@ -369,22 +352,14 @@ public class RestEntitlementsCheckAction extends BaseRestHandler {
|
|||
|
||||
@SuppressWarnings("deprecation")
|
||||
@SuppressForbidden(reason = "We're required to prevent calls to this forbidden API")
|
||||
private static void serverSocket$$setSocketFactory() {
|
||||
try {
|
||||
ServerSocket.setSocketFactory(() -> { throw new IllegalStateException(); });
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
private static void serverSocket$$setSocketFactory() throws IOException {
|
||||
ServerSocket.setSocketFactory(() -> { throw new IllegalStateException(); });
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@SuppressForbidden(reason = "We're required to prevent calls to this forbidden API")
|
||||
private static void socket$$setSocketImplFactory() {
|
||||
try {
|
||||
Socket.setSocketImplFactory(() -> { throw new IllegalStateException(); });
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
private static void socket$$setSocketImplFactory() throws IOException {
|
||||
Socket.setSocketImplFactory(() -> { throw new IllegalStateException(); });
|
||||
}
|
||||
|
||||
private static void url$$setURLStreamHandlerFactory() {
|
||||
|
@ -399,6 +374,51 @@ public class RestEntitlementsCheckAction extends BaseRestHandler {
|
|||
URLConnection.setContentHandlerFactory(__ -> { throw new IllegalStateException(); });
|
||||
}
|
||||
|
||||
private static void bindDatagramSocket() throws SocketException {
|
||||
try (var socket = new DatagramSocket(null)) {
|
||||
socket.bind(null);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressForbidden(reason = "testing entitlements")
|
||||
private static void connectDatagramSocket() throws SocketException {
|
||||
try (var socket = new DummyImplementations.DummyDatagramSocket()) {
|
||||
socket.connect(new InetSocketAddress(1234));
|
||||
}
|
||||
}
|
||||
|
||||
private static void joinGroupDatagramSocket() throws IOException {
|
||||
try (var socket = new DummyImplementations.DummyDatagramSocket()) {
|
||||
socket.joinGroup(
|
||||
new InetSocketAddress(InetAddress.getByAddress(new byte[] { (byte) 230, 0, 0, 1 }), 1234),
|
||||
NetworkInterface.getByIndex(0)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static void leaveGroupDatagramSocket() throws IOException {
|
||||
try (var socket = new DummyImplementations.DummyDatagramSocket()) {
|
||||
socket.leaveGroup(
|
||||
new InetSocketAddress(InetAddress.getByAddress(new byte[] { (byte) 230, 0, 0, 1 }), 1234),
|
||||
NetworkInterface.getByIndex(0)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressForbidden(reason = "testing entitlements")
|
||||
private static void sendDatagramSocket() throws IOException {
|
||||
try (var socket = new DummyImplementations.DummyDatagramSocket()) {
|
||||
socket.send(new DatagramPacket(new byte[] { 0 }, 1, InetAddress.getLocalHost(), 1234));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressForbidden(reason = "testing entitlements")
|
||||
private static void receiveDatagramSocket() throws IOException {
|
||||
try (var socket = new DummyImplementations.DummyDatagramSocket()) {
|
||||
socket.receive(new DatagramPacket(new byte[1], 1, InetAddress.getLocalHost(), 1234));
|
||||
}
|
||||
}
|
||||
|
||||
public RestEntitlementsCheckAction(String prefix) {
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
ALL-UNNAMED:
|
||||
- create_class_loader
|
||||
- set_https_connection_properties
|
||||
- network:
|
||||
actions:
|
||||
- listen
|
||||
- accept
|
||||
- connect
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
org.elasticsearch.entitlement.qa.common:
|
||||
- create_class_loader
|
||||
- set_https_connection_properties
|
||||
- network:
|
||||
actions:
|
||||
- listen
|
||||
- accept
|
||||
- connect
|
||||
|
|
|
@ -10,16 +10,23 @@
|
|||
package org.elasticsearch.entitlement.runtime.api;
|
||||
|
||||
import org.elasticsearch.entitlement.bridge.EntitlementChecker;
|
||||
import org.elasticsearch.entitlement.runtime.policy.NetworkEntitlement;
|
||||
import org.elasticsearch.entitlement.runtime.policy.PolicyManager;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.ContentHandlerFactory;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.DatagramSocketImplFactory;
|
||||
import java.net.FileNameMap;
|
||||
import java.net.InetAddress;
|
||||
import java.net.MulticastSocket;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.ProxySelector;
|
||||
import java.net.ResponseCache;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.SocketImplFactory;
|
||||
import java.net.URL;
|
||||
import java.net.URLStreamHandler;
|
||||
|
@ -349,4 +356,68 @@ public class ElasticsearchEntitlementChecker implements EntitlementChecker {
|
|||
public void check$sun_security_ssl_SSLSessionImpl$getSessionContext(Class<?> callerClass, SSLSession sslSession) {
|
||||
policyManager.checkReadSensitiveNetworkInformation(callerClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void check$java_net_DatagramSocket$bind(Class<?> callerClass, DatagramSocket that, SocketAddress addr) {
|
||||
policyManager.checkNetworkAccess(callerClass, NetworkEntitlement.LISTEN_ACTION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void check$java_net_DatagramSocket$connect(Class<?> callerClass, DatagramSocket that, InetAddress addr) {
|
||||
policyManager.checkNetworkAccess(callerClass, NetworkEntitlement.CONNECT_ACTION | NetworkEntitlement.ACCEPT_ACTION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void check$java_net_DatagramSocket$connect(Class<?> callerClass, DatagramSocket that, SocketAddress addr) {
|
||||
policyManager.checkNetworkAccess(callerClass, NetworkEntitlement.CONNECT_ACTION | NetworkEntitlement.ACCEPT_ACTION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void check$java_net_DatagramSocket$send(Class<?> callerClass, DatagramSocket that, DatagramPacket p) {
|
||||
var actions = NetworkEntitlement.CONNECT_ACTION;
|
||||
if (p.getAddress().isMulticastAddress()) {
|
||||
actions |= NetworkEntitlement.ACCEPT_ACTION;
|
||||
}
|
||||
policyManager.checkNetworkAccess(callerClass, actions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void check$java_net_DatagramSocket$receive(Class<?> callerClass, DatagramSocket that, DatagramPacket p) {
|
||||
policyManager.checkNetworkAccess(callerClass, NetworkEntitlement.ACCEPT_ACTION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void check$java_net_DatagramSocket$joinGroup(Class<?> caller, DatagramSocket that, SocketAddress addr, NetworkInterface ni) {
|
||||
policyManager.checkNetworkAccess(caller, NetworkEntitlement.CONNECT_ACTION | NetworkEntitlement.ACCEPT_ACTION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void check$java_net_DatagramSocket$leaveGroup(Class<?> caller, DatagramSocket that, SocketAddress addr, NetworkInterface ni) {
|
||||
policyManager.checkNetworkAccess(caller, NetworkEntitlement.CONNECT_ACTION | NetworkEntitlement.ACCEPT_ACTION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void check$java_net_MulticastSocket$joinGroup(Class<?> callerClass, MulticastSocket that, InetAddress addr) {
|
||||
policyManager.checkNetworkAccess(callerClass, NetworkEntitlement.CONNECT_ACTION | NetworkEntitlement.ACCEPT_ACTION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void check$java_net_MulticastSocket$joinGroup(Class<?> caller, MulticastSocket that, SocketAddress addr, NetworkInterface ni) {
|
||||
policyManager.checkNetworkAccess(caller, NetworkEntitlement.CONNECT_ACTION | NetworkEntitlement.ACCEPT_ACTION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void check$java_net_MulticastSocket$leaveGroup(Class<?> caller, MulticastSocket that, InetAddress addr) {
|
||||
policyManager.checkNetworkAccess(caller, NetworkEntitlement.CONNECT_ACTION | NetworkEntitlement.ACCEPT_ACTION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void check$java_net_MulticastSocket$leaveGroup(Class<?> caller, MulticastSocket that, SocketAddress addr, NetworkInterface ni) {
|
||||
policyManager.checkNetworkAccess(caller, NetworkEntitlement.CONNECT_ACTION | NetworkEntitlement.ACCEPT_ACTION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void check$java_net_MulticastSocket$send(Class<?> callerClass, MulticastSocket that, DatagramPacket p, byte ttl) {
|
||||
policyManager.checkNetworkAccess(callerClass, NetworkEntitlement.CONNECT_ACTION | NetworkEntitlement.ACCEPT_ACTION);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* 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.runtime.policy;
|
||||
|
||||
import org.elasticsearch.core.Strings;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
import static java.util.Map.entry;
|
||||
|
||||
/**
|
||||
* Describes a network entitlement (sockets) with actions.
|
||||
*/
|
||||
public class NetworkEntitlement implements Entitlement {
|
||||
|
||||
public static final int LISTEN_ACTION = 0x1;
|
||||
public static final int CONNECT_ACTION = 0x2;
|
||||
public static final int ACCEPT_ACTION = 0x4;
|
||||
|
||||
static final String LISTEN = "listen";
|
||||
static final String CONNECT = "connect";
|
||||
static final String ACCEPT = "accept";
|
||||
|
||||
private static final Map<String, Integer> ACTION_MAP = Map.ofEntries(
|
||||
entry(LISTEN, LISTEN_ACTION),
|
||||
entry(CONNECT, CONNECT_ACTION),
|
||||
entry(ACCEPT, ACCEPT_ACTION)
|
||||
);
|
||||
|
||||
private final int actions;
|
||||
|
||||
@ExternalEntitlement(parameterNames = { "actions" }, esModulesOnly = false)
|
||||
public NetworkEntitlement(List<String> actionsList) {
|
||||
|
||||
int actionsInt = 0;
|
||||
|
||||
for (String actionString : actionsList) {
|
||||
var action = ACTION_MAP.get(actionString);
|
||||
if (action == null) {
|
||||
throw new IllegalArgumentException("unknown network action [" + actionString + "]");
|
||||
}
|
||||
if ((actionsInt & action) == action) {
|
||||
throw new IllegalArgumentException(Strings.format("network action [%s] specified multiple times", actionString));
|
||||
}
|
||||
actionsInt |= action;
|
||||
}
|
||||
|
||||
this.actions = actionsInt;
|
||||
}
|
||||
|
||||
public static Object printActions(int actions) {
|
||||
var joiner = new StringJoiner(",");
|
||||
for (var entry : ACTION_MAP.entrySet()) {
|
||||
var action = entry.getValue();
|
||||
if ((actions & action) == action) {
|
||||
joiner.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
return joiner.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* For the actions to match, the actions present in this entitlement must be a superset
|
||||
* of the actions required by a check.
|
||||
* There is only one "negative" case (action required by the check but not present in the entitlement),
|
||||
* and it can be expressed efficiently via this truth table:
|
||||
* this.actions | requiredActions |
|
||||
* 0 | 0 | 0
|
||||
* 0 | 1 | 1 --> NOT this.action AND requiredActions
|
||||
* 1 | 0 | 0
|
||||
* 1 | 1 | 0
|
||||
*
|
||||
* @param requiredActions the actions required to be present for a check to pass
|
||||
* @return true if requiredActions are present, false otherwise
|
||||
*/
|
||||
public boolean matchActions(int requiredActions) {
|
||||
return (~this.actions & requiredActions) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
NetworkEntitlement that = (NetworkEntitlement) o;
|
||||
return actions == that.actions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(actions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NetworkEntitlement{actions=" + actions + '}';
|
||||
}
|
||||
}
|
|
@ -52,7 +52,11 @@ public class PolicyManager {
|
|||
}
|
||||
|
||||
public <E extends Entitlement> Stream<E> getEntitlements(Class<E> entitlementClass) {
|
||||
return entitlementsByType.get(entitlementClass).stream().map(entitlementClass::cast);
|
||||
var entitlements = entitlementsByType.get(entitlementClass);
|
||||
if (entitlements == null) {
|
||||
return Stream.empty();
|
||||
}
|
||||
return entitlements.stream().map(entitlementClass::cast);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -190,6 +194,34 @@ public class PolicyManager {
|
|||
return methodName.substring(methodName.indexOf('$'));
|
||||
}
|
||||
|
||||
public void checkNetworkAccess(Class<?> callerClass, int actions) {
|
||||
var requestingClass = requestingClass(callerClass);
|
||||
if (isTriviallyAllowed(requestingClass)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ModuleEntitlements entitlements = getEntitlements(requestingClass);
|
||||
if (entitlements.getEntitlements(NetworkEntitlement.class).anyMatch(n -> n.matchActions(actions))) {
|
||||
logger.debug(
|
||||
() -> Strings.format(
|
||||
"Entitled: class [%s], module [%s], entitlement [Network], actions [Ox%X]",
|
||||
requestingClass,
|
||||
requestingClass.getModule().getName(),
|
||||
actions
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
throw new NotEntitledException(
|
||||
Strings.format(
|
||||
"Missing entitlement: class [%s], module [%s], entitlement [Network], actions [%s]",
|
||||
requestingClass,
|
||||
requestingClass.getModule().getName(),
|
||||
NetworkEntitlement.printActions(actions)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private void checkEntitlementPresent(Class<?> callerClass, Class<? extends Entitlement> entitlementClass) {
|
||||
var requestingClass = requestingClass(callerClass);
|
||||
if (isTriviallyAllowed(requestingClass)) {
|
||||
|
|
|
@ -37,7 +37,8 @@ public class PolicyParser {
|
|||
private static final Map<String, Class<?>> EXTERNAL_ENTITLEMENTS = Stream.of(
|
||||
FileEntitlement.class,
|
||||
CreateClassLoaderEntitlement.class,
|
||||
SetHttpsConnectionPropertiesEntitlement.class
|
||||
SetHttpsConnectionPropertiesEntitlement.class,
|
||||
NetworkEntitlement.class
|
||||
).collect(Collectors.toUnmodifiableMap(PolicyParser::getEntitlementTypeName, Function.identity()));
|
||||
|
||||
protected final XContentParser policyParser;
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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.runtime.policy;
|
||||
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
public class NetworkEntitlementTests extends ESTestCase {
|
||||
|
||||
public void testMatchesActions() {
|
||||
var listenEntitlement = new NetworkEntitlement(List.of(NetworkEntitlement.LISTEN));
|
||||
var emptyEntitlement = new NetworkEntitlement(List.of());
|
||||
var connectAcceptEntitlement = new NetworkEntitlement(List.of(NetworkEntitlement.CONNECT, NetworkEntitlement.ACCEPT));
|
||||
|
||||
assertThat(listenEntitlement.matchActions(0), is(true));
|
||||
assertThat(listenEntitlement.matchActions(NetworkEntitlement.LISTEN_ACTION), is(true));
|
||||
assertThat(listenEntitlement.matchActions(NetworkEntitlement.ACCEPT_ACTION), is(false));
|
||||
assertThat(listenEntitlement.matchActions(NetworkEntitlement.CONNECT_ACTION), is(false));
|
||||
assertThat(listenEntitlement.matchActions(NetworkEntitlement.LISTEN_ACTION | NetworkEntitlement.ACCEPT_ACTION), is(false));
|
||||
assertThat(listenEntitlement.matchActions(NetworkEntitlement.LISTEN_ACTION | NetworkEntitlement.CONNECT_ACTION), is(false));
|
||||
assertThat(listenEntitlement.matchActions(NetworkEntitlement.CONNECT_ACTION | NetworkEntitlement.ACCEPT_ACTION), is(false));
|
||||
|
||||
assertThat(connectAcceptEntitlement.matchActions(0), is(true));
|
||||
assertThat(connectAcceptEntitlement.matchActions(NetworkEntitlement.LISTEN_ACTION), is(false));
|
||||
assertThat(connectAcceptEntitlement.matchActions(NetworkEntitlement.ACCEPT_ACTION), is(true));
|
||||
assertThat(connectAcceptEntitlement.matchActions(NetworkEntitlement.CONNECT_ACTION), is(true));
|
||||
assertThat(connectAcceptEntitlement.matchActions(NetworkEntitlement.LISTEN_ACTION | NetworkEntitlement.ACCEPT_ACTION), is(false));
|
||||
assertThat(connectAcceptEntitlement.matchActions(NetworkEntitlement.LISTEN_ACTION | NetworkEntitlement.CONNECT_ACTION), is(false));
|
||||
assertThat(connectAcceptEntitlement.matchActions(NetworkEntitlement.CONNECT_ACTION | NetworkEntitlement.ACCEPT_ACTION), is(true));
|
||||
|
||||
assertThat(emptyEntitlement.matchActions(0), is(true));
|
||||
assertThat(emptyEntitlement.matchActions(NetworkEntitlement.LISTEN_ACTION), is(false));
|
||||
assertThat(emptyEntitlement.matchActions(NetworkEntitlement.ACCEPT_ACTION), is(false));
|
||||
assertThat(emptyEntitlement.matchActions(NetworkEntitlement.CONNECT_ACTION), is(false));
|
||||
assertThat(emptyEntitlement.matchActions(NetworkEntitlement.LISTEN_ACTION | NetworkEntitlement.ACCEPT_ACTION), is(false));
|
||||
assertThat(emptyEntitlement.matchActions(NetworkEntitlement.LISTEN_ACTION | NetworkEntitlement.CONNECT_ACTION), is(false));
|
||||
assertThat(emptyEntitlement.matchActions(NetworkEntitlement.CONNECT_ACTION | NetworkEntitlement.ACCEPT_ACTION), is(false));
|
||||
}
|
||||
}
|
|
@ -52,6 +52,22 @@ public class PolicyParserTests extends ESTestCase {
|
|||
assertEquals(expected, parsedPolicy);
|
||||
}
|
||||
|
||||
public void testParseNetwork() throws IOException {
|
||||
Policy parsedPolicy = new PolicyParser(new ByteArrayInputStream("""
|
||||
entitlement-module-name:
|
||||
- network:
|
||||
actions:
|
||||
- listen
|
||||
- accept
|
||||
- connect
|
||||
""".getBytes(StandardCharsets.UTF_8)), "test-policy.yaml", false).parsePolicy();
|
||||
Policy expected = new Policy(
|
||||
"test-policy.yaml",
|
||||
List.of(new Scope("entitlement-module-name", List.of(new NetworkEntitlement(List.of("listen", "accept", "connect")))))
|
||||
);
|
||||
assertEquals(expected, parsedPolicy);
|
||||
}
|
||||
|
||||
public void testParseCreateClassloader() throws IOException {
|
||||
Policy parsedPolicy = new PolicyParser(new ByteArrayInputStream("""
|
||||
entitlement-module-name:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue