From 9e107b1eb16c6fdd86e852f21fafb1e406105672 Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Fri, 20 Sep 2019 18:51:14 +0900 Subject: [PATCH] Try to read the authorized keys file from the configuration --- .../Configuration/BTCPayServerOptions.cs | 1 + .../Configuration/DefaultConfiguration.cs | 1 + BTCPayServer/Controllers/ServerController.cs | 81 +++++++++++++++---- .../ServerViewModels/SSHServiceViewModel.cs | 1 - BTCPayServer/SSH/SSHSettings.cs | 1 + BTCPayServer/Views/Server/SSHService.cshtml | 2 +- 6 files changed, 69 insertions(+), 18 deletions(-) diff --git a/BTCPayServer/Configuration/BTCPayServerOptions.cs b/BTCPayServer/Configuration/BTCPayServerOptions.cs index 54e99348b..124883957 100644 --- a/BTCPayServer/Configuration/BTCPayServerOptions.cs +++ b/BTCPayServer/Configuration/BTCPayServerOptions.cs @@ -245,6 +245,7 @@ namespace BTCPayServer.Configuration } settings.Password = conf.GetOrDefault("sshpassword", ""); settings.KeyFile = conf.GetOrDefault("sshkeyfile", ""); + settings.AuthorizedKeysFile = conf.GetOrDefault("sshauthorizedkeys", ""); settings.KeyFilePassword = conf.GetOrDefault("sshkeyfilepassword", ""); return settings; } diff --git a/BTCPayServer/Configuration/DefaultConfiguration.cs b/BTCPayServer/Configuration/DefaultConfiguration.cs index 1276cdaa0..26df92160 100644 --- a/BTCPayServer/Configuration/DefaultConfiguration.cs +++ b/BTCPayServer/Configuration/DefaultConfiguration.cs @@ -40,6 +40,7 @@ namespace BTCPayServer.Configuration app.Option("--sshpassword", "SSH password to manage BTCPay (default: empty)", CommandOptionType.SingleValue); app.Option("--sshkeyfile", "SSH private key file to manage BTCPay (default: empty)", CommandOptionType.SingleValue); app.Option("--sshkeyfilepassword", "Password of the SSH keyfile (default: empty)", CommandOptionType.SingleValue); + app.Option("--sshauthorizedkeys", "Path to a authorized_keys file that BTCPayServer can modify from the website (default: empty)", CommandOptionType.SingleValue); app.Option("--sshtrustedfingerprints", "SSH Host public key fingerprint or sha256 (default: empty, it will allow untrusted connections)", CommandOptionType.SingleValue); app.Option("--torrcfile", "Path to torrc file containing hidden services directories (default: empty)", CommandOptionType.SingleValue); app.Option("--socksendpoint", "Socks endpoint to connect to onion urls (default: empty)", CommandOptionType.SingleValue); diff --git a/BTCPayServer/Controllers/ServerController.cs b/BTCPayServer/Controllers/ServerController.cs index 47c61b7ad..05f01b233 100644 --- a/BTCPayServer/Controllers/ServerController.cs +++ b/BTCPayServer/Controllers/ServerController.cs @@ -474,7 +474,8 @@ namespace BTCPayServer.Controllers if (appIdsToFetch.Any()) { var apps = (await _AppService.GetApps(appIdsToFetch.ToArray())) - .ToDictionary(data => data.Id, data => Enum.Parse(data.AppType));; + .ToDictionary(data => data.Id, data => Enum.Parse(data.AppType)); + ; if (!string.IsNullOrEmpty(settings.RootAppId)) { settings.RootAppType = apps[settings.RootAppId]; @@ -504,7 +505,7 @@ namespace BTCPayServer.Controllers Link = this.Request.GetAbsoluteUriNoPathBase(externalService.Value).AbsoluteUri }); } - if (_sshState.CanUseSSH) + if (CanShowSSHService()) { result.OtherExternalServices.Add(new ServicesViewModel.OtherExternalService() { @@ -835,7 +836,7 @@ namespace BTCPayServer.Controllers return View(viewModel); } var settings = (await _SettingsRepository.GetSettingAsync()) ?? new DynamicDnsSettings(); - + var i = settings.Services.FindIndex(d => d.Hostname.Equals(hostname, StringComparison.OrdinalIgnoreCase)); if (i == -1) return NotFound(); @@ -900,10 +901,10 @@ namespace BTCPayServer.Controllers [Route("server/services/ssh")] public async Task SSHService() { - var settings = _Options.SSHSettings; - if (settings == null) + if (!CanShowSSHService()) return NotFound(); + var settings = _Options.SSHSettings; var server = Extensions.IsLocalNetwork(settings.Server) ? this.Request.Host.Host : settings.Server; SSHServiceViewModel vm = new SSHServiceViewModel(); string port = settings.Port == 22 ? "" : $" -p {settings.Port}"; @@ -911,8 +912,19 @@ namespace BTCPayServer.Controllers vm.Password = settings.Password; vm.KeyFilePassword = settings.KeyFilePassword; vm.HasKeyFile = !string.IsNullOrEmpty(settings.KeyFile); - vm.CanConnect = _sshState.CanUseSSH; - if (vm.CanConnect) + + // Let's try to just read the authorized key file + if (CanAccessAuthorizedKeyFile()) + { + try + { + vm.SSHKeyFileContent = await System.IO.File.ReadAllTextAsync(settings.AuthorizedKeysFile); + } + catch { } + } + + // If that fail, just fallback to ssh + if (vm.SSHKeyFileContent == null && _sshState.CanUseSSH) { try { @@ -922,31 +934,68 @@ namespace BTCPayServer.Controllers vm.SSHKeyFileContent = result.Output; } } - catch - { - - } + catch { } } return View(vm); } + bool CanShowSSHService() + { + return _Options.SSHSettings != null && (_sshState.CanUseSSH || CanAccessAuthorizedKeyFile()); + } + + private bool CanAccessAuthorizedKeyFile() + { + return _Options.SSHSettings.AuthorizedKeysFile != null && System.IO.File.Exists(_Options.SSHSettings.AuthorizedKeysFile); + } + [HttpPost] [Route("server/services/ssh")] public async Task SSHService(SSHServiceViewModel viewModel) { string newContent = viewModel?.SSHKeyFileContent ?? string.Empty; newContent = newContent.Replace("\r\n", "\n", StringComparison.OrdinalIgnoreCase); - try + + Exception exception = null; + + // Let's try to just write the file + if (CanAccessAuthorizedKeyFile()) { - using (var sshClient = await _Options.SSHSettings.ConnectAsync()) + try { - await sshClient.RunBash($"mkdir -p ~/.ssh && echo '{newContent.EscapeSingleQuotes()}' > ~/.ssh/authorized_keys", TimeSpan.FromSeconds(10)); + await System.IO.File.WriteAllTextAsync(_Options.SSHSettings.AuthorizedKeysFile, newContent); + StatusMessage = "authorized_keys has been updated"; } + catch (Exception ex) + { + exception = ex; + } + } + + // If that fail, fallback to ssh + if (exception != null && _sshState.CanUseSSH) + { + try + { + using (var sshClient = await _Options.SSHSettings.ConnectAsync()) + { + await sshClient.RunBash($"mkdir -p ~/.ssh && echo '{newContent.EscapeSingleQuotes()}' > ~/.ssh/authorized_keys", TimeSpan.FromSeconds(10)); + } + exception = null; + } + catch (Exception ex) + { + exception = ex; + } + } + + if (exception is null) + { StatusMessage = "authorized_keys has been updated"; } - catch (Exception ex) + else { - StatusMessage = $"Error: {ex.Message}"; + StatusMessage = $"Error: {exception.Message}"; } return RedirectToAction(nameof(SSHService)); } diff --git a/BTCPayServer/Models/ServerViewModels/SSHServiceViewModel.cs b/BTCPayServer/Models/ServerViewModels/SSHServiceViewModel.cs index cfb893dc0..b206d9841 100644 --- a/BTCPayServer/Models/ServerViewModels/SSHServiceViewModel.cs +++ b/BTCPayServer/Models/ServerViewModels/SSHServiceViewModel.cs @@ -11,7 +11,6 @@ namespace BTCPayServer.Models.ServerViewModels public string Password { get; set; } public string KeyFilePassword { get; set; } public bool HasKeyFile { get; set; } - public bool CanConnect { get; set; } public string SSHKeyFileContent { get; set; } } } diff --git a/BTCPayServer/SSH/SSHSettings.cs b/BTCPayServer/SSH/SSHSettings.cs index c00c575cc..d0b7eab0a 100644 --- a/BTCPayServer/SSH/SSHSettings.cs +++ b/BTCPayServer/SSH/SSHSettings.cs @@ -13,6 +13,7 @@ namespace BTCPayServer.SSH public int Port { get; set; } = 22; public string KeyFile { get; set; } public string KeyFilePassword { get; set; } + public string AuthorizedKeysFile { get; set; } public string Username { get; set; } public string Password { get; set; } public List TrustedFingerprints { get; set; } = new List(); diff --git a/BTCPayServer/Views/Server/SSHService.cshtml b/BTCPayServer/Views/Server/SSHService.cshtml index 7c99be3b0..e15b56524 100644 --- a/BTCPayServer/Views/Server/SSHService.cshtml +++ b/BTCPayServer/Views/Server/SSHService.cshtml @@ -45,7 +45,7 @@ -@if (Model.CanConnect) +@if (Model.SSHKeyFileContent != null) {

Authorized keys