예제 #1
0
        private static ProcessStartInfo GetProcessStartInfo(TLocator locator)
        {
            if (File.Exists(locator.ServerPath) == false)
            {
                throw new FileNotFoundException($"Server file was not found at '{locator.ServerPath}'.", locator.ServerPath);
            }

            using (var currentProcess = Process.GetCurrentProcess())
            {
                var commandArguments = new List <string>
                {
                    locator.CommandArguments,
                    $"-c {CommandLineArgumentEscaper.EscapeSingleArg(EmptySettingsFile.FullName)}",
                    "--ServerUrl=http://127.0.0.1:0",
                    "--RunInMemory=true",
                    "--Testing.ParentProcessId=" + currentProcess.Id,
                    "--Setup.Mode=None",
                    "--License.Eula.Accepted=true"
                };

                var argumentsString = string.Join(" ", commandArguments);
                return(new ProcessStartInfo
                {
                    FileName = locator.Command,
                    Arguments = argumentsString,
                    CreateNoWindow = true,
                    RedirectStandardOutput = true,
                    RedirectStandardError = true,
                    RedirectStandardInput = true,
                    UseShellExecute = false,
                });
            }
        }
예제 #2
0
        private static void Execute(string args, int processId, string output)
        {
            var ravenDebugExec = Path.Combine(AppContext.BaseDirectory,
                                              PlatformDetails.RunningOnPosix ? "Raven.Debug" : "Raven.Debug.exe"
                                              );

            if (File.Exists(ravenDebugExec) == false)
            {
                throw new FileNotFoundException($"Could not find debugger tool at '{ravenDebugExec}'");
            }

            var sb = new StringBuilder($"{args} --pid {processId} --output {CommandLineArgumentEscaper.EscapeSingleArg(output)}");

            var startup = new ProcessStartInfo
            {
                Arguments              = sb.ToString(),
                FileName               = ravenDebugExec,
                WindowStyle            = ProcessWindowStyle.Normal,
                RedirectStandardError  = true,
                RedirectStandardOutput = true,
                RedirectStandardInput  = true,
                UseShellExecute        = false
            };

            if (PlatformDetails.RunningOnPosix == false)
            {
#pragma warning disable CA1416 // Validate platform compatibility
                startup.LoadUserProfile = false;
#pragma warning restore CA1416 // Validate platform compatibility
            }

            var process = new Process
            {
                StartInfo           = startup,
                EnableRaisingEvents = true
            };

            sb.Clear();
            process.ErrorDataReceived += (sender, args) => sb.Append(args.Data);

            process.Start();

            process.BeginErrorReadLine();

            process.WaitForExit();

            if (process.ExitCode != 0)
            {
                throw new InvalidOperationException($"Could not read stack traces, exit code: {process.ExitCode}, error: {sb}");
            }

            AssertOutputExists(output, ravenDebugExec, sb.ToString());
        }
예제 #3
0
        private static IDocumentStore RunServer()
        {
            var options = _globalServerOptions ?? new TestServerOptions();

            options.CommandLineArgs.Insert(0, $"-c {CommandLineArgumentEscaper.EscapeSingleArg(EmptySettingsFile.FullName)}");
            options.CommandLineArgs.Add("--RunInMemory=true");

            TestServer.StartServer(options);

            var url = AsyncHelpers.RunSync(() => TestServer.GetServerUriAsync());

            var store = new DocumentStore
            {
                Urls = new[] { url.AbsoluteUri }
            };

            store.Initialize();

            return(store);
        }
예제 #4
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));
        }
