mirror of
https://github.com/elastic/elasticsearch.git
synced 2025-04-24 23:27:25 -04:00
[Entitlements] Add set_https_connection_properties
entitlement and checks (#118577)
This commit is contained in:
parent
9862a43cb6
commit
5df57fda72
21 changed files with 265 additions and 8 deletions
|
@ -13,6 +13,11 @@ import java.net.URL;
|
|||
import java.net.URLStreamHandlerFactory;
|
||||
import java.util.List;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
@SuppressWarnings("unused") // Called from instrumentation code inserted by the Entitlements agent
|
||||
public interface EntitlementChecker {
|
||||
|
||||
|
@ -21,7 +26,7 @@ public interface EntitlementChecker {
|
|||
|
||||
void check$java_lang_Runtime$halt(Class<?> callerClass, Runtime runtime, int status);
|
||||
|
||||
// URLClassLoader ctor
|
||||
// URLClassLoader constructors
|
||||
void check$java_net_URLClassLoader$(Class<?> callerClass, URL[] urls);
|
||||
|
||||
void check$java_net_URLClassLoader$(Class<?> callerClass, URL[] urls, ClassLoader parent);
|
||||
|
@ -32,6 +37,15 @@ public interface EntitlementChecker {
|
|||
|
||||
void check$java_net_URLClassLoader$(Class<?> callerClass, String name, URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory);
|
||||
|
||||
// "setFactory" methods
|
||||
void check$javax_net_ssl_HttpsURLConnection$setSSLSocketFactory(Class<?> callerClass, HttpsURLConnection conn, SSLSocketFactory sf);
|
||||
|
||||
void check$javax_net_ssl_HttpsURLConnection$$setDefaultSSLSocketFactory(Class<?> callerClass, SSLSocketFactory sf);
|
||||
|
||||
void check$javax_net_ssl_HttpsURLConnection$$setDefaultHostnameVerifier(Class<?> callerClass, HostnameVerifier hv);
|
||||
|
||||
void check$javax_net_ssl_SSLContext$$setDefault(Class<?> callerClass, SSLContext context);
|
||||
|
||||
// Process creation
|
||||
void check$java_lang_ProcessBuilder$start(Class<?> callerClass, ProcessBuilder that);
|
||||
|
||||
|
|
|
@ -23,12 +23,17 @@ import java.io.IOException;
|
|||
import java.io.UncheckedIOException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
||||
import static java.util.Map.entry;
|
||||
import static org.elasticsearch.entitlement.qa.common.RestEntitlementsCheckAction.CheckAction.alwaysDenied;
|
||||
import static org.elasticsearch.entitlement.qa.common.RestEntitlementsCheckAction.CheckAction.deniedToPlugins;
|
||||
import static org.elasticsearch.entitlement.qa.common.RestEntitlementsCheckAction.CheckAction.forPlugins;
|
||||
import static org.elasticsearch.rest.RestRequest.Method.GET;
|
||||
|
@ -49,6 +54,10 @@ public class RestEntitlementsCheckAction extends BaseRestHandler {
|
|||
static CheckAction forPlugins(Runnable action) {
|
||||
return new CheckAction(action, false);
|
||||
}
|
||||
|
||||
static CheckAction alwaysDenied(Runnable action) {
|
||||
return new CheckAction(action, true);
|
||||
}
|
||||
}
|
||||
|
||||
private static final Map<String, CheckAction> checkActions = Map.ofEntries(
|
||||
|
@ -56,9 +65,32 @@ public class RestEntitlementsCheckAction extends BaseRestHandler {
|
|||
entry("runtime_halt", deniedToPlugins(RestEntitlementsCheckAction::runtimeHalt)),
|
||||
entry("create_classloader", forPlugins(RestEntitlementsCheckAction::createClassLoader)),
|
||||
entry("processBuilder_start", deniedToPlugins(RestEntitlementsCheckAction::processBuilder_start)),
|
||||
entry("processBuilder_startPipeline", deniedToPlugins(RestEntitlementsCheckAction::processBuilder_startPipeline))
|
||||
entry("processBuilder_startPipeline", deniedToPlugins(RestEntitlementsCheckAction::processBuilder_startPipeline)),
|
||||
entry("set_https_connection_properties", forPlugins(RestEntitlementsCheckAction::setHttpsConnectionProperties)),
|
||||
entry("set_default_ssl_socket_factory", alwaysDenied(RestEntitlementsCheckAction::setDefaultSSLSocketFactory)),
|
||||
entry("set_default_hostname_verifier", alwaysDenied(RestEntitlementsCheckAction::setDefaultHostnameVerifier)),
|
||||
entry("set_default_ssl_context", alwaysDenied(RestEntitlementsCheckAction::setDefaultSSLContext))
|
||||
);
|
||||
|
||||
private static void setDefaultSSLContext() {
|
||||
logger.info("Calling SSLContext.setDefault");
|
||||
try {
|
||||
SSLContext.setDefault(SSLContext.getDefault());
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setDefaultHostnameVerifier() {
|
||||
logger.info("Calling HttpsURLConnection.setDefaultHostnameVerifier");
|
||||
HttpsURLConnection.setDefaultHostnameVerifier((hostname, session) -> false);
|
||||
}
|
||||
|
||||
private static void setDefaultSSLSocketFactory() {
|
||||
logger.info("Calling HttpsURLConnection.setDefaultSSLSocketFactory");
|
||||
HttpsURLConnection.setDefaultSSLSocketFactory(new TestSSLSocketFactory());
|
||||
}
|
||||
|
||||
@SuppressForbidden(reason = "Specifically testing Runtime.exit")
|
||||
private static void runtimeExit() {
|
||||
Runtime.getRuntime().exit(123);
|
||||
|
@ -93,11 +125,17 @@ public class RestEntitlementsCheckAction extends BaseRestHandler {
|
|||
}
|
||||
}
|
||||
|
||||
private static void setHttpsConnectionProperties() {
|
||||
logger.info("Calling setSSLSocketFactory");
|
||||
var connection = new TestHttpsURLConnection();
|
||||
connection.setSSLSocketFactory(new TestSSLSocketFactory());
|
||||
}
|
||||
|
||||
public RestEntitlementsCheckAction(String prefix) {
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
||||
public static Set<String> getServerAndPluginsCheckActions() {
|
||||
public static Set<String> getCheckActionsAllowedInPlugins() {
|
||||
return checkActions.entrySet()
|
||||
.stream()
|
||||
.filter(kv -> kv.getValue().isAlwaysDeniedToPlugins() == false)
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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.security.cert.Certificate;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLPeerUnverifiedException;
|
||||
|
||||
class TestHttpsURLConnection extends HttpsURLConnection {
|
||||
TestHttpsURLConnection() {
|
||||
super(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect() throws IOException {}
|
||||
|
||||
@Override
|
||||
public void disconnect() {}
|
||||
|
||||
@Override
|
||||
public boolean usingProxy() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCipherSuite() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate[] getLocalCertificates() {
|
||||
return new Certificate[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate[] getServerCertificates() throws SSLPeerUnverifiedException {
|
||||
return new Certificate[0];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
class TestSSLSocketFactory extends SSLSocketFactory {
|
||||
@Override
|
||||
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(InetAddress host, int port) throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getDefaultCipherSuites() {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getSupportedCipherSuites() {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,2 +1,3 @@
|
|||
ALL-UNNAMED:
|
||||
- create_class_loader
|
||||
- set_https_connection_properties
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
org.elasticsearch.entitlement.qa.common:
|
||||
- create_class_loader
|
||||
- set_https_connection_properties
|
||||
|
|
|
@ -46,7 +46,7 @@ public class EntitlementsAllowedIT extends ESRestTestCase {
|
|||
public static Iterable<Object[]> data() {
|
||||
return Stream.of("allowed", "allowed_nonmodular")
|
||||
.flatMap(
|
||||
path -> RestEntitlementsCheckAction.getServerAndPluginsCheckActions().stream().map(action -> new Object[] { path, action })
|
||||
path -> RestEntitlementsCheckAction.getCheckActionsAllowedInPlugins().stream().map(action -> new Object[] { path, action })
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
package org.elasticsearch.entitlement.initialization;
|
||||
|
||||
import org.elasticsearch.core.Strings;
|
||||
import org.elasticsearch.core.internal.provider.ProviderLocator;
|
||||
import org.elasticsearch.entitlement.bootstrap.EntitlementBootstrap;
|
||||
import org.elasticsearch.entitlement.bridge.EntitlementChecker;
|
||||
|
@ -120,7 +121,15 @@ public class EntitlementInitialization {
|
|||
// TODO: should this check actually be part of the parser?
|
||||
for (Scope scope : policy.scopes) {
|
||||
if (moduleNames.contains(scope.name) == false) {
|
||||
throw new IllegalStateException("policy [" + policyFile + "] contains invalid module [" + scope.name + "]");
|
||||
throw new IllegalStateException(
|
||||
Strings.format(
|
||||
"Invalid module name in policy: plugin [%s] does not have module [%s]; available modules [%s]; policy file [%s]",
|
||||
pluginName,
|
||||
scope.name,
|
||||
String.join(", ", moduleNames),
|
||||
policyFile
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
return policy;
|
||||
|
|
|
@ -16,6 +16,11 @@ import java.net.URL;
|
|||
import java.net.URLStreamHandlerFactory;
|
||||
import java.util.List;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
/**
|
||||
* Implementation of the {@link EntitlementChecker} interface, providing additional
|
||||
* API methods for managing the checks.
|
||||
|
@ -78,4 +83,28 @@ public class ElasticsearchEntitlementChecker implements EntitlementChecker {
|
|||
public void check$java_lang_ProcessBuilder$$startPipeline(Class<?> callerClass, List<ProcessBuilder> builders) {
|
||||
policyManager.checkStartProcess(callerClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void check$javax_net_ssl_HttpsURLConnection$setSSLSocketFactory(
|
||||
Class<?> callerClass,
|
||||
HttpsURLConnection connection,
|
||||
SSLSocketFactory sf
|
||||
) {
|
||||
policyManager.checkSetHttpsConnectionProperties(callerClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void check$javax_net_ssl_HttpsURLConnection$$setDefaultSSLSocketFactory(Class<?> callerClass, SSLSocketFactory sf) {
|
||||
policyManager.checkSetGlobalHttpsConnectionProperties(callerClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void check$javax_net_ssl_HttpsURLConnection$$setDefaultHostnameVerifier(Class<?> callerClass, HostnameVerifier hv) {
|
||||
policyManager.checkSetGlobalHttpsConnectionProperties(callerClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void check$javax_net_ssl_SSLContext$$setDefault(Class<?> callerClass, SSLContext context) {
|
||||
policyManager.checkSetGlobalHttpsConnectionProperties(callerClass);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -130,6 +130,14 @@ public class PolicyManager {
|
|||
checkEntitlementPresent(callerClass, CreateClassLoaderEntitlement.class);
|
||||
}
|
||||
|
||||
public void checkSetHttpsConnectionProperties(Class<?> callerClass) {
|
||||
checkEntitlementPresent(callerClass, SetHttpsConnectionPropertiesEntitlement.class);
|
||||
}
|
||||
|
||||
public void checkSetGlobalHttpsConnectionProperties(Class<?> callerClass) {
|
||||
neverEntitled(callerClass, "set global https connection properties");
|
||||
}
|
||||
|
||||
private void checkEntitlementPresent(Class<?> callerClass, Class<? extends Entitlement> entitlementClass) {
|
||||
var requestingModule = requestingModule(callerClass);
|
||||
if (isTriviallyAllowed(requestingModule)) {
|
||||
|
|
|
@ -34,8 +34,11 @@ import java.util.stream.Stream;
|
|||
*/
|
||||
public class PolicyParser {
|
||||
|
||||
private static final Map<String, Class<?>> EXTERNAL_ENTITLEMENTS = Stream.of(FileEntitlement.class, CreateClassLoaderEntitlement.class)
|
||||
.collect(Collectors.toUnmodifiableMap(PolicyParser::getEntitlementTypeName, Function.identity()));
|
||||
private static final Map<String, Class<?>> EXTERNAL_ENTITLEMENTS = Stream.of(
|
||||
FileEntitlement.class,
|
||||
CreateClassLoaderEntitlement.class,
|
||||
SetHttpsConnectionPropertiesEntitlement.class
|
||||
).collect(Collectors.toUnmodifiableMap(PolicyParser::getEntitlementTypeName, Function.identity()));
|
||||
|
||||
protected final XContentParser policyParser;
|
||||
protected final String policyName;
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* An Entitlement to allow setting properties to a single Https connection after this has been created
|
||||
*/
|
||||
public class SetHttpsConnectionPropertiesEntitlement implements Entitlement {
|
||||
@ExternalEntitlement(esModulesOnly = false)
|
||||
public SetHttpsConnectionPropertiesEntitlement() {}
|
||||
}
|
|
@ -74,4 +74,23 @@ public class PolicyParserTests extends ESTestCase {
|
|||
)
|
||||
);
|
||||
}
|
||||
|
||||
public void testParseSetHttpsConnectionProperties() throws IOException {
|
||||
Policy parsedPolicy = new PolicyParser(new ByteArrayInputStream("""
|
||||
entitlement-module-name:
|
||||
- set_https_connection_properties
|
||||
""".getBytes(StandardCharsets.UTF_8)), "test-policy.yaml", true).parsePolicy();
|
||||
Policy builtPolicy = new Policy(
|
||||
"test-policy.yaml",
|
||||
List.of(new Scope("entitlement-module-name", List.of(new CreateClassLoaderEntitlement())))
|
||||
);
|
||||
assertThat(
|
||||
parsedPolicy.scopes,
|
||||
contains(
|
||||
both(transformedMatch((Scope scope) -> scope.name, equalTo("entitlement-module-name"))).and(
|
||||
transformedMatch(scope -> scope.entitlements, contains(instanceOf(SetHttpsConnectionPropertiesEntitlement.class)))
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
elastic.apm.agent:
|
||||
- set_https_connection_properties
|
|
@ -0,0 +1,2 @@
|
|||
ALL-UNNAMED:
|
||||
- set_https_connection_properties # required by google-http-client
|
|
@ -0,0 +1,2 @@
|
|||
ALL-UNNAMED:
|
||||
- set_https_connection_properties # required by google-http-client
|
|
@ -213,7 +213,6 @@ class Elasticsearch {
|
|||
// load the plugin Java modules and layers now for use in entitlements
|
||||
var pluginsLoader = PluginsLoader.createPluginsLoader(nodeEnv.modulesFile(), nodeEnv.pluginsFile());
|
||||
bootstrap.setPluginsLoader(pluginsLoader);
|
||||
var pluginsResolver = PluginsResolver.create(pluginsLoader);
|
||||
|
||||
if (Boolean.parseBoolean(System.getProperty("es.entitlements.enabled"))) {
|
||||
LogManager.getLogger(Elasticsearch.class).info("Bootstrapping Entitlements");
|
||||
|
@ -227,6 +226,8 @@ class Elasticsearch {
|
|||
.map(bundle -> new EntitlementBootstrap.PluginData(bundle.getDir(), bundle.pluginDescriptor().isModular(), true))
|
||||
).toList();
|
||||
|
||||
var pluginsResolver = PluginsResolver.create(pluginsLoader);
|
||||
|
||||
EntitlementBootstrap.bootstrap(pluginData, pluginsResolver::resolveClassToPluginName);
|
||||
} else if (RuntimeVersionFeature.isSecurityManagerAvailable()) {
|
||||
// install SM after natives, shutdown hooks, etc.
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
ALL-UNNAMED:
|
||||
- set_https_connection_properties # potentially required by apache.httpcomponents
|
|
@ -0,0 +1,2 @@
|
|||
com.google.api.client:
|
||||
- set_https_connection_properties
|
|
@ -0,0 +1,2 @@
|
|||
ALL-UNNAMED:
|
||||
- set_https_connection_properties # potentially required by apache.httpcomponents
|
|
@ -0,0 +1,2 @@
|
|||
org.elasticsearch.security:
|
||||
- set_https_connection_properties # for CommandLineHttpClient
|
Loading…
Add table
Add a link
Reference in a new issue