-
Notifications
You must be signed in to change notification settings - Fork 25.6k
Validate build hash in handshake #65601
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,6 +22,7 @@ | |
| import org.apache.logging.log4j.LogManager; | ||
| import org.apache.logging.log4j.Logger; | ||
| import org.apache.logging.log4j.message.ParameterizedMessage; | ||
| import org.elasticsearch.Build; | ||
| import org.elasticsearch.Version; | ||
| import org.elasticsearch.action.ActionListener; | ||
| import org.elasticsearch.action.ActionListenerResponseHandler; | ||
|
|
@@ -37,6 +38,7 @@ | |
| import org.elasticsearch.common.io.stream.StreamOutput; | ||
| import org.elasticsearch.common.io.stream.Writeable; | ||
| import org.elasticsearch.common.lease.Releasable; | ||
| import org.elasticsearch.common.logging.DeprecationLogger; | ||
| import org.elasticsearch.common.logging.Loggers; | ||
| import org.elasticsearch.common.regex.Regex; | ||
| import org.elasticsearch.common.settings.ClusterSettings; | ||
|
|
@@ -73,10 +75,14 @@ | |
| import java.util.function.Predicate; | ||
| import java.util.function.Supplier; | ||
|
|
||
| public class TransportService extends AbstractLifecycleComponent implements ReportingService<TransportInfo>, TransportMessageListener, | ||
| TransportConnectionListener { | ||
| public class TransportService extends AbstractLifecycleComponent | ||
| implements ReportingService<TransportInfo>, TransportMessageListener, TransportConnectionListener { | ||
|
|
||
| private static final Logger logger = LogManager.getLogger(TransportService.class); | ||
|
|
||
| private static final String PERMIT_HANDSHAKES_FROM_INCOMPATIBLE_BUILDS_KEY = "es.unsafely_permit_handshake_from_incompatible_builds"; | ||
| private static final boolean PERMIT_HANDSHAKES_FROM_INCOMPATIBLE_BUILDS = getPermitHandshakesFromIncompatibleBuilds(); | ||
|
|
||
| public static final String DIRECT_RESPONSE_PROFILE = ".direct"; | ||
| public static final String HANDSHAKE_ACTION_NAME = "internal:transport/handshake"; | ||
|
|
||
|
|
@@ -115,6 +121,7 @@ protected boolean removeEldestEntry(Map.Entry eldest) { | |
| private final RemoteClusterService remoteClusterService; | ||
|
|
||
| private final boolean validateConnections; | ||
| private final boolean requireCompatibleBuild; | ||
|
|
||
| /** if set will call requests sent to this id to shortcut and executed locally */ | ||
| volatile DiscoveryNode localNode = null; | ||
|
|
@@ -160,9 +167,15 @@ public TransportService(Settings settings, Transport transport, ThreadPool threa | |
| public TransportService(Settings settings, Transport transport, ThreadPool threadPool, TransportInterceptor transportInterceptor, | ||
| Function<BoundTransportAddress, DiscoveryNode> localNodeFactory, @Nullable ClusterSettings clusterSettings, | ||
| Set<String> taskHeaders, ConnectionManager connectionManager) { | ||
|
|
||
| final boolean isTransportClient = TransportClient.CLIENT_TYPE.equals(settings.get(Client.CLIENT_TYPE_SETTING_S.getKey())); | ||
|
|
||
| // If we are a transport client then we skip the check that the remote node has a compatible build hash | ||
| this.requireCompatibleBuild = isTransportClient == false; | ||
|
|
||
| // The only time we do not want to validate node connections is when this is a transport client using the simple node sampler | ||
| this.validateConnections = TransportClient.CLIENT_TYPE.equals(settings.get(Client.CLIENT_TYPE_SETTING_S.getKey())) == false || | ||
| TransportClient.CLIENT_TRANSPORT_SNIFF.get(settings); | ||
| this.validateConnections = isTransportClient == false || TransportClient.CLIENT_TRANSPORT_SNIFF.get(settings); | ||
|
|
||
| this.transport = transport; | ||
| transport.setSlowLogThreshold(TransportSettings.SLOW_OPERATION_THRESHOLD_SETTING.get(settings)); | ||
| this.threadPool = threadPool; | ||
|
|
@@ -192,7 +205,14 @@ public TransportService(Settings settings, Transport transport, ThreadPool threa | |
| false, false, | ||
| HandshakeRequest::new, | ||
| (request, channel, task) -> channel.sendResponse( | ||
| new HandshakeResponse(localNode, clusterName, localNode.getVersion()))); | ||
| new HandshakeResponse(localNode.getVersion(), Build.CURRENT.hash(), localNode, clusterName))); | ||
|
|
||
| if (PERMIT_HANDSHAKES_FROM_INCOMPATIBLE_BUILDS) { | ||
| logger.warn("transport handshakes from incompatible builds are unsafely permitted on this node; remove system property [" + | ||
| PERMIT_HANDSHAKES_FROM_INCOMPATIBLE_BUILDS_KEY + "] to resolve this warning"); | ||
| DeprecationLogger.getLogger(TransportService.class).deprecate("permit_handshake_from_incompatible_builds", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. NIT: little much to log two messages for one thing? This seems like it's kind of a dev-only option anyway, why bother with the deprecation logger?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ehh I worry we'll get people using this, and the two logs are for two separate aspects. Deprecation logs don't go into the main server log (by default at least) but I think this deserves a visible warning whenever it's used. |
||
| "system property [" + PERMIT_HANDSHAKES_FROM_INCOMPATIBLE_BUILDS_KEY + "] is deprecated and should be removed"); | ||
| } | ||
| } | ||
|
|
||
| public RemoteClusterService getRemoteClusterService() { | ||
|
|
@@ -482,7 +502,7 @@ public void onFailure(Exception e) { | |
| listener.onFailure(e); | ||
| } | ||
| } | ||
| , HandshakeResponse::new, ThreadPool.Names.GENERIC | ||
| , in -> new HandshakeResponse(in, requireCompatibleBuild), ThreadPool.Names.GENERIC | ||
| )); | ||
| } | ||
|
|
||
|
|
@@ -504,28 +524,89 @@ private HandshakeRequest() { | |
| } | ||
|
|
||
| public static class HandshakeResponse extends TransportResponse { | ||
|
|
||
| private static final Version BUILD_HASH_HANDSHAKE_VERSION = Version.V_7_11_0; | ||
|
|
||
| private final Version version; | ||
|
|
||
| @Nullable // if version < BUILD_HASH_HANDSHAKE_VERSION | ||
| private final String buildHash; | ||
|
|
||
| private final DiscoveryNode discoveryNode; | ||
|
|
||
| private final ClusterName clusterName; | ||
| private final Version version; | ||
|
|
||
| public HandshakeResponse(DiscoveryNode discoveryNode, ClusterName clusterName, Version version) { | ||
| this.discoveryNode = discoveryNode; | ||
| this.version = version; | ||
| this.clusterName = clusterName; | ||
| public HandshakeResponse(Version version, String buildHash, DiscoveryNode discoveryNode, ClusterName clusterName) { | ||
| this.buildHash = Objects.requireNonNull(buildHash); | ||
| this.discoveryNode = Objects.requireNonNull(discoveryNode); | ||
| this.version = Objects.requireNonNull(version); | ||
| this.clusterName = Objects.requireNonNull(clusterName); | ||
| } | ||
|
|
||
| public HandshakeResponse(StreamInput in) throws IOException { | ||
| public HandshakeResponse(StreamInput in, boolean requireCompatibleBuild) throws IOException { | ||
| super(in); | ||
| discoveryNode = in.readOptionalWriteable(DiscoveryNode::new); | ||
| clusterName = new ClusterName(in); | ||
| version = Version.readVersion(in); | ||
| if (in.getVersion().onOrAfter(BUILD_HASH_HANDSHAKE_VERSION)) { | ||
| // the first two fields need only VInts and raw (ASCII) characters, so we cross our fingers and hope that they appear | ||
| // on the wire as we expect them to even if this turns out to be an incompatible build | ||
| version = Version.readVersion(in); | ||
| buildHash = in.readString(); | ||
|
|
||
| try { | ||
| // If the remote node is incompatible then make an effort to identify it anyway, so we can mention it in the exception | ||
| // message, but recognise that this may fail | ||
| discoveryNode = new DiscoveryNode(in); | ||
| } catch (Exception e) { | ||
| if (isIncompatibleBuild(version, buildHash, requireCompatibleBuild)) { | ||
| throw new IllegalArgumentException("unidentifiable remote node is build [" + buildHash + | ||
| "] of version [" + version + "] but this node is build [" + Build.CURRENT.hash() + | ||
| "] of version [" + Version.CURRENT + "] which has an incompatible wire format", e); | ||
| } else { | ||
| throw e; | ||
| } | ||
| } | ||
|
|
||
| if (isIncompatibleBuild(version, buildHash, requireCompatibleBuild)) { | ||
| if (PERMIT_HANDSHAKES_FROM_INCOMPATIBLE_BUILDS) { | ||
| logger.warn("remote node [{}] is build [{}] of version [{}] but this node is build [{}] of version [{}] " + | ||
| "which may not be compatible; remove system property [{}] to resolve this warning", | ||
| discoveryNode, buildHash, version, Build.CURRENT.hash(), Version.CURRENT, | ||
| PERMIT_HANDSHAKES_FROM_INCOMPATIBLE_BUILDS_KEY); | ||
| } else { | ||
| throw new IllegalArgumentException("remote node [" + discoveryNode + "] is build [" + buildHash + | ||
| "] of version [" + version + "] but this node is build [" + Build.CURRENT.hash() + | ||
| "] of version [" + Version.CURRENT + "] which has an incompatible wire format"); | ||
| } | ||
| } | ||
|
|
||
| clusterName = new ClusterName(in); | ||
| } else { | ||
| discoveryNode = in.readOptionalWriteable(DiscoveryNode::new); | ||
| clusterName = new ClusterName(in); | ||
| version = Version.readVersion(in); | ||
| buildHash = null; | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public void writeTo(StreamOutput out) throws IOException { | ||
| out.writeOptionalWriteable(discoveryNode); | ||
| clusterName.writeTo(out); | ||
| Version.writeVersion(version, out); | ||
| if (out.getVersion().onOrAfter(BUILD_HASH_HANDSHAKE_VERSION)) { | ||
| Version.writeVersion(version, out); | ||
| out.writeString(buildHash); | ||
| discoveryNode.writeTo(out); | ||
| clusterName.writeTo(out); | ||
| } else { | ||
| out.writeOptionalWriteable(discoveryNode); | ||
| clusterName.writeTo(out); | ||
| Version.writeVersion(version, out); | ||
| } | ||
| } | ||
|
|
||
| public Version getVersion() { | ||
| return version; | ||
| } | ||
|
|
||
| public String getBuildHash() { | ||
| return buildHash; | ||
| } | ||
|
|
||
| public DiscoveryNode getDiscoveryNode() { | ||
|
|
@@ -535,6 +616,10 @@ public DiscoveryNode getDiscoveryNode() { | |
| public ClusterName getClusterName() { | ||
| return clusterName; | ||
| } | ||
|
|
||
| private static boolean isIncompatibleBuild(Version version, String buildHash, boolean requireCompatibleBuild) { | ||
| return requireCompatibleBuild && version == Version.CURRENT && Build.CURRENT.hash().equals(buildHash) == false; | ||
| } | ||
| } | ||
|
|
||
| public void disconnectFromNode(DiscoveryNode node) { | ||
|
|
@@ -1354,4 +1439,17 @@ public void onResponseReceived(long requestId, Transport.ResponseContext holder) | |
| } | ||
| } | ||
| } | ||
|
|
||
| private static boolean getPermitHandshakesFromIncompatibleBuilds() { | ||
| final String value = System.getProperty(PERMIT_HANDSHAKES_FROM_INCOMPATIBLE_BUILDS_KEY); | ||
| if (value == null) { | ||
| return false; | ||
| } | ||
| if (Boolean.parseBoolean(value)) { | ||
| return true; | ||
| } | ||
| throw new IllegalArgumentException("invalid value [" + value + "] for system property [" | ||
| + PERMIT_HANDSHAKES_FROM_INCOMPATIBLE_BUILDS_KEY + "]"); | ||
| } | ||
|
|
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NIT: maybe just put the logic in this method in a
static { }block below these two constants to make this easier to read in one go?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yeah not sure why I didn't do that. Done in 8132bf3