Ejemplo n.º 1
0
        public async Task Edit()
        {
            ServerStore.EnsureNotPassive();

            var feature    = HttpContext.Features.Get <IHttpAuthenticationFeature>() as RavenServer.AuthenticateConnection;
            var clientCert = feature?.Certificate;

            using (ServerStore.ContextPool.AllocateOperationContext(out TransactionOperationContext ctx))
                using (var certificateJson = ctx.ReadForDisk(RequestBodyStream(), "edit-certificate"))
                {
                    var newCertificate = JsonDeserializationServer.CertificateDefinition(certificateJson);

                    ValidateCertificateDefinition(newCertificate, ServerStore);

                    var key = Constants.Certificates.Prefix + newCertificate.Thumbprint;

                    CertificateDefinition existingCertificate;
                    using (ctx.OpenWriteTransaction())
                    {
                        var certificate = ServerStore.Cluster.Read(ctx, key);
                        if (certificate == null)
                        {
                            throw new InvalidOperationException($"Cannot edit permissions for certificate with thumbprint '{newCertificate.Thumbprint}'. It doesn't exist in the cluster.");
                        }

                        existingCertificate = JsonDeserializationServer.CertificateDefinition(certificate);

                        if ((existingCertificate.SecurityClearance == SecurityClearance.ClusterAdmin || existingCertificate.SecurityClearance == SecurityClearance.ClusterNode) && IsClusterAdmin() == false)
                        {
                            var clientCertDef = ReadCertificateFromCluster(ctx, Constants.Certificates.Prefix + clientCert?.Thumbprint);
                            throw new InvalidOperationException($"Cannot edit the certificate '{existingCertificate.Name}'. It has '{existingCertificate.SecurityClearance}' security clearance while the current client certificate being used has a lower clearance: {clientCertDef.SecurityClearance}");
                        }

                        if ((newCertificate.SecurityClearance == SecurityClearance.ClusterAdmin || newCertificate.SecurityClearance == SecurityClearance.ClusterNode) && IsClusterAdmin() == false)
                        {
                            var clientCertDef = ReadCertificateFromCluster(ctx, Constants.Certificates.Prefix + clientCert?.Thumbprint);
                            throw new InvalidOperationException($"Cannot edit security clearance to '{newCertificate.SecurityClearance}' for certificate '{existingCertificate.Name}'. Only a 'Cluster Admin' can do that and your current client certificate has a lower clearance: {clientCertDef.SecurityClearance}");
                        }

                        ServerStore.Cluster.DeleteLocalState(ctx, key);
                    }

                    var putResult = await ServerStore.PutValueInClusterAsync(new PutCertificateCommand(Constants.Certificates.Prefix + newCertificate.Thumbprint,
                                                                                                       new CertificateDefinition
                    {
                        Name = newCertificate.Name,
                        Certificate = existingCertificate.Certificate,
                        Permissions = newCertificate.Permissions,
                        SecurityClearance = newCertificate.SecurityClearance,
                        Thumbprint = existingCertificate.Thumbprint,
                        NotAfter = existingCertificate.NotAfter
                    }));

                    await ServerStore.Cluster.WaitForIndexNotification(putResult.Index);

                    NoContentStatus();
                    HttpContext.Response.StatusCode = (int)HttpStatusCode.Created;
                }
        }
Ejemplo n.º 2
0
        public async Task Generate()
        {
            // one of the first admin action is to create a certificate, so let
            // us also use that to indicate that we are the seed node
            ServerStore.EnsureNotPassive();
            using (ServerStore.ContextPool.AllocateOperationContext(out TransactionOperationContext ctx))
            {
                var stream = TryGetRequestFormStream("Options") ?? RequestBodyStream();

                var certificateJson = ctx.ReadForDisk(stream, "certificate-generation");

                var certificate = JsonDeserializationServer.CertificateDefinition(certificateJson);

                ValidateCertificate(certificate, ServerStore);

                if (certificate.SecurityClearance == SecurityClearance.ClusterAdmin && IsClusterAdmin() == false)
                {
                    var clientCert = (HttpContext.Features.Get <IHttpAuthenticationFeature>() as RavenServer.AuthenticateConnection)?.Certificate;
                    throw new InvalidOperationException($"Cannot generate the certificate '{certificate.Name}' with 'Cluster Admin' security clearance because the current client certificate being used has a lower clearance: {clientCert}");
                }

                if (Server.ClusterCertificateHolder?.Certificate == null)
                {
                    throw new InvalidOperationException($"Cannot generate the client certificate '{certificate.Name}' becuase the server certificate is not loaded. " +
                                                        $"You can supply a server certificate by using the following configuration keys: " +
                                                        $"'{RavenConfiguration.GetKey(x => x.Security.CertificatePath)}'/'{RavenConfiguration.GetKey(x => x.Security.CertificateExec)}'/" +
                                                        $"'{RavenConfiguration.GetKey(x => x.Security.ClusterCertificatePath)}'/'{RavenConfiguration.GetKey(x => x.Security.ClusterCertificateExec)}'. " +
                                                        $"For a more detailed explanation please read about authentication and certificates in the RavenDB documentation.");
                }

                // this creates a client certificate which is signed by the current server certificate
                var selfSignedCertificate = CertificateUtils.CreateSelfSignedClientCertificate(certificate.Name, Server.ClusterCertificateHolder);

                var res = await ServerStore.PutValueInClusterAsync(new PutCertificateCommand(Constants.Certificates.Prefix + selfSignedCertificate.Thumbprint,
                                                                                             new CertificateDefinition
                {
                    Name = certificate.Name,
                    // this does not include the private key, that is only for the client
                    Certificate = Convert.ToBase64String(selfSignedCertificate.Export(X509ContentType.Cert)),
                    Permissions = certificate.Permissions,
                    SecurityClearance = certificate.SecurityClearance,
                    Thumbprint = selfSignedCertificate.Thumbprint
                }));

                await ServerStore.Cluster.WaitForIndexNotification(res.Index);

                HttpContext.Response.StatusCode = (int)HttpStatusCode.Created;

                var contentDisposition = "attachment; filename=" + Uri.EscapeDataString(certificate.Name) + ".pfx";
                HttpContext.Response.Headers["Content-Disposition"] = contentDisposition;
                HttpContext.Response.ContentType = "binary/octet-stream";
                var pfx = selfSignedCertificate.Export(X509ContentType.Pfx, certificate.Password);
                HttpContext.Response.Body.Write(pfx, 0, pfx.Length);
            }
        }
Ejemplo n.º 3
0
        public async Task ReplaceClusterCert()
        {
            var replaceImmediately = GetBoolValueQueryString("replaceImmediately", required: false) ?? false;

            ServerStore.EnsureNotPassive();
            using (ServerStore.ContextPool.AllocateOperationContext(out TransactionOperationContext ctx))
                using (var certificateJson = ctx.ReadForDisk(RequestBodyStream(), "replace-cluster-cert"))
                {
                    try
                    {
                        var certificate = JsonDeserializationServer.CertificateDefinition(certificateJson);

                        if (string.IsNullOrWhiteSpace(certificate.Name))
                        {
                            certificate.Name = "Cluster-Wide Certificate";
                        }

                        // This restriction should be removed when updating to .net core 2.1 when export of collection is fixed in Linux.
                        // With export, we'll be able to load the certificate and export it without a password, and propogate it through the cluster.
                        if (string.IsNullOrWhiteSpace(certificate.Password) == false)
                        {
                            throw new NotSupportedException("Replacing the cluster certificate with a password protected certificates is currently not supported.");
                        }

                        if (string.IsNullOrWhiteSpace(certificate.Certificate))
                        {
                            throw new ArgumentException($"{nameof(certificate.Certificate)} is a required field in the certificate definition.");
                        }

                        if (IsClusterAdmin() == false)
                        {
                            throw new InvalidOperationException("Cannot replace the server certificate. Only a ClusterAdmin can do this.");
                        }

                        var timeoutTask = TimeoutManager.WaitFor(TimeSpan.FromSeconds(60), ServerStore.ServerShutdown);

                        var replicationTask = Server.StartCertificateReplicationAsync(certificate.Certificate, certificate.Name, replaceImmediately);

                        await Task.WhenAny(replicationTask, timeoutTask);

                        if (replicationTask.IsCompleted == false)
                        {
                            throw new TimeoutException("Timeout when trying to replace the server certificate.");
                        }
                    }
                    catch (Exception e)
                    {
                        throw new InvalidOperationException("Failed to replace the server certificate.", e);
                    }
                }

            NoContentStatus();
            HttpContext.Response.StatusCode = (int)HttpStatusCode.Created;
        }
Ejemplo n.º 4
0
        private CertificateDefinition ReadCertificateFromCluster(TransactionOperationContext ctx, string key)
        {
            var certificate = ServerStore.Cluster.Read(ctx, key);

            if (certificate == null)
            {
                return(null);
            }

            return(JsonDeserializationServer.CertificateDefinition(certificate));
        }
Ejemplo n.º 5
0
        public async Task RemoteWatch()
        {
            var thumbprint = GetStringQueryString("thumbprint", required: false);

            CertificateDefinition clientConnectedCertificate = null;

            var canAccessDatabase = GetDatabaseAccessValidationFunc();

            var currentCertificate = GetCurrentCertificate();

            if (string.IsNullOrEmpty(thumbprint) == false && currentCertificate != null && thumbprint != currentCertificate.Thumbprint)
            {
                using (ServerStore.ContextPool.AllocateOperationContext(out TransactionOperationContext ctx))
                {
                    using (ctx.OpenReadTransaction())
                    {
                        var certByThumbprint = ServerStore.Cluster.GetCertificateByThumbprint(ctx, thumbprint) ?? ServerStore.Cluster.GetLocalStateByThumbprint(ctx, thumbprint);

                        if (certByThumbprint != null)
                        {
                            clientConnectedCertificate = JsonDeserializationServer.CertificateDefinition(certByThumbprint);
                        }
                    }

                    if (clientConnectedCertificate != null)
                    {
                        // we're already connected as ClusterAdmin, here we're just limiting the access to databases based on the thumbprint of the originally connected certificated
                        // so we'll send notifications only about relevant databases

                        var authenticationStatus = new RavenServer.AuthenticateConnection();

                        authenticationStatus.SetBasedOnCertificateDefinition(clientConnectedCertificate);

                        canAccessDatabase = GetDatabaseAccessValidationFunc(authenticationStatus);
                    }
                }
            }

            using (var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync())
            {
                try
                {
                    await SendNotifications(canAccessDatabase, webSocket);
                }
                catch (OperationCanceledException)
                {
                    // ignored
                }
                catch (Exception ex)
                {
                    await HandleException(ex, webSocket);
                }
            }
        }
