mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-23 06:35:13 +01:00
Merge remote-tracking branch 'source/master'
# Conflicts: # BTCPayServer/BTCPayServer.csproj
This commit is contained in:
commit
76a36d1829
17 changed files with 539 additions and 67 deletions
|
@ -849,6 +849,22 @@ namespace BTCPayServer.Tests
|
|||
Assert.Equal("abed2", search.Filters["status"].Skip(1).First());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanParseFingerprint()
|
||||
{
|
||||
Assert.True(SSH.SSHFingerprint.TryParse("4e343c6fc6cfbf9339c02d06a151e1dd", out var unused));
|
||||
Assert.Equal("4e:34:3c:6f:c6:cf:bf:93:39:c0:2d:06:a1:51:e1:dd", unused.ToString());
|
||||
Assert.True(SSH.SSHFingerprint.TryParse("4e:34:3c:6f:c6:cf:bf:93:39:c0:2d:06:a1:51:e1:dd", out unused));
|
||||
Assert.True(SSH.SSHFingerprint.TryParse("SHA256:Wl7CdRgT4u5T7yPMsxSrlFP+HIJJWwidGkzphJ8di5w", out unused));
|
||||
Assert.True(SSH.SSHFingerprint.TryParse("SHA256:Wl7CdRgT4u5T7yPMsxSrlFP+HIJJWwidGkzphJ8di5w=", out unused));
|
||||
Assert.True(SSH.SSHFingerprint.TryParse("Wl7CdRgT4u5T7yPMsxSrlFP+HIJJWwidGkzphJ8di5w=", out unused));
|
||||
Assert.Equal("SHA256:Wl7CdRgT4u5T7yPMsxSrlFP+HIJJWwidGkzphJ8di5w", unused.ToString());
|
||||
|
||||
Assert.True(SSH.SSHFingerprint.TryParse("Wl7CdRgT4u5T7yPMsxSrlFP+HIJJWwidGkzphJ8di5w=", out var f1));
|
||||
Assert.True(SSH.SSHFingerprint.TryParse("SHA256:Wl7CdRgT4u5T7yPMsxSrlFP+HIJJWwidGkzphJ8di5w", out var f2));
|
||||
Assert.Equal(f1.ToString(), f2.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestAccessBitpayAPI()
|
||||
{
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<Version>1.0.2.87</Version>
|
||||
<Version>1.0.2.93</Version>
|
||||
<NoWarn>NU1701,CA1816,CA1308,CA1810,CA2208</NoWarn>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
|
@ -116,6 +116,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Update="Views\Server\SSHService.cshtml">
|
||||
<Content Update="Views\Apps\PayButton.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
|
|
|
@ -11,6 +11,9 @@ using StandardConfiguration;
|
|||
using Microsoft.Extensions.Configuration;
|
||||
using NBXplorer;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using Renci.SshNet;
|
||||
using NBitcoin.DataEncoders;
|
||||
using BTCPayServer.SSH;
|
||||
|
||||
namespace BTCPayServer.Configuration
|
||||
{
|
||||
|
@ -117,6 +120,45 @@ namespace BTCPayServer.Configuration
|
|||
BundleJsCss = conf.GetOrDefault<bool>("bundlejscss", true);
|
||||
ExternalUrl = conf.GetOrDefault<Uri>("externalurl", null);
|
||||
|
||||
var sshSettings = ParseSSHConfiguration(conf);
|
||||
if ((!string.IsNullOrEmpty(sshSettings.Password) || !string.IsNullOrEmpty(sshSettings.KeyFile)) && !string.IsNullOrEmpty(sshSettings.Server))
|
||||
{
|
||||
int waitTime = 0;
|
||||
while (!string.IsNullOrEmpty(sshSettings.KeyFile) && !File.Exists(sshSettings.KeyFile))
|
||||
{
|
||||
if(waitTime++ < 5)
|
||||
System.Threading.Thread.Sleep(1000);
|
||||
else
|
||||
throw new ConfigException($"sshkeyfile does not exist");
|
||||
}
|
||||
|
||||
if (sshSettings.Port > ushort.MaxValue ||
|
||||
sshSettings.Port < ushort.MinValue)
|
||||
throw new ConfigException($"ssh port is invalid");
|
||||
if (!string.IsNullOrEmpty(sshSettings.Password) && !string.IsNullOrEmpty(sshSettings.KeyFile))
|
||||
throw new ConfigException($"sshpassword or sshkeyfile should be provided, but not both");
|
||||
try
|
||||
{
|
||||
sshSettings.CreateConnectionInfo();
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw new ConfigException($"sshkeyfilepassword is invalid");
|
||||
}
|
||||
SSHSettings = sshSettings;
|
||||
}
|
||||
|
||||
var fingerPrints = conf.GetOrDefault<string>("sshtrustedfingerprints", "");
|
||||
if (!string.IsNullOrEmpty(fingerPrints))
|
||||
{
|
||||
foreach (var fingerprint in fingerPrints.Split(';', StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
if (!SSHFingerprint.TryParse(fingerprint, out var f))
|
||||
throw new ConfigException($"Invalid ssh fingerprint format {fingerprint}");
|
||||
TrustedFingerprints.Add(f);
|
||||
}
|
||||
}
|
||||
|
||||
RootPath = conf.GetOrDefault<string>("rootpath", "/");
|
||||
if (!RootPath.StartsWith("/", StringComparison.InvariantCultureIgnoreCase))
|
||||
RootPath = "/" + RootPath;
|
||||
|
@ -124,6 +166,53 @@ namespace BTCPayServer.Configuration
|
|||
if (old != null)
|
||||
throw new ConfigException($"internallightningnode should not be used anymore, use btclightning instead");
|
||||
}
|
||||
|
||||
private SSHSettings ParseSSHConfiguration(IConfiguration conf)
|
||||
{
|
||||
var externalUrl = conf.GetOrDefault<Uri>("externalurl", null);
|
||||
var settings = new SSHSettings();
|
||||
settings.Server = conf.GetOrDefault<string>("sshconnection", null);
|
||||
if (settings.Server != null)
|
||||
{
|
||||
var parts = settings.Server.Split(':');
|
||||
if (parts.Length == 2 && int.TryParse(parts[1], out int port))
|
||||
{
|
||||
settings.Port = port;
|
||||
settings.Server = parts[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
settings.Port = 22;
|
||||
}
|
||||
|
||||
parts = settings.Server.Split('@');
|
||||
if (parts.Length == 2)
|
||||
{
|
||||
settings.Username = parts[0];
|
||||
settings.Server = parts[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
settings.Username = "root";
|
||||
}
|
||||
}
|
||||
else if (externalUrl != null)
|
||||
{
|
||||
settings.Port = 22;
|
||||
settings.Username = "root";
|
||||
settings.Server = externalUrl.DnsSafeHost;
|
||||
}
|
||||
settings.Password = conf.GetOrDefault<string>("sshpassword", "");
|
||||
settings.KeyFile = conf.GetOrDefault<string>("sshkeyfile", "");
|
||||
settings.KeyFilePassword = conf.GetOrDefault<string>("sshkeyfilepassword", "");
|
||||
return settings;
|
||||
}
|
||||
|
||||
internal bool IsTrustedFingerprint(byte[] fingerPrint, byte[] hostKey)
|
||||
{
|
||||
return TrustedFingerprints.Any(f => f.Match(fingerPrint, hostKey));
|
||||
}
|
||||
|
||||
public string RootPath { get; set; }
|
||||
public Dictionary<string, LightningConnectionString> InternalLightningByCryptoCode { get; set; } = new Dictionary<string, LightningConnectionString>();
|
||||
public ExternalServices ExternalServicesByCryptoCode { get; set; } = new ExternalServices();
|
||||
|
@ -144,6 +233,12 @@ namespace BTCPayServer.Configuration
|
|||
get;
|
||||
set;
|
||||
}
|
||||
public List<SSHFingerprint> TrustedFingerprints { get; set; } = new List<SSHFingerprint>();
|
||||
public SSHSettings SSHSettings
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
internal string GetRootUri()
|
||||
{
|
||||
|
@ -154,7 +249,7 @@ namespace BTCPayServer.Configuration
|
|||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class ExternalServices : MultiValueDictionary<string, ExternalService>
|
||||
{
|
||||
public IEnumerable<T> GetServices<T>(string cryptoCode) where T : ExternalService
|
||||
|
|
|
@ -34,6 +34,11 @@ namespace BTCPayServer.Configuration
|
|||
app.Option("--externalurl", $"The expected external URL of this service, to use if BTCPay is behind a reverse proxy (default: empty, use the incoming HTTP request to figure out)", CommandOptionType.SingleValue);
|
||||
app.Option("--bundlejscss", $"Bundle JavaScript and CSS files for better performance (default: true)", CommandOptionType.SingleValue);
|
||||
app.Option("--rootpath", "The root path in the URL to access BTCPay (default: /)", CommandOptionType.SingleValue);
|
||||
app.Option("--sshconnection", "SSH server to manage BTCPay under the form user@server:port (default: root@externalhost or 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("--sshkeyfilepassword", "Password of the SSH keyfile (default: empty)", CommandOptionType.SingleValue);
|
||||
app.Option("--sshtrustedfingerprints", "SSH Host public key fingerprint or sha256 (default: empty, it will allow untrusted connections)", CommandOptionType.SingleValue);
|
||||
foreach (var network in provider.GetAll())
|
||||
{
|
||||
var crypto = network.CryptoCode.ToLowerInvariant();
|
||||
|
|
|
@ -160,6 +160,7 @@ namespace BTCPayServer.Controllers
|
|||
MaintenanceViewModel vm = new MaintenanceViewModel();
|
||||
vm.UserName = "btcpayserver";
|
||||
vm.DNSDomain = this.Request.Host.Host;
|
||||
vm.SetConfiguredSSH(_Options.SSHSettings);
|
||||
if (IPAddress.TryParse(vm.DNSDomain, out var unused))
|
||||
vm.DNSDomain = null;
|
||||
return View(vm);
|
||||
|
@ -170,6 +171,7 @@ namespace BTCPayServer.Controllers
|
|||
{
|
||||
if (!ModelState.IsValid)
|
||||
return View(vm);
|
||||
vm.SetConfiguredSSH(_Options.SSHSettings);
|
||||
if (command == "changedomain")
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(vm.DNSDomain))
|
||||
|
@ -178,6 +180,8 @@ namespace BTCPayServer.Controllers
|
|||
return View(vm);
|
||||
}
|
||||
vm.DNSDomain = vm.DNSDomain.Trim().ToLowerInvariant();
|
||||
if (vm.DNSDomain.Equals(this.Request.Host.Host, StringComparison.OrdinalIgnoreCase))
|
||||
return View(vm);
|
||||
if (IPAddress.TryParse(vm.DNSDomain, out var unused))
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.DNSDomain), $"This should be a domain name");
|
||||
|
@ -198,12 +202,13 @@ namespace BTCPayServer.Controllers
|
|||
{
|
||||
builder.Scheme = this.Request.Scheme;
|
||||
builder.Host = vm.DNSDomain;
|
||||
if (this.Request.Host.Port != null)
|
||||
builder.Port = this.Request.Host.Port.Value;
|
||||
builder.Path = "runid";
|
||||
builder.Query = $"expected={RunId}";
|
||||
var response = await client.GetAsync(builder.Uri);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
var addresses1 = Dns.GetHostAddressesAsync(this.Request.Host.Host);
|
||||
var addresses2 = Dns.GetHostAddressesAsync(vm.DNSDomain);
|
||||
await Task.WhenAll(addresses1, addresses2);
|
||||
|
||||
var addressesSet = addresses1.GetAwaiter().GetResult().Select(c => c.ToString()).ToHashSet();
|
||||
var hasCommonAddress = addresses2.GetAwaiter().GetResult().Select(c => c.ToString()).Any(s => addressesSet.Contains(s));
|
||||
if (!hasCommonAddress)
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.DNSDomain), $"Invalid host ({vm.DNSDomain} is not pointing to this BTCPay instance)");
|
||||
return View(vm);
|
||||
|
@ -256,7 +261,31 @@ namespace BTCPayServer.Controllers
|
|||
private IActionResult RunSSH(MaintenanceViewModel vm, string ssh)
|
||||
{
|
||||
ssh = $"sudo bash -c '. /etc/profile.d/btcpay-env.sh && nohup {ssh} > /dev/null 2>&1 & disown'";
|
||||
var sshClient = vm.CreateSSHClient(this.Request.Host.Host);
|
||||
var sshClient = _Options.SSHSettings == null ? vm.CreateSSHClient(this.Request.Host.Host)
|
||||
: new SshClient(_Options.SSHSettings.CreateConnectionInfo());
|
||||
|
||||
if (_Options.TrustedFingerprints.Count != 0)
|
||||
{
|
||||
sshClient.HostKeyReceived += (object sender, Renci.SshNet.Common.HostKeyEventArgs e) =>
|
||||
{
|
||||
if (_Options.TrustedFingerprints.Count == 0)
|
||||
{
|
||||
Logs.Configuration.LogWarning($"SSH host fingerprint for {e.HostKeyName} is untrusted, start BTCPay with -sshtrustedfingerprints \"{Encoders.Hex.EncodeData(e.FingerPrint)}\"");
|
||||
e.CanTrust = true; // Not a typo, we want the connection to succeed with a warning
|
||||
}
|
||||
else
|
||||
{
|
||||
e.CanTrust = _Options.IsTrustedFingerprint(e.FingerPrint, e.HostKey);
|
||||
if(!e.CanTrust)
|
||||
Logs.Configuration.LogError($"SSH host fingerprint for {e.HostKeyName} is untrusted, start BTCPay with -sshtrustedfingerprints \"{Encoders.Hex.EncodeData(e.FingerPrint)}\"");
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
sshClient.Connect();
|
||||
|
@ -404,13 +433,14 @@ namespace BTCPayServer.Controllers
|
|||
}
|
||||
}
|
||||
}
|
||||
result.HasSSH = _Options.SSHSettings != null;
|
||||
return View(result);
|
||||
}
|
||||
|
||||
[Route("server/services/lnd-grpc/{cryptoCode}/{index}")]
|
||||
public IActionResult LNDGRPCServices(string cryptoCode, int index, uint? nonce)
|
||||
{
|
||||
if(!_dashBoard.IsFullySynched(cryptoCode, out var unusud))
|
||||
if (!_dashBoard.IsFullySynched(cryptoCode, out var unusud))
|
||||
{
|
||||
StatusMessage = $"Error: {cryptoCode} is not fully synched";
|
||||
return RedirectToAction(nameof(Services));
|
||||
|
@ -470,6 +500,7 @@ namespace BTCPayServer.Controllers
|
|||
LightningConfigurations confs = new LightningConfigurations();
|
||||
LightningConfiguration conf = new LightningConfiguration();
|
||||
conf.Type = "grpc";
|
||||
conf.ChainType = _Options.NetworkType.ToString();
|
||||
conf.CryptoCode = cryptoCode;
|
||||
conf.Host = external.BaseUri.DnsSafeHost;
|
||||
conf.Port = external.BaseUri.Port;
|
||||
|
@ -506,6 +537,27 @@ namespace BTCPayServer.Controllers
|
|||
return connectionString;
|
||||
}
|
||||
|
||||
[Route("server/services/ssh")]
|
||||
public IActionResult SSHService(bool downloadKeyFile = false)
|
||||
{
|
||||
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");
|
||||
}
|
||||
SSHServiceViewModel vm = new SSHServiceViewModel();
|
||||
string port = settings.Port == 22 ? "" : $" -p {settings.Port}";
|
||||
vm.CommandLine = $"ssh {settings.Username}@{settings.Server}{port}";
|
||||
vm.Password = settings.Password;
|
||||
vm.KeyFilePassword = settings.KeyFilePassword;
|
||||
vm.HasKeyFile = !string.IsNullOrEmpty(settings.KeyFile);
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
[Route("server/theme")]
|
||||
public async Task<IActionResult> Theme()
|
||||
{
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
using System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Services;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using System.Threading;
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Logging;
|
||||
using NBitcoin.DataEncoders;
|
||||
|
||||
namespace BTCPayServer.HostedServices
|
||||
{
|
||||
public class CheckConfigurationHostedService : IHostedService
|
||||
{
|
||||
private readonly BTCPayServerOptions _options;
|
||||
|
||||
public CheckConfigurationHostedService(BTCPayServerOptions options)
|
||||
{
|
||||
_options = options;
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
new Thread(() =>
|
||||
{
|
||||
if (_options.SSHSettings != null)
|
||||
{
|
||||
Logs.Configuration.LogInformation($"SSH settings detected, testing connection to {_options.SSHSettings.Username}@{_options.SSHSettings.Server} on port {_options.SSHSettings.Port} ...");
|
||||
var connection = new Renci.SshNet.SshClient(_options.SSHSettings.CreateConnectionInfo());
|
||||
connection.HostKeyReceived += (object sender, Renci.SshNet.Common.HostKeyEventArgs e) =>
|
||||
{
|
||||
e.CanTrust = true;
|
||||
if (!_options.IsTrustedFingerprint(e.FingerPrint, e.HostKey))
|
||||
{
|
||||
Logs.Configuration.LogWarning($"SSH host fingerprint for {e.HostKeyName} is untrusted, start BTCPay with -sshtrustedfingerprints \"{Encoders.Hex.EncodeData(e.FingerPrint)}\"");
|
||||
}
|
||||
};
|
||||
try
|
||||
{
|
||||
connection.Connect();
|
||||
connection.Disconnect();
|
||||
Logs.Configuration.LogInformation($"SSH connection succeeded");
|
||||
}
|
||||
catch (Renci.SshNet.Common.SshAuthenticationException)
|
||||
{
|
||||
Logs.Configuration.LogWarning($"SSH invalid credentials");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var message = ex.Message;
|
||||
if (ex is AggregateException aggrEx && aggrEx.InnerException?.Message != null)
|
||||
{
|
||||
message = aggrEx.InnerException.Message;
|
||||
}
|
||||
Logs.Configuration.LogWarning($"SSH connection issue: {message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
connection.Dispose();
|
||||
}
|
||||
}
|
||||
})
|
||||
{ IsBackground = true }.Start();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -118,6 +118,8 @@ namespace BTCPayServer.Hosting
|
|||
services.AddSingleton<Payments.IPaymentMethodHandler<DerivationStrategy>, Payments.Bitcoin.BitcoinLikePaymentHandler>();
|
||||
services.AddSingleton<IHostedService, Payments.Bitcoin.NBXplorerListener>();
|
||||
|
||||
services.AddSingleton<IHostedService, HostedServices.CheckConfigurationHostedService>();
|
||||
|
||||
services.AddSingleton<Payments.IPaymentMethodHandler<Payments.Lightning.LightningSupportedPaymentMethod>, Payments.Lightning.LightningLikePaymentHandler>();
|
||||
services.AddSingleton<IHostedService, Payments.Lightning.LightningListener>();
|
||||
|
||||
|
|
|
@ -3,12 +3,15 @@ using System.Collections.Generic;
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.SSH;
|
||||
using Renci.SshNet;
|
||||
|
||||
namespace BTCPayServer.Models.ServerViewModels
|
||||
{
|
||||
public class MaintenanceViewModel
|
||||
{
|
||||
public bool ExposedSSH { get; set; }
|
||||
[Required]
|
||||
public string UserName { get; set; }
|
||||
[Required]
|
||||
|
@ -20,5 +23,15 @@ namespace BTCPayServer.Models.ServerViewModels
|
|||
{
|
||||
return new SshClient(host, UserName, Password);
|
||||
}
|
||||
|
||||
internal void SetConfiguredSSH(SSHSettings settings)
|
||||
{
|
||||
if(settings != null)
|
||||
{
|
||||
ExposedSSH = true;
|
||||
UserName = "unknown";
|
||||
Password = "unknown";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
15
BTCPayServer/Models/ServerViewModels/SSHServiceViewModel.cs
Normal file
15
BTCPayServer/Models/ServerViewModels/SSHServiceViewModel.cs
Normal file
|
@ -0,0 +1,15 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Models.ServerViewModels
|
||||
{
|
||||
public class SSHServiceViewModel
|
||||
{
|
||||
public string CommandLine { get; set; }
|
||||
public string Password { get; set; }
|
||||
public string KeyFilePassword { get; set; }
|
||||
public bool HasKeyFile { get; set; }
|
||||
}
|
||||
}
|
|
@ -14,5 +14,6 @@ namespace BTCPayServer.Models.ServerViewModels
|
|||
public int Index { get; set; }
|
||||
}
|
||||
public List<LNDServiceViewModel> LNDServices { get; set; } = new List<LNDServiceViewModel>();
|
||||
public bool HasSSH { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,18 +3,18 @@
|
|||
"Docker-Regtest": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"BTCPAY_NETWORK": "regtest",
|
||||
"BTCPAY_LTCEXPLORERURL": "http://127.0.0.1:32838/",
|
||||
"BTCPAY_BTCEXPLORERURL": "http://127.0.0.1:32838/",
|
||||
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||
"BTCPAY_CHAINS": "btc,ltc",
|
||||
"BTCPAY_BTCLIGHTNING": "type=charge;server=http://127.0.0.1:54938/;api-token=foiewnccewuify",
|
||||
"BTCPAY_BTCEXTERNALLNDGRPC": "type=lnd-grpc;server=https://lnd:lnd@127.0.0.1:53280/;allowinsecure=true",
|
||||
"BTCPAY_POSTGRES": "User ID=postgres;Host=127.0.0.1;Port=39372;Database=btcpayserver",
|
||||
"BTCPAY_BUNDLEJSCSS": "false"
|
||||
},
|
||||
"environmentVariables": {
|
||||
"BTCPAY_NETWORK": "regtest",
|
||||
"BTCPAY_BUNDLEJSCSS": "false",
|
||||
"BTCPAY_LTCEXPLORERURL": "http://127.0.0.1:32838/",
|
||||
"BTCPAY_BTCLIGHTNING": "type=charge;server=http://127.0.0.1:54938/;api-token=foiewnccewuify",
|
||||
"BTCPAY_BTCEXTERNALLNDGRPC": "type=lnd-grpc;server=https://lnd:lnd@127.0.0.1:53280/;allowinsecure=true",
|
||||
"BTCPAY_BTCEXPLORERURL": "http://127.0.0.1:32838/",
|
||||
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||
"BTCPAY_CHAINS": "btc,ltc",
|
||||
"BTCPAY_POSTGRES": "User ID=postgres;Host=127.0.0.1;Port=39372;Database=btcpayserver"
|
||||
},
|
||||
"applicationUrl": "http://127.0.0.1:14142/"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
98
BTCPayServer/SSH/SSHFingerprint.cs
Normal file
98
BTCPayServer/SSH/SSHFingerprint.cs
Normal file
|
@ -0,0 +1,98 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
|
||||
namespace BTCPayServer.SSH
|
||||
{
|
||||
public class SSHFingerprint
|
||||
{
|
||||
public static bool TryParse(string str, out SSHFingerprint fingerPrint)
|
||||
{
|
||||
if (str == null)
|
||||
throw new ArgumentNullException(nameof(str));
|
||||
fingerPrint = null;
|
||||
str = str.Trim();
|
||||
try
|
||||
{
|
||||
var shortFingerprint = str.Replace(":", "", StringComparison.OrdinalIgnoreCase);
|
||||
if (HexEncoder.IsWellFormed(shortFingerprint))
|
||||
{
|
||||
var hash = Encoders.Hex.DecodeData(shortFingerprint);
|
||||
if (hash.Length == 16)
|
||||
{
|
||||
fingerPrint = new SSHFingerprint(hash);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
if (str.StartsWith("SHA256:", StringComparison.OrdinalIgnoreCase))
|
||||
str = str.Substring("SHA256:".Length).Trim();
|
||||
if (str.Contains(':', StringComparison.OrdinalIgnoreCase))
|
||||
return false;
|
||||
if (!str.EndsWith('='))
|
||||
str = str + "=";
|
||||
try
|
||||
{
|
||||
var hash = Encoders.Base64.DecodeData(str);
|
||||
if (hash.Length == 32)
|
||||
{
|
||||
fingerPrint = new SSHFingerprint(hash);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public SSHFingerprint(byte[] hash)
|
||||
{
|
||||
if (hash.Length == 16)
|
||||
{
|
||||
_ShortFingerprint = hash;
|
||||
_Original = string.Join(':', hash.Select(b => b.ToString("x2", CultureInfo.InvariantCulture))
|
||||
.ToArray());
|
||||
}
|
||||
else if (hash.Length == 32)
|
||||
{
|
||||
_FullHash = hash;
|
||||
_Original = "SHA256:" + Encoders.Base64.EncodeData(hash);
|
||||
if (_Original.EndsWith("=", StringComparison.OrdinalIgnoreCase))
|
||||
_Original = _Original.Substring(0, _Original.Length - 1);
|
||||
}
|
||||
else
|
||||
throw new ArgumentException(paramName:nameof(hash), message: "Invalid length, expected 16 or 32");
|
||||
}
|
||||
|
||||
byte[] _ShortFingerprint;
|
||||
byte[] _FullHash;
|
||||
|
||||
public bool Match(byte[] shortFingerprint, byte[] hostKey)
|
||||
{
|
||||
if (shortFingerprint == null)
|
||||
throw new ArgumentNullException(nameof(shortFingerprint));
|
||||
if (hostKey == null)
|
||||
throw new ArgumentNullException(nameof(hostKey));
|
||||
if (_ShortFingerprint != null)
|
||||
return Utils.ArrayEqual(shortFingerprint, _ShortFingerprint);
|
||||
return Utils.ArrayEqual(_FullHash, NBitcoin.Crypto.Hashes.SHA256(hostKey));
|
||||
}
|
||||
|
||||
string _Original;
|
||||
public override string ToString()
|
||||
{
|
||||
return _Original;
|
||||
}
|
||||
}
|
||||
}
|
31
BTCPayServer/SSH/SSHSettings.cs
Normal file
31
BTCPayServer/SSH/SSHSettings.cs
Normal file
|
@ -0,0 +1,31 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Renci.SshNet;
|
||||
|
||||
namespace BTCPayServer.SSH
|
||||
{
|
||||
public class SSHSettings
|
||||
{
|
||||
public string Server { get; set; }
|
||||
public int Port { get; set; } = 22;
|
||||
public string KeyFile { get; set; }
|
||||
public string KeyFilePassword { get; set; }
|
||||
public string Username { get; set; }
|
||||
public string Password { get; set; }
|
||||
|
||||
public ConnectionInfo CreateConnectionInfo()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(KeyFile))
|
||||
{
|
||||
return new ConnectionInfo(Server, Port, Username, new[] { new PrivateKeyAuthenticationMethod(Username, new PrivateKeyFile(KeyFile, KeyFilePassword)) });
|
||||
}
|
||||
else
|
||||
{
|
||||
return new ConnectionInfo(Server, Port, Username, new[] { new PasswordAuthenticationMethod(Username, Password) });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -43,6 +43,7 @@ namespace BTCPayServer.Services
|
|||
}
|
||||
public class LightningConfiguration
|
||||
{
|
||||
public string ChainType { get; set; }
|
||||
public string Type { get; set; }
|
||||
public string CryptoCode { get; set; }
|
||||
public string Host { get; set; }
|
||||
|
|
|
@ -11,22 +11,31 @@
|
|||
|
||||
<div class="col-md-8">
|
||||
<form method="post">
|
||||
<div class="form-group">
|
||||
<h5>SSH Settings</h5>
|
||||
<span>For changing any settings, you need to enter your SSH credentials:</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="UserName"></label>
|
||||
<input asp-for="UserName" class="form-control" />
|
||||
<span asp-validation-for="UserName" class="text-danger"></span>
|
||||
</div>
|
||||
@if(!Model.ExposedSSH)
|
||||
{
|
||||
<div class="form-group">
|
||||
<h5>SSH Settings</h5>
|
||||
<span>For changing any settings, you need to enter your SSH credentials:</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="UserName"></label>
|
||||
<input asp-for="UserName" class="form-control" />
|
||||
<span asp-validation-for="UserName" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label asp-for="Password"></label>
|
||||
<input asp-for="Password" class="form-control" />
|
||||
<div class="form-group">
|
||||
<label asp-for="Password"></label>
|
||||
<input asp-for="Password" class="form-control" />
|
||||
<span asp-validation-for="Password" class="text-danger"></span>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<input asp-for="Password" type="hidden" class="form-control" />
|
||||
<span asp-validation-for="Password" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<input asp-for="UserName" type="hidden" class="form-control" />
|
||||
<span asp-validation-for="UserName" class="text-danger"></span>
|
||||
}
|
||||
<div class="form-group">
|
||||
<h5>Change domain name</h5>
|
||||
<span>You can change the domain name of your server by following <a href="https://github.com/btcpayserver/btcpayserver-doc/blob/master/ChangeDomain.md">this guide</a></span>
|
||||
|
|
50
BTCPayServer/Views/Server/SSHService.cshtml
Normal file
50
BTCPayServer/Views/Server/SSHService.cshtml
Normal file
|
@ -0,0 +1,50 @@
|
|||
@model BTCPayServer.Models.ServerViewModels.SSHServiceViewModel
|
||||
@{
|
||||
ViewData.SetActivePageAndTitle(ServerNavPages.Services);
|
||||
}
|
||||
|
||||
|
||||
<h4>SSH settings</h4>
|
||||
<partial name="_StatusMessage" for="@TempData["StatusMessage"]" />
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col-md-8">
|
||||
<div class="form-group">
|
||||
<p>
|
||||
<span>SSH servies are used by the maintenance operations<br /></span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<div class="form-group">
|
||||
<label asp-for="CommandLine"></label>
|
||||
<input asp-for="CommandLine" readonly class="form-control" />
|
||||
</div>
|
||||
@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))
|
||||
{
|
||||
<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>
|
|
@ -13,39 +13,46 @@
|
|||
</div>
|
||||
|
||||
<div class="row">
|
||||
@if(Model.LNDServices.Count != 0)
|
||||
{
|
||||
<div class="col-md-8">
|
||||
<div class="form-group">
|
||||
<h5>LND nodes</h5>
|
||||
<span>You can get access to internal LND services here. For gRPC, only BTC is supported.</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<table class="table table-sm table-responsive-md">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Crypto</th>
|
||||
<th>Access Type</th>
|
||||
<th style="text-align:right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach(var lnd in Model.LNDServices)
|
||||
{
|
||||
<tr>
|
||||
<td>@lnd.Crypto</td>
|
||||
<td>@lnd.Type</td>
|
||||
<td style="text-align:right">
|
||||
<a asp-action="LNDGRPCServices" asp-route-cryptoCode="@lnd.Crypto" asp-route-index="@lnd.Index">See information</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class="form-group">
|
||||
<span>You can get access here to LND-gRPC or SSH services exposed by your server</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="form-group">
|
||||
<table class="table table-sm table-responsive-md">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Crypto</th>
|
||||
<th>Access Type</th>
|
||||
<th style="text-align:right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach(var lnd in Model.LNDServices)
|
||||
{
|
||||
<tr>
|
||||
<td>@lnd.Crypto</td>
|
||||
<td>@lnd.Type</td>
|
||||
<td style="text-align:right">
|
||||
<a asp-action="LNDGRPCServices" asp-route-cryptoCode="@lnd.Crypto" asp-route-index="@lnd.Index">See information</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@if(Model.HasSSH)
|
||||
{
|
||||
<tr>
|
||||
<td>None</td>
|
||||
<td>SSH</td>
|
||||
<td style="text-align:right">
|
||||
<a asp-action="SSHService">See information</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
|
|
Loading…
Add table
Reference in a new issue