예제 #5
0
        public static void OnDirectoryInitialize(StorageEnvironmentOptions options, DirectoryParameters parameters, Logger log)
        {
            Process process = null;

            try
            {
                var journalPath = string.Empty;
                if (options is StorageEnvironmentOptions.DirectoryStorageEnvironmentOptions dirOptions)
                {
                    journalPath = dirOptions.JournalPath.FullPath;
                }

                var userArgs = parameters.OnDirectoryInitializeExecArguments ?? string.Empty;
                var args     = $"{userArgs} {parameters.Type} {parameters.DatabaseName} " +
                               $"{CommandLineArgumentEscaper.EscapeSingleArg(options.BasePath.ToString())} " +
                               $"{CommandLineArgumentEscaper.EscapeSingleArg(options.TempPath.ToString())} " +
                               $"{CommandLineArgumentEscaper.EscapeSingleArg(journalPath)}";


                var startInfo = new ProcessStartInfo
                {
                    FileName               = parameters.OnDirectoryInitializeExec,
                    Arguments              = args,
                    UseShellExecute        = false,
                    RedirectStandardOutput = true,
                    RedirectStandardError  = true,
                    CreateNoWindow         = true
                };

                var sw = Stopwatch.StartNew();

                try
                {
                    process = Process.Start(startInfo);
                }
                catch (Exception e)
                {
                    throw new InvalidOperationException($"Unable to execute '{parameters.OnDirectoryInitializeExec} {args}'. Failed to start process.", e);
                }

                var readStdOut = process.StandardOutput.ReadToEndAsync();
                var readErrors = process.StandardError.ReadToEndAsync();

                string GetStdError()
                {
                    try
                    {
                        return(readErrors.Result);
                    }
                    catch (Exception e)
                    {
                        return($"Unable to get stderr, got exception: {e}");
                    }
                }

                string GetStdOut()
                {
                    try
                    {
                        return(readStdOut.Result);
                    }
                    catch (Exception e)
                    {
                        return($"Unable to get stdout, got exception: {e}");
                    }
                }

                if (process.WaitForExit((int)parameters.OnDirectoryInitializeExecTimeout.TotalMilliseconds) == false)
                {
                    process.Kill();
                    throw new InvalidOperationException($"Unable to execute '{parameters.OnDirectoryInitializeExec} {args}', waited for {(int)parameters.OnDirectoryInitializeExecTimeout.TotalMilliseconds} ms but the process didn't exit. Output: {GetStdOut()}{Environment.NewLine}Errors: {GetStdError()}");
                }

                try
                {
                    readStdOut.Wait(parameters.OnDirectoryInitializeExecTimeout);
                    readErrors.Wait(parameters.OnDirectoryInitializeExecTimeout);
                }
                catch (Exception e)
                {
                    throw new InvalidOperationException($"Unable to read redirected stderr and stdout when executing '{parameters.OnDirectoryInitializeExec} {args}'", e);
                }

                // Can have exit code o (success) but still get errors. We log the errors anyway.
                if (log.IsOperationsEnabled)
                {
                    log.Operations(string.Format($"Executing '{parameters.OnDirectoryInitializeExec} {args}' took {sw.ElapsedMilliseconds:#,#;;0} ms. Exit code: {process.ExitCode}{Environment.NewLine}Output: {GetStdOut()}{Environment.NewLine}Errors: {GetStdError()}{Environment.NewLine}"));
                }

                if (process.ExitCode != 0)
                {
                    throw new InvalidOperationException(
                              $"Command or executable '{parameters.OnDirectoryInitializeExec} {args}' failed. Exit code: {process.ExitCode}{Environment.NewLine}Output: {GetStdOut()}{Environment.NewLine}Errors: {GetStdError()}{Environment.NewLine}");
                }
            }
            finally
            {
                process?.Dispose();
            }
        }