Ejemplo n.º 6
0
        public Task GetHosts()
        {
            AssertOnlyInSetupMode();

            using (ServerStore.ContextPool.AllocateOperationContext(out JsonOperationContext context))
                using (var certificateJson = context.ReadForMemory(RequestBodyStream(), "setup-certificate"))
                {
                    var certDef = JsonDeserializationServer.CertificateDefinition(certificateJson);

                    X509Certificate2 certificate = null;
                    string           cn;

                    try
                    {
                        certificate = certDef.Password == null
                        ? new X509Certificate2(Convert.FromBase64String(certDef.Certificate))
                        : new X509Certificate2(Convert.FromBase64String(certDef.Certificate), certDef.Password);

                        cn = certificate.GetNameInfo(X509NameType.DnsName, false);
                    }
                    catch (Exception e)
                    {
                        throw new BadRequestException($"Failed to extract CN and SAN from certificate {certificate?.FriendlyName}. Maybe password is wrong?", e);
                    }

                    using (var writer = new BlittableJsonTextWriter(context, ResponseBodyStream()))
                    {
                        writer.WriteStartObject();
                        writer.WritePropertyName("CN");
                        writer.WriteString(cn);
                        writer.WriteComma();
                        writer.WritePropertyName("AlternativeNames");
                        writer.WriteStartArray();

                        var first = true;
                        foreach (var value in SetupManager.GetCertificateAlternativeNames(certificate))
                        {
                            if (first == false)
                            {
                                writer.WriteComma();
                            }
                            first = false;

                            writer.WriteString(value);
                        }
                        writer.WriteEndArray();

                        writer.WriteEndObject();
                    }
                }
            return(Task.CompletedTask);
        }
Ejemplo n.º 7
0
        public async Task Put()
        {
            // one of the first admin action is to create a certificate, so let
            // us also use that to indicate that we are the seed node

            ServerStore.EnsureNotPassive();
            using (ServerStore.ContextPool.AllocateOperationContext(out TransactionOperationContext ctx))
                using (var certificateJson = ctx.ReadForDisk(RequestBodyStream(), "put-certificate"))
                {
                    var certificate = JsonDeserializationServer.CertificateDefinition(certificateJson);

                    ValidateCertificateDefinition(certificate, ServerStore);

                    using (ctx.OpenReadTransaction())
                    {
                        var clientCert    = (HttpContext.Features.Get <IHttpAuthenticationFeature>() as RavenServer.AuthenticateConnection)?.Certificate;
                        var clientCertDef = ReadCertificateFromCluster(ctx, Constants.Certificates.Prefix + clientCert?.Thumbprint);

                        if ((certificate.SecurityClearance == SecurityClearance.ClusterAdmin || certificate.SecurityClearance == SecurityClearance.ClusterNode) && IsClusterAdmin() == false)
                        {
                            throw new InvalidOperationException($"Cannot save the certificate '{certificate.Name}' with '{certificate.SecurityClearance}' security clearance because the current client certificate being used has a lower clearance: {clientCertDef.SecurityClearance}");
                        }

                        if (string.IsNullOrWhiteSpace(certificate.Certificate))
                        {
                            throw new ArgumentException($"{nameof(certificate.Certificate)} is a mandatory property when saving an existing certificate");
                        }
                    }

                    byte[] certBytes;
                    try
                    {
                        certBytes = Convert.FromBase64String(certificate.Certificate);
                    }
                    catch (Exception e)
                    {
                        throw new ArgumentException($"Unable to parse the {nameof(certificate.Certificate)} property, expected a Base64 value", e);
                    }

                    try
                    {
                        await PutCertificateCollectionInCluster(certificate, certBytes, certificate.Password, ServerStore, ctx);
                    }
                    catch (Exception e)
                    {
                        throw new InvalidOperationException($"Failed to put certificate {certificate.Name} in the cluster.", e);
                    }

                    NoContentStatus();
                    HttpContext.Response.StatusCode = (int)HttpStatusCode.Created;
                }
        }
Ejemplo n.º 8
0
        public Task GetClusterCertificates()
        {
            var collection = new X509Certificate2Collection();

            using (ServerStore.ContextPool.AllocateOperationContext(out TransactionOperationContext context))
                using (context.OpenReadTransaction())
                {
                    List <(string ItemName, BlittableJsonReaderObject Value)> allItems = null;
                    try
                    {
                        allItems = ServerStore.Cluster.ItemsStartingWith(context, Constants.Certificates.Prefix, 0, int.MaxValue).ToList();
                        var clusterNodes = allItems.Select(item => JsonDeserializationServer.CertificateDefinition(item.Value))
                                           .Where(certificateDef => certificateDef.SecurityClearance == SecurityClearance.ClusterNode)
                                           .ToList();

                        if (clusterNodes.Count == 0)
                        {
                            throw new InvalidOperationException("Cannot get ClusterNode certificates, there should be at least one but it doesn't exist. This shouldn't happen!");
                        }

                        foreach (var cert in clusterNodes)
                        {
                            var x509Certificate2 = new X509Certificate2(Convert.FromBase64String(cert.Certificate));
                            collection.Import(x509Certificate2.Export(X509ContentType.Cert));
                        }
                    }
                    finally
                    {
                        if (allItems != null)
                        {
                            foreach (var cert in allItems)
                            {
                                cert.Value?.Dispose();
                            }
                        }
                    }
                }

            var pfx = collection.Export(X509ContentType.Pfx);

            var contentDisposition = "attachment; filename=ClusterCertificatesCollection.pfx";

            HttpContext.Response.Headers["Content-Disposition"] = contentDisposition;
            HttpContext.Response.ContentType = "binary/octet-stream";

            HttpContext.Response.StatusCode = (int)HttpStatusCode.Created;
            HttpContext.Response.Body.Write(pfx, 0, pfx.Length);

            return(Task.CompletedTask);
        }
Ejemplo n.º 9
0
        public async Task Put()
        {
            // one of the first admin action is to create a certificate, so let
            // us also use that to indicate that we are the seed node

            ServerStore.EnsureNotPassive();
            using (ServerStore.ContextPool.AllocateOperationContext(out TransactionOperationContext ctx))
                using (var certificateJson = ctx.ReadForDisk(RequestBodyStream(), "put-certificate"))
                {
                    var certificate = JsonDeserializationServer.CertificateDefinition(certificateJson);

                    ValidateCertificate(certificate, ServerStore);

                    if (certificate.SecurityClearance == SecurityClearance.ClusterAdmin && IsClusterAdmin() == false)
                    {
                        var clientCert = (HttpContext.Features.Get <IHttpAuthenticationFeature>() as RavenServer.AuthenticateConnection)?.Certificate;
                        throw new InvalidOperationException($"Cannot save the certificate '{certificate.Name}' with 'Cluster Admin' security clearance because the current client certificate being used has a lower clearance: {clientCert}");
                    }

                    if (string.IsNullOrWhiteSpace(certificate.Certificate))
                    {
                        throw new ArgumentException($"{nameof(certificate.Certificate)} is a mandatory property when saving an existing certificate");
                    }

                    byte[] certBytes;
                    try
                    {
                        certBytes = Convert.FromBase64String(certificate.Certificate);
                    }
                    catch (Exception e)
                    {
                        throw new ArgumentException($"Unable to parse the {nameof(certificate.Certificate)} property, expected a Base64 value", e);
                    }
                    var x509Certificate = new X509Certificate2(certBytes);
                    if (x509Certificate.HasPrivateKey)
                    {
                        // avoid storing the private key
                        certificate.Certificate = Convert.ToBase64String(x509Certificate.Export(X509ContentType.Cert));
                    }
                    certificate.Thumbprint = x509Certificate.Thumbprint;

                    var res = await ServerStore.PutValueInClusterAsync(new PutCertificateCommand(Constants.Certificates.Prefix + x509Certificate.Thumbprint, certificate));

                    await ServerStore.Cluster.WaitForIndexNotification(res.Index);

                    NoContentStatus();
                    HttpContext.Response.StatusCode = (int)HttpStatusCode.Created;
                }
        }
