diff --git a/CHANGELOG.md b/CHANGELOG.md index ab4138c452894..f1502f42b874b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Introduce a setting to disable download of full cluster state from remote on term mismatch([#16798](https://github.com/opensearch-project/OpenSearch/pull/16798/)) - Added ability to retrieve value from DocValues in a flat_object filed([#16802](https://github.com/opensearch-project/OpenSearch/pull/16802)) - Improve performace of NumericTermAggregation by avoiding unnecessary sorting([#17252](https://github.com/opensearch-project/OpenSearch/pull/17252)) +- Add TLS enabled SecureNetty4GrpcServerTransport ([#17406](https://github.com/opensearch-project/OpenSearch/pull/17406)) ### Dependencies - Bump `org.awaitility:awaitility` from 4.2.0 to 4.2.2 ([#17230](https://github.com/opensearch-project/OpenSearch/pull/17230)) diff --git a/plugins/arrow-flight-rpc/src/main/java/org/opensearch/arrow/flight/bootstrap/FlightService.java b/plugins/arrow-flight-rpc/src/main/java/org/opensearch/arrow/flight/bootstrap/FlightService.java index 7735fc3df73e0..fdcbbf43d75bf 100644 --- a/plugins/arrow-flight-rpc/src/main/java/org/opensearch/arrow/flight/bootstrap/FlightService.java +++ b/plugins/arrow-flight-rpc/src/main/java/org/opensearch/arrow/flight/bootstrap/FlightService.java @@ -134,6 +134,7 @@ public StreamManager getStreamManager() { * Retrieves the bound address of the FlightService. * @return The BoundTransportAddress instance. */ + @Override public BoundTransportAddress getBoundAddress() { return serverComponents.getBoundAddress(); } diff --git a/plugins/transport-grpc/README.md b/plugins/transport-grpc/README.md new file mode 100644 index 0000000000000..f72fa5bc4588d --- /dev/null +++ b/plugins/transport-grpc/README.md @@ -0,0 +1,22 @@ +# transport-grpc + +An auxiliary transport which runs in parallel to the REST API. +The `transport-grpc` plugin initializes a new client/server transport implementing a gRPC protocol on Netty4. + +Enable this transport with: +``` +setting 'aux.transport.types', '[experimental-transport-grpc]' +setting 'aux.transport.experimental-transport-grpc.port', '9400-9500' //optional +``` + +## Testing + +### Unit Tests + +``` +./gradlew :plugins:transport-grpc:test +``` + +### Integration Tests + +COMING SOON - Fill this out with PR completion diff --git a/plugins/transport-grpc/build.gradle b/plugins/transport-grpc/build.gradle index 5c6bc8efe1098..a84a58bcaccd5 100644 --- a/plugins/transport-grpc/build.gradle +++ b/plugins/transport-grpc/build.gradle @@ -8,11 +8,21 @@ import org.gradle.api.attributes.java.TargetJvmEnvironment * compatible open source license. */ +apply plugin: 'opensearch.testclusters' +apply plugin: 'opensearch.internal-cluster-test' + opensearchplugin { description = 'gRPC based transport implementation' classname = 'org.opensearch.transport.grpc.GrpcPlugin' } +testClusters { + integTest { + plugin(project.path) + setting 'aux.transport.types', '[experimental-transport-grpc]' + } +} + dependencies { compileOnly "com.google.code.findbugs:jsr305:3.0.2" runtimeOnly "com.google.guava:guava:${versions.guava}" @@ -27,6 +37,9 @@ dependencies { implementation "io.grpc:grpc-stub:${versions.grpc}" implementation "io.grpc:grpc-util:${versions.grpc}" implementation "io.perfmark:perfmark-api:0.26.0" + + testImplementation project(':test:framework') + testImplementation project(':libs:opensearch-core') } tasks.named("dependencyLicenses").configure { diff --git a/plugins/transport-grpc/src/main/java/org/opensearch/transport/grpc/GrpcPlugin.java b/plugins/transport-grpc/src/main/java/org/opensearch/transport/grpc/GrpcPlugin.java index 7f02983010f98..d264c4e25fe9e 100644 --- a/plugins/transport-grpc/src/main/java/org/opensearch/transport/grpc/GrpcPlugin.java +++ b/plugins/transport-grpc/src/main/java/org/opensearch/transport/grpc/GrpcPlugin.java @@ -14,8 +14,10 @@ import org.opensearch.core.indices.breaker.CircuitBreakerService; import org.opensearch.plugins.NetworkPlugin; import org.opensearch.plugins.Plugin; +import org.opensearch.plugins.SecureAuxTransportSettingsProvider; import org.opensearch.telemetry.tracing.Tracer; import org.opensearch.threadpool.ThreadPool; +import org.opensearch.transport.grpc.ssl.SecureNetty4GrpcServerTransport; import java.util.Collections; import java.util.List; @@ -55,6 +57,22 @@ public Map> getAuxTransports( ); } + @Override + public Map> getSecureAuxTransports( + Settings settings, + ThreadPool threadPool, + CircuitBreakerService circuitBreakerService, + NetworkService networkService, + ClusterSettings clusterSettings, + SecureAuxTransportSettingsProvider secureAuxTransportSettingsProvider, + Tracer tracer + ) { + return Collections.singletonMap( + GRPC_TRANSPORT_SETTING_KEY, + () -> new SecureNetty4GrpcServerTransport(settings, Collections.emptyList(), networkService, secureAuxTransportSettingsProvider) + ); + } + @Override public List> getSettings() { return List.of( diff --git a/plugins/transport-grpc/src/main/java/org/opensearch/transport/grpc/Netty4GrpcServerTransport.java b/plugins/transport-grpc/src/main/java/org/opensearch/transport/grpc/Netty4GrpcServerTransport.java index 1fb6a0bca03ea..fccfc212656a9 100644 --- a/plugins/transport-grpc/src/main/java/org/opensearch/transport/grpc/Netty4GrpcServerTransport.java +++ b/plugins/transport-grpc/src/main/java/org/opensearch/transport/grpc/Netty4GrpcServerTransport.java @@ -32,9 +32,9 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; +import java.util.function.UnaryOperator; import io.grpc.BindableService; -import io.grpc.InsecureServerCredentials; import io.grpc.Server; import io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder; import io.grpc.netty.shaded.io.netty.channel.EventLoopGroup; @@ -115,14 +115,23 @@ public class Netty4GrpcServerTransport extends NetworkPlugin.AuxTransport { Setting.Property.NodeScope ); - private final Settings settings; + /** + * Port range on which servers bind. + */ + protected PortsRange port; + + /** + * Settings. + */ + protected final Settings settings; + private final NetworkService networkService; private final List services; - private final CopyOnWriteArrayList servers = new CopyOnWriteArrayList<>(); private final String[] bindHosts; private final String[] publishHosts; - private final PortsRange port; private final int nettyEventLoopThreads; + private final CopyOnWriteArrayList servers = new CopyOnWriteArrayList<>(); + private final List> serverBuilderConfigs = new ArrayList<>(); private volatile BoundTransportAddress boundAddress; private volatile EventLoopGroup eventLoopGroup; @@ -152,10 +161,20 @@ public Netty4GrpcServerTransport(Settings settings, List servic this.nettyEventLoopThreads = SETTING_GRPC_WORKER_COUNT.get(settings); } - BoundTransportAddress boundAddress() { + // public for tests + @Override + public BoundTransportAddress getBoundAddress() { return this.boundAddress; } + /** + * Inject a NettyServerBuilder configuration to be applied at server bind and start. + * @param configModifier builder configuration to set. + */ + protected void addServerConfig(UnaryOperator configModifier) { + serverBuilderConfigs.add(configModifier); + } + @Override protected void doStart() { boolean success = false; @@ -198,7 +217,7 @@ protected void doStop() { @Override protected void doClose() { - + eventLoopGroup.close(); } private void bindServer() { @@ -249,13 +268,18 @@ private TransportAddress bindAddress(InetAddress hostAddress, PortsRange portRan try { final InetSocketAddress address = new InetSocketAddress(hostAddress, portNumber); - final NettyServerBuilder serverBuilder = NettyServerBuilder.forAddress(address, InsecureServerCredentials.create()) + final NettyServerBuilder serverBuilder = NettyServerBuilder.forAddress(address) + .directExecutor() .bossEventLoopGroup(eventLoopGroup) .workerEventLoopGroup(eventLoopGroup) .channelType(NioServerSocketChannel.class) .addService(new HealthStatusManager().getHealthService()) .addService(ProtoReflectionService.newInstance()); + for (UnaryOperator op : serverBuilderConfigs) { + op.apply(serverBuilder); + } + services.forEach(serverBuilder::addService); Server srv = serverBuilder.build().start(); diff --git a/plugins/transport-grpc/src/main/java/org/opensearch/transport/grpc/ssl/SecureNetty4GrpcServerTransport.java b/plugins/transport-grpc/src/main/java/org/opensearch/transport/grpc/ssl/SecureNetty4GrpcServerTransport.java new file mode 100644 index 0000000000000..33a8459f28607 --- /dev/null +++ b/plugins/transport-grpc/src/main/java/org/opensearch/transport/grpc/ssl/SecureNetty4GrpcServerTransport.java @@ -0,0 +1,88 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.transport.grpc.ssl; + +import org.opensearch.common.network.NetworkService; +import org.opensearch.common.settings.Settings; +import org.opensearch.plugins.SecureAuxTransportSettingsProvider; +import org.opensearch.transport.grpc.Netty4GrpcServerTransport; + +import javax.net.ssl.SSLException; + +import java.util.List; +import java.util.Locale; + +import io.grpc.BindableService; +import io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder; +import io.grpc.netty.shaded.io.netty.handler.ssl.ApplicationProtocolConfig; +import io.grpc.netty.shaded.io.netty.handler.ssl.ApplicationProtocolNames; +import io.grpc.netty.shaded.io.netty.handler.ssl.ClientAuth; +import io.grpc.netty.shaded.io.netty.handler.ssl.SslContext; +import io.grpc.netty.shaded.io.netty.handler.ssl.SslContextBuilder; +import io.grpc.netty.shaded.io.netty.handler.ssl.SslProvider; + +/** + * Netty4GrpcServerTransport with TLS enabled. + * Security settings injected through a SecureAuxTransportSettingsProvider. + */ +public class SecureNetty4GrpcServerTransport extends Netty4GrpcServerTransport { + private final SecureAuxTransportSettingsProvider secureAuxTransportSettingsProvider; + private final SslContext sslContext; + + /** + * Creates a new SecureNetty4GrpcServerTransport instance. + * @param settings the configured settings. + * @param services the gRPC compatible services to be registered with the server. + * @param networkService the bind/publish addresses. + * @param secureTransportSettingsProvider TLS configuration settings. + */ + public SecureNetty4GrpcServerTransport( + Settings settings, + List services, + NetworkService networkService, + SecureAuxTransportSettingsProvider secureTransportSettingsProvider + ) { + super(settings, services, networkService); + this.secureAuxTransportSettingsProvider = secureTransportSettingsProvider; + this.port = SecureNetty4GrpcServerTransport.SETTING_GRPC_PORT.get(settings); + + try { + this.sslContext = buildSslContext(); + } catch (SSLException e) { + throw new RuntimeException(SecureNetty4GrpcServerTransport.class + " failed to build SslContext", e); + } + + this.addServerConfig((NettyServerBuilder builder) -> builder.sslContext(this.sslContext)); + } + + /** + * @return io.grpc SslContext from SecureAuxTransportSettingsProvider. + */ + private SslContext buildSslContext() throws SSLException { + if (secureAuxTransportSettingsProvider.parameters(settings).isEmpty()) { + throw new SSLException("SSLContext could not be built from SecureAuxTransportSettingsProvider: provider empty"); + } + SecureAuxTransportSettingsProvider.SecureTransportParameters params = secureAuxTransportSettingsProvider.parameters(settings).get(); + return SslContextBuilder.forServer(params.keyManagerFactory()) + .trustManager(params.trustManagerFactory()) + .sslProvider(SslProvider.valueOf(params.sslProvider().toUpperCase(Locale.ROOT))) + .clientAuth(ClientAuth.valueOf(params.clientAuth().toUpperCase(Locale.ROOT))) + .protocols(params.protocols()) + .ciphers(params.cipherSuites()) + .applicationProtocolConfig( + new ApplicationProtocolConfig( + ApplicationProtocolConfig.Protocol.ALPN, + ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE, + ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT, + ApplicationProtocolNames.HTTP_2 + ) + ) + .build(); + } +} diff --git a/plugins/transport-grpc/src/main/java/org/opensearch/transport/grpc/ssl/package-info.java b/plugins/transport-grpc/src/main/java/org/opensearch/transport/grpc/ssl/package-info.java new file mode 100644 index 0000000000000..bffc3e762a0f4 --- /dev/null +++ b/plugins/transport-grpc/src/main/java/org/opensearch/transport/grpc/ssl/package-info.java @@ -0,0 +1,12 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +/** + * gRPC transport for OpenSearch implementing TLS. + */ +package org.opensearch.transport.grpc.ssl; diff --git a/plugins/transport-grpc/src/test/java/org/opensearch/transport/grpc/Netty4GrpcServerTransportIT.java b/plugins/transport-grpc/src/test/java/org/opensearch/transport/grpc/Netty4GrpcServerTransportIT.java new file mode 100644 index 0000000000000..2284a335b06db --- /dev/null +++ b/plugins/transport-grpc/src/test/java/org/opensearch/transport/grpc/Netty4GrpcServerTransportIT.java @@ -0,0 +1,59 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.transport.grpc; + +import org.opensearch.action.admin.cluster.health.ClusterHealthResponse; +import org.opensearch.cluster.health.ClusterHealthStatus; +import org.opensearch.common.settings.Settings; +import org.opensearch.core.common.transport.TransportAddress; +import org.opensearch.plugins.Plugin; +import org.opensearch.test.OpenSearchIntegTestCase; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import io.grpc.health.v1.HealthCheckResponse; + +import static org.opensearch.plugins.NetworkPlugin.AuxTransport.AUX_TRANSPORT_TYPES_KEY; +import static org.opensearch.transport.grpc.Netty4GrpcServerTransport.GRPC_TRANSPORT_SETTING_KEY; + +public class Netty4GrpcServerTransportIT extends OpenSearchIntegTestCase { + + private TransportAddress randomNetty4GrpcServerTransportAddr() { + List addresses = new ArrayList<>(); + for (Netty4GrpcServerTransport transport : internalCluster().getInstances(Netty4GrpcServerTransport.class)) { + TransportAddress tAddr = new TransportAddress(transport.getBoundAddress().publishAddress().address()); + addresses.add(tAddr); + } + return randomFrom(addresses); + } + + @Override + protected Settings nodeSettings(int nodeOrdinal) { + return Settings.builder().put(super.nodeSettings(nodeOrdinal)).put(AUX_TRANSPORT_TYPES_KEY, GRPC_TRANSPORT_SETTING_KEY).build(); + } + + @Override + protected Collection> nodePlugins() { + return Collections.singleton(GrpcPlugin.class); + } + + public void testStartGrpcTransportClusterHealth() throws Exception { + // REST api cluster health + ClusterHealthResponse healthResponse = client().admin().cluster().prepareHealth().get(); + assertEquals(ClusterHealthStatus.GREEN, healthResponse.getStatus()); + + // gRPC transport service health + try (NettyGrpcClient client = new NettyGrpcClient.Builder().setAddress(randomNetty4GrpcServerTransportAddr()).build()) { + assertEquals(client.checkHealth(), HealthCheckResponse.ServingStatus.SERVING); + } + } +} diff --git a/plugins/transport-grpc/src/test/java/org/opensearch/transport/grpc/Netty4GrpcServerTransportTests.java b/plugins/transport-grpc/src/test/java/org/opensearch/transport/grpc/Netty4GrpcServerTransportTests.java index 8cf44eebb293e..33182a354c20e 100644 --- a/plugins/transport-grpc/src/test/java/org/opensearch/transport/grpc/Netty4GrpcServerTransportTests.java +++ b/plugins/transport-grpc/src/test/java/org/opensearch/transport/grpc/Netty4GrpcServerTransportTests.java @@ -10,6 +10,7 @@ import org.opensearch.common.network.NetworkService; import org.opensearch.common.settings.Settings; +import org.opensearch.core.common.transport.TransportAddress; import org.opensearch.test.OpenSearchTestCase; import org.hamcrest.MatcherAssert; import org.junit.Before; @@ -17,12 +18,12 @@ import java.util.List; import io.grpc.BindableService; +import io.grpc.health.v1.HealthCheckResponse; import static org.hamcrest.Matchers.emptyArray; import static org.hamcrest.Matchers.not; public class Netty4GrpcServerTransportTests extends OpenSearchTestCase { - private NetworkService networkService; private List services; @@ -32,14 +33,25 @@ public void setup() { services = List.of(); } - public void test() { + public void testGrpcTransportStartStop() { try (Netty4GrpcServerTransport transport = new Netty4GrpcServerTransport(createSettings(), services, networkService)) { transport.start(); + MatcherAssert.assertThat(transport.getBoundAddress().boundAddresses(), not(emptyArray())); + assertNotNull(transport.getBoundAddress().publishAddress().address()); + transport.stop(); + } + } - MatcherAssert.assertThat(transport.boundAddress().boundAddresses(), not(emptyArray())); - assertNotNull(transport.boundAddress().publishAddress().address()); - + public void testGrpcTransportHealthcheck() { + try (Netty4GrpcServerTransport transport = new Netty4GrpcServerTransport(createSettings(), services, networkService)) { + transport.start(); + final TransportAddress remoteAddress = randomFrom(transport.getBoundAddress().boundAddresses()); + try (NettyGrpcClient client = new NettyGrpcClient.Builder().setAddress(remoteAddress).build()) { + assertEquals(client.checkHealth(), HealthCheckResponse.ServingStatus.SERVING); + } transport.stop(); + } catch (Exception e) { + throw new RuntimeException(e); } } diff --git a/plugins/transport-grpc/src/test/java/org/opensearch/transport/grpc/NettyGrpcClient.java b/plugins/transport-grpc/src/test/java/org/opensearch/transport/grpc/NettyGrpcClient.java new file mode 100644 index 0000000000000..50b2f0b92a57f --- /dev/null +++ b/plugins/transport-grpc/src/test/java/org/opensearch/transport/grpc/NettyGrpcClient.java @@ -0,0 +1,164 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.transport.grpc; + +import org.opensearch.core.common.transport.TransportAddress; +import org.opensearch.plugins.SecureAuxTransportSettingsProvider; + +import javax.net.ssl.SSLException; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import io.grpc.ManagedChannel; +import io.grpc.ProxyDetector; +import io.grpc.health.v1.HealthCheckRequest; +import io.grpc.health.v1.HealthCheckResponse; +import io.grpc.health.v1.HealthGrpc; +import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder; +import io.grpc.netty.shaded.io.netty.handler.ssl.ApplicationProtocolConfig; +import io.grpc.netty.shaded.io.netty.handler.ssl.ApplicationProtocolNames; +import io.grpc.netty.shaded.io.netty.handler.ssl.ClientAuth; +import io.grpc.netty.shaded.io.netty.handler.ssl.SslContext; +import io.grpc.netty.shaded.io.netty.handler.ssl.SslContextBuilder; +import io.grpc.netty.shaded.io.netty.handler.ssl.SslProvider; +import io.grpc.reflection.v1alpha.ServerReflectionGrpc; +import io.grpc.reflection.v1alpha.ServerReflectionRequest; +import io.grpc.reflection.v1alpha.ServerReflectionResponse; +import io.grpc.reflection.v1alpha.ServiceResponse; +import io.grpc.stub.StreamObserver; + +import static org.opensearch.transport.grpc.SecureNetty4GrpcServerTransportTests.createSettings; +import static io.grpc.internal.GrpcUtil.NOOP_PROXY_DETECTOR; + +public class NettyGrpcClient implements AutoCloseable { + private final ManagedChannel channel; + private final HealthGrpc.HealthBlockingStub healthStub; + private final ServerReflectionGrpc.ServerReflectionStub reflectionStub; + + public NettyGrpcClient(TransportAddress addr, NettyChannelBuilder channelBuilder) { + channel = channelBuilder.build(); + healthStub = HealthGrpc.newBlockingStub(channel); + reflectionStub = ServerReflectionGrpc.newStub(channel); + } + + public void shutdown() throws InterruptedException { + channel.shutdown().awaitTermination(5, TimeUnit.SECONDS); + if (!channel.awaitTermination(5, TimeUnit.SECONDS)) { + channel.shutdownNow(); + } + } + + @Override + public void close() throws Exception { + shutdown(); + } + + /** + * List available gRPC services available on server. + * Note: ProtoReflectionService only implements a streaming interface and has no blocking stub. + * @return services registered on the server. + */ + public List listServices() { + List respServices = new ArrayList<>(); + CountDownLatch latch = new CountDownLatch(1); + + StreamObserver responseObserver = new StreamObserver<>() { + @Override + public void onNext(ServerReflectionResponse response) { + if (response.hasListServicesResponse()) { + respServices.addAll(response.getListServicesResponse().getServiceList()); + } + } + + @Override + public void onError(Throwable t) { + latch.countDown(); + throw new RuntimeException(t); + } + + @Override + public void onCompleted() { + latch.countDown(); + } + }; + + StreamObserver requestObserver = reflectionStub.serverReflectionInfo(responseObserver); + requestObserver.onNext(ServerReflectionRequest.newBuilder().setListServices("").build()); + requestObserver.onCompleted(); + + try { + if (!latch.await(5, TimeUnit.SECONDS)) { + throw new RuntimeException(NettyGrpcClient.class.getSimpleName() + " timed out waiting for response."); + } + } catch (InterruptedException e) { + throw new RuntimeException(NettyGrpcClient.class.getSimpleName() + " interrupted waiting for response: " + e.getMessage()); + } + + return respServices; + } + + /** + * Request server status. + * @return HealthCheckResponse.ServingStatus. + */ + public HealthCheckResponse.ServingStatus checkHealth() { + return healthStub.check(HealthCheckRequest.newBuilder().build()).getStatus(); + } + + public static class Builder { + private SecureAuxTransportSettingsProvider settingsProvider = null; + private TransportAddress addr; + private final ProxyDetector proxyDetector = NOOP_PROXY_DETECTOR; // No proxy detection for test client + + Builder() {} + + public NettyGrpcClient build() throws SSLException { + NettyChannelBuilder channelBuilder = NettyChannelBuilder.forAddress(addr.getAddress(), addr.getPort()) + .proxyDetector(proxyDetector); + + if (settingsProvider == null) { + channelBuilder.usePlaintext(); + } else { + SecureAuxTransportSettingsProvider.SecureTransportParameters params = settingsProvider.parameters(createSettings()).get(); + SslContext ctxt = SslContextBuilder.forClient() + .trustManager(params.trustManagerFactory()) + .sslProvider(SslProvider.valueOf(params.sslProvider().toUpperCase(Locale.ROOT))) + .clientAuth(ClientAuth.valueOf(params.clientAuth().toUpperCase(Locale.ROOT))) + .protocols(params.protocols()) + .ciphers(params.cipherSuites()) + .applicationProtocolConfig( + new ApplicationProtocolConfig( + ApplicationProtocolConfig.Protocol.ALPN, + ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE, + ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT, + ApplicationProtocolNames.HTTP_2 + ) + ) + .build(); + channelBuilder.sslContext(ctxt); + } + + return new NettyGrpcClient(addr, channelBuilder); + } + + public Builder setSecureSettingsProvider(SecureAuxTransportSettingsProvider settingsProvider) { + this.settingsProvider = settingsProvider; + return this; + } + + public Builder setAddress(TransportAddress addr) { + this.addr = addr; + return this; + } + } +} diff --git a/plugins/transport-grpc/src/test/java/org/opensearch/transport/grpc/SecureNetty4GrpcServerTransportTests.java b/plugins/transport-grpc/src/test/java/org/opensearch/transport/grpc/SecureNetty4GrpcServerTransportTests.java new file mode 100644 index 0000000000000..ea0ff8a4ad6ac --- /dev/null +++ b/plugins/transport-grpc/src/test/java/org/opensearch/transport/grpc/SecureNetty4GrpcServerTransportTests.java @@ -0,0 +1,168 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.transport.grpc; + +import org.opensearch.common.network.NetworkService; +import org.opensearch.common.settings.Settings; +import org.opensearch.core.common.transport.TransportAddress; +import org.opensearch.plugins.SecureAuxTransportSettingsProvider; +import org.opensearch.test.OpenSearchTestCase; +import org.opensearch.transport.grpc.ssl.SecureNetty4GrpcServerTransport; +import org.junit.After; +import org.junit.Before; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.TrustManagerFactory; + +import java.io.IOException; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import io.grpc.BindableService; +import io.grpc.health.v1.HealthCheckResponse; +import io.grpc.netty.shaded.io.netty.handler.ssl.util.InsecureTrustManagerFactory; + +public class SecureNetty4GrpcServerTransportTests extends OpenSearchTestCase { + private NetworkService networkService; + private final List services = new ArrayList<>(); + private SecureAuxTransportSettingsProvider settingsProvider; + + private static SecureAuxTransportSettingsProvider getSecureSettingsProvider() { + return settings -> Optional.of(new SecureAuxTransportSettingsProvider.SecureTransportParameters() { + @Override + public boolean dualModeEnabled() { + return false; + } + + @Override + public String sslProvider() { + return "JDK"; + } + + @Override + public String clientAuth() { + return "NONE"; + } + + @Override + public Iterable protocols() { + return List.of("TLSv1.3", "TLSv1.2"); + } + + @Override + public Iterable cipherSuites() { + /** + * Attempt to fetch supported ciphers from default provider. + * Else fall back to common defaults. + */ + try { + SSLContext context = SSLContext.getInstance("TLS"); + context.init(null, null, null); + SSLEngine engine = context.createSSLEngine(); + return List.of(engine.getSupportedCipherSuites()); + } catch (NoSuchAlgorithmException | KeyManagementException e) { + return List.of( + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", // TLSv1.2 + "TLS_AES_128_GCM_SHA256" // TLSv1.3 + ); + } + } + + @Override + public KeyManagerFactory keyManagerFactory() { + try { + final KeyStore keyStore = KeyStore.getInstance("PKCS12"); + keyStore.load( + SecureNetty4GrpcServerTransport.class.getResourceAsStream("/netty4-secure.jks"), + "password".toCharArray() + ); + final KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509"); + keyManagerFactory.init(keyStore, "password".toCharArray()); + return keyManagerFactory; + } catch (UnrecoverableKeyException | CertificateException | KeyStoreException | IOException | NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + @Override + public TrustManagerFactory trustManagerFactory() { + return InsecureTrustManagerFactory.INSTANCE; + } + }); + } + + static Settings createSettings() { + return Settings.builder().put(SecureNetty4GrpcServerTransport.SETTING_GRPC_PORT.getKey(), getPortRange()).build(); + } + + @Before + public void setup() { + networkService = new NetworkService(Collections.emptyList()); + settingsProvider = getSecureSettingsProvider(); + } + + @After + public void shutdown() { + networkService = null; + } + + public void testGrpcSecureTransportStartStop() { + try ( + SecureNetty4GrpcServerTransport transport = new SecureNetty4GrpcServerTransport( + createSettings(), + services, + networkService, + settingsProvider + ) + ) { + transport.start(); + assertTrue(transport.getBoundAddress().boundAddresses().length > 0); + assertNotNull(transport.getBoundAddress().publishAddress().address()); + transport.stop(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public void testGrpcSecureTransportHealthcheck() { + try ( + SecureNetty4GrpcServerTransport transport = new SecureNetty4GrpcServerTransport( + createSettings(), + services, + networkService, + settingsProvider + ) + ) { + transport.start(); + assertTrue(transport.getBoundAddress().boundAddresses().length > 0); + assertNotNull(transport.getBoundAddress().publishAddress().address()); + final TransportAddress remoteAddress = randomFrom(transport.getBoundAddress().boundAddresses()); + try ( + NettyGrpcClient client = new NettyGrpcClient.Builder().setAddress(remoteAddress) + .setSecureSettingsProvider(settingsProvider) + .build() + ) { + assertEquals(client.checkHealth(), HealthCheckResponse.ServingStatus.SERVING); + } + transport.stop(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/plugins/transport-grpc/src/test/resources/README.txt b/plugins/transport-grpc/src/test/resources/README.txt new file mode 100644 index 0000000000000..c8cec5d3803a4 --- /dev/null +++ b/plugins/transport-grpc/src/test/resources/README.txt @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# +# This is README describes how the certificates in this directory were created. +# This file can also be executed as a script +# + +# 1. Create certificate key + +openssl req -x509 -sha256 -newkey rsa:2048 -keyout certificate.key -out certificate.crt -days 1024 -nodes + +# 2. Export the certificate in pkcs12 format + +openssl pkcs12 -export -in certificate.crt -inkey certificate.key -out server.p12 -name netty4-secure -password pass:password + +# 3. Import the certificate into JDK keystore (PKCS12 type) + +keytool -importkeystore -srcstorepass password -destkeystore netty4-secure.jks -srckeystore server.p12 -srcstoretype PKCS12 -alias netty4-secure -deststorepass password \ No newline at end of file diff --git a/plugins/transport-grpc/src/test/resources/certificate.crt b/plugins/transport-grpc/src/test/resources/certificate.crt new file mode 100644 index 0000000000000..60821d4eaffa0 --- /dev/null +++ b/plugins/transport-grpc/src/test/resources/certificate.crt @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDpTCCAo2gAwIBAgIUO/AexJsrSJwdOuLWYlMbbZh6ZQIwDQYJKoZIhvcNAQEL +BQAwYjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAldBMRAwDgYDVQQHDAdTZWF0dGxl +MQwwCgYDVQQKDANBV1MxEzARBgNVBAsMCk9wZW5TZWFyY2gxETAPBgNVBAMMCFRl +c3RDZXJ0MB4XDTI1MDEyMjE3MjU0OVoXDTI3MTExMjE3MjU0OVowYjELMAkGA1UE +BhMCVVMxCzAJBgNVBAgMAldBMRAwDgYDVQQHDAdTZWF0dGxlMQwwCgYDVQQKDANB +V1MxEzARBgNVBAsMCk9wZW5TZWFyY2gxETAPBgNVBAMMCFRlc3RDZXJ0MIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsYwNo3DgxS8SeDi8KodRSixj11T9 +NpqWM0elm6Fabr2oPxod9IcqRRC0zD2T7aznW6YeBaZ26x9wkv9T32jKFTfYoDf6 +cS7BpsG8FPCrNheSd98x5iogWtPWc6PxyUhioJ3LCIKcEa7YAP6/ssmnPazZVkMa +SdHpP1WFvYleeLcZizvCxMi05vrbqPlJo8e5OUINu5Ly01grkgWeDTkYty13GcdA +HNVNiN+oFUOTvz51PpqeoxoUEphjT21jPR4c6Yi7O+eRrezvuOxxOn6dUlGHzj06 +RoEDAIx7cqHePo+iG227S4/xPxbt6CoZs0rAKmTl4FXjqkmM2NxyuwpVcQIDAQAB +o1MwUTAdBgNVHQ4EFgQU8JKhJPriAup/FCmr36A9R+skeEAwHwYDVR0jBBgwFoAU +8JKhJPriAup/FCmr36A9R+skeEAwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0B +AQsFAAOCAQEAofz97HfxydkilszLOATlxGdQE0LR+w05IoGTEW5u1+3DzKiSK7Kz +HLzjioUfwuwfYZnQVOEa3hGn9iRJp+l2idzAr8o37nNqTOw9R/XfulakqGNY+Wjn +1+57EL/RziZUzoTLJT4KvCWGXk3atylITm2REtknYLmD5XUH+vNUXpr+sBS8LGM/ +jdc6CuYaWZVVmozkTxA64xBobW2WpkjKWSYthPn9c5kScnZyGKKvpN/Xzfo7OYds +V86OnHmOe70UAMmcXmM+lSzBJ4zSLwMEpMoNpPsHMo37IEIfllc0aYa5lGhb4KaJ +U20/BY1iedLpkcTGqjwg58Ymq1nF/1Cvkg== +-----END CERTIFICATE----- diff --git a/plugins/transport-grpc/src/test/resources/certificate.key b/plugins/transport-grpc/src/test/resources/certificate.key new file mode 100644 index 0000000000000..c7b394f56e8e9 --- /dev/null +++ b/plugins/transport-grpc/src/test/resources/certificate.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCxjA2jcODFLxJ4 +OLwqh1FKLGPXVP02mpYzR6WboVpuvag/Gh30hypFELTMPZPtrOdbph4FpnbrH3CS +/1PfaMoVN9igN/pxLsGmwbwU8Ks2F5J33zHmKiBa09Zzo/HJSGKgncsIgpwRrtgA +/r+yyac9rNlWQxpJ0ek/VYW9iV54txmLO8LEyLTm+tuo+Umjx7k5Qg27kvLTWCuS +BZ4NORi3LXcZx0Ac1U2I36gVQ5O/PnU+mp6jGhQSmGNPbWM9HhzpiLs755Gt7O+4 +7HE6fp1SUYfOPTpGgQMAjHtyod4+j6IbbbtLj/E/Fu3oKhmzSsAqZOXgVeOqSYzY +3HK7ClVxAgMBAAECggEAJUYkp9s/CQ6il0Q79sa9YW/TzyV72n6WSXJBeZyklmqx +O3wxbUCCNok1F8rWt1dtI+/KTj/eJ+sMEIRTmKvQfydgDnTqGmBpTefO42uKWjsV +KB7exDY0YDUoiNMSpAITGKq/8QuwlcLJ/N1+o9uThY53+1TvnC9aQ05iW0IPaBKY +s2Rks/GDa/m+wlNG/z9QTM2N9KYoNBlNtEQ0/3/T+PjYHTID88uXV6qcGxOkNg5A +uwpjpEevGEWZ1rbbTY74Cy4bKHS/oudT7uEXsI6rlpWh+NGr5Kh6ZcAcNPa+Slqf +DpApYvOKVTtcgtzYi4IuDC83yFO+urBVQugYhZGPtQKBgQDrGzpVsrbQuSNfeHe7 +4b4v0QqrIxAaolFyULjC+O26Vj9RZ3N/mKiCzPp+Q3wrePnKSQ0Ia2M8NLDC64t2 ++9s3l5SJwnRRzJvwMT5daHXc5soifdJp/Qv1rEelyRPsxoTHQpl25vadWJgbxxE5 +Xvw7gJVtXV8h27HiuyQqGkhTjQKBgQDBU1Oo39jr1MUjuWzAISgJS61LGfjQ68TI +I4uxm0cAWI0wIR2/v2cdl673onodqf6Gn/YwMPafmiCnJJ85NSqyQvWDPlSNT++4 +/B9l53im8XZoOOabYgsS70rI6AMha5itwEdX+otNdR+TOMMvvnAQfOPI6wTz4L3k +ZWAjMsA+dQKBgQDmS7GEqJ6zLeccaaC/hY0KwbW1lY78x+sIE1IoijYzxLAZSUC1 +yA+osKjebbR/0Oy93XjKuoZmPya2iIwAbQx7FBwIJVZTmh1V1qbEUMLDM77ksmMe +NyUz7SHxn1nJIVyG4xH4ip0f29yDuSeCDyz8DCRTEJdTTyScd7whEcWvMQKBgQCq +U0vF4VIwlMkLbHaP36ZyaiZHoJ5DEzXQTuDonbG0cFAUM1kOcwfaXqVcr91+/SKu +YYh5dOoUO6rBF9bghCMV40CDXQsJZYADLr5K/eCi1OJJeLhT9dFj4Ue2MhNwAmgF +zP4OWUMZ/zLOdpghHFuHa0EU51r5suwaqeZFnJUbcQKBgC4oPdlu7kD9EkOQC3ON +E08SLwvqq0hUjUWJkMyM/LSqtLD8hY7GqRmBCBAf5MtHLO1V+V8DsvL7NsoVuHI2 +sNBTExE4hw2rwTk2X9ERTVDCpDsh+ZoQ5tAdypbe0R48yc4MBQbQpqzMVwKRy2dc +ZYAeVm9QvuOmJL4OsZI8TYS3 +-----END PRIVATE KEY----- diff --git a/plugins/transport-grpc/src/test/resources/netty4-secure.jks b/plugins/transport-grpc/src/test/resources/netty4-secure.jks new file mode 100644 index 0000000000000..2f203f4391a6c Binary files /dev/null and b/plugins/transport-grpc/src/test/resources/netty4-secure.jks differ diff --git a/server/src/main/java/org/opensearch/common/network/NetworkModule.java b/server/src/main/java/org/opensearch/common/network/NetworkModule.java index 5d55fb52c323d..222f344437ef7 100644 --- a/server/src/main/java/org/opensearch/common/network/NetworkModule.java +++ b/server/src/main/java/org/opensearch/common/network/NetworkModule.java @@ -55,6 +55,7 @@ import org.opensearch.http.HttpServerTransport; import org.opensearch.index.shard.PrimaryReplicaSyncer.ResyncTask; import org.opensearch.plugins.NetworkPlugin; +import org.opensearch.plugins.SecureAuxTransportSettingsProvider; import org.opensearch.plugins.SecureHttpTransportSettingsProvider; import org.opensearch.plugins.SecureSettingsFactory; import org.opensearch.plugins.SecureTransportSettingsProvider; @@ -210,6 +211,18 @@ public NetworkModule( ); } + final Collection secureAuxTransportSettingsProviders = secureSettingsFactories.stream() + .map(p -> p.getSecureAuxTransportSettingsProvider(settings)) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toList()); + + if (secureAuxTransportSettingsProviders.size() > 1) { + throw new IllegalArgumentException( + "there is more than one secure auxiliary transport settings provider: " + secureAuxTransportSettingsProviders + ); + } + for (NetworkPlugin plugin : plugins) { Map> httpTransportFactory = plugin.getHttpTransports( settings, @@ -274,6 +287,24 @@ public NetworkModule( } } + // Register any secure auxiliary transports if available + if (secureAuxTransportSettingsProviders.isEmpty() == false) { + final SecureAuxTransportSettingsProvider secureSettingProvider = secureAuxTransportSettingsProviders.iterator().next(); + + final Map> secureAuxTransportFactory = plugin.getSecureAuxTransports( + settings, + threadPool, + circuitBreakerService, + networkService, + clusterSettings, + secureSettingProvider, + tracer + ); + for (Map.Entry> entry : secureAuxTransportFactory.entrySet()) { + registerAuxTransport(entry.getKey(), entry.getValue()); + } + } + // Register any secure transports if available if (secureTransportSettingsProviders.isEmpty() == false) { final SecureTransportSettingsProvider secureSettingProvider = secureTransportSettingsProviders.iterator().next(); diff --git a/server/src/main/java/org/opensearch/plugins/NetworkPlugin.java b/server/src/main/java/org/opensearch/plugins/NetworkPlugin.java index 4442189373c93..b294c64e5cdce 100644 --- a/server/src/main/java/org/opensearch/plugins/NetworkPlugin.java +++ b/server/src/main/java/org/opensearch/plugins/NetworkPlugin.java @@ -42,6 +42,7 @@ import org.opensearch.common.util.PageCacheRecycler; import org.opensearch.common.util.concurrent.ThreadContext; import org.opensearch.core.common.io.stream.NamedWriteableRegistry; +import org.opensearch.core.common.transport.BoundTransportAddress; import org.opensearch.core.indices.breaker.CircuitBreakerService; import org.opensearch.core.xcontent.NamedXContentRegistry; import org.opensearch.http.HttpServerTransport; @@ -75,6 +76,7 @@ public interface NetworkPlugin { * bootstrap. To allow pluggable AuxTransports access to configurable port ranges we require the port range be provided * through an {@link org.opensearch.common.settings.Setting.AffixSetting} of the form 'AUX_SETTINGS_PREFIX.{aux-transport-key}.ports'. */ + @ExperimentalApi abstract class AuxTransport extends AbstractLifecycleComponent { public static final String AUX_SETTINGS_PREFIX = "aux.transport."; public static final String AUX_TRANSPORT_TYPES_KEY = AUX_SETTINGS_PREFIX + "types"; @@ -91,6 +93,9 @@ abstract class AuxTransport extends AbstractLifecycleComponent { Function.identity(), Setting.Property.NodeScope ); + + // public for tests + public abstract BoundTransportAddress getBoundAddress(); } /** @@ -159,6 +164,23 @@ default Map> getHttpTransports( return Collections.emptyMap(); } + /** + * Returns a map of secure {@link AuxTransport} suppliers. + * See {@link org.opensearch.plugins.NetworkPlugin.AuxTransport#AUX_TRANSPORT_TYPES_SETTING} to configure a specific implementation. + */ + @ExperimentalApi + default Map> getSecureAuxTransports( + Settings settings, + ThreadPool threadPool, + CircuitBreakerService circuitBreakerService, + NetworkService networkService, + ClusterSettings clusterSettings, + SecureAuxTransportSettingsProvider secureAuxTransportSettingsProvider, + Tracer tracer + ) { + return Collections.emptyMap(); + } + /** * Returns a map of secure {@link Transport} suppliers. * See {@link org.opensearch.common.network.NetworkModule#TRANSPORT_TYPE_KEY} to configure a specific implementation. diff --git a/server/src/main/java/org/opensearch/plugins/SecureAuxTransportSettingsProvider.java b/server/src/main/java/org/opensearch/plugins/SecureAuxTransportSettingsProvider.java new file mode 100644 index 0000000000000..790990ed3678f --- /dev/null +++ b/server/src/main/java/org/opensearch/plugins/SecureAuxTransportSettingsProvider.java @@ -0,0 +1,83 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.plugins; + +import org.opensearch.common.annotation.ExperimentalApi; +import org.opensearch.common.settings.Settings; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.TrustManagerFactory; + +import java.util.Optional; + +/** + * A provider for security related settings for gRPC transports. + * + * @opensearch.experimental + */ +@ExperimentalApi +public interface SecureAuxTransportSettingsProvider { + /** + * Provides access to SSL params directly for cases where it is not convenient to consume a pre-built javax.net.ssl SSLContext. + * @param settings settings + * @return an instance of {@link SecureAuxTransportSettingsProvider.SecureTransportParameters} + */ + Optional parameters(Settings settings); + + /** + * Parameters for configuring secure transport connections. + * Provides access to SSL/TLS configuration settings. + */ + @ExperimentalApi + interface SecureTransportParameters { + /** + * Determines if dual mode is enabled for handling both TLS and plaintext connections. + * When enabled, the server can accept both secure and insecure connections on the same port. + * @return true if dual mode is enabled, false otherwise + */ + boolean dualModeEnabled(); + + /** + * Get the SSL provider implementation to use (e.g., "JDK", "OPENSSL"). + * @return the name of the SSL provider + */ + String sslProvider(); + + /** + * Get the client authentication mode (e.g., "NONE", "OPTIONAL", "REQUIRE"). + * Determines whether client certificates are requested/required during handshake. + * @return the client authentication setting + */ + String clientAuth(); + + /** + * Get enabled TLS protocols (e.g., "TLSv1.2", "TLSv1.3"). + * @return the enabled protocols + */ + Iterable protocols(); + + /** + * Get enabled cipher suites for TLS connections. + * @return the enabled cipher suites + */ + Iterable cipherSuites(); + + /** + * KeyManagerFactory which manages the server's identity credentials. + * @return the key manager factory + */ + KeyManagerFactory keyManagerFactory(); + + /** + * TrustManagerFactory which determines trusted client certificates. + * @return the trust manager factory + */ + TrustManagerFactory trustManagerFactory(); + } +} diff --git a/server/src/main/java/org/opensearch/plugins/SecureSettingsFactory.java b/server/src/main/java/org/opensearch/plugins/SecureSettingsFactory.java index ec2276ecc62ef..0fdf4b6927eb0 100644 --- a/server/src/main/java/org/opensearch/plugins/SecureSettingsFactory.java +++ b/server/src/main/java/org/opensearch/plugins/SecureSettingsFactory.java @@ -33,4 +33,11 @@ public interface SecureSettingsFactory { * @return optionally, the instance of the {@link SecureHttpTransportSettingsProvider} */ Optional getSecureHttpTransportSettingsProvider(Settings settings); + + /** + * Creates (or provides pre-created) instance of the {@link SecureAuxTransportSettingsProvider} + * @param settings settings + * @return optionally, the instance of the {@link SecureAuxTransportSettingsProvider} + */ + Optional getSecureAuxTransportSettingsProvider(Settings settings); } diff --git a/server/src/main/java/org/opensearch/transport/TransportAdapterProvider.java b/server/src/main/java/org/opensearch/transport/TransportAdapterProvider.java index 36dbd5a699b40..7e39445b1699c 100644 --- a/server/src/main/java/org/opensearch/transport/TransportAdapterProvider.java +++ b/server/src/main/java/org/opensearch/transport/TransportAdapterProvider.java @@ -32,7 +32,7 @@ public interface TransportAdapterProvider { * Provides a new transport adapter of required transport adapter class and transport instance. * @param transport adapter class * @param settings settings - * @param transport HTTP transport instance + * @param transport transport instance * @param adapterClass required transport adapter class * @return the non-empty {@link Optional} if the transport adapter could be created, empty one otherwise */ diff --git a/server/src/test/java/org/opensearch/common/network/NetworkModuleTests.java b/server/src/test/java/org/opensearch/common/network/NetworkModuleTests.java index 447377e372e61..195a95b7a48eb 100644 --- a/server/src/test/java/org/opensearch/common/network/NetworkModuleTests.java +++ b/server/src/test/java/org/opensearch/common/network/NetworkModuleTests.java @@ -47,6 +47,7 @@ import org.opensearch.http.HttpStats; import org.opensearch.http.NullDispatcher; import org.opensearch.plugins.NetworkPlugin; +import org.opensearch.plugins.SecureAuxTransportSettingsProvider; import org.opensearch.plugins.SecureHttpTransportSettingsProvider; import org.opensearch.plugins.SecureSettingsFactory; import org.opensearch.plugins.SecureTransportSettingsProvider; @@ -130,6 +131,11 @@ public Optional buildHttpServerExceptionHandler( } }); } + + @Override + public Optional getSecureAuxTransportSettingsProvider(Settings settings) { + return Optional.of(settings1 -> Optional.empty()); + } }; }