예제 #6
0
        public static Process Run(ServerOptions options)
        {
            if (string.IsNullOrWhiteSpace(options.ServerDirectory))
            {
                throw new ArgumentNullException(nameof(options.ServerDirectory));
            }

            if (string.IsNullOrWhiteSpace(options.DataDirectory))
            {
                throw new ArgumentNullException(nameof(options.DataDirectory));
            }

            if (string.IsNullOrWhiteSpace(options.LogsPath))
            {
                throw new ArgumentNullException(nameof(options.LogsPath));
            }

            (string exec, string fstArg) = GetExecAndFirstArgument(options);

            var commandLineArgs = new List <string>(options.CommandLineArgs);

            using (var currentProcess = Process.GetCurrentProcess())
            {
                commandLineArgs.Add($"--Embedded.ParentProcessId={currentProcess.Id}");
            }

            commandLineArgs.Add($"--License.Eula.Accepted={options.AcceptEula}");
            commandLineArgs.Add("--Setup.Mode=None");
            commandLineArgs.Add($"--DataDir={CommandLineArgumentEscaper.EscapeSingleArg(options.DataDirectory)}");
            commandLineArgs.Add($"--Logs.Path={CommandLineArgumentEscaper.EscapeSingleArg(options.LogsPath)}");

            if (options.Security != null)
            {
                if (string.IsNullOrWhiteSpace(options.ServerUrl))
                {
                    options.ServerUrl = "https://127.0.0.1:0";
                }

                if (options.Security.CertificatePath != null)
                {
                    commandLineArgs.Add($"--Security.Certificate.Path={CommandLineArgumentEscaper.EscapeSingleArg(options.Security.CertificatePath)}");
                    if (options.Security.CertificatePassword != null)
                    {
                        commandLineArgs.Add($"--Security.Certificate.Password={CommandLineArgumentEscaper.EscapeSingleArg(options.Security.CertificatePassword)}");
                    }
                }
                else
                {
                    commandLineArgs.Add($"--Security.Certificate.Load.Exec={CommandLineArgumentEscaper.EscapeSingleArg(options.Security.CertificateLoadExec)}");
                    commandLineArgs.Add($"--Security.Certificate.Load.Exec.Arguments={CommandLineArgumentEscaper.EscapeSingleArg(options.Security.CertificateLoadExecArguments)}");
                }
                commandLineArgs.Add($"--Security.WellKnownCertificates.Admin={CommandLineArgumentEscaper.EscapeSingleArg(options.Security.ClientCertificate.Thumbprint)}");
            }
            else
            {
                if (string.IsNullOrWhiteSpace(options.ServerUrl))
                {
                    options.ServerUrl = "http://127.0.0.1:0";
                }
            }

            commandLineArgs.Add($"--ServerUrl={options.ServerUrl}");

            if (fstArg != null)
            {
                commandLineArgs.Insert(0, CommandLineArgumentEscaper.EscapeSingleArg(fstArg));

                if (string.IsNullOrWhiteSpace(options.FrameworkVersion) == false)
                {
                    commandLineArgs.Insert(0, $"--fx-version {options.FrameworkVersion}");
                }
            }

            var argumentsString = string.Join(" ", commandLineArgs);

            var processStartInfo = new ProcessStartInfo
            {
                FileName               = exec,
                Arguments              = argumentsString,
                CreateNoWindow         = true,
                RedirectStandardOutput = true,
                RedirectStandardError  = true,
                RedirectStandardInput  = true,
                UseShellExecute        = false
            };

            RemoveEnvironmentVariables(processStartInfo);

            Process process = null;

            try
            {
                process = Process.Start(processStartInfo);
                process.EnableRaisingEvents = true;
            }
            catch (Exception e)
            {
                process?.Kill();
                throw new InvalidOperationException("Unable to execute server." + Environment.NewLine +
                                                    "Command was: " + Environment.NewLine +
                                                    (processStartInfo.WorkingDirectory ?? Directory.GetCurrentDirectory()) + "> "
                                                    + processStartInfo.FileName + " " + processStartInfo.Arguments, e);
            }

            return(process);
        }