Ejemplo n.º 10
0
        public async Task Generate()
        {
            // one of the first admin action is to create a certificate, so let
            // us also use that to indicate that we are the seed node
            ServerStore.EnsureNotPassive();
            using (ServerStore.ContextPool.AllocateOperationContext(out TransactionOperationContext ctx))
                using (ctx.OpenReadTransaction())
                {
                    var operationId = GetLongQueryString("operationId", false);
                    if (operationId.HasValue == false)
                    {
                        operationId = ServerStore.Operations.GetNextOperationId();
                    }

                    var stream = TryGetRequestFromStream("Options") ?? RequestBodyStream();

                    var certificateJson = ctx.ReadForDisk(stream, "certificate-generation");

                    var certificate = JsonDeserializationServer.CertificateDefinition(certificateJson);

                    if (certificate.SecurityClearance == SecurityClearance.ClusterAdmin && IsClusterAdmin() == false)
                    {
                        var clientCert    = (HttpContext.Features.Get <IHttpAuthenticationFeature>() as RavenServer.AuthenticateConnection)?.Certificate;
                        var clientCertDef = ReadCertificateFromCluster(ctx, Constants.Certificates.Prefix + clientCert?.Thumbprint);
                        throw new InvalidOperationException($"Cannot generate the client certificate '{certificate.Name}' with 'Cluster Admin' security clearance because the current client certificate being used has a lower clearance: {clientCertDef.SecurityClearance}");
                    }

                    byte[] certs = null;
                    await
                    ServerStore.Operations.AddOperation(
                        null,
                        "Generate certificate: " + certificate.Name,
                        Documents.Operations.Operations.OperationType.CertificateGeneration,
                        async onProgress =>
                    {
                        certs = await GenerateCertificateInternal(certificate, ServerStore);

                        return(ClientCertificateGenerationResult.Instance);
                    },
                        operationId.Value);

                    var contentDisposition = "attachment; filename=" + Uri.EscapeDataString(certificate.Name) + ".zip";
                    HttpContext.Response.Headers["Content-Disposition"] = contentDisposition;
                    HttpContext.Response.ContentType = "application/octet-stream";

                    HttpContext.Response.Body.Write(certs, 0, certs.Length);
                }
        }
Ejemplo n.º 11
0
        internal static bool UpdateCertificatesTableInternal(UpdateStep step)
        {
            var schema = ClusterStateMachine.CertificatesSchema;

            var readCertsTable  = step.ReadTx.OpenTable(schema, ClusterStateMachine.CertificatesSlice);
            var writeCertsTable = step.WriteTx.OpenTable(schema, ClusterStateMachine.CertificatesSlice);

            using (var context = JsonOperationContext.ShortTermSingleUse())
            {
                foreach (var cert in readCertsTable.SeekByPrimaryKey(Slices.Empty, 0))
                {
                    (string key, BlittableJsonReaderObject doc) = GetCurrentItem(step.WriteTx, context, cert);

                    using (doc)
                    {
                        var def = JsonDeserializationServer.CertificateDefinition(doc);
                        From11.DropCertificatePrefixFromDefinition(def, out var touched);

                        var loweredKey = key.ToLowerInvariant();

                        if (loweredKey != key)
                        {
                            // we have upper cased key (thumbprint)
                            // let's remove current record from table and force writing it again with lower cased key value

                            using (Slice.From(step.WriteTx.Allocator, key, out Slice keySlice))
                                writeCertsTable.DeleteByKey(keySlice);

                            touched = true;
                        }

                        if (touched)
                        {
                            using (Slice.From(step.WriteTx.Allocator, def.PublicKeyPinningHash, out Slice hashSlice))
                                using (Slice.From(step.WriteTx.Allocator, loweredKey, out Slice keySlice))
                                    using (var newCert = context.ReadObject(def.ToJson(), "certificate/updated"))
                                    {
                                        ClusterStateMachine.UpdateCertificate(writeCertsTable, keySlice, hashSlice, newCert);
                                    }
                        }
                    }
                }
            }

            return(true);
        }
Ejemplo n.º 12
0
        public override void VerifyCanExecuteCommand(ServerStore store, TransactionOperationContext context, bool isClusterAdmin)
        {
            using (context.OpenReadTransaction())
            {
                var read = store.Cluster.Read(context, Name);
                if (read == null)
                {
                    return;
                }
                var definition = JsonDeserializationServer.CertificateDefinition(read);
                if (definition.SecurityClearance != SecurityClearance.ClusterAdmin)
                {
                    return;
                }
            }

            AssertClusterAdmin(isClusterAdmin);
        }
Ejemplo n.º 13
0
        public async Task Generate()
        {
            // one of the first admin action is to create a certificate, so let
            // us also use that to indicate that we are the seed node
            ServerStore.EnsureNotPassive();
            using (ServerStore.ContextPool.AllocateOperationContext(out TransactionOperationContext ctx))
            {
                var operationId = GetLongQueryString("operationId", false);
                if (operationId.HasValue == false)
                {
                    operationId = ServerStore.Operations.GetNextOperationId();
                }

                var stream = TryGetRequestFormStream("Options") ?? RequestBodyStream();

                var certificateJson = ctx.ReadForDisk(stream, "certificate-generation");

                var certificate = JsonDeserializationServer.CertificateDefinition(certificateJson);

                byte[] pfx = null;
                await
                ServerStore.Operations.AddOperation(
                    null,
                    "Generate certificate: " + certificate.Name,
                    Documents.Operations.Operations.OperationType.CertificateGeneration,
                    async onProgress =>
                {
                    pfx = await GenerateCertificateInternal(ctx, certificate);

                    return(ClientCertificateGenerationResult.Instance);
                },
                    operationId.Value);

                var contentDisposition = "attachment; filename=" + Uri.EscapeDataString(certificate.Name) + ".pfx";
                HttpContext.Response.Headers["Content-Disposition"] = contentDisposition;
                HttpContext.Response.ContentType = "binary/octet-stream";

                HttpContext.Response.StatusCode = (int)HttpStatusCode.Created;
                HttpContext.Response.Body.Write(pfx, 0, pfx.Length);
            }
        }
Ejemplo n.º 14
0
        public async Task Delete()
        {
            var thumbprint = GetQueryStringValueAndAssertIfSingleAndNotEmpty("thumbprint");

            var feature    = HttpContext.Features.Get <IHttpAuthenticationFeature>() as RavenServer.AuthenticateConnection;
            var clientCert = feature?.Certificate;

            if (clientCert != null && clientCert.Thumbprint.Equals(thumbprint))
            {
                throw new InvalidOperationException($"Cannot delete {clientCert.FriendlyName} becuase it's the current client certificate being used");
            }

            using (ServerStore.ContextPool.AllocateOperationContext(out TransactionOperationContext ctx))
            {
                var key = Constants.Certificates.Prefix + thumbprint;
                using (ctx.OpenWriteTransaction())
                {
                    var certificate = ServerStore.Cluster.Read(ctx, key);

                    var definition = JsonDeserializationServer.CertificateDefinition(certificate);

                    if (definition?.SecurityClearance == SecurityClearance.ClusterAdmin && IsClusterAdmin() == false)
                    {
                        throw new InvalidOperationException(
                                  $"Cannot delete the certificate '{definition?.Name}' with 'Cluster Admin' security clearance because the current client certificate being used has a lower clearance: {clientCert}");
                    }

                    ServerStore.Cluster.DeleteLocalState(ctx, key);
                }

                var res = await ServerStore.SendToLeaderAsync(new DeleteCertificateFromClusterCommand
                {
                    Name = Constants.Certificates.Prefix + thumbprint
                });

                await ServerStore.Cluster.WaitForIndexNotification(res.Index);

                HttpContext.Response.StatusCode = (int)HttpStatusCode.NoContent;
            }
        }
Ejemplo n.º 15
0
        public Task GetCertificates()
        {
            var thumbprint    = GetStringQueryString("thumbprint", required: false);
            var showSecondary = GetBoolValueQueryString("secondary", required: false) ?? false;

            var start    = GetStart();
            var pageSize = GetPageSize();

            using (ServerStore.ContextPool.AllocateOperationContext(out TransactionOperationContext context))
            {
                var certificates = new List <(string ItemName, BlittableJsonReaderObject Value)>();
                try
                {
                    if (string.IsNullOrEmpty(thumbprint))
                    {
                        if (ServerStore.CurrentRachisState == RachisState.Passive)
                        {
                            List <string> localCertKeys;
                            using (context.OpenReadTransaction())
                                localCertKeys = ServerStore.Cluster.GetCertificateKeysFromLocalState(context).ToList();

                            var serverCertKey = Constants.Certificates.Prefix + Server.Certificate.Certificate?.Thumbprint;
                            if (Server.Certificate.Certificate != null && localCertKeys.Contains(serverCertKey) == false)
                            {
                                // Since we didn't go through EnsureNotPassive(), the server certificate is not registered yet so we'll add it to the local state.
                                var serverCertDef = new CertificateDefinition
                                {
                                    Name              = "Server Certificate",
                                    Certificate       = Convert.ToBase64String(Server.Certificate.Certificate.Export(X509ContentType.Cert)),
                                    Permissions       = new Dictionary <string, DatabaseAccess>(),
                                    SecurityClearance = SecurityClearance.ClusterNode,
                                    Thumbprint        = Server.Certificate.Certificate.Thumbprint,
                                    NotAfter          = Server.Certificate.Certificate.NotAfter
                                };

                                var serverCert = context.ReadObject(serverCertDef.ToJson(), "Server/Certificate/Definition");
                                using (var tx = context.OpenWriteTransaction())
                                {
                                    ServerStore.Cluster.PutLocalState(context, Constants.Certificates.Prefix + Server.Certificate.Certificate.Thumbprint, serverCert);
                                    tx.Commit();
                                }
                                certificates.Add((serverCertKey, serverCert));
                            }

                            foreach (var localCertKey in localCertKeys)
                            {
                                BlittableJsonReaderObject localCertificate;
                                using (context.OpenReadTransaction())
                                    localCertificate = ServerStore.Cluster.GetLocalState(context, localCertKey);

                                if (localCertificate == null)
                                {
                                    continue;
                                }

                                var def = JsonDeserializationServer.CertificateDefinition(localCertificate);

                                if (showSecondary || string.IsNullOrEmpty(def.CollectionPrimaryKey))
                                {
                                    certificates.Add((localCertKey, localCertificate));
                                }
                                else
                                {
                                    localCertificate.Dispose();
                                }
                            }
                        }
                        else
                        {
                            using (context.OpenReadTransaction())
                            {
                                foreach (var item in ServerStore.Cluster.ItemsStartingWith(context, Constants.Certificates.Prefix, start, pageSize))
                                {
                                    var def = JsonDeserializationServer.CertificateDefinition(item.Value);

                                    if (showSecondary || string.IsNullOrEmpty(def.CollectionPrimaryKey))
                                    {
                                        certificates.Add(item);
                                    }
                                    else
                                    {
                                        item.Value.Dispose();
                                    }
                                }
                            }
                        }
                    }
                    else
                    {
                        using (context.OpenReadTransaction())
                        {
                            var key = Constants.Certificates.Prefix + thumbprint;

                            var certificate = ServerStore.CurrentRachisState == RachisState.Passive
                                ? ServerStore.Cluster.GetLocalState(context, key)
                                : ServerStore.Cluster.Read(context, key);

                            if (certificate == null)
                            {
                                HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
                                return(Task.CompletedTask);
                            }

                            var definition = JsonDeserializationServer.CertificateDefinition(certificate);
                            if (string.IsNullOrEmpty(definition.CollectionPrimaryKey) == false)
                            {
                                certificate = ServerStore.Cluster.Read(context, definition.CollectionPrimaryKey);
                                if (certificate == null)
                                {
                                    HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
                                    return(Task.CompletedTask);
                                }
                            }

                            certificates.Add((key, certificate));
                        }
                    }

                    using (var writer = new BlittableJsonTextWriter(context, ResponseBodyStream()))
                    {
                        writer.WriteStartObject();
                        writer.WriteArray(context, "Results", certificates.ToArray(), (w, c, cert) =>
                        {
                            c.Write(w, cert.Value);
                        });
                        writer.WriteComma();
                        writer.WritePropertyName("LoadedServerCert");
                        writer.WriteString(Server.Certificate.Certificate?.Thumbprint);
                        writer.WriteEndObject();
                    }
                }
                finally
                {
                    foreach (var cert in certificates)
                    {
                        cert.Value?.Dispose();
                    }
                }
            }

            return(Task.CompletedTask);
        }
