Try to read the authorized keys file from the configuration

This commit is contained in:
nicolas.dorier
2019-09-20 18:51:14 +09:00
parent a8e2a99faa
commit 9e107b1eb1
6 changed files with 69 additions and 18 deletions

View File

@@ -245,6 +245,7 @@ namespace BTCPayServer.Configuration
} }
settings.Password = conf.GetOrDefault<string>("sshpassword", ""); settings.Password = conf.GetOrDefault<string>("sshpassword", "");
settings.KeyFile = conf.GetOrDefault<string>("sshkeyfile", ""); settings.KeyFile = conf.GetOrDefault<string>("sshkeyfile", "");
settings.AuthorizedKeysFile = conf.GetOrDefault<string>("sshauthorizedkeys", "");
settings.KeyFilePassword = conf.GetOrDefault<string>("sshkeyfilepassword", ""); settings.KeyFilePassword = conf.GetOrDefault<string>("sshkeyfilepassword", "");
return settings; return settings;
} }

View File

@@ -40,6 +40,7 @@ namespace BTCPayServer.Configuration
app.Option("--sshpassword", "SSH password to manage BTCPay (default: empty)", CommandOptionType.SingleValue); 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("--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("--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("--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("--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); app.Option("--socksendpoint", "Socks endpoint to connect to onion urls (default: empty)", CommandOptionType.SingleValue);

View File

@@ -474,7 +474,8 @@ namespace BTCPayServer.Controllers
if (appIdsToFetch.Any()) if (appIdsToFetch.Any())
{ {
var apps = (await _AppService.GetApps(appIdsToFetch.ToArray())) var apps = (await _AppService.GetApps(appIdsToFetch.ToArray()))
.ToDictionary(data => data.Id, data => Enum.Parse<AppType>(data.AppType));; .ToDictionary(data => data.Id, data => Enum.Parse<AppType>(data.AppType));
;
if (!string.IsNullOrEmpty(settings.RootAppId)) if (!string.IsNullOrEmpty(settings.RootAppId))
{ {
settings.RootAppType = apps[settings.RootAppId]; settings.RootAppType = apps[settings.RootAppId];
@@ -504,7 +505,7 @@ namespace BTCPayServer.Controllers
Link = this.Request.GetAbsoluteUriNoPathBase(externalService.Value).AbsoluteUri Link = this.Request.GetAbsoluteUriNoPathBase(externalService.Value).AbsoluteUri
}); });
} }
if (_sshState.CanUseSSH) if (CanShowSSHService())
{ {
result.OtherExternalServices.Add(new ServicesViewModel.OtherExternalService() result.OtherExternalServices.Add(new ServicesViewModel.OtherExternalService()
{ {
@@ -900,10 +901,10 @@ namespace BTCPayServer.Controllers
[Route("server/services/ssh")] [Route("server/services/ssh")]
public async Task<IActionResult> SSHService() public async Task<IActionResult> SSHService()
{ {
var settings = _Options.SSHSettings; if (!CanShowSSHService())
if (settings == null)
return NotFound(); return NotFound();
var settings = _Options.SSHSettings;
var server = Extensions.IsLocalNetwork(settings.Server) ? this.Request.Host.Host : settings.Server; var server = Extensions.IsLocalNetwork(settings.Server) ? this.Request.Host.Host : settings.Server;
SSHServiceViewModel vm = new SSHServiceViewModel(); SSHServiceViewModel vm = new SSHServiceViewModel();
string port = settings.Port == 22 ? "" : $" -p {settings.Port}"; string port = settings.Port == 22 ? "" : $" -p {settings.Port}";
@@ -911,8 +912,19 @@ namespace BTCPayServer.Controllers
vm.Password = settings.Password; vm.Password = settings.Password;
vm.KeyFilePassword = settings.KeyFilePassword; vm.KeyFilePassword = settings.KeyFilePassword;
vm.HasKeyFile = !string.IsNullOrEmpty(settings.KeyFile); 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 try
{ {
@@ -922,31 +934,68 @@ namespace BTCPayServer.Controllers
vm.SSHKeyFileContent = result.Output; vm.SSHKeyFileContent = result.Output;
} }
} }
catch catch { }
{
}
} }
return View(vm); 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] [HttpPost]
[Route("server/services/ssh")] [Route("server/services/ssh")]
public async Task<IActionResult> SSHService(SSHServiceViewModel viewModel) public async Task<IActionResult> SSHService(SSHServiceViewModel viewModel)
{ {
string newContent = viewModel?.SSHKeyFileContent ?? string.Empty; string newContent = viewModel?.SSHKeyFileContent ?? string.Empty;
newContent = newContent.Replace("\r\n", "\n", StringComparison.OrdinalIgnoreCase); newContent = newContent.Replace("\r\n", "\n", StringComparison.OrdinalIgnoreCase);
Exception exception = null;
// Let's try to just write the file
if (CanAccessAuthorizedKeyFile())
{
try
{
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 try
{ {
using (var sshClient = await _Options.SSHSettings.ConnectAsync()) using (var sshClient = await _Options.SSHSettings.ConnectAsync())
{ {
await sshClient.RunBash($"mkdir -p ~/.ssh && echo '{newContent.EscapeSingleQuotes()}' > ~/.ssh/authorized_keys", TimeSpan.FromSeconds(10)); await sshClient.RunBash($"mkdir -p ~/.ssh && echo '{newContent.EscapeSingleQuotes()}' > ~/.ssh/authorized_keys", TimeSpan.FromSeconds(10));
} }
StatusMessage = "authorized_keys has been updated"; exception = null;
} }
catch (Exception ex) catch (Exception ex)
{ {
StatusMessage = $"Error: {ex.Message}"; exception = ex;
}
}
if (exception is null)
{
StatusMessage = "authorized_keys has been updated";
}
else
{
StatusMessage = $"Error: {exception.Message}";
} }
return RedirectToAction(nameof(SSHService)); return RedirectToAction(nameof(SSHService));
} }

View File

@@ -11,7 +11,6 @@ namespace BTCPayServer.Models.ServerViewModels
public string Password { get; set; } public string Password { get; set; }
public string KeyFilePassword { get; set; } public string KeyFilePassword { get; set; }
public bool HasKeyFile { get; set; } public bool HasKeyFile { get; set; }
public bool CanConnect { get; set; }
public string SSHKeyFileContent { get; set; } public string SSHKeyFileContent { get; set; }
} }
} }

View File

@@ -13,6 +13,7 @@ namespace BTCPayServer.SSH
public int Port { get; set; } = 22; public int Port { get; set; } = 22;
public string KeyFile { get; set; } public string KeyFile { get; set; }
public string KeyFilePassword { get; set; } public string KeyFilePassword { get; set; }
public string AuthorizedKeysFile { get; set; }
public string Username { get; set; } public string Username { get; set; }
public string Password { get; set; } public string Password { get; set; }
public List<SSHFingerprint> TrustedFingerprints { get; set; } = new List<SSHFingerprint>(); public List<SSHFingerprint> TrustedFingerprints { get; set; } = new List<SSHFingerprint>();

View File

@@ -45,7 +45,7 @@
</div> </div>
</div> </div>
@if (Model.CanConnect) @if (Model.SSHKeyFileContent != null)
{ {
<h4>Authorized keys</h4> <h4>Authorized keys</h4>
<div class="row"> <div class="row">