Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Configs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,14 @@ public record MessagingConfig(string ConnectionString);
/// <param name="Storage">The storage</param>
/// <param name="RedirectUrl">The redirect URL for unmatched requests to the server</param>
/// <param name="MachineName">The machine name</param>
/// <param name="DisableReportEncryption">Whether to disable encryption on stored reports</param>
public record EnvironmentConfig(
string? Hostname,
bool IsProd,
string Storage,
string? RedirectUrl = null,
string? MachineName = null
string? MachineName = null,
bool? DisableReportEncryption = null
);

/// <summary>
Expand Down
2 changes: 2 additions & 0 deletions DuplicatiIngress.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
<PackageReference Include="SharpAESCrypt" Version="2.0.2" />
<PackageReference Include="UuidExtensions" Version="1.2.0" />
<PackageReference Include="RobotsTxtCore" Version="3.0.0" />

<PackageReference Include="SimpleSecurityFilter" Version="1.0.2" />
</ItemGroup>

</Project>
30 changes: 21 additions & 9 deletions IngressHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,14 @@ namespace DuplicatiIngress;
/// <summary>
/// Represents the ingress handler
/// </summary>
/// <param name="environmentConfig">The environment configuration</param>
/// <param name="httpContextAccessor">The HTTP context accessor</param>
/// <param name="legacyTokens">The legacy tokens</param>
/// <param name="jWTValidator">The JWT validator</param>
/// <param name="busControl">The bus control</param>
/// <param name="storage">The storage</param>
public class IngressHandler(
EnvironmentConfig environmentConfig,
IHttpContextAccessor httpContextAccessor,
IPreconfiguredTokens preconfiguredTokens,
IJWTValidator jWTValidator,
Expand Down Expand Up @@ -71,7 +73,7 @@ public async Task MapPost(string token, CancellationToken ct)

// 2. Generate a filename
var uuid = Uuid7.String();
var filename = $"{parsedToken.OrganizationId}/{uuid}.json.aes";
var filename = $"{parsedToken.OrganizationId}/{uuid}.json{(environmentConfig.DisableReportEncryption ?? false ? "" : ".aes")}";

// 4. Rudimentary validation of input
tempFileToDelete = Path.GetTempFileName();
Expand Down Expand Up @@ -102,20 +104,30 @@ public async Task MapPost(string token, CancellationToken ct)
throw new UserReportedException("Payload is not valid JSON", statuscode: 400, exception: ex);
}

// 5. Encrypt with AESCrypt
encFileToDelete = Path.GetTempFileName();
var encHeaders = new KeyValuePair<string, byte[]>("key", Encoding.UTF8.GetBytes(parsedToken.KeyId));
var options = new SharpAESCrypt.EncryptionOptions(InsertPlaceholder: false, AdditionalExtensions: [encHeaders]);
string fileToUpload;
if (environmentConfig.DisableReportEncryption ?? false)
{
fileToUpload = tempFileToDelete;
}
else
{
// 5. Encrypt with AESCrypt
encFileToDelete = Path.GetTempFileName();
var encHeaders = new KeyValuePair<string, byte[]>("key", Encoding.UTF8.GetBytes(parsedToken.KeyId));
var options = new SharpAESCrypt.EncryptionOptions(InsertPlaceholder: false, AdditionalExtensions: [encHeaders]);

using (var s1 = File.OpenRead(tempFileToDelete))
using (var s2 = File.OpenWrite(encFileToDelete))
await SharpAESCrypt.AESCrypt.EncryptAsync(parsedToken.EncryptionKey, s1, s2, options, ct);

using (var s1 = File.OpenRead(tempFileToDelete))
using (var s2 = File.OpenWrite(encFileToDelete))
await SharpAESCrypt.AESCrypt.EncryptAsync(parsedToken.EncryptionKey, s1, s2, options, ct);
fileToUpload = encFileToDelete;
}

// 6. Store the payload with IKVPS
var uploaded = false;
try
{
using (var fs = File.OpenRead(encFileToDelete))
using (var fs = File.OpenRead(fileToUpload))
await storage.WriteAsync(filename, fs, ct);
uploaded = true;
}
Expand Down
11 changes: 10 additions & 1 deletion JWTValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,21 @@ public class JWTValidator : IJWTValidator
/// </summary>
private readonly IEncryptionKeyProvider EncryptionKeyProvider;

/// <summary>
/// The environment configuration
/// </summary>
private readonly EnvironmentConfig EnvironmentConfig;

/// <summary>
/// Initializes a new instance of the <see cref="JWTValidator"/> class
/// </summary>
/// <param name="encryptionKeyProvider">The encryption key provider</param>
/// <param name="jWTConfig">The JWT configuration</param>
public JWTValidator(IEncryptionKeyProvider encryptionKeyProvider, JWTConfig jWTConfig)
public JWTValidator(IEncryptionKeyProvider encryptionKeyProvider, JWTConfig jWTConfig, EnvironmentConfig environmentConfig)
{
EncryptionKeyProvider = encryptionKeyProvider;
TokenValidationParameters = GetTokenValidationParameters(jWTConfig);
EnvironmentConfig = environmentConfig;
}

/// <summary>
Expand Down Expand Up @@ -126,6 +132,9 @@ public ParsedIngressToken Validate(string token)
if (string.IsNullOrWhiteSpace(orgId))
throw new SecurityTokenValidationException("Organization ID is missing");

if (EnvironmentConfig.DisableReportEncryption ?? false)
return new ParsedIngressToken(orgId, null!, null!);

if (string.IsNullOrWhiteSpace(keyId))
throw new SecurityTokenValidationException("Key ID is missing");