Ejemplo n.º 16
0
        public async Task AddNode()
        {
            SetupCORSHeaders();

            var nodeUrl       = GetQueryStringValueAndAssertIfSingleAndNotEmpty("url");
            var watcher       = GetBoolValueQueryString("watcher", false);
            var assignedCores = GetIntValueQueryString("assignedCores", false);

            if (assignedCores <= 0)
            {
                throw new ArgumentException("Assigned cores must be greater than 0!");
            }

            nodeUrl = UrlHelper.TryGetLeftPart(nodeUrl);
            var remoteIsHttps = nodeUrl.StartsWith("https:", StringComparison.OrdinalIgnoreCase);

            if (HttpContext.Request.IsHttps != remoteIsHttps)
            {
                throw new InvalidOperationException($"Cannot add node '{nodeUrl}' to cluster because it will create invalid mix of HTTPS & HTTP endpoints. A cluster must be only HTTPS or only HTTP.");
            }

            NodeInfo nodeInfo;

            using (ServerStore.ContextPool.AllocateOperationContext(out TransactionOperationContext ctx))
                using (var requestExecutor = ClusterRequestExecutor.CreateForSingleNode(nodeUrl, Server.Certificate.Certificate))
                {
                    requestExecutor.DefaultTimeout = ServerStore.Engine.OperationTimeout;

                    var infoCmd = new GetNodeInfoCommand();
                    try
                    {
                        await requestExecutor.ExecuteAsync(infoCmd, ctx);
                    }
                    catch (AllTopologyNodesDownException e)
                    {
                        throw new InvalidOperationException($"Couldn't contact node at {nodeUrl}", e);
                    }

                    nodeInfo = infoCmd.Result;

                    if (ServerStore.IsPassive() && nodeInfo.TopologyId != null)
                    {
                        throw new TopologyMismatchException("You can't add new node to an already existing cluster");
                    }
                }

            if (assignedCores != null && assignedCores > nodeInfo.NumberOfCores)
            {
                throw new ArgumentException("Cannot add node because the assigned cores is larger " +
                                            $"than the available cores on that machine: {nodeInfo.NumberOfCores}");
            }

            ServerStore.EnsureNotPassive();

            if (assignedCores == null)
            {
                assignedCores = ServerStore.LicenseManager.GetCoresToAssign(nodeInfo.NumberOfCores);
            }

            Debug.Assert(assignedCores <= nodeInfo.NumberOfCores);

            ServerStore.LicenseManager.AssertCanAddNode(nodeUrl, assignedCores.Value);

            if (ServerStore.IsLeader())
            {
                using (ServerStore.ContextPool.AllocateOperationContext(out TransactionOperationContext ctx))
                {
                    string          topologyId;
                    ClusterTopology clusterTopology;
                    using (ctx.OpenReadTransaction())
                    {
                        clusterTopology = ServerStore.GetClusterTopology(ctx);
                        topologyId      = clusterTopology.TopologyId;
                    }

                    var possibleNode = clusterTopology.TryGetNodeTagByUrl(nodeUrl);
                    if (possibleNode.HasUrl)
                    {
                        throw new InvalidOperationException($"Can't add a new node on {nodeUrl} to cluster because this url is already used by node {possibleNode.NodeTag}");
                    }

                    if (nodeInfo.ServerId == ServerStore.GetServerId())
                    {
                        throw new InvalidOperationException($"Can't add a new node on {nodeUrl} to cluster because it's a synonym of the current node URL:{ServerStore.GetNodeHttpServerUrl()}");
                    }

                    if (nodeInfo.TopologyId != null)
                    {
                        if (topologyId != nodeInfo.TopologyId)
                        {
                            throw new TopologyMismatchException(
                                      $"Adding a new node to cluster failed. The new node is already in another cluster. " +
                                      $"Expected topology id: {topologyId}, but we get {nodeInfo.TopologyId}");
                        }

                        if (nodeInfo.CurrentState != RachisState.Passive)
                        {
                            throw new InvalidOperationException($"Can't add a new node on {nodeUrl} to cluster " +
                                                                $"because it's already in the cluster under tag :{nodeInfo.NodeTag} " +
                                                                $"and URL: {clusterTopology.GetUrlFromTag(nodeInfo.NodeTag)}");
                        }
                    }

                    var nodeTag = nodeInfo.NodeTag == RachisConsensus.InitialTag ? null : nodeInfo.NodeTag;
                    CertificateDefinition oldServerCert = null;
                    X509Certificate2      certificate   = null;

                    if (remoteIsHttps)
                    {
                        if (nodeInfo.Certificate == null)
                        {
                            throw new InvalidOperationException($"Cannot add node {nodeTag} with url {nodeUrl} to cluster because it has no certificate while trying to use HTTPS");
                        }

                        certificate = new X509Certificate2(Convert.FromBase64String(nodeInfo.Certificate), (string)null, X509KeyStorageFlags.MachineKeySet);

                        var now = DateTime.UtcNow;
                        if (certificate.NotBefore.ToUniversalTime() > now)
                        {
                            // Because of time zone and time drift issues, we can't assume that the certificate generation will be
                            // proper. Because of that, we allow tolerance of the NotBefore to be a bit earlier / later than the
                            // current time. Clients may still fail to work with our certificate because of timing issues,
                            // but the admin needs to setup time sync properly and there isn't much we can do at that point
                            if ((certificate.NotBefore.ToUniversalTime() - now).TotalDays > 1)
                            {
                                throw new InvalidOperationException(
                                          $"Cannot add node {nodeTag} with url {nodeUrl} to cluster because its certificate '{certificate.FriendlyName}' is not yet valid. It starts on {certificate.NotBefore}");
                            }
                        }

                        if (certificate.NotAfter.ToUniversalTime() < now)
                        {
                            throw new InvalidOperationException($"Cannot add node {nodeTag} with url {nodeUrl} to cluster because its certificate '{certificate.FriendlyName}' expired on {certificate.NotAfter}");
                        }

                        var expected = GetStringQueryString("expectedThumbprint", required: false);
                        if (expected != null)
                        {
                            if (certificate.Thumbprint != expected)
                            {
                                throw new InvalidOperationException($"Cannot add node {nodeTag} with url {nodeUrl} to cluster because its certificate thumbprint '{certificate.Thumbprint}' doesn't match the expected thumbprint '{expected}'.");
                            }
                        }

                        using (ctx.OpenReadTransaction())
                        {
                            var key      = Constants.Certificates.Prefix + certificate.Thumbprint;
                            var readCert = ServerStore.Cluster.Read(ctx, key);
                            if (readCert != null)
                            {
                                oldServerCert = JsonDeserializationServer.CertificateDefinition(readCert);
                            }
                        }

                        if (oldServerCert == null)
                        {
                            var certificateDefinition = new CertificateDefinition
                            {
                                Certificate       = nodeInfo.Certificate,
                                Thumbprint        = certificate.Thumbprint,
                                NotAfter          = certificate.NotAfter,
                                Name              = "Server Certificate for " + nodeUrl,
                                SecurityClearance = SecurityClearance.ClusterNode
                            };

                            var res = await ServerStore.PutValueInClusterAsync(new PutCertificateCommand(Constants.Certificates.Prefix + certificate.Thumbprint, certificateDefinition));

                            await ServerStore.Cluster.WaitForIndexNotification(res.Index);
                        }
                    }

                    await ServerStore.AddNodeToClusterAsync(nodeUrl, nodeTag, validateNotInTopology : false, asWatcher : watcher ?? false);

                    using (ctx.OpenReadTransaction())
                    {
                        clusterTopology = ServerStore.GetClusterTopology(ctx);
                        possibleNode    = clusterTopology.TryGetNodeTagByUrl(nodeUrl);
                        nodeTag         = possibleNode.HasUrl ? possibleNode.NodeTag : null;

                        if (certificate != null)
                        {
                            var key = Constants.Certificates.Prefix + certificate.Thumbprint;

                            var modifiedServerCert = JsonDeserializationServer.CertificateDefinition(ServerStore.Cluster.Read(ctx, key));

                            if (modifiedServerCert == null)
                            {
                                throw new ConcurrencyException("After adding the certificate, it was removed, shouldn't happen unless another admin removed it midway through.");
                            }

                            if (oldServerCert == null)
                            {
                                modifiedServerCert.Name = "Server certificate for Node " + nodeTag;
                            }
                            else
                            {
                                var value = "Node " + nodeTag;
                                if (modifiedServerCert.Name.Contains(value) == false)
                                {
                                    modifiedServerCert.Name += ", " + value;
                                }
                            }

                            var res = await ServerStore.PutValueInClusterAsync(new PutCertificateCommand(key, modifiedServerCert));

                            await ServerStore.Cluster.WaitForIndexNotification(res.Index);
                        }

                        var nodeDetails = new NodeDetails
                        {
                            NodeTag             = nodeTag,
                            AssignedCores       = assignedCores.Value,
                            NumberOfCores       = nodeInfo.NumberOfCores,
                            InstalledMemoryInGb = nodeInfo.InstalledMemoryInGb,
                            UsableMemoryInGb    = nodeInfo.UsableMemoryInGb,
                            BuildInfo           = nodeInfo.BuildInfo
                        };
                        await ServerStore.LicenseManager.CalculateLicenseLimits(nodeDetails, forceFetchingNodeInfo : true, waitToUpdate : true);
                    }

                    NoContentStatus();
                    return;
                }
            }
            RedirectToLeader();
        }
