mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-18 22:44:29 +01:00
Ensure the ssh connection is trusted
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||||
<Version>1.0.2.88</Version>
|
<Version>1.0.2.89</Version>
|
||||||
<NoWarn>NU1701,CA1816,CA1308,CA1810,CA2208</NoWarn>
|
<NoWarn>NU1701,CA1816,CA1308,CA1810,CA2208</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ using Microsoft.Extensions.Configuration;
|
|||||||
using NBXplorer;
|
using NBXplorer;
|
||||||
using BTCPayServer.Payments.Lightning;
|
using BTCPayServer.Payments.Lightning;
|
||||||
using Renci.SshNet;
|
using Renci.SshNet;
|
||||||
|
using NBitcoin.DataEncoders;
|
||||||
|
|
||||||
namespace BTCPayServer.Configuration
|
namespace BTCPayServer.Configuration
|
||||||
{
|
{
|
||||||
@@ -182,7 +183,7 @@ namespace BTCPayServer.Configuration
|
|||||||
ExternalUrl = conf.GetOrDefault<Uri>("externalurl", null);
|
ExternalUrl = conf.GetOrDefault<Uri>("externalurl", null);
|
||||||
|
|
||||||
var sshSettings = SSHSettings.ParseConfiguration(conf);
|
var sshSettings = SSHSettings.ParseConfiguration(conf);
|
||||||
if (!string.IsNullOrEmpty(sshSettings.Password) || !string.IsNullOrEmpty(sshSettings.KeyFile))
|
if ((!string.IsNullOrEmpty(sshSettings.Password) || !string.IsNullOrEmpty(sshSettings.KeyFile)) && !string.IsNullOrEmpty(sshSettings.Server))
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(sshSettings.KeyFile) && !File.Exists(sshSettings.KeyFile))
|
if (!string.IsNullOrEmpty(sshSettings.KeyFile) && !File.Exists(sshSettings.KeyFile))
|
||||||
throw new ConfigException($"sshkeyfile does not exist");
|
throw new ConfigException($"sshkeyfile does not exist");
|
||||||
@@ -199,10 +200,19 @@ namespace BTCPayServer.Configuration
|
|||||||
{
|
{
|
||||||
throw new ConfigException($"sshkeyfilepassword is invalid");
|
throw new ConfigException($"sshkeyfilepassword is invalid");
|
||||||
}
|
}
|
||||||
|
|
||||||
SSHSettings = sshSettings;
|
SSHSettings = sshSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var fingerPrints = conf.GetOrDefault<string>("sshtrustedfingerprints", "");
|
||||||
|
if (!string.IsNullOrEmpty(fingerPrints))
|
||||||
|
{
|
||||||
|
foreach (var fingerprint in fingerPrints.Split(';', StringSplitOptions.RemoveEmptyEntries)
|
||||||
|
.Select(str => str.Replace(":", "", StringComparison.OrdinalIgnoreCase)))
|
||||||
|
{
|
||||||
|
TrustedFingerprints.Add(DecodeFingerprint(fingerprint));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
RootPath = conf.GetOrDefault<string>("rootpath", "/");
|
RootPath = conf.GetOrDefault<string>("rootpath", "/");
|
||||||
if (!RootPath.StartsWith("/", StringComparison.InvariantCultureIgnoreCase))
|
if (!RootPath.StartsWith("/", StringComparison.InvariantCultureIgnoreCase))
|
||||||
RootPath = "/" + RootPath;
|
RootPath = "/" + RootPath;
|
||||||
@@ -210,6 +220,45 @@ namespace BTCPayServer.Configuration
|
|||||||
if (old != null)
|
if (old != null)
|
||||||
throw new ConfigException($"internallightningnode should not be used anymore, use btclightning instead");
|
throw new ConfigException($"internallightningnode should not be used anymore, use btclightning instead");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static byte[] DecodeFingerprint(string fingerprint)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Encoders.Hex.DecodeData(fingerprint.Trim());
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
var localFingerprint = fingerprint;
|
||||||
|
if (localFingerprint.StartsWith("SHA256", StringComparison.OrdinalIgnoreCase))
|
||||||
|
localFingerprint = localFingerprint.Substring("SHA256".Length).Trim();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Encoders.Base64.DecodeData(localFingerprint);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!localFingerprint.EndsWith('='))
|
||||||
|
localFingerprint = localFingerprint + "=";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Encoders.Base64.DecodeData(localFingerprint);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
throw new ConfigException($"sshtrustedfingerprints is invalid");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool IsTrustedFingerprint(byte[] fingerPrint)
|
||||||
|
{
|
||||||
|
return TrustedFingerprints.Any(f => Utils.ArrayEqual(f, fingerPrint));
|
||||||
|
}
|
||||||
|
|
||||||
public string RootPath { get; set; }
|
public string RootPath { get; set; }
|
||||||
public Dictionary<string, LightningConnectionString> InternalLightningByCryptoCode { get; set; } = new Dictionary<string, LightningConnectionString>();
|
public Dictionary<string, LightningConnectionString> InternalLightningByCryptoCode { get; set; } = new Dictionary<string, LightningConnectionString>();
|
||||||
public ExternalServices ExternalServicesByCryptoCode { get; set; } = new ExternalServices();
|
public ExternalServices ExternalServicesByCryptoCode { get; set; } = new ExternalServices();
|
||||||
@@ -230,6 +279,7 @@ namespace BTCPayServer.Configuration
|
|||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
public List<byte[]> TrustedFingerprints { get; set; } = new List<byte[]>();
|
||||||
public SSHSettings SSHSettings
|
public SSHSettings SSHSettings
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
|
|||||||
@@ -38,6 +38,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("--sshtrustedfingerprints", "SSH Host SHA256 rsa fingerprint in base64 or hex (default: empty, it will allow untrusted connections)", CommandOptionType.SingleValue);
|
||||||
foreach (var network in provider.GetAll())
|
foreach (var network in provider.GetAll())
|
||||||
{
|
{
|
||||||
var crypto = network.CryptoCode.ToLowerInvariant();
|
var crypto = network.CryptoCode.ToLowerInvariant();
|
||||||
|
|||||||
@@ -260,6 +260,29 @@ namespace BTCPayServer.Controllers
|
|||||||
ssh = $"sudo bash -c '. /etc/profile.d/btcpay-env.sh && nohup {ssh} > /dev/null 2>&1 & disown'";
|
ssh = $"sudo bash -c '. /etc/profile.d/btcpay-env.sh && nohup {ssh} > /dev/null 2>&1 & disown'";
|
||||||
var sshClient = _Options.SSHSettings == null ? vm.CreateSSHClient(this.Request.Host.Host)
|
var sshClient = _Options.SSHSettings == null ? vm.CreateSSHClient(this.Request.Host.Host)
|
||||||
: new SshClient(_Options.SSHSettings.CreateConnectionInfo());
|
: 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.HostKey} 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);
|
||||||
|
if(!e.CanTrust)
|
||||||
|
Logs.Configuration.LogError($"SSH host fingerprint for {e.HostKey} is untrusted, start BTCPay with -sshtrustedfingerprints \"{Encoders.Hex.EncodeData(e.FingerPrint)}\"");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
sshClient.Connect();
|
sshClient.Connect();
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ using Microsoft.Extensions.Hosting;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using BTCPayServer.Configuration;
|
using BTCPayServer.Configuration;
|
||||||
using BTCPayServer.Logging;
|
using BTCPayServer.Logging;
|
||||||
|
using NBitcoin.DataEncoders;
|
||||||
|
|
||||||
namespace BTCPayServer.HostedServices
|
namespace BTCPayServer.HostedServices
|
||||||
{
|
{
|
||||||
@@ -30,6 +31,14 @@ namespace BTCPayServer.HostedServices
|
|||||||
{
|
{
|
||||||
Logs.Configuration.LogInformation($"SSH settings detected, testing connection to {_options.SSHSettings.Username}@{_options.SSHSettings.Server} on port {_options.SSHSettings.Port} ...");
|
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());
|
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))
|
||||||
|
{
|
||||||
|
Logs.Configuration.LogWarning($"SSH host fingerprint for {e.HostKey} is untrusted, start BTCPay with -sshtrustedfingerprints \"{Encoders.Hex.EncodeData(e.FingerPrint)}\"");
|
||||||
|
}
|
||||||
|
};
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
connection.Connect();
|
connection.Connect();
|
||||||
|
|||||||
Reference in New Issue
Block a user