예제 #7
0
        public static Process Run(ServerOptions options)
        {
            if (string.IsNullOrWhiteSpace(options.ServerDirectory))
            {
                throw new ArgumentNullException(nameof(options.ServerDirectory));
            }

            if (string.IsNullOrWhiteSpace(options.DataDirectory))
            {
                throw new ArgumentNullException(nameof(options.DataDirectory));
            }

            var serverDllPath = Path.Combine(options.ServerDirectory, "Raven.Server.dll");

            if (File.Exists(serverDllPath) == false)
            {
                throw new FileNotFoundException("Server file was not found", serverDllPath);
            }

            if (string.IsNullOrWhiteSpace(options.DotNetPath))
            {
                throw new ArgumentNullException(nameof(options.DotNetPath));
            }

            using (var currentProcess = Process.GetCurrentProcess())
            {
                options.CommandLineArgs.Add($"--Embedded.ParentProcessId={currentProcess.Id}");
            }

            options.CommandLineArgs.Add($"--License.Eula.Accepted={options.AcceptEula}");
            options.CommandLineArgs.Add("--Setup.Mode=None");
            options.CommandLineArgs.Add($"--DataDir={CommandLineArgumentEscaper.EscapeSingleArg(options.DataDirectory)}");

            if (options.Security != null)
            {
                if (string.IsNullOrWhiteSpace(options.ServerUrl))
                {
                    options.ServerUrl = "https://127.0.0.1:0";
                }

                if (options.Security.CertificatePath != null)
                {
                    options.CommandLineArgs.Add($"--Security.Certificate.Path={CommandLineArgumentEscaper.EscapeSingleArg(options.Security.CertificatePath)}");
                    if (options.Security.CertificatePassword != null)
                    {
                        options.CommandLineArgs.Add($"--Security.Certificate.Password={CommandLineArgumentEscaper.EscapeSingleArg(options.Security.CertificatePassword)}");
                    }
                }
                else
                {
                    options.CommandLineArgs.Add($"--Security.Certificate.Exec={CommandLineArgumentEscaper.EscapeSingleArg(options.Security.CertificateExec)}");
                    options.CommandLineArgs.Add($"--Security.Certificate.Exec.Arguments={CommandLineArgumentEscaper.EscapeSingleArg(options.Security.CertificateArguments)}");
                }
                options.CommandLineArgs.Add($"--Security.WellKnownCertificates.Admin={CommandLineArgumentEscaper.EscapeSingleArg(options.Security.ClientCertificate.Thumbprint)}");
            }
            else
            {
                if (string.IsNullOrWhiteSpace(options.ServerUrl))
                {
                    options.ServerUrl = "http://127.0.0.1:0";
                }
            }

            options.CommandLineArgs.Add($"--ServerUrl={options.ServerUrl}");
            options.CommandLineArgs.Insert(0, CommandLineArgumentEscaper.EscapeSingleArg(serverDllPath));

            if (string.IsNullOrWhiteSpace(options.FrameworkVersion) == false)
            {
                options.CommandLineArgs.Insert(0, $"--fx-version {options.FrameworkVersion}");
            }

            var argumentsString = string.Join(" ", options.CommandLineArgs);

            var processStartInfo = new ProcessStartInfo
            {
                FileName               = options.DotNetPath,
                Arguments              = argumentsString,
                CreateNoWindow         = true,
                RedirectStandardOutput = true,
                RedirectStandardError  = true,
                RedirectStandardInput  = true,
                UseShellExecute        = false
            };

            RemoveEnvironmentVariables(processStartInfo);

            Process process = null;

            try
            {
                process = Process.Start(processStartInfo);
            }
            catch (Exception e)
            {
                process?.Kill();
                throw new InvalidOperationException("Unable to execute server." + Environment.NewLine +
                                                    "Command was: " + Environment.NewLine +
                                                    (processStartInfo.WorkingDirectory ?? Directory.GetCurrentDirectory()) + "> "
                                                    + processStartInfo.FileName + " " + processStartInfo.Arguments, e);
            }

            return(process);
        }
        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);
                }
            }
        }
        private CachedValue CheckExternalCertificateValidation(string senderHostname, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors, Logger log)
        {
            var base64Cert = Convert.ToBase64String(certificate.Export(X509ContentType.Cert));

            var timeout = _server.Configuration.Security.CertificateValidationExecTimeout.AsTimeSpan;

            var args = $"{_server.Configuration.Security.CertificateValidationExecArguments ?? string.Empty} " +
                       $"{CommandLineArgumentEscaper.EscapeSingleArg(senderHostname)} " +
                       $"{CommandLineArgumentEscaper.EscapeSingleArg(base64Cert)} " +
                       $"{CommandLineArgumentEscaper.EscapeSingleArg(sslPolicyErrors.ToString())}";

            var startInfo = new ProcessStartInfo
            {
                FileName = _server.Configuration.Security.CertificateValidationExec,
                Arguments = args,
                UseShellExecute = false,
                RedirectStandardOutput = true,
                RedirectStandardError = true,
                CreateNoWindow = true
            };

            var sw = Stopwatch.StartNew();
            Process process;
            try
            {
                process = Process.Start(startInfo);
            }
            catch (Exception e)
            {
                throw new InvalidOperationException($"Unable to execute '{_server.Configuration.Security.CertificateValidationExec} {args}'. Failed to start process.", e);
            }

            var readStdOut = process.StandardOutput.ReadToEndAsync();
            var readErrors = process.StandardError.ReadToEndAsync();

            string GetStdError()
            {
                try
                {
                    return readErrors.Result;
                }
                catch (Exception e)
                {
                    return $"Unable to get stderr, got exception: {e}";
                }
            }

            string GetStdOut()
            {
                try
                {
                    return readStdOut.Result;
                }
                catch (Exception e)
                {
                    return $"Unable to get stdout, got exception: {e}";
                }
            }

            if (process.WaitForExit((int)timeout.TotalMilliseconds) == false)
            {
                process.Kill();
                throw new InvalidOperationException($"Unable to execute '{_server.Configuration.Security.CertificateValidationExec} {args}', waited for {(int)timeout.TotalMilliseconds} ms but the process didn't exit. Output: {GetStdOut()}{Environment.NewLine}Errors: {GetStdError()}");
            }

            try
            {
                readStdOut.Wait(timeout);
                readErrors.Wait(timeout);
            }
            catch (Exception e)
            {
                throw new InvalidOperationException($"Unable to read redirected stderr and stdout when executing '{_server.Configuration.Security.CertificateValidationExec} {args}'", e);
            }

            string output = GetStdOut();
            string errors = GetStdError();

            // Can have exit code 0 (success) but still get errors. We log the errors anyway.
            if (log.IsOperationsEnabled)
                log.Operations($"Executing '{_server.Configuration.Security.CertificateValidationExec} {args}' took {sw.ElapsedMilliseconds:#,#;;0} ms. Exit code: {process.ExitCode}{Environment.NewLine}Output: {output}{Environment.NewLine}Errors: {errors}{Environment.NewLine}");

            if (process.ExitCode != 0)
            {
                throw new InvalidOperationException(
                    $"Command or executable '{_server.Configuration.Security.CertificateValidationExec} {args}' failed. Exit code: {process.ExitCode}{Environment.NewLine}Output: {output}{Environment.NewLine}Errors: {errors}{Environment.NewLine}");
            }

            if (bool.TryParse(output, out bool result) == false)
            {
                throw new InvalidOperationException(
                    $"Cannot parse to boolean the result of Command or executable '{_server.Configuration.Security.CertificateValidationExec} {args}'. Exit code: {process.ExitCode}{Environment.NewLine}Output: {output}{Environment.NewLine}Errors: {errors}{Environment.NewLine}");
            }

            return new CachedValue { Valid = result, Until = result ? DateTime.UtcNow.AddMinutes(15) : DateTime.UtcNow.AddSeconds(30) };
        }