Ejemplo n.º 17
0
        public async Task Put()
        {
            // one of the first admin action is to create a certificate, so let
            // us also use that to indicate that we are the seed node

            ServerStore.EnsureNotPassive();
            using (ServerStore.ContextPool.AllocateOperationContext(out TransactionOperationContext ctx))
                using (var certificateJson = ctx.ReadForDisk(RequestBodyStream(), "put-certificate"))
                {
                    var certificate = JsonDeserializationServer.CertificateDefinition(certificateJson);

                    ValidateCertificate(certificate, ServerStore);

                    if (certificate.SecurityClearance == SecurityClearance.ClusterAdmin && IsClusterAdmin() == false)
                    {
                        var clientCert = (HttpContext.Features.Get <IHttpAuthenticationFeature>() as RavenServer.AuthenticateConnection)?.Certificate;
                        throw new InvalidOperationException($"Cannot save the certificate '{certificate.Name}' with 'Cluster Admin' security clearance because the current client certificate being used has a lower clearance: {clientCert}");
                    }

                    if (string.IsNullOrWhiteSpace(certificate.Certificate))
                    {
                        throw new ArgumentException($"{nameof(certificate.Certificate)} is a mandatory property when saving an existing certificate");
                    }

                    byte[] certBytes;
                    try
                    {
                        certBytes = Convert.FromBase64String(certificate.Certificate);
                    }
                    catch (Exception e)
                    {
                        throw new ArgumentException($"Unable to parse the {nameof(certificate.Certificate)} property, expected a Base64 value", e);
                    }
                    var x509Certificate = new X509Certificate2(certBytes);

                    if (PlatformDetails.RunningOnPosix)
                    {
                        // For the client certificate to work properly, we need that the issuer will be registered in the trusted root store.
                        // In Linux, when using SslStream AuthenticateAsServer, the server sends the list of allowed CAs to the client. So the client's issuer CA must be in the list. See RavenDB-8524
                        using (var currentUserStore = new X509Store(StoreName.Root, StoreLocation.CurrentUser))
                        {
                            currentUserStore.Open(OpenFlags.ReadOnly);
                            var userCerts = currentUserStore.Certificates.Find(X509FindType.FindByIssuerDistinguishedName, x509Certificate.IssuerName, true);

                            if (userCerts.Contains(x509Certificate) == false)
                            {
                                throw new InvalidOperationException($"Cannot save the client certificate '{certificate.Name}'. " +
                                                                    $"First, you must register the issuer certificate '{x509Certificate.IssuerName}' in the trusted root store, on the server machine." +
                                                                    "This step is required because you are using a self-signed certificate or one with unknown issuer.");
                            }
                        }
                    }

                    if (x509Certificate.HasPrivateKey)
                    {
                        // avoid storing the private key
                        certificate.Certificate = Convert.ToBase64String(x509Certificate.Export(X509ContentType.Cert));
                    }
                    certificate.Thumbprint = x509Certificate.Thumbprint;

                    var res = await ServerStore.PutValueInClusterAsync(new PutCertificateCommand(Constants.Certificates.Prefix + x509Certificate.Thumbprint, certificate));

                    await ServerStore.Cluster.WaitForIndexNotification(res.Index);

                    NoContentStatus();
                    HttpContext.Response.StatusCode = (int)HttpStatusCode.Created;
                }
        }
Ejemplo n.º 18
0
        public Task GetHosts()
        {
            AssertOnlyInSetupMode();

            using (ServerStore.ContextPool.AllocateOperationContext(out JsonOperationContext context))
                using (var certificateJson = context.ReadForMemory(RequestBodyStream(), "setup-certificate"))
                {
                    var certDef = JsonDeserializationServer.CertificateDefinition(certificateJson);

                    X509Certificate2 certificate = null;
                    string           cn;

                    try
                    {
                        certificate = certDef.Password == null
                        ? new X509Certificate2(Convert.FromBase64String(certDef.Certificate), (string)null, X509KeyStorageFlags.MachineKeySet)
                        : new X509Certificate2(Convert.FromBase64String(certDef.Certificate), certDef.Password, X509KeyStorageFlags.MachineKeySet);

                        cn = certificate.GetNameInfo(X509NameType.SimpleName, false);
                    }
                    catch (Exception e)
                    {
                        throw new BadRequestException($"Failed to extract the CN property from the certificate {certificate?.FriendlyName}. Maybe the password is wrong?", e);
                    }

                    if (cn == null)
                    {
                        throw new BadRequestException($"Failed to extract the CN property from the certificate. CN is null");
                    }

                    if (cn.LastIndexOf('*') > 0)
                    {
                        throw new NotSupportedException("The wildcard CN name contains a '*' which is not at the first character of the string. It is not supported in the Setup Wizard, you can do a manual setup instead.");
                    }

                    try
                    {
                        SecretProtection.ValidateKeyUsages("Setup Wizard", certificate);
                    }
                    catch (Exception e)
                    {
                        throw new InvalidOperationException($"Failed to load the uploaded certificate. Did you accidentally upload a client certificate?", e);
                    }

                    using (var writer = new BlittableJsonTextWriter(context, ResponseBodyStream()))
                    {
                        writer.WriteStartObject();
                        writer.WritePropertyName("CN");
                        writer.WriteString(cn);
                        writer.WriteComma();
                        writer.WritePropertyName("AlternativeNames");
                        writer.WriteStartArray();

                        var first = true;
                        foreach (var value in SetupManager.GetCertificateAlternativeNames(certificate))
                        {
                            if (first == false)
                            {
                                writer.WriteComma();
                            }
                            first = false;

                            writer.WriteString(value);
                        }

                        writer.WriteEndArray();

                        writer.WriteEndObject();
                    }
                }

            return(Task.CompletedTask);
        }
