mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-17 22:14:26 +01:00
Can edit authorized_keys in SSH Services, remove download keyfile support
This commit is contained in:
@@ -77,7 +77,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
public string RegisterNewUser(bool isAdmin = false)
|
||||
{
|
||||
var usr = RandomUtils.GetUInt256().ToString() + "@a.com";
|
||||
var usr = RandomUtils.GetUInt256().ToString().Substring(64 - 20) + "@a.com";
|
||||
Driver.FindElement(By.Id("Register")).Click();
|
||||
Driver.FindElement(By.Id("Email")).SendKeys(usr);
|
||||
Driver.FindElement(By.Id("Password")).SendKeys("123456");
|
||||
|
||||
@@ -113,6 +113,16 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
s.Driver.Navigate().GoToUrl(s.Link("/server/services/ssh"));
|
||||
s.Driver.AssertNoError();
|
||||
s.Driver.FindElement(By.Id("SSHKeyFileContent")).Clear();
|
||||
s.Driver.FindElement(By.Id("SSHKeyFileContent")).SendKeys("tes't\r\ntest2");
|
||||
s.Driver.FindElement(By.Id("submit")).ForceClick();
|
||||
s.Driver.AssertNoError();
|
||||
|
||||
var text = s.Driver.FindElement(By.Id("SSHKeyFileContent")).Text;
|
||||
// Browser replace \n to \r\n, so it is hard to compare exactly what we want
|
||||
Assert.Contains("tes't", text);
|
||||
Assert.Contains("test2", text);
|
||||
Assert.True(s.Driver.PageSource.Contains("authorized_keys has been updated", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -898,17 +898,11 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
[Route("server/services/ssh")]
|
||||
public IActionResult SSHService(bool downloadKeyFile = false)
|
||||
public async Task<IActionResult> SSHService()
|
||||
{
|
||||
var settings = _Options.SSHSettings;
|
||||
if (settings == null)
|
||||
return NotFound();
|
||||
if (downloadKeyFile)
|
||||
{
|
||||
if (!System.IO.File.Exists(settings.KeyFile))
|
||||
return NotFound();
|
||||
return File(System.IO.File.ReadAllBytes(settings.KeyFile), "application/octet-stream", "id_rsa");
|
||||
}
|
||||
|
||||
var server = Extensions.IsLocalNetwork(settings.Server) ? this.Request.Host.Host : settings.Server;
|
||||
SSHServiceViewModel vm = new SSHServiceViewModel();
|
||||
@@ -917,9 +911,46 @@ namespace BTCPayServer.Controllers
|
||||
vm.Password = settings.Password;
|
||||
vm.KeyFilePassword = settings.KeyFilePassword;
|
||||
vm.HasKeyFile = !string.IsNullOrEmpty(settings.KeyFile);
|
||||
vm.CanConnect = _sshState.CanUseSSH;
|
||||
if (vm.CanConnect)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var sshClient = await _Options.SSHSettings.ConnectAsync())
|
||||
{
|
||||
var result = await sshClient.RunBash("cat ~/.ssh/authorized_keys", TimeSpan.FromSeconds(10));
|
||||
vm.SSHKeyFileContent = result.Output;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("server/services/ssh")]
|
||||
public async Task<IActionResult> SSHService(SSHServiceViewModel viewModel)
|
||||
{
|
||||
string newContent = viewModel?.SSHKeyFileContent ?? string.Empty;
|
||||
newContent = newContent.Replace("\r\n", "\n", StringComparison.OrdinalIgnoreCase);
|
||||
try
|
||||
{
|
||||
using (var sshClient = await _Options.SSHSettings.ConnectAsync())
|
||||
{
|
||||
await sshClient.RunBash($"mkdir -p ~/.ssh && echo '{newContent.EscapeSingleQuotes()}' > ~/.ssh/authorized_keys", TimeSpan.FromSeconds(10));
|
||||
}
|
||||
StatusMessage = "authorized_keys has been updated";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StatusMessage = $"Error: {ex.Message}";
|
||||
}
|
||||
return RedirectToAction(nameof(SSHService));
|
||||
}
|
||||
|
||||
[Route("server/theme")]
|
||||
public async Task<IActionResult> Theme()
|
||||
{
|
||||
|
||||
@@ -49,13 +49,18 @@ namespace BTCPayServer
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
public static string EscapeSingleQuotes(this string command)
|
||||
{
|
||||
return command.Replace("'", "'\"'\"'", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public static Task<SSHCommandResult> RunBash(this SshClient sshClient, string command, TimeSpan? timeout = null)
|
||||
{
|
||||
if (sshClient == null)
|
||||
throw new ArgumentNullException(nameof(sshClient));
|
||||
if (command == null)
|
||||
throw new ArgumentNullException(nameof(command));
|
||||
command = $"sudo bash -c '{command}'";
|
||||
command = $"bash -c '{command.EscapeSingleQuotes()}'";
|
||||
var sshCommand = sshClient.CreateCommand(command);
|
||||
if (timeout is TimeSpan v)
|
||||
sshCommand.CommandTimeout = v;
|
||||
|
||||
@@ -11,5 +11,7 @@ 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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
|
||||
<h4>SSH settings</h4>
|
||||
<partial name="_StatusMessage" for="@TempData["StatusMessage"]" />
|
||||
<partial name="_StatusMessage" for="@TempData["TempDataProperty-StatusMessage"]" />
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
@@ -27,24 +27,43 @@
|
||||
<label asp-for="CommandLine"></label>
|
||||
<input asp-for="CommandLine" readonly class="form-control" />
|
||||
</div>
|
||||
@if(!string.IsNullOrEmpty(Model.Password))
|
||||
@if (!string.IsNullOrEmpty(Model.Password))
|
||||
{
|
||||
<div class="form-group">
|
||||
<label asp-for="Password"></label>
|
||||
<input asp-for="Password" readonly class="form-control" />
|
||||
</div>
|
||||
}
|
||||
@if(!string.IsNullOrEmpty(Model.KeyFilePassword))
|
||||
@if (!string.IsNullOrEmpty(Model.KeyFilePassword))
|
||||
{
|
||||
<div class="form-group">
|
||||
<label asp-for="KeyFilePassword"></label>
|
||||
<input asp-for="KeyFilePassword" readonly class="form-control" />
|
||||
</div>
|
||||
}
|
||||
@if(Model.HasKeyFile)
|
||||
{
|
||||
<a class="btn btn-primary form-control" asp-action="SSHService" asp-route-downloadKeyFile="true">Download Key File</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (Model.CanConnect)
|
||||
{
|
||||
<h4>Authorized keys</h4>
|
||||
<div class="row">
|
||||
|
||||
<div class="col-md-8">
|
||||
<div class="form-group">
|
||||
<p>
|
||||
<span>You can enter here SSH public keys authorized to connect to your server.<br /></span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form method="post">
|
||||
<div class="form-group">
|
||||
<textarea asp-for="SSHKeyFileContent" rows="20" cols="80" class="form-control"></textarea>
|
||||
<span asp-validation-for="SSHKeyFileContent" class="text-danger"></span>
|
||||
</div>
|
||||
<button id="submit" type="submit" class="btn btn-primary" value="Save">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user