Esempio n. 1
0
        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);
            }
        }
Esempio n. 2
0
        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();
 }
Esempio n. 5
0
        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);
        }
Esempio n. 6
0
        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}");
                }
            }
        }
Esempio n. 7
0
 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();
 }
Esempio n. 8
0
            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;
                }
            }
Esempio n. 9
0
        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);
        }
Esempio n. 10
0
        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);
        }
Esempio n. 11
0
 private static void ThrowTimeoutOnDatabaseLoad(TcpConnectionHeaderMessage header)
 {
     throw new DatabaseLoadTimeoutException($"Timeout when loading database {header.DatabaseName}, try again later");
 }
Esempio n. 12
0
 private static void ThrowNoSuchDatabase(TcpConnectionHeaderMessage header)
 {
     throw new DatabaseDoesNotExistsException("There is no database named " + header.DatabaseName);
 }