Ejemplo n.º 19
0
        public async Task AddNode()
        {
            SetupCORSHeaders();

            var nodeUrl       = GetStringQueryString("url").TrimEnd('/');
            var watcher       = GetBoolValueQueryString("watcher", false);
            var assignedCores = GetIntValueQueryString("assignedCores", false);

            if (assignedCores <= 0)
            {
                throw new ArgumentException("Assigned cores must be greater than 0!");
            }

            var remoteIsHttps = nodeUrl.StartsWith("https:", StringComparison.OrdinalIgnoreCase);

            if (HttpContext.Request.IsHttps != remoteIsHttps)
            {
                throw new InvalidOperationException($"Cannot add node '{nodeUrl}' to cluster because it will create invalid mix of HTTPS & HTTP endpoints. A cluster must be only HTTPS or only HTTP.");
            }

            NodeInfo nodeInfo;

            using (ServerStore.ContextPool.AllocateOperationContext(out TransactionOperationContext ctx))
                using (var requestExecutor = ClusterRequestExecutor.CreateForSingleNode(nodeUrl, Server.ClusterCertificateHolder.Certificate))
                {
                    requestExecutor.DefaultTimeout = ServerStore.Engine.OperationTimeout;

                    var infoCmd = new GetNodeInfoCommand();
                    try
                    {
                        await requestExecutor.ExecuteAsync(infoCmd, ctx);
                    }
                    catch (AllTopologyNodesDownException e)
                    {
                        throw new InvalidOperationException($"Couldn't contact node at {nodeUrl}", e);
                    }

                    nodeInfo = infoCmd.Result;

                    if (ServerStore.IsPassive() && nodeInfo.TopologyId != null)
                    {
                        throw new TopologyMismatchException("You can't add new node to an already existing cluster");
                    }
                }

            if (assignedCores == null)
            {
                assignedCores = ServerStore.LicenseManager.GetCoresToAssign(nodeInfo.NumberOfCores);
            }

            if (assignedCores != null && assignedCores > nodeInfo.NumberOfCores)
            {
                throw new ArgumentException("Cannot add node because the assigned cores is larger " +
                                            $"than the available cores on that machine: {nodeInfo.NumberOfCores}");
            }

            ServerStore.EnsureNotPassive();

            if (ServerStore.LicenseManager.CanAddNode(nodeUrl, assignedCores, out var licenseLimit) == false)
            {
                SetLicenseLimitResponse(licenseLimit);
                return;
            }

            if (ServerStore.IsLeader())
            {
                using (ServerStore.ContextPool.AllocateOperationContext(out TransactionOperationContext ctx))
                {
                    string topologyId;
                    using (ctx.OpenReadTransaction())
                    {
                        var clusterTopology = ServerStore.GetClusterTopology(ctx);
                        var possibleNode    = clusterTopology.TryGetNodeTagByUrl(nodeUrl);
                        if (possibleNode.HasUrl)
                        {
                            throw new InvalidOperationException($"Can't add a new node on {nodeUrl} to cluster because this url is already used by node {possibleNode.NodeTag}");
                        }
                        topologyId = clusterTopology.TopologyId;
                    }

                    if (nodeInfo.TopologyId != null && topologyId != nodeInfo.TopologyId)
                    {
                        throw new TopologyMismatchException(
                                  $"Adding a new node to cluster failed. The new node is already in another cluster. Expected topology id: {topologyId}, but we get {nodeInfo.TopologyId}");
                    }

                    var nodeTag = nodeInfo.NodeTag == RachisConsensus.InitialTag ? null : nodeInfo.NodeTag;
                    CertificateDefinition oldServerCert = null;
                    X509Certificate2      certificate   = null;

                    if (remoteIsHttps)
                    {
                        if (nodeInfo.Certificate == null)
                        {
                            throw  new InvalidOperationException($"Cannot add node {nodeTag} with url {nodeUrl} to cluster because it has no certificate while trying to use HTTPS");
                        }

                        certificate = new X509Certificate2(Convert.FromBase64String(nodeInfo.Certificate));

                        if (certificate.NotBefore > DateTime.UtcNow)
                        {
                            throw new InvalidOperationException($"Cannot add node {nodeTag} with url {nodeUrl} to cluster because its certificate '{certificate.FriendlyName}' is not yet valid. It starts on {certificate.NotBefore}");
                        }

                        if (certificate.NotAfter < DateTime.UtcNow)
                        {
                            throw new InvalidOperationException($"Cannot add node {nodeTag} with url {nodeUrl} to cluster because its certificate '{certificate.FriendlyName}' expired on {certificate.NotAfter}");
                        }

                        var expected = GetStringQueryString("expectedThumbrpint", required: false);
                        if (expected != null)
                        {
                            if (certificate.Thumbprint != expected)
                            {
                                throw new InvalidOperationException($"Cannot add node {nodeTag} with url {nodeUrl} to cluster because its certificate thumbprint '{certificate.Thumbprint}' doesn't match the expected thumbprint '{expected}'.");
                            }
                        }

                        using (ctx.OpenReadTransaction())
                        {
                            var key      = Constants.Certificates.Prefix + certificate.Thumbprint;
                            var readCert = ServerStore.Cluster.Read(ctx, key);
                            if (readCert != null)
                            {
                                oldServerCert = JsonDeserializationServer.CertificateDefinition(readCert);
                            }
                        }

                        if (oldServerCert == null)
                        {
                            var certificateDefinition = new CertificateDefinition
                            {
                                Certificate       = nodeInfo.Certificate,
                                Thumbprint        = certificate.Thumbprint,
                                Name              = "Server Certificate for " + nodeUrl,
                                SecurityClearance = SecurityClearance.ClusterNode
                            };

                            var res = await ServerStore.PutValueInClusterAsync(new PutCertificateCommand(Constants.Certificates.Prefix + certificate.Thumbprint, certificateDefinition));

                            await ServerStore.Cluster.WaitForIndexNotification(res.Index);
                        }
                    }

                    await ServerStore.AddNodeToClusterAsync(nodeUrl, nodeTag, validateNotInTopology : false, asWatcher : watcher ?? false);

                    using (ctx.OpenReadTransaction())
                    {
                        var clusterTopology = ServerStore.GetClusterTopology(ctx);
                        var possibleNode    = clusterTopology.TryGetNodeTagByUrl(nodeUrl);
                        nodeTag = possibleNode.HasUrl ? possibleNode.NodeTag : null;

                        if (certificate != null)
                        {
                            var key = Constants.Certificates.Prefix + certificate.Thumbprint;

                            var modifiedServerCert = JsonDeserializationServer.CertificateDefinition(ServerStore.Cluster.Read(ctx, key));

                            if (modifiedServerCert == null)
                            {
                                throw new ConcurrencyException("After adding the certificate, it was removed, shouldn't happen unless another admin removed it midway through.");
                            }

                            if (oldServerCert == null)
                            {
                                modifiedServerCert.Name = "Server certificate for Node " + nodeTag;
                            }
                            else
                            {
                                var value = "Node " + nodeTag;
                                if (modifiedServerCert.Name.Contains(value) == false)
                                {
                                    modifiedServerCert.Name += ", " + value;
                                }
                            }

                            var res = await ServerStore.PutValueInClusterAsync(new PutCertificateCommand(key, modifiedServerCert));

                            await ServerStore.Cluster.WaitForIndexNotification(res.Index);
                        }

                        ServerStore.LicenseManager.CalculateLicenseLimits(nodeTag, assignedCores, nodeInfo, forceFetchingNodeInfo: true);
                    }

                    NoContentStatus();
                    return;
                }
            }
            RedirectToLeader();
        }
