[Entitlements] Network entitlement classes + Datagram socket check functions (#119735)

This commit is contained in:
Lorenzo Dematté 2025-01-14 09:08:15 +01:00 committed by GitHub
parent 5123b948a9
commit 1be9253779
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 485 additions and 73 deletions

View file

@ -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);
}

View file

@ -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");
}
}

View file

@ -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 {
private static void createURLWithURLStreamHandler() throws MalformedURLException {
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);
}
}
@SuppressWarnings("deprecation")
private static void createURLWithURLStreamHandler2() {
try {
private static void createURLWithURLStreamHandler2() throws MalformedURLException {
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 createInetAddressResolverProvider() {
@ -215,12 +216,8 @@ public class RestEntitlementsCheckAction extends BaseRestHandler {
ProxySelector.setDefault(null);
}
private static void setDefaultSSLContext() {
try {
private static void setDefaultSSLContext() throws NoSuchAlgorithmException {
SSLContext.setDefault(SSLContext.getDefault());
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
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 {
private static void processBuilder_start() throws IOException {
new ProcessBuilder("").start();
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
private static void processBuilder_startPipeline() {
try {
private static void processBuilder_startPipeline() throws IOException {
ProcessBuilder.startPipeline(List.of());
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
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 {
private static void datagramSocket$$setDatagramSocketImplFactory() throws IOException {
DatagramSocket.setDatagramSocketImplFactory(() -> { throw new IllegalStateException(); });
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
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 {
private static void serverSocket$$setSocketFactory() throws IOException {
ServerSocket.setSocketFactory(() -> { throw new IllegalStateException(); });
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
@SuppressWarnings("deprecation")
@SuppressForbidden(reason = "We're required to prevent calls to this forbidden API")
private static void socket$$setSocketImplFactory() {
try {
private static void socket$$setSocketImplFactory() throws IOException {
Socket.setSocketImplFactory(() -> { throw new IllegalStateException(); });
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
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;
}

View file

@ -1,3 +1,8 @@
ALL-UNNAMED:
- create_class_loader
- set_https_connection_properties
- network:
actions:
- listen
- accept
- connect

View file

@ -1,3 +1,8 @@
org.elasticsearch.entitlement.qa.common:
- create_class_loader
- set_https_connection_properties
- network:
actions:
- listen
- accept
- connect

View file

@ -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);
}
}

View file

@ -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 + '}';
}
}

View file

@ -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)) {

View file

@ -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;

View file

@ -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));
}
}

View file

@ -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: