Esempio n. 1
0
        public void CertificateAndMasterKeyExecTest()
        {
            string script;
            IDictionary <string, string> customSettings = new ConcurrentDictionary <string, string>();
            var keyPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
            var buffer  = new byte[256 / 8];

            using (var cryptoRandom = new RNGCryptoServiceProvider())
            {
                cryptoRandom.GetBytes(buffer);
            }
            File.WriteAllBytes(keyPath, buffer);
            var certPath = GenerateAndSaveSelfSignedCertificate();

            if (PlatformDetails.RunningOnPosix)
            {
                var scriptPath = Path.Combine(Path.GetTempPath(), Path.ChangeExtension(Guid.NewGuid().ToString(), ".sh"));
                var keyArgs    = CommandLineArgumentEscaper.EscapeAndConcatenate(new List <string> {
                    scriptPath, keyPath
                });
                var certArgs = CommandLineArgumentEscaper.EscapeAndConcatenate(new List <string> {
                    scriptPath, certPath
                });

                customSettings[RavenConfiguration.GetKey(x => x.Security.MasterKeyExec)]                = "bash";
                customSettings[RavenConfiguration.GetKey(x => x.Security.MasterKeyExecArguments)]       = $"{keyArgs}";
                customSettings[RavenConfiguration.GetKey(x => x.Security.CertificateLoadExec)]          = "bash";
                customSettings[RavenConfiguration.GetKey(x => x.Security.CertificateLoadExecArguments)] = $"{certArgs}";
                customSettings[RavenConfiguration.GetKey(x => x.Core.ServerUrls)] = "https://" + Environment.MachineName + ":0";

                script = "#!/bin/bash\ncat \"$1\"";
                File.WriteAllText(scriptPath, script);
                Process.Start("chmod", $"700 {scriptPath}");
            }
            else
            {
                var scriptPath = Path.Combine(Path.GetTempPath(), Path.ChangeExtension(Guid.NewGuid().ToString(), ".ps1"));
                var keyArgs    = CommandLineArgumentEscaper.EscapeAndConcatenate(new List <string> {
                    "-NoProfile", scriptPath, keyPath
                });
                var certArgs = CommandLineArgumentEscaper.EscapeAndConcatenate(new List <string> {
                    "-NoProfile", scriptPath, certPath
                });

                customSettings[RavenConfiguration.GetKey(x => x.Security.MasterKeyExec)]                = "powershell";
                customSettings[RavenConfiguration.GetKey(x => x.Security.MasterKeyExecArguments)]       = $"{keyArgs}";
                customSettings[RavenConfiguration.GetKey(x => x.Security.CertificateLoadExec)]          = "powershell";
                customSettings[RavenConfiguration.GetKey(x => x.Security.CertificateRenewExec)]         = "powershell";
                customSettings[RavenConfiguration.GetKey(x => x.Security.CertificateChangeExec)]        = "powershell";
                customSettings[RavenConfiguration.GetKey(x => x.Security.CertificateLoadExecArguments)] = $"{certArgs}";
                customSettings[RavenConfiguration.GetKey(x => x.Core.ServerUrls)] = "https://" + Environment.MachineName + ":0";
                script = @"param([string]$userArg)
try {
    $bytes = Get-Content -path $userArg -encoding Byte
    $stdout = [System.Console]::OpenStandardOutput()
    $stdout.Write($bytes, 0, $bytes.Length)
}
catch {
    Write-Error $_.Exception
    exit 1
}
exit 0";
                File.WriteAllText(scriptPath, script);
            }

            UseNewLocalServer(customSettings: customSettings, runInMemory: false);
            // The master key loading is lazy, let's put a database secret key to invoke it.
            var dbName      = GetDatabaseName();
            var databaseKey = new byte[32];

            using (var rand = RandomNumberGenerator.Create())
            {
                rand.GetBytes(databaseKey);
            }
            var base64Key = Convert.ToBase64String(databaseKey);

            // sometimes when using `dotnet xunit` we get platform not supported from ProtectedData
            try
            {
                ProtectedData.Protect(Encoding.UTF8.GetBytes("Is supported?"), null, DataProtectionScope.CurrentUser);
            }
            catch (PlatformNotSupportedException)
            {
                return;
            }
            Server.ServerStore.PutSecretKey(base64Key, dbName, true);
            X509Certificate2 serverCertificate;

            try
            {
                serverCertificate = new X509Certificate2(certPath, (string)null, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet);
            }
            catch (CryptographicException e)
            {
                throw new CryptographicException($"Failed to load the test certificate from {certPath}.", e);
            }
            using (var store = GetDocumentStore(new Options
            {
                AdminCertificate = serverCertificate,
                ClientCertificate = serverCertificate,
                ModifyDatabaseName = s => dbName,
                ModifyDatabaseRecord = record => record.Encrypted = true,
                Path = NewDataPath()
            }))
            {
            }
            var secrets         = Server.ServerStore.Secrets;
            var serverMasterKey = (Lazy <byte[]>) typeof(SecretProtection).GetField("_serverMasterKey", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(secrets);

            Assert.True(serverMasterKey.Value.SequenceEqual(buffer));
            Assert.True(Server.Certificate.Certificate.Equals(serverCertificate));
        }
        public async Task CanReplaceClusterCertWithExtensionPoint()
        {
            string loadAndRenewScript;
            string certChangedScript;

            IDictionary <string, string> customSettings1 = new ConcurrentDictionary <string, string>();
            IDictionary <string, string> customSettings2 = new ConcurrentDictionary <string, string>();
            IDictionary <string, string> customSettings3 = new ConcurrentDictionary <string, string>();

            var certPath   = GenerateAndSaveSelfSignedCertificate();
            var cert2Path  = GenerateAndSaveSelfSignedCertificate(createNew: true);
            var outputFile = Path.Combine(Path.GetTempPath(), Path.ChangeExtension(Guid.NewGuid().ToString(), ".txt"));

            var firstServerCert = new X509Certificate2(certPath, (string)null, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet);
            var newServerCert   = new X509Certificate2(cert2Path, (string)null, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet);

            if (PlatformDetails.RunningOnPosix)
            {
                var loadAndRenewScriptNode1Path = Path.Combine(Path.GetTempPath(), Path.ChangeExtension(Guid.NewGuid().ToString(), ".sh"));
                var loadAndRenewScriptNode2Path = Path.Combine(Path.GetTempPath(), Path.ChangeExtension(Guid.NewGuid().ToString(), ".sh"));
                var loadAndRenewScriptNode3Path = Path.Combine(Path.GetTempPath(), Path.ChangeExtension(Guid.NewGuid().ToString(), ".sh"));
                var certChangedScriptNode1Path  = Path.Combine(Path.GetTempPath(), Path.ChangeExtension(Guid.NewGuid().ToString(), ".sh"));
                var certChangedScriptNode2Path  = Path.Combine(Path.GetTempPath(), Path.ChangeExtension(Guid.NewGuid().ToString(), ".sh"));
                var certChangedScriptNode3Path  = Path.Combine(Path.GetTempPath(), Path.ChangeExtension(Guid.NewGuid().ToString(), ".sh"));

                var loadArgsNode1 = CommandLineArgumentEscaper.EscapeAndConcatenate(new List <string> {
                    loadAndRenewScriptNode1Path, certPath
                });
                var renewArgsNode1 = CommandLineArgumentEscaper.EscapeAndConcatenate(new List <string> {
                    loadAndRenewScriptNode1Path, cert2Path
                });
                var certChangedArgsNode1 = CommandLineArgumentEscaper.EscapeAndConcatenate(new List <string> {
                    certChangedScriptNode1Path, outputFile
                });
                var loadArgsNode2 = CommandLineArgumentEscaper.EscapeAndConcatenate(new List <string> {
                    loadAndRenewScriptNode2Path, certPath
                });
                var renewArgsNode2 = CommandLineArgumentEscaper.EscapeAndConcatenate(new List <string> {
                    loadAndRenewScriptNode2Path, cert2Path
                });
                var certChangedArgsNode2 = CommandLineArgumentEscaper.EscapeAndConcatenate(new List <string> {
                    certChangedScriptNode2Path, outputFile
                });
                var loadArgsNode3 = CommandLineArgumentEscaper.EscapeAndConcatenate(new List <string> {
                    loadAndRenewScriptNode3Path, certPath
                });
                var renewArgsNode3 = CommandLineArgumentEscaper.EscapeAndConcatenate(new List <string> {
                    loadAndRenewScriptNode3Path, cert2Path
                });
                var certChangedArgsNode3 = CommandLineArgumentEscaper.EscapeAndConcatenate(new List <string> {
                    certChangedScriptNode3Path, outputFile
                });

                customSettings1[RavenConfiguration.GetKey(x => x.Security.CertificateLoadExec)]            = "bash";
                customSettings1[RavenConfiguration.GetKey(x => x.Security.CertificateLoadExecArguments)]   = $"{loadArgsNode1}";
                customSettings1[RavenConfiguration.GetKey(x => x.Security.CertificateRenewExec)]           = "bash";
                customSettings1[RavenConfiguration.GetKey(x => x.Security.CertificateRenewExecArguments)]  = $"{renewArgsNode1}";
                customSettings1[RavenConfiguration.GetKey(x => x.Security.CertificateChangeExec)]          = "bash";
                customSettings1[RavenConfiguration.GetKey(x => x.Security.CertificateChangeExecArguments)] = $"{certChangedArgsNode1}";
                customSettings1[RavenConfiguration.GetKey(x => x.Core.ServerUrls)] = "https://" + Environment.MachineName + ":0";

                customSettings2[RavenConfiguration.GetKey(x => x.Security.CertificateLoadExec)]            = "bash";
                customSettings2[RavenConfiguration.GetKey(x => x.Security.CertificateLoadExecArguments)]   = $"{loadArgsNode2}";
                customSettings2[RavenConfiguration.GetKey(x => x.Security.CertificateRenewExec)]           = "bash";
                customSettings2[RavenConfiguration.GetKey(x => x.Security.CertificateRenewExecArguments)]  = $"{renewArgsNode2}";
                customSettings2[RavenConfiguration.GetKey(x => x.Security.CertificateChangeExec)]          = "bash";
                customSettings2[RavenConfiguration.GetKey(x => x.Security.CertificateChangeExecArguments)] = $"{certChangedArgsNode2}";
                customSettings2[RavenConfiguration.GetKey(x => x.Core.ServerUrls)] = "https://" + Environment.MachineName + ":0";

                customSettings3[RavenConfiguration.GetKey(x => x.Security.CertificateLoadExec)]            = "bash";
                customSettings3[RavenConfiguration.GetKey(x => x.Security.CertificateLoadExecArguments)]   = $"{loadArgsNode3}";
                customSettings3[RavenConfiguration.GetKey(x => x.Security.CertificateRenewExec)]           = "bash";
                customSettings3[RavenConfiguration.GetKey(x => x.Security.CertificateRenewExecArguments)]  = $"{renewArgsNode3}";
                customSettings3[RavenConfiguration.GetKey(x => x.Security.CertificateChangeExec)]          = "bash";
                customSettings3[RavenConfiguration.GetKey(x => x.Security.CertificateChangeExecArguments)] = $"{certChangedArgsNode3}";
                customSettings3[RavenConfiguration.GetKey(x => x.Core.ServerUrls)] = "https://" + Environment.MachineName + ":0";

                loadAndRenewScript = "#!/bin/bash\ncat -u \"$1\"";
                certChangedScript  = "#!/bin/bash\nwhile read line; do\n\techo \"$line\" >> \"$1\"\ndone < /dev/stdin";

                File.WriteAllText(loadAndRenewScriptNode1Path, loadAndRenewScript);
                File.WriteAllText(loadAndRenewScriptNode2Path, loadAndRenewScript);
                File.WriteAllText(loadAndRenewScriptNode3Path, loadAndRenewScript);
                File.WriteAllText(certChangedScriptNode1Path, certChangedScript);
                File.WriteAllText(certChangedScriptNode2Path, certChangedScript);
                File.WriteAllText(certChangedScriptNode3Path, certChangedScript);

                Process.Start("chmod", $"700 {loadAndRenewScriptNode1Path}");
                Process.Start("chmod", $"700 {loadAndRenewScriptNode2Path}");
                Process.Start("chmod", $"700 {loadAndRenewScriptNode3Path}");
                Process.Start("chmod", $"700 {certChangedScriptNode1Path}");
                Process.Start("chmod", $"700 {certChangedScriptNode2Path}");
                Process.Start("chmod", $"700 {certChangedScriptNode3Path}");
            }
            else
            {
                var loadAndRenewScriptNode1Path = Path.Combine(Path.GetTempPath(), Path.ChangeExtension(Guid.NewGuid().ToString(), ".ps1"));
                var loadAndRenewScriptNode2Path = Path.Combine(Path.GetTempPath(), Path.ChangeExtension(Guid.NewGuid().ToString(), ".ps1"));
                var loadAndRenewScriptNode3Path = Path.Combine(Path.GetTempPath(), Path.ChangeExtension(Guid.NewGuid().ToString(), ".ps1"));
                var certChangedScriptNode1Path  = Path.Combine(Path.GetTempPath(), Path.ChangeExtension(Guid.NewGuid().ToString(), ".ps1"));
                var certChangedScriptNode2Path  = Path.Combine(Path.GetTempPath(), Path.ChangeExtension(Guid.NewGuid().ToString(), ".ps1"));
                var certChangedScriptNode3Path  = Path.Combine(Path.GetTempPath(), Path.ChangeExtension(Guid.NewGuid().ToString(), ".ps1"));

                var loadArgsNode1 = CommandLineArgumentEscaper.EscapeAndConcatenate(new List <string> {
                    "-NoProfile", loadAndRenewScriptNode1Path, certPath
                });
                var renewArgsNode1 = CommandLineArgumentEscaper.EscapeAndConcatenate(new List <string> {
                    "-NoProfile", loadAndRenewScriptNode1Path, cert2Path
                });
                var certChangedArgsNode1 = CommandLineArgumentEscaper.EscapeAndConcatenate(new List <string> {
                    "-NoProfile", certChangedScriptNode1Path, outputFile
                });
                var loadArgsNode2 = CommandLineArgumentEscaper.EscapeAndConcatenate(new List <string> {
                    "-NoProfile", loadAndRenewScriptNode2Path, certPath
                });
                var renewArgsNode2 = CommandLineArgumentEscaper.EscapeAndConcatenate(new List <string> {
                    "-NoProfile", loadAndRenewScriptNode2Path, cert2Path
                });
                var certChangedArgsNode2 = CommandLineArgumentEscaper.EscapeAndConcatenate(new List <string> {
                    "-NoProfile", certChangedScriptNode2Path, outputFile
                });
                var loadArgsNode3 = CommandLineArgumentEscaper.EscapeAndConcatenate(new List <string> {
                    "-NoProfile", loadAndRenewScriptNode3Path, certPath
                });
                var renewArgsNode3 = CommandLineArgumentEscaper.EscapeAndConcatenate(new List <string> {
                    "-NoProfile", loadAndRenewScriptNode3Path, cert2Path
                });
                var certChangedArgsNode3 = CommandLineArgumentEscaper.EscapeAndConcatenate(new List <string> {
                    "-NoProfile", certChangedScriptNode3Path, outputFile
                });

                customSettings1[RavenConfiguration.GetKey(x => x.Security.CertificateLoadExec)]            = "powershell";
                customSettings1[RavenConfiguration.GetKey(x => x.Security.CertificateLoadExecArguments)]   = $"{loadArgsNode1}";
                customSettings1[RavenConfiguration.GetKey(x => x.Security.CertificateRenewExec)]           = "powershell";
                customSettings1[RavenConfiguration.GetKey(x => x.Security.CertificateRenewExecArguments)]  = $"{renewArgsNode1}";
                customSettings1[RavenConfiguration.GetKey(x => x.Security.CertificateChangeExec)]          = "powershell";
                customSettings1[RavenConfiguration.GetKey(x => x.Security.CertificateChangeExecArguments)] = $"{certChangedArgsNode1}";
                customSettings1[RavenConfiguration.GetKey(x => x.Core.ServerUrls)] = "https://" + Environment.MachineName + ":0";

                customSettings2[RavenConfiguration.GetKey(x => x.Security.CertificateLoadExec)]            = "powershell";
                customSettings2[RavenConfiguration.GetKey(x => x.Security.CertificateLoadExecArguments)]   = $"{loadArgsNode2}";
                customSettings2[RavenConfiguration.GetKey(x => x.Security.CertificateRenewExec)]           = "powershell";
                customSettings2[RavenConfiguration.GetKey(x => x.Security.CertificateRenewExecArguments)]  = $"{renewArgsNode2}";
                customSettings2[RavenConfiguration.GetKey(x => x.Security.CertificateChangeExec)]          = "powershell";
                customSettings2[RavenConfiguration.GetKey(x => x.Security.CertificateChangeExecArguments)] = $"{certChangedArgsNode2}";
                customSettings2[RavenConfiguration.GetKey(x => x.Core.ServerUrls)] = "https://" + Environment.MachineName + ":0";

                customSettings3[RavenConfiguration.GetKey(x => x.Security.CertificateLoadExec)]            = "powershell";
                customSettings3[RavenConfiguration.GetKey(x => x.Security.CertificateLoadExecArguments)]   = $"{loadArgsNode3}";
                customSettings3[RavenConfiguration.GetKey(x => x.Security.CertificateRenewExec)]           = "powershell";
                customSettings3[RavenConfiguration.GetKey(x => x.Security.CertificateRenewExecArguments)]  = $"{renewArgsNode3}";
                customSettings3[RavenConfiguration.GetKey(x => x.Security.CertificateChangeExec)]          = "powershell";
                customSettings3[RavenConfiguration.GetKey(x => x.Security.CertificateChangeExecArguments)] = $"{certChangedArgsNode3}";
                customSettings3[RavenConfiguration.GetKey(x => x.Core.ServerUrls)] = "https://" + Environment.MachineName + ":0";


                loadAndRenewScript = @"param([string]$userArg1)
try {
    $bytes = Get-Content -path $userArg1 -encoding Byte
    $stdout = [System.Console]::OpenStandardOutput()
    $stdout.Write($bytes, 0, $bytes.Length)  
}
catch {
    Write-Error $_.Exception
    exit 1
}
exit 0";

                certChangedScript = @"param([string]$userArg1)
try {
    $certBase64 = [System.Console]::ReadLine()
    Add-Content $userArg1 $certBase64
}
catch {
    Write-Error $_.Exception
    exit 1
}
exit 0";

                File.WriteAllText(loadAndRenewScriptNode1Path, loadAndRenewScript);
                File.WriteAllText(loadAndRenewScriptNode2Path, loadAndRenewScript);
                File.WriteAllText(loadAndRenewScriptNode3Path, loadAndRenewScript);
                File.WriteAllText(certChangedScriptNode1Path, certChangedScript);
                File.WriteAllText(certChangedScriptNode2Path, certChangedScript);
                File.WriteAllText(certChangedScriptNode3Path, certChangedScript);
            }

            var clusterSize  = 3;
            var databaseName = GetDatabaseName();
            var leader       = await CreateRaftClusterAndGetLeader(clusterSize, false, useSsl : true, customSettingsList : new List <IDictionary <string, string> > {
                customSettings1, customSettings2, customSettings3
            });

            Assert.True(leader?.Certificate?.Certificate?.Thumbprint?.Equals(firstServerCert.Thumbprint), "Cert is identical");

            var adminCertificate = AskServerForClientCertificate(certPath, new Dictionary <string, DatabaseAccess>(), SecurityClearance.ClusterAdmin, server: leader);

            DatabasePutResult databaseResult;

            using (var store = new DocumentStore
            {
                Urls = new[] { leader.WebUrl },
                Database = databaseName,
                Certificate = adminCertificate,
                Conventions =
                {
                    DisableTopologyUpdates = true
                }
            }.Initialize())
            {
                var doc = new DatabaseRecord(databaseName);
                databaseResult = await store.Maintenance.Server.SendAsync(new CreateDatabaseOperation(doc, clusterSize));
            }

            Assert.Equal(clusterSize, databaseResult.Topology.AllNodes.Count());
            foreach (var server in Servers)
            {
                await server.ServerStore.Cluster.WaitForIndexNotification(databaseResult.RaftCommandIndex);
            }

            foreach (var server in Servers.Where(s => databaseResult.NodesAddedTo.Any(n => n == s.WebUrl)))
            {
                await server.ServerStore.DatabasesLandlord.TryGetOrCreateResourceStore(databaseName);
            }

            using (var store = new DocumentStore()
            {
                Urls = new[] { databaseResult.NodesAddedTo[0] },
                Database = databaseName,
                Certificate = adminCertificate,
                Conventions =
                {
                    DisableTopologyUpdates = true
                }
            }.Initialize())
            {
                using (var session = store.OpenAsyncSession())
                {
                    await session.StoreAsync(new User { Name = "Karmelush" }, "users/1");

                    await session.SaveChangesAsync();
                }

                var mre = new ManualResetEventSlim();

                leader.ServerCertificateChanged += (sender, args) => mre.Set();

                // This will initiate the refresh cycle, and ask a new certificate from the executable
                leader.RefreshClusterCertificate(false, RaftIdGenerator.NewId());

                Assert.True(mre.Wait(Debugger.IsAttached ? TimeSpan.FromMinutes(10) : TimeSpan.FromMinutes(2)), "Waited too long");
                Assert.NotNull(leader.Certificate.Certificate.Thumbprint);
                Assert.True(leader.Certificate.Certificate.Thumbprint.Equals(newServerCert.Thumbprint), "New cert is identical");

                var base64NewCertWrittenByExecutable = File.ReadAllLines(outputFile)[0];
                var loadedCertificate = new X509Certificate2(Convert.FromBase64String(base64NewCertWrittenByExecutable));

                Assert.Equal(newServerCert.Thumbprint, loadedCertificate.Thumbprint);

                using (var session = store.OpenSession())
                {
                    var user1 = session.Load <User>("users/1");
                    Assert.NotNull(user1);
                    Assert.Equal("Karmelush", user1.Name);
                }
            }
        }