Ejemplo n.º 20
0
        public async Task AddNode()
        {
            var nodeUrl       = GetQueryStringValueAndAssertIfSingleAndNotEmpty("url");
            var tag           = GetStringQueryString("tag", false);
            var watcher       = GetBoolValueQueryString("watcher", false);
            var raftRequestId = GetRaftRequestIdFromQuery();

            var maxUtilizedCores = GetIntValueQueryString("maxUtilizedCores", false);

            if (maxUtilizedCores != null && maxUtilizedCores <= 0)
            {
                throw new ArgumentException("Max utilized cores cores must be greater than 0");
            }

            nodeUrl = nodeUrl.Trim();
            if (Uri.IsWellFormedUriString(nodeUrl, UriKind.Absolute) == false)
            {
                throw new InvalidOperationException($"Given node URL '{nodeUrl}' is not in a correct format.");
            }

            nodeUrl = UrlHelper.TryGetLeftPart(nodeUrl);
            var remoteIsHttps = nodeUrl.StartsWith("https:", StringComparison.OrdinalIgnoreCase);

            if (HttpContext.Request.IsHttps != remoteIsHttps)
            {
                throw new InvalidOperationException($"Cannot add node '{nodeUrl}' to cluster because it will create invalid mix of HTTPS & HTTP endpoints. A cluster must be only HTTPS or only HTTP.");
            }

            tag = tag?.Trim();

            NodeInfo nodeInfo;

            using (ServerStore.ContextPool.AllocateOperationContext(out TransactionOperationContext ctx))
                using (var requestExecutor = ClusterRequestExecutor.CreateForSingleNode(nodeUrl, Server.Certificate.Certificate))
                {
                    requestExecutor.DefaultTimeout = ServerStore.Engine.OperationTimeout;

                    // test connection to remote.
                    var result = await ServerStore.TestConnectionToRemote(nodeUrl, database : null);

                    if (result.Success == false)
                    {
                        throw new InvalidOperationException(result.Error);
                    }

                    // test connection from remote to destination
                    result = await ServerStore.TestConnectionFromRemote(requestExecutor, ctx, nodeUrl);

                    if (result.Success == false)
                    {
                        throw new InvalidOperationException(result.Error);
                    }

                    var infoCmd = new GetNodeInfoCommand();
                    try
                    {
                        await requestExecutor.ExecuteAsync(infoCmd, ctx);
                    }
                    catch (AllTopologyNodesDownException e)
                    {
                        throw new InvalidOperationException($"Couldn't contact node at {nodeUrl}", e);
                    }

                    nodeInfo = infoCmd.Result;

                    if (SchemaUpgrader.CurrentVersion.ServerVersion != nodeInfo.ServerSchemaVersion)
                    {
                        var nodesVersion = nodeInfo.ServerSchemaVersion == 0 ? "Pre 4.2 version" : nodeInfo.ServerSchemaVersion.ToString();
                        throw new InvalidOperationException($"Can't add node with mismatched storage schema version.{Environment.NewLine}" +
                                                            $"My version is {SchemaUpgrader.CurrentVersion.ServerVersion}, while node's version is {nodesVersion}");
                    }

                    if (ServerStore.IsPassive() && nodeInfo.TopologyId != null)
                    {
                        throw new TopologyMismatchException("You can't add new node to an already existing cluster");
                    }
                }

            if (ServerStore.ValidateFixedPort && nodeInfo.HasFixedPort == false)
            {
                throw new InvalidOperationException($"Failed to add node '{nodeUrl}' to cluster. " +
                                                    $"Node '{nodeUrl}' has port '0' in 'Configuration.Core.ServerUrls' setting. " +
                                                    "Adding a node with non fixed port is forbidden. Define a fixed port for the node to enable cluster creation.");
            }

            await ServerStore.EnsureNotPassiveAsync();

            ServerStore.LicenseManager.AssertCanAddNode();

            if (ServerStore.IsLeader())
            {
                using (ServerStore.ContextPool.AllocateOperationContext(out TransactionOperationContext ctx))
                {
                    var clusterTopology = ServerStore.GetClusterTopology();

                    var possibleNode = clusterTopology.TryGetNodeTagByUrl(nodeUrl);
                    if (possibleNode.HasUrl)
                    {
                        throw new InvalidOperationException($"Can't add a new node on {nodeUrl} to cluster because this url is already used by node {possibleNode.NodeTag}");
                    }

                    if (nodeInfo.ServerId == ServerStore.GetServerId())
                    {
                        throw new InvalidOperationException($"Can't add a new node on {nodeUrl} to cluster because it's a synonym of the current node URL:{ServerStore.GetNodeHttpServerUrl()}");
                    }

                    if (nodeInfo.TopologyId != null)
                    {
                        AssertCanAddNodeWithTopologyId(clusterTopology, nodeInfo, nodeUrl);
                    }

                    var nodeTag = nodeInfo.NodeTag == RachisConsensus.InitialTag ? tag : nodeInfo.NodeTag;
                    CertificateDefinition oldServerCert = null;
                    X509Certificate2      certificate   = null;

                    if (remoteIsHttps)
                    {
                        if (nodeInfo.Certificate == null)
                        {
                            throw new InvalidOperationException($"Cannot add node {nodeTag} with url {nodeUrl} to cluster because it has no certificate while trying to use HTTPS");
                        }

                        certificate = new X509Certificate2(Convert.FromBase64String(nodeInfo.Certificate), (string)null, X509KeyStorageFlags.MachineKeySet);

                        var now = DateTime.UtcNow;
                        if (certificate.NotBefore.ToUniversalTime() > now)
                        {
                            // Because of time zone and time drift issues, we can't assume that the certificate generation will be
                            // proper. Because of that, we allow tolerance of the NotBefore to be a bit earlier / later than the
                            // current time. Clients may still fail to work with our certificate because of timing issues,
                            // but the admin needs to setup time sync properly and there isn't much we can do at that point
                            if ((certificate.NotBefore.ToUniversalTime() - now).TotalDays > 1)
                            {
                                throw new InvalidOperationException(
                                          $"Cannot add node {nodeTag} with url {nodeUrl} to cluster because its certificate '{certificate.FriendlyName}' is not yet valid. It starts on {certificate.NotBefore}");
                            }
                        }

                        if (certificate.NotAfter.ToUniversalTime() < now)
                        {
                            throw new InvalidOperationException($"Cannot add node {nodeTag} with url {nodeUrl} to cluster because its certificate '{certificate.FriendlyName}' expired on {certificate.NotAfter}");
                        }

                        var expected = GetStringQueryString("expectedThumbprint", required: false);
                        if (expected != null)
                        {
                            if (certificate.Thumbprint != expected)
                            {
                                throw new InvalidOperationException($"Cannot add node {nodeTag} with url {nodeUrl} to cluster because its certificate thumbprint '{certificate.Thumbprint}' doesn't match the expected thumbprint '{expected}'.");
                            }
                        }

                        // if it's the same server certificate as our own, we don't want to add it to the cluster
                        if (certificate.Thumbprint != Server.Certificate.Certificate.Thumbprint)
                        {
                            using (ctx.OpenReadTransaction())
                            {
                                var readCert = ServerStore.Cluster.GetCertificateByThumbprint(ctx, certificate.Thumbprint);
                                if (readCert != null)
                                {
                                    oldServerCert = JsonDeserializationServer.CertificateDefinition(readCert);
                                }
                            }

                            if (oldServerCert == null)
                            {
                                var certificateDefinition = new CertificateDefinition
                                {
                                    Certificate          = nodeInfo.Certificate,
                                    Thumbprint           = certificate.Thumbprint,
                                    PublicKeyPinningHash = certificate.GetPublicKeyPinningHash(),
                                    NotAfter             = certificate.NotAfter,
                                    Name = "Server Certificate for " + nodeUrl,
                                    SecurityClearance = SecurityClearance.ClusterNode
                                };

                                var res = await ServerStore.PutValueInClusterAsync(new PutCertificateCommand(certificate.Thumbprint, certificateDefinition,
                                                                                                             $"{raftRequestId}/put-new-certificate"));

                                await ServerStore.Cluster.WaitForIndexNotification(res.Index);
                            }
                        }
                    }

                    await ServerStore.AddNodeToClusterAsync(nodeUrl, nodeTag, validateNotInTopology : true, asWatcher : watcher ?? false);

                    using (ctx.OpenReadTransaction())
                    {
                        clusterTopology = ServerStore.GetClusterTopology(ctx);
                        possibleNode    = clusterTopology.TryGetNodeTagByUrl(nodeUrl);
                        nodeTag         = possibleNode.HasUrl ? possibleNode.NodeTag : null;

                        if (certificate != null && certificate.Thumbprint != Server.Certificate.Certificate.Thumbprint)
                        {
                            var modifiedServerCert = JsonDeserializationServer.CertificateDefinition(ServerStore.Cluster.GetCertificateByThumbprint(ctx, certificate.Thumbprint));

                            if (modifiedServerCert == null)
                            {
                                throw new ConcurrencyException("After adding the certificate, it was removed, shouldn't happen unless another admin removed it midway through.");
                            }

                            if (oldServerCert == null)
                            {
                                modifiedServerCert.Name = "Server certificate for Node " + nodeTag;
                            }
                            else
                            {
                                var value = "Node " + nodeTag;
                                if (modifiedServerCert.Name.Contains(value) == false)
                                {
                                    modifiedServerCert.Name += ", " + value;
                                }
                            }

                            var res = await ServerStore.PutValueInClusterAsync(new PutCertificateCommand(certificate.Thumbprint, modifiedServerCert, $"{raftRequestId}/put-modified-certificate"));

                            await ServerStore.Cluster.WaitForIndexNotification(res.Index);
                        }

                        var detailsPerNode = new DetailsPerNode
                        {
                            MaxUtilizedCores    = maxUtilizedCores,
                            NumberOfCores       = nodeInfo.NumberOfCores,
                            InstalledMemoryInGb = nodeInfo.InstalledMemoryInGb,
                            UsableMemoryInGb    = nodeInfo.UsableMemoryInGb,
                            BuildInfo           = nodeInfo.BuildInfo,
                            OsInfo = nodeInfo.OsInfo
                        };

                        var maxCores = ServerStore.LicenseManager.LicenseStatus.MaxCores;

                        try
                        {
                            await ServerStore.PutNodeLicenseLimitsAsync(nodeTag, detailsPerNode, maxCores, $"{raftRequestId}/put-license-limits");
                        }
                        catch
                        {
                            // we'll retry this again later
                        }
                    }

                    NoContentStatus();
                    return;
                }
            }
            RedirectToLeader();
        }
Ejemplo n.º 21
0
        public Task GetCertificates()
        {
            var thumbprint    = GetStringQueryString("thumbprint", required: false);
            var showSecondary = GetBoolValueQueryString("secondary", required: false) ?? false;

            var start    = GetStart();
            var pageSize = GetPageSize();

            using (ServerStore.ContextPool.AllocateOperationContext(out TransactionOperationContext context))
                using (context.OpenReadTransaction())
                {
                    var certificates = new List <(string ItemName, BlittableJsonReaderObject Value)>();
                    try
                    {
                        if (string.IsNullOrEmpty(thumbprint))
                        {
                            foreach (var item in ServerStore.Cluster.ItemsStartingWith(context, Constants.Certificates.Prefix, start, pageSize))
                            {
                                var def = JsonDeserializationServer.CertificateDefinition(item.Value);

                                if (showSecondary || string.IsNullOrEmpty(def.CollectionPrimaryKey))
                                {
                                    certificates.Add(item);
                                }
                            }
                        }
                        else
                        {
                            var key         = Constants.Certificates.Prefix + thumbprint;
                            var certificate = ServerStore.Cluster.Read(context, key);
                            if (certificate == null)
                            {
                                HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
                                return(Task.CompletedTask);
                            }

                            var definition = JsonDeserializationServer.CertificateDefinition(certificate);
                            if (string.IsNullOrEmpty(definition.CollectionPrimaryKey) == false)
                            {
                                certificate = ServerStore.Cluster.Read(context, definition.CollectionPrimaryKey);
                                if (certificate == null)
                                {
                                    HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
                                    return(Task.CompletedTask);
                                }
                            }

                            certificates.Add((key, certificate));
                        }

                        using (var writer = new BlittableJsonTextWriter(context, ResponseBodyStream()))
                        {
                            writer.WriteStartObject();
                            writer.WriteArray(context, "Results", certificates.ToArray(), (w, c, cert) =>
                            {
                                c.Write(w, cert.Value);
                            });
                            writer.WriteEndObject();
                        }
                    }
                    finally
                    {
                        foreach (var cert in certificates)
                        {
                            cert.Value?.Dispose();
                        }
                    }
                }

            return(Task.CompletedTask);
        }
Ejemplo n.º 22
0
        public async Task Put()
        {
            // one of the first admin action is to create a certificate, so let
            // us also use that to indicate that we are the seed node

            ServerStore.EnsureNotPassive();
            using (ServerStore.ContextPool.AllocateOperationContext(out TransactionOperationContext ctx))
                using (var certificateJson = ctx.ReadForDisk(RequestBodyStream(), "put-certificate"))
                {
                    var certificate = JsonDeserializationServer.CertificateDefinition(certificateJson);

                    ValidateCertificate(certificate, ServerStore);

                    if ((certificate.SecurityClearance == SecurityClearance.ClusterAdmin || certificate.SecurityClearance == SecurityClearance.ClusterNode) && IsClusterAdmin() == false)
                    {
                        var clientCert    = (HttpContext.Features.Get <IHttpAuthenticationFeature>() as RavenServer.AuthenticateConnection)?.Certificate;
                        var clientCertDef = ReadCertificateFromCluster(ctx, Constants.Certificates.Prefix + clientCert?.Thumbprint);
                        throw new InvalidOperationException($"Cannot save the certificate '{certificate.Name}' with '{certificate.SecurityClearance}' security clearance because the current client certificate being used has a lower clearance: {clientCertDef.SecurityClearance}");
                    }

                    if (string.IsNullOrWhiteSpace(certificate.Certificate))
                    {
                        throw new ArgumentException($"{nameof(certificate.Certificate)} is a mandatory property when saving an existing certificate");
                    }

                    byte[] certBytes;
                    try
                    {
                        certBytes = Convert.FromBase64String(certificate.Certificate);
                    }
                    catch (Exception e)
                    {
                        throw new ArgumentException($"Unable to parse the {nameof(certificate.Certificate)} property, expected a Base64 value", e);
                    }

                    var collection = new X509Certificate2Collection();
                    collection.Import(certBytes);

                    var first = true;
                    var collectionPrimaryKey = string.Empty;

                    foreach (var x509Certificate in collection)
                    {
                        var currentCertificate = new CertificateDefinition
                        {
                            Name              = certificate.Name,
                            Permissions       = certificate.Permissions,
                            SecurityClearance = certificate.SecurityClearance,
                            Password          = certificate.Password
                        };

                        if (x509Certificate.HasPrivateKey)
                        {
                            // avoid storing the private key
                            currentCertificate.Certificate = Convert.ToBase64String(x509Certificate.Export(X509ContentType.Cert));
                        }

                        // In case of a collection, we group all the certificates together and treat them as one unit.
                        // They all have the same name and permissions but a different thumbprint.
                        // The first certificate in the collection will be the primary certificate and its thumbprint will be the one shown in a GET request
                        // The other certificates are secondary certificates and will contain a link to the primary certificate.
                        currentCertificate.Thumbprint  = x509Certificate.Thumbprint;
                        currentCertificate.NotAfter    = x509Certificate.NotAfter;
                        currentCertificate.Certificate = Convert.ToBase64String(x509Certificate.Export(X509ContentType.Cert));

                        if (first)
                        {
                            var firstKey = Constants.Certificates.Prefix + x509Certificate.Thumbprint;
                            collectionPrimaryKey = firstKey;

                            foreach (var cert in collection)
                            {
                                if (Constants.Certificates.Prefix + cert.Thumbprint != firstKey)
                                {
                                    currentCertificate.CollectionSecondaryKeys.Add(Constants.Certificates.Prefix + cert.Thumbprint);
                                }
                            }
                        }
                        else
                        {
                            currentCertificate.CollectionPrimaryKey = collectionPrimaryKey;
                        }

                        var res = await ServerStore.PutValueInClusterAsync(new PutCertificateCommand(Constants.Certificates.Prefix + x509Certificate.Thumbprint, currentCertificate));

                        await ServerStore.Cluster.WaitForIndexNotification(res.Index);

                        first = false;
                    }

                    NoContentStatus();
                    HttpContext.Response.StatusCode = (int)HttpStatusCode.Created;
                }
        }
