private bool TryAuthorize(RavenConfiguration configuration, Stream stream, TcpConnectionHeaderMessage header, out string msg) { msg = null; if (configuration.Security.AuthenticationEnabled == false) { return(true); } if (!(stream is SslStream sslStream)) { msg = "TCP connection is required to use SSL when authentication is enabled"; return(false); } var certificate = (X509Certificate2)sslStream.RemoteCertificate; var auth = AuthenticateConnectionCertificate(certificate); switch (auth.Status) { case AuthenticationStatus.Expired: msg = "The provided client certificate " + certificate.FriendlyName + " is expired on " + certificate.NotAfter; return(false); case AuthenticationStatus.NotYetValid: msg = "The provided client certificate " + certificate.FriendlyName + " is not yet valid because it starts on " + certificate.NotBefore; return(false); case AuthenticationStatus.ClusterAdmin: case AuthenticationStatus.Operator: msg = "Admin can do it all"; return(true); case AuthenticationStatus.Allowed: switch (header.Operation) { case TcpConnectionHeaderMessage.OperationTypes.Cluster: case TcpConnectionHeaderMessage.OperationTypes.Heartbeats: msg = header.Operation + " is a server wide operation and the certificate " + certificate.FriendlyName + "is not ClusterAdmin/Operator"; return(false); case TcpConnectionHeaderMessage.OperationTypes.Subscription: case TcpConnectionHeaderMessage.OperationTypes.Replication: if (auth.CanAccess(header.DatabaseName, requireAdmin: false)) { return(true); } msg = "The certificate " + certificate.FriendlyName + " does not allow access to " + header.DatabaseName; return(false); default: throw new InvalidOperationException("Unknown operation " + header.Operation); } default: msg = "Cannot allow access to a certificate with status: " + auth.Status; return(false); } }
private bool MatchingOperationVersion(TcpConnectionHeaderMessage header, out string error) { var version = TcpConnectionHeaderMessage.GetOperationTcpVersion(header.Operation); if (version == header.OperationVersion) { error = null; return(true); } error = $"Message of type {header.Operation} version should be {version} but got a message with version {header.OperationVersion}"; return(false); }
internal static TcpConnectionHeaderMessage.SupportedFeatures NegotiateProtocolVersion(this TcpNegotiation.SyncTcpNegotiation syncTcpNegotiation, JsonOperationContext context, Stream stream, TcpNegotiateParameters parameters) { if (Log.IsInfoEnabled) { Log.Info($"Start negotiation for {parameters.Operation} operation with {parameters.DestinationNodeTag ?? parameters.DestinationUrl}"); } using (var writer = new BlittableJsonTextWriter(context, stream)) { var current = parameters.Version; while (true) { if (parameters.CancellationToken.IsCancellationRequested) { throw new OperationCanceledException($"Stopped TCP negotiation for {parameters.Operation} because of cancellation request"); } SendTcpVersionInfo(context, writer, parameters, current); var version = parameters.ReadResponseAndGetVersionCallback(context, writer, stream, parameters.DestinationUrl); if (Log.IsInfoEnabled) { Log.Info($"Read response from {parameters.SourceNodeTag ?? parameters.DestinationUrl} for '{parameters.Operation}', received version is '{version}'"); } if (version == current) { break; } //In this case we usually throw internally but for completeness we better handle it if (version == TcpNegotiation.DropStatus) { return(TcpConnectionHeaderMessage.GetSupportedFeaturesFor(TcpConnectionHeaderMessage.OperationTypes.Drop, TcpConnectionHeaderMessage.DropBaseLine)); } var status = TcpConnectionHeaderMessage.OperationVersionSupported(parameters.Operation, version, out current); if (status == TcpConnectionHeaderMessage.SupportedStatus.OutOfRange) { SendTcpVersionInfo(context, writer, parameters, TcpNegotiation.OutOfRangeStatus); throw new ArgumentException($"The {parameters.Operation} version {parameters.Version} is out of range, our lowest version is {current}"); } if (Log.IsInfoEnabled) { Log.Info($"The version {version} is {status}, will try to agree on '{current}' for {parameters.Operation} with {parameters.DestinationNodeTag ?? parameters.DestinationUrl}."); } } if (Log.IsInfoEnabled) { Log.Info($"{parameters.DestinationNodeTag ?? parameters.DestinationUrl} agreed on version '{current}' for {parameters.Operation}."); } return(TcpConnectionHeaderMessage.GetSupportedFeaturesFor(parameters.Operation, current)); } }
private void SendDropMessage(JsonOperationContext context, BlittableJsonTextWriter writer, TcpConnectionHeaderResponse headerResponse) { context.Write(writer, new DynamicJsonValue { [nameof(TcpConnectionHeaderMessage.DatabaseName)] = Destination.Database, [nameof(TcpConnectionHeaderMessage.Operation)] = TcpConnectionHeaderMessage.OperationTypes.Drop.ToString(), [nameof(TcpConnectionHeaderMessage.SourceNodeTag)] = _parent._server.NodeTag, [nameof(TcpConnectionHeaderMessage.OperationVersion)] = TcpConnectionHeaderMessage.GetOperationTcpVersion(TcpConnectionHeaderMessage.OperationTypes.Drop), [nameof(TcpConnectionHeaderMessage.Info)] = $"Couldn't agree on replication TCP version ours:{TcpConnectionHeaderMessage.ReplicationTcpVersion} theirs:{headerResponse.Version}" }); writer.Flush(); }
public ClusterMaintenanceWorker(TcpConnectionOptions tcp, CancellationToken externalToken, ServerStore serverStore, string leader, long term) { _tcp = tcp; _cts = CancellationTokenSource.CreateLinkedTokenSource(externalToken); _token = _cts.Token; _server = serverStore; _logger = LoggingSource.Instance.GetLogger <ClusterMaintenanceWorker>(serverStore.NodeTag); _name = $"Heartbeats worker connection to leader {leader} in term {term}"; WorkerSamplePeriod = _server.Configuration.Cluster.WorkerSamplePeriod.AsTimeSpan; CurrentTerm = term; SupportedFeatures = TcpConnectionHeaderMessage.GetSupportedFeaturesFor(TcpConnectionHeaderMessage.OperationTypes.Heartbeats, _tcp.ProtocolVersion); }
private int ReadHeaderResponseAndThrowIfUnAuthorized(JsonOperationContext jsonContext, BlittableJsonTextWriter writer, Stream stream, string url) { const int timeout = 2 * 60 * 1000; using (var replicationTcpConnectReplyMessage = _interruptibleRead.ParseToMemory( _connectionDisposed, "replication acknowledge response", timeout, _buffer, CancellationToken)) { if (replicationTcpConnectReplyMessage.Timeout) { ThrowTimeout(timeout); } if (replicationTcpConnectReplyMessage.Interrupted) { ThrowConnectionClosed(); } var headerResponse = JsonDeserializationServer.TcpConnectionHeaderResponse(replicationTcpConnectReplyMessage.Document); switch (headerResponse.Status) { case TcpConnectionStatus.Ok: return(headerResponse.Version); case TcpConnectionStatus.AuthorizationFailed: throw new AuthorizationException($"{Destination.FromString()} replied with failure {headerResponse.Message}"); case TcpConnectionStatus.TcpVersionMismatch: if (headerResponse.Version != -1) { return(headerResponse.Version); } //Kindly request the server to drop the connection jsonContext.Write(writer, new DynamicJsonValue { [nameof(TcpConnectionHeaderMessage.DatabaseName)] = Destination.Database, [nameof(TcpConnectionHeaderMessage.Operation)] = TcpConnectionHeaderMessage.OperationTypes.Drop.ToString(), [nameof(TcpConnectionHeaderMessage.SourceNodeTag)] = _parent._server.NodeTag, [nameof(TcpConnectionHeaderMessage.OperationVersion)] = TcpConnectionHeaderMessage.GetOperationTcpVersion(TcpConnectionHeaderMessage.OperationTypes.Drop), [nameof(TcpConnectionHeaderMessage.Info)] = $"Couldn't agree on replication tcp version ours:{TcpConnectionHeaderMessage.ReplicationTcpVersion} theirs:{headerResponse.Version}" }); writer.Flush(); throw new InvalidOperationException($"{Destination.FromString()} replied with failure {headerResponse.Message}"); default: throw new InvalidOperationException($"{Destination.FromString()} replied with unknown status {headerResponse.Status}, message:{headerResponse.Message}"); } } }
private static async ValueTask WriteOperationHeaderToRemote(AsyncBlittableJsonTextWriter writer, TcpConnectionHeaderMessage.OperationTypes operation, string databaseName) { writer.WriteStartObject(); { writer.WritePropertyName(nameof(TcpConnectionHeaderMessage.Operation)); writer.WriteString(operation.ToString()); writer.WriteComma(); writer.WritePropertyName(nameof(TcpConnectionHeaderMessage.OperationVersion)); writer.WriteInteger(TcpConnectionHeaderMessage.GetOperationTcpVersion(operation)); writer.WriteComma(); writer.WritePropertyName(nameof(TcpConnectionHeaderMessage.DatabaseName)); writer.WriteString(databaseName); } writer.WriteEndObject(); await writer.FlushAsync(); }
public override async Task <RachisConnection> ConnectToPeer(string url, string tag, X509Certificate2 certificate, CancellationToken token) { TimeSpan time; using (ContextPoolForReadOnlyOperations.AllocateOperationContext(out ClusterOperationContext ctx)) using (ctx.OpenReadTransaction()) { time = _parent.ElectionTimeout * (_parent.GetTopology(ctx).AllNodes.Count - 2); } var tcpClient = await TcpUtils.ConnectAsync(url, time, token : token); try { var stream = tcpClient.GetStream(); var conn = new RachisConnection { Stream = stream, SupportedFeatures = TcpConnectionHeaderMessage.GetSupportedFeaturesFor( TcpConnectionHeaderMessage.OperationTypes.Cluster, TcpConnectionHeaderMessage.ClusterWithMultiTree), Disconnect = () => { using (tcpClient) { tcpClient.Client.Disconnect(false); } } }; return(conn); } catch { using (tcpClient) { tcpClient.Client.Disconnect(false); } throw; } }
private async Task <bool> DispatchDatabaseTcpConnection(TcpConnectionOptions tcp, TcpConnectionHeaderMessage header) { var databaseLoadingTask = ServerStore.DatabasesLandlord.TryGetOrCreateResourceStore(header.DatabaseName); if (databaseLoadingTask == null) { DatabaseDoesNotExistException.Throw(header.DatabaseName); return(true); } var databaseLoadTimeout = ServerStore.DatabasesLandlord.DatabaseLoadTimeout; if (databaseLoadingTask.IsCompleted == false) { var resultingTask = await Task.WhenAny(databaseLoadingTask, Task.Delay(databaseLoadTimeout)); if (resultingTask != databaseLoadingTask) { ThrowTimeoutOnDatabaseLoad(header); } } tcp.DocumentDatabase = await databaseLoadingTask; if (tcp.DocumentDatabase == null) { DatabaseDoesNotExistException.Throw(header.DatabaseName); } Debug.Assert(tcp.DocumentDatabase != null); if (tcp.DocumentDatabase.DatabaseShutdown.IsCancellationRequested) { ThrowDatabaseShutdown(tcp.DocumentDatabase); } tcp.DocumentDatabase.RunningTcpConnections.Add(tcp); switch (header.Operation) { case TcpConnectionHeaderMessage.OperationTypes.Subscription: SubscriptionConnection.SendSubscriptionDocuments(tcp); break; case TcpConnectionHeaderMessage.OperationTypes.Replication: var documentReplicationLoader = tcp.DocumentDatabase.ReplicationLoader; documentReplicationLoader.AcceptIncomingConnection(tcp); break; default: throw new InvalidOperationException("Unknown operation for TCP " + header.Operation); } //since the responses to TCP connections mostly continue to run //beyond this point, no sense to dispose the connection now, so set it to null. //this way the responders are responsible to dispose the connection and the context // ReSharper disable once RedundantAssignment tcp = null; return(false); }
private async Task <bool> DispatchServerWideTcpConnection(TcpConnectionOptions tcp, TcpConnectionHeaderMessage header) { tcp.Operation = header.Operation; if (tcp.Operation == TcpConnectionHeaderMessage.OperationTypes.Cluster) { ServerStore.ClusterAcceptNewConnection(tcp.Stream); return(true); } if (tcp.Operation == TcpConnectionHeaderMessage.OperationTypes.Heartbeats) { // check for the term using (_tcpContextPool.AllocateOperationContext(out JsonOperationContext context)) using (var headerJson = await context.ParseToMemoryAsync( tcp.Stream, "maintenance-heartbeat-header", BlittableJsonDocumentBuilder.UsageMode.None, tcp.PinnedBuffer )) { var maintenanceHeader = JsonDeserializationRachis <ClusterMaintenanceSupervisor.ClusterMaintenanceConnectionHeader> .Deserialize(headerJson); if (_clusterMaintenanceWorker?.CurrentTerm > maintenanceHeader.Term) { if (_tcpLogger.IsInfoEnabled) { _tcpLogger.Info($"Request for maintenance with term {maintenanceHeader.Term} was rejected, " + $"because we are already connected to the recent leader with the term {_clusterMaintenanceWorker.CurrentTerm}"); } tcp.Dispose(); return(true); } var old = _clusterMaintenanceWorker; using (old) { _clusterMaintenanceWorker = new ClusterMaintenanceWorker(tcp, ServerStore.ServerShutdown, ServerStore, maintenanceHeader.Term); _clusterMaintenanceWorker.Start(); } return(true); } } return(false); }
private static void ThrowTimeoutOnDatabaseLoad(TcpConnectionHeaderMessage header) { throw new DatabaseLoadTimeoutException($"Timeout when loading database {header.DatabaseName}, try again later"); }
private static void ThrowNoSuchDatabase(TcpConnectionHeaderMessage header) { throw new DatabaseDoesNotExistsException("There is no database named " + header.DatabaseName); }