What is the bug?
Using the PUT /_plugins/_security/api/nodesdn API incorrectly, to set the dynamic nodesdn for some remote cluster name but with the certificate DN of the local cluster completely breaks all inter-node trust and causes the cluster to become completely unavailable. In our case this was an accidental misconfiguration, but ideally would not cause an entire cluster outage. This makes it particularly hard to undo, as the same API can no longer be used to revert the change.
Once triggered, the cluster can't form due to inter-node trust issues. I can't think of a valid reason for why this would be the case, since the dynamic nodes_dn updated is for a different cluster name, and the opensearch.yml still correctly trusts the local certificate DN in the nodes_dn setting.
The logs on all nodes will be filled with the following error, and the cluster will be completely unavailable and unable to respond to further requests:
[WARN ][o.o.d.HandshakingTransportAddressConnector] [ip-10-1-148-128] handshake failed for [connectToRemoteMasterNode[10.1.180.182:9300]]
at io.netty.handler.logging.LoggingHandler.channelRead(LoggingHandler.java:280) ~[?:?]
at io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1377) ~[?:?]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[?:?]
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:796) ~[?:?]
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:660) ~[?:?]
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:998) ~[?:?]
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[?:?]
at com.google.common.base.Preconditions.checkArgument(Preconditions.java:129) ~[?:?]
at org.opensearch.security.support.WildcardMatcher$MatcherCombiner.<init>(WildcardMatcher.java:525) ~[?:?]
at org.opensearch.security.transport.DefaultInterClusterRequestEvaluator.isInterClusterRequest(DefaultInterClusterRequestEvaluator.java:95) ~[?:?]
at org.opensearch.security.ssl.transport.SecuritySSLRequestHandler.messageReceived(SecuritySSLRequestHandler.java:140) ~[?:?]
at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:107) ~[?:?]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) ~[?:?]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[?:?]
at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1519) ~[?:?]
at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:530) ~[?:?]
at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:469) ~[?:?]
at org.opensearch.security.OpenSearchSecurityPlugin$6$1.messageReceived(OpenSearchSecurityPlugin.java:873) ~[?:?]
at org.opensearch.transport.RequestHandlerRegistry.processMessageReceived(RequestHandlerRegistry.java:108) ~[opensearch-2.19.4.jar:2.19.4]
at org.opensearch.transport.NativeMessageHandler.messageReceived(NativeMessageHandler.java:126) ~[opensearch-2.19.4.jar:2.19.4]
at org.opensearch.transport.InboundHandler.messageReceivedFromPipeline(InboundHandler.java:120) ~[opensearch-2.19.4.jar:2.19.4]
at org.opensearch.transport.TcpTransport.inboundMessage(TcpTransport.java:768) ~[opensearch-2.19.4.jar:2.19.4]
at org.opensearch.transport.InboundBytesHandler.doHandleBytes(InboundBytesHandler.java:77) ~[opensearch-2.19.4.jar:2.19.4]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[?:?]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[?:?]
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1357) ~[?:?]
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:868) ~[?:?]
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166) ~[?:?]
at io.netty.channel.nio.NioEventLoop.processSelectedKeysPlain(NioEventLoop.java:697) ~[?:?]
org.opensearch.transport.RemoteTransportException: [ip-10-1-180-182][10.1.180.182:9300][internal:transport/handshake]
Caused by: java.lang.IllegalArgumentException
at org.opensearch.security.support.WildcardMatcher.concat(WildcardMatcher.java:221) ~[?:?]
at org.opensearch.security.transport.DefaultInterClusterRequestEvaluator.getNodesDnToEvaluate(DefaultInterClusterRequestEvaluator.java:75) ~[?:?]
at org.opensearch.security.transport.SecurityRequestHandler.addAdditionalContextValues(SecurityRequestHandler.java:362) ~[?:?]
at org.opensearch.wlm.WorkloadManagementTransportInterceptor$RequestHandler.messageReceived(WorkloadManagementTransportInterceptor.java:63) ~[opensearch-2.19.4.jar:2.19.4]
at org.opensearch.transport.NativeMessageHandler.handleRequest(NativeMessageHandler.java:278) ~[opensearch-2.19.4.jar:2.19.4]
at org.opensearch.transport.InboundHandler.inboundMessage(InboundHandler.java:112) ~[opensearch-2.19.4.jar:2.19.4]
at org.opensearch.transport.InboundPipeline.doHandleBytes(InboundPipeline.java:124) ~[opensearch-2.19.4.jar:2.19.4]
at org.opensearch.transport.InboundPipeline.handleBytes(InboundPipeline.java:113) ~[opensearch-2.19.4.jar:2.19.4]
at org.opensearch.transport.netty4.Netty4MessageChannelHandler.channelRead(Netty4MessageChannelHandler.java:95) ~[?:?]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[?:?]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[?:?]
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:290) ~[?:?]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440) ~[?:?]
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562) ~[?:?]
at java.lang.Thread.run(Thread.java:1583) [?:?]
at org.opensearch.security.transport.DefaultInterClusterRequestEvaluator.isInterClusterRequest(DefaultInterClusterRequestEvaluator.java:95) ~[?:?]
at org.opensearch.security.transport.SecurityRequestHandler.addAdditionalContextValues(SecurityRequestHandler.java:362) ~[?:?]
at org.opensearch.transport.RequestHandlerRegistry.processMessageReceived(RequestHandlerRegistry.java:108) ~[opensearch-2.19.4.jar:2.19.4]
at org.opensearch.transport.NativeMessageHandler.handleRequest(NativeMessageHandler.java:278) ~[opensearch-2.19.4.jar:2.19.4]
at org.opensearch.transport.netty4.Netty4MessageChannelHandler.channelRead(Netty4MessageChannelHandler.java:95) ~[?:?]
at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:107) ~[?:?]
at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:530) ~[?:?]
at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:469) ~[?:?]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[?:?]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440) ~[?:?]
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[?:?]
The only way I was able to fix this was to:
- Disable the
plugins.security.nodes_dn_dynamic_config_enabled setting in opensearch.yml for all nodes - this is the only way to get the cluster to start again.
- Restart all nodes.
- Manually edit the
.opendistro_security/_doc/nodesdn dynamic nodesdn configuration document to remove the broken configuration. This is obviously not the intended way and quite error prone, particularly as the document content is a base64-encoded JSON document.
- Re-enable the
plugins.security.nodes_dn_dynamic_config_enabled setting in opensearch.yml for all nodes, and restart all nodes.
How can one reproduce the bug?
Steps to reproduce the behavior:
For example, with the following configuration in opensearch.yml:
plugins.security.nodes_dn_dynamic_config_enabled: true
plugins.security.nodes_dn:
- CN=<local cluster's own certificate DN>
This request completely breaks the cluster:
PUT /_plugins/_security/api/nodesdn/<remote cluster name, I don't think this matters>
{
"nodes_dn": [
"CN=<local cluster's own certificate DN>"
]
}
What is the expected behavior?
I would expect:
- the request to be rejected if it is completely invalid, and/or,
- this configuration to not completely break inter-node cluster trust - maybe we could always fall back to the
nodes_dn list in opensearch.yml even if the dynamic configuration is invalid?
What is your host/environment?
- OS: Debian 12
- Version: 2.19.4
What is the bug?
Using the
PUT /_plugins/_security/api/nodesdnAPI incorrectly, to set the dynamic nodesdn for some remote cluster name but with the certificate DN of the local cluster completely breaks all inter-node trust and causes the cluster to become completely unavailable. In our case this was an accidental misconfiguration, but ideally would not cause an entire cluster outage. This makes it particularly hard to undo, as the same API can no longer be used to revert the change.Once triggered, the cluster can't form due to inter-node trust issues. I can't think of a valid reason for why this would be the case, since the dynamic nodes_dn updated is for a different cluster name, and the
opensearch.ymlstill correctly trusts the local certificate DN in thenodes_dnsetting.The logs on all nodes will be filled with the following error, and the cluster will be completely unavailable and unable to respond to further requests:
The only way I was able to fix this was to:
plugins.security.nodes_dn_dynamic_config_enabledsetting inopensearch.ymlfor all nodes - this is the only way to get the cluster to start again..opendistro_security/_doc/nodesdndynamic nodesdn configuration document to remove the broken configuration. This is obviously not the intended way and quite error prone, particularly as the document content is a base64-encoded JSON document.plugins.security.nodes_dn_dynamic_config_enabledsetting inopensearch.ymlfor all nodes, and restart all nodes.How can one reproduce the bug?
Steps to reproduce the behavior:
For example, with the following configuration in
opensearch.yml:This request completely breaks the cluster:
What is the expected behavior?
I would expect:
nodes_dnlist inopensearch.ymleven if the dynamic configuration is invalid?What is your host/environment?