Ejemplo n.º 23
0
        private AuthenticateConnection AuthenticateConnectionCertificate(X509Certificate2 certificate)
        {
            var authenticationStatus = new AuthenticateConnection {
                Certificate = certificate
            };

            if (certificate == null)
            {
                authenticationStatus.Status = AuthenticationStatus.NoCertificateProvided;
            }
            else if (certificate.NotAfter.ToUniversalTime() < DateTime.UtcNow)
            {
                authenticationStatus.Status = AuthenticationStatus.Expired;
            }
            else if (certificate.NotBefore.ToUniversalTime() > DateTime.UtcNow)
            {
                authenticationStatus.Status = AuthenticationStatus.NotYetValid;
            }
            else if (certificate.Equals(ClusterCertificateHolder.Certificate))
            {
                authenticationStatus.Status = AuthenticationStatus.ClusterAdmin;
            }
            else
            {
                using (ServerStore.ContextPool.AllocateOperationContext(out TransactionOperationContext ctx))
                {
                    var certKey = Constants.Certificates.Prefix + certificate.Thumbprint;
                    BlittableJsonReaderObject cert;
                    using (ctx.OpenReadTransaction())
                    {
                        cert = ServerStore.Cluster.Read(ctx, certKey) ??
                               ServerStore.Cluster.GetLocalState(ctx, certKey);

                        if (cert == null && _sslProxyCertificate != null)
                        {
                            var proxyThumbprint = GetCertificateThumbprintFromProxy(certificate);
                            if (proxyThumbprint != null)
                            {
                                certKey = Constants.Certificates.Prefix + proxyThumbprint;
                                cert    = ServerStore.Cluster.Read(ctx, certKey) ??
                                          ServerStore.Cluster.GetLocalState(ctx, certKey);
                            }
                        }
                    }
                    if (cert == null)
                    {
                        authenticationStatus.Status = AuthenticationStatus.UnfamiliarCertificate;
                    }
                    else
                    {
                        var definition = JsonDeserializationServer.CertificateDefinition(cert);
                        authenticationStatus.Definition = definition;
                        if (definition.SecurityClearance == SecurityClearance.ClusterAdmin)
                        {
                            authenticationStatus.Status = AuthenticationStatus.ClusterAdmin;
                        }
                        else if (definition.SecurityClearance == SecurityClearance.Operator)
                        {
                            authenticationStatus.Status = AuthenticationStatus.Operator;
                        }
                        else
                        {
                            authenticationStatus.Status = AuthenticationStatus.Allowed;
                            foreach (var kvp in definition.Permissions)
                            {
                                authenticationStatus.AuthorizedDatabases.Add(kvp.Key, kvp.Value);
                            }
                        }
                    }
                }
            }
            return(authenticationStatus);
        }
Ejemplo n.º 24
0
        public async Task ReplaceClusterCert()
        {
            var replaceImmediately = GetBoolValueQueryString("replaceImmediately", required: false) ?? false;

            ServerStore.EnsureNotPassive();
            using (ServerStore.ContextPool.AllocateOperationContext(out TransactionOperationContext ctx))
                using (var certificateJson = ctx.ReadForDisk(RequestBodyStream(), "replace-cluster-cert"))
                {
                    try
                    {
                        var certificate = JsonDeserializationServer.CertificateDefinition(certificateJson);

                        if (string.IsNullOrWhiteSpace(certificate.Name))
                        {
                            certificate.Name = "Cluster-Wide Certificate";
                        }

                        if (string.IsNullOrWhiteSpace(certificate.Certificate))
                        {
                            throw new ArgumentException($"{nameof(certificate.Certificate)} is a required field in the certificate definition.");
                        }

                        if (IsClusterAdmin() == false)
                        {
                            throw new InvalidOperationException("Cannot replace the server certificate. Only a ClusterAdmin can do this.");
                        }

                        byte[] certBytes;
                        try
                        {
                            certBytes = Convert.FromBase64String(certificate.Certificate);
                        }
                        catch (Exception e)
                        {
                            throw new ArgumentException($"Unable to parse the {nameof(certificate.Certificate)} property, expected a Base64 value", e);
                        }

                        X509Certificate2 newCertificate;
                        try
                        {
                            newCertificate = new X509Certificate2(certBytes, certificate.Password, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.PersistKeySet);
                        }
                        catch (Exception e)
                        {
                            throw new InvalidOperationException("Failed to load the new certificate.", e);
                        }

                        var timeoutTask = TimeoutManager.WaitFor(TimeSpan.FromSeconds(60), ServerStore.ServerShutdown);

                        var replicationTask = Server.StartCertificateReplicationAsync(newCertificate, certificate.Name, replaceImmediately);

                        await Task.WhenAny(replicationTask, timeoutTask);

                        if (replicationTask.IsCompleted == false)
                        {
                            throw new TimeoutException("Timeout when trying to replace the server certificate.");
                        }
                    }
                    catch (Exception e)
                    {
                        throw new InvalidOperationException("Failed to replace the server certificate.", e);
                    }
                }

            NoContentStatus();
            HttpContext.Response.StatusCode = (int)HttpStatusCode.Created;
        }
Ejemplo n.º 25
0
        public bool Update(UpdateStep step)
        {
            ItemsSchema.Create(step.WriteTx, Items, 32);
            var itemsTable = step.WriteTx.OpenTable(ItemsSchema, Items);

            CertificatesSchema.Create(step.WriteTx, CertificatesSlice, 32);
            var certsTable = step.WriteTx.OpenTable(CertificatesSchema, CertificatesSlice);

            using (var context = JsonOperationContext.ShortTermSingleUse())
            {
                // First we'll update all the certs in the local state with the pinning hash
                var localCertKeys = GetCertificateKeysFromLocalState(step.ReadTx).ToList();

                foreach (var localCertKey in localCertKeys)
                {
                    using (var localCertificate = GetLocalStateByThumbprint(step.ReadTx, context, localCertKey))
                    {
                        if (localCertificate == null)
                        {
                            continue;
                        }

                        using (localCertificate)
                        {
                            var def = JsonDeserializationServer.CertificateDefinition(localCertificate);
                            def.PublicKeyPinningHash = new X509Certificate2(Convert.FromBase64String(def.Certificate)).GetPublicKeyPinningHash();

                            using (var cert = context.ReadObject(def.ToJson(), "updated/certificate"))
                                PutLocalState(step.WriteTx, localCertKey, cert);
                        }
                    }
                }

                // Read all the certs from the items table, add the pinning hash and store them in the new table. Then delete the original.
                var allClusterCerts = ItemsStartingWith(step.WriteTx, context, step.WriteTx.Allocator, Constants.Certificates.Prefix, 0, int.MaxValue).ToList();

                foreach (var cert in allClusterCerts)
                {
                    using (cert.Value)
                    {
                        var def = JsonDeserializationServer.CertificateDefinition(cert.Value);
                        def.PublicKeyPinningHash = new X509Certificate2(Convert.FromBase64String(def.Certificate)).GetPublicKeyPinningHash();

                        using (Slice.From(step.WriteTx.Allocator, def.PublicKeyPinningHash, out Slice hashSlice))
                            using (Slice.From(step.WriteTx.Allocator, cert.ItemName, out Slice oldKeySlice)) // includes the 'certificates/' prefix
                                using (Slice.From(step.WriteTx.Allocator, def.Thumbprint.ToLowerInvariant(), out Slice newKeySlice))
                                {
                                    // in this update we trim 'certificates/' prefix from key name, CollectionPrimaryKey and CollectionSecondaryKeys
                                    DropCertificatePrefixFromDefinition(def, out _);

                                    using (var newCert = context.ReadObject(def.ToJson(), "certificate/new/schema"))
                                    {
                                        ClusterStateMachine.UpdateCertificate(certsTable, newKeySlice, hashSlice, newCert);
                                        itemsTable.DeleteByKey(oldKeySlice);
                                    }
                                }
                    }
                }
            }

            return(true);
        }