Expand Down
18 changes: 12 additions & 6 deletions PreconfigureTokens.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ internal static PreconfiguredTokensConfig CreateEmpty()
/// <summary>
/// Service for getting information about preconfigured tokens
/// </summary>
public class PreconfiguredTokens(IEncryptionKeyProvider encryptionKeyProvider, PreconfiguredTokensConfig preconfiguredTokensConfig) : IPreconfiguredTokens
public class PreconfiguredTokens(IEncryptionKeyProvider encryptionKeyProvider, PreconfiguredTokensConfig preconfiguredTokensConfig, EnvironmentConfig environmentConfig) : IPreconfiguredTokens
{
/// <inheritdoc />
public ParsedIngressToken? GetPreconfiguredToken(string token)
Expand All @@ -101,12 +101,18 @@ public class PreconfiguredTokens(IEncryptionKeyProvider encryptionKeyProvider, P
if (preconfiguredTokensConfig.BlacklistTokens.Contains(token))
throw new SecurityTokenValidationException("Invalid token, blacklisted");

if (preconfiguredTokensConfig.WhitelistTokens.TryGetValue(token, out var tokenEntry) && !string.IsNullOrWhiteSpace(tokenEntry?.OrganizationId) && !string.IsNullOrWhiteSpace(tokenEntry?.KeyId))
if (preconfiguredTokensConfig.WhitelistTokens.TryGetValue(token, out var tokenEntry) && !string.IsNullOrWhiteSpace(tokenEntry?.OrganizationId))
{
var encryptionKey = encryptionKeyProvider.GetEncryptionKey(tokenEntry.KeyId);
if (string.IsNullOrWhiteSpace(encryptionKey))
throw new SecurityTokenValidationException("Invalid token, key not found");
return new ParsedIngressToken(tokenEntry.OrganizationId, tokenEntry.KeyId, encryptionKey);
if (environmentConfig.DisableReportEncryption ?? false)
return new ParsedIngressToken(tokenEntry.OrganizationId, null!, null!);

if (!string.IsNullOrWhiteSpace(tokenEntry?.KeyId))
{
var encryptionKey = encryptionKeyProvider.GetEncryptionKey(tokenEntry.KeyId);
if (string.IsNullOrWhiteSpace(encryptionKey))
throw new SecurityTokenValidationException("Invalid token, key not found");
return new ParsedIngressToken(tokenEntry.OrganizationId, tokenEntry.KeyId, encryptionKey);
}
}

return null;
Expand Down
6 changes: 5 additions & 1 deletion Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
using Serilog;
using Serilog.Core;
using Serilog.Events;
using SimpleSecurityFilter;

var builder = WebApplication.CreateBuilder(args);

Expand Down Expand Up @@ -85,6 +86,9 @@

builder.Services.AddHttpContextAccessor();

var securityconfig = builder.Configuration.GetSection("Security").Get<SimpleSecurityOptions>();
builder.AddSimpleSecurityFilter(securityconfig, msg => Log.Warning(msg));

// Load encryption keys
var encryptionKeys = builder.Configuration.GetSection("EncryptionKey")
.GetChildren()
Expand Down Expand Up @@ -164,7 +168,7 @@
};
});

app.UseSecurityFilter();
app.UseSimpleSecurityFilter(securityconfig);

app.MapPost("/backupreports/{token}",
async ([FromServices] IngressHandler handler, [FromRoute] string token, CancellationToken ct) =>
Expand Down
20 changes: 12 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,18 @@ The ingress server is intended to have very few moving parts and generally just

The following environment variables are optional, and should be considered for a production deployment:

| Variable | Description |
| -------------------------------- | ----------------------------------------------------------------------------- |
| ENVIRONMENT\_\_HOSTNAME | The server hostname for logging purposes |
| ENVIRONMENT\_\_MACHINENAME | Name of the machine for logging purposes |
| ENVIRONMENT\_\_REDIRECTURL | Url to redirect to when visiting the root path |
| PRECONFIGUREDTOKENS\_\_STORAGE | The KVPSButter connection string to the storage that contains an IP blacklist |
| PRECONFIGUREDTOKENS\_\_WHITELIST | The key that contains the IP blacklist |
| PRECONFIGUREDTOKENS\_\_BLACKLIST | The key that contains the IP blacklist |
| Variable | Description |
| -------------------------------------- | ------------------------------------------------------------------------------ |
| ENVIRONMENT\_\_HOSTNAME | The server hostname for logging purposes |
| ENVIRONMENT\_\_MACHINENAME | Name of the machine for logging purposes |
| ENVIRONMENT\_\_REDIRECTURL | Url to redirect to when visiting the root path |
| ENVIRONMENT\_\_DISABLEREPORTENCRYPTION | Disables encrypting received backup reports on storage |
| PRECONFIGUREDTOKENS\_\_STORAGE | The KVPSButter connection string to the storage that contains an IP blacklist |
| PRECONFIGUREDTOKENS\_\_WHITELIST | The key that contains the IP blacklist |
| PRECONFIGUREDTOKENS\_\_BLACKLIST | The key that contains the IP blacklist |
| SECURITY\_\_MAXREQUESTSPERSECONDPERIP | The maximum number of request from a single IP per second before throttling it |
| SECURITY\_\_FILTERPATTERNS | Boolean toggling filtering of scanning patterns |
| SECURITY\_\_RATELIMITENABLED | Boolean toggling if IP rate limiting is enabled |

## Setting Up Local Development Environment

Expand Down
130 changes: 0 additions & 130 deletions SecurityMiddleware.cs

This file was deleted.