forked from Kieran/void.cat
Add virus scanner
This commit is contained in:
parent
000d7bac92
commit
5d07cc93eb
10
VoidCat/Model/VirusScanResult.cs
Normal file
10
VoidCat/Model/VirusScanResult.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace VoidCat.Model;
|
||||
|
||||
public sealed class VirusScanResult
|
||||
{
|
||||
public DateTimeOffset ScanTime { get; init; } = DateTimeOffset.UtcNow;
|
||||
|
||||
public bool IsVirus { get; init; }
|
||||
|
||||
public List<string> VirusNames { get; init; } = new();
|
||||
}
|
@ -30,6 +30,11 @@ namespace VoidCat.Model
|
||||
/// Traffic stats for this file
|
||||
/// </summary>
|
||||
public Bandwidth? Bandwidth { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Virus scanner results
|
||||
/// </summary>
|
||||
public VirusScanResult? VirusScan { get; init; }
|
||||
}
|
||||
|
||||
public sealed record PublicVoidFile : VoidFile<VoidFileMeta>
|
||||
|
@ -19,6 +19,8 @@ namespace VoidCat.Model
|
||||
public List<Uri> CorsOrigins { get; init; } = new();
|
||||
|
||||
public CloudStorageSettings? CloudStorage { get; init; }
|
||||
|
||||
public VirusScannerSettings? VirusScanner { get; init; }
|
||||
}
|
||||
|
||||
public sealed record TorSettings(Uri TorControl, string PrivateKey, string ControlPassword);
|
||||
@ -46,4 +48,9 @@ namespace VoidCat.Model
|
||||
public string? Region { get; init; }
|
||||
public string? BucketName { get; init; } = "void-cat";
|
||||
}
|
||||
|
||||
public sealed record VirusScannerSettings
|
||||
{
|
||||
public Uri? ClamAV { get; init; }
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,10 @@ using VoidCat.Model;
|
||||
|
||||
namespace VoidCat.Services.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Main interface for getting file info to serve to clients.
|
||||
/// This interface should wrap all stores and return the combined result
|
||||
/// </summary>
|
||||
public interface IFileInfoManager
|
||||
{
|
||||
ValueTask<PublicVoidFile?> Get(Guid id);
|
||||
|
7
VoidCat/Services/Abstractions/IVirusScanStore.cs
Normal file
7
VoidCat/Services/Abstractions/IVirusScanStore.cs
Normal file
@ -0,0 +1,7 @@
|
||||
using VoidCat.Model;
|
||||
|
||||
namespace VoidCat.Services.Abstractions;
|
||||
|
||||
public interface IVirusScanStore : IBasicStore<VirusScanResult>
|
||||
{
|
||||
}
|
8
VoidCat/Services/Abstractions/IVirusScanner.cs
Normal file
8
VoidCat/Services/Abstractions/IVirusScanner.cs
Normal file
@ -0,0 +1,8 @@
|
||||
using VoidCat.Model;
|
||||
|
||||
namespace VoidCat.Services.Abstractions;
|
||||
|
||||
public interface IVirusScanner
|
||||
{
|
||||
ValueTask<VirusScanResult> ScanFile(Guid id, CancellationToken cts);
|
||||
}
|
56
VoidCat/Services/Background/VirusScannerService.cs
Normal file
56
VoidCat/Services/Background/VirusScannerService.cs
Normal file
@ -0,0 +1,56 @@
|
||||
using VoidCat.Services.Abstractions;
|
||||
|
||||
namespace VoidCat.Services.Background;
|
||||
|
||||
public class VirusScannerService : BackgroundService
|
||||
{
|
||||
private readonly ILogger<VirusScannerService> _logger;
|
||||
private readonly IVirusScanner _scanner;
|
||||
private readonly IFileStore _fileStore;
|
||||
private readonly IVirusScanStore _scanStore;
|
||||
|
||||
public VirusScannerService(ILogger<VirusScannerService> logger, IVirusScanner scanner, IVirusScanStore scanStore,
|
||||
IFileStore fileStore)
|
||||
{
|
||||
_scanner = scanner;
|
||||
_logger = logger;
|
||||
_scanStore = scanStore;
|
||||
_fileStore = fileStore;
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
_logger.LogInformation("Virus scanner background service starting..");
|
||||
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
var page = 0;
|
||||
while (true)
|
||||
{
|
||||
var files = await _fileStore.ListFiles(new(page, 10));
|
||||
if (files.Pages < page) break;
|
||||
page++;
|
||||
|
||||
await foreach (var file in files.Results.WithCancellation(stoppingToken))
|
||||
{
|
||||
// check for scans
|
||||
var scan = await _scanStore.Get(file.Id);
|
||||
if (scan == default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _scanner.ScanFile(file.Id, stoppingToken);
|
||||
await _scanStore.Set(file.Id, result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to scan file {Id} error={Message}", file.Id, ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
|
||||
}
|
||||
}
|
||||
}
|
@ -9,14 +9,16 @@ public class FileInfoManager : IFileInfoManager
|
||||
private readonly IPaywallStore _paywallStore;
|
||||
private readonly IStatsReporter _statsReporter;
|
||||
private readonly IUserStore _userStore;
|
||||
private readonly IVirusScanStore _virusScanStore;
|
||||
|
||||
public FileInfoManager(IFileMetadataStore metadataStore, IPaywallStore paywallStore, IStatsReporter statsReporter,
|
||||
IUserStore userStore)
|
||||
IUserStore userStore, IVirusScanStore virusScanStore)
|
||||
{
|
||||
_metadataStore = metadataStore;
|
||||
_paywallStore = paywallStore;
|
||||
_statsReporter = statsReporter;
|
||||
_userStore = userStore;
|
||||
_virusScanStore = virusScanStore;
|
||||
}
|
||||
|
||||
public async ValueTask<PublicVoidFile?> Get(Guid id)
|
||||
@ -24,7 +26,8 @@ public class FileInfoManager : IFileInfoManager
|
||||
var meta = _metadataStore.Get<VoidFileMeta>(id);
|
||||
var paywall = _paywallStore.Get(id);
|
||||
var bandwidth = _statsReporter.GetBandwidth(id);
|
||||
await Task.WhenAll(meta.AsTask(), paywall.AsTask(), bandwidth.AsTask());
|
||||
var virusScan = _virusScanStore.Get(id);
|
||||
await Task.WhenAll(meta.AsTask(), paywall.AsTask(), bandwidth.AsTask(), virusScan.AsTask());
|
||||
|
||||
if (meta.Result == default) return default;
|
||||
|
||||
@ -37,7 +40,8 @@ public class FileInfoManager : IFileInfoManager
|
||||
Metadata = meta.Result,
|
||||
Paywall = paywall.Result,
|
||||
Bandwidth = bandwidth.Result,
|
||||
Uploader = user?.Flags.HasFlag(VoidUserFlags.PublicProfile) == true ? user : null
|
||||
Uploader = user?.Flags.HasFlag(VoidUserFlags.PublicProfile) == true ? user : null,
|
||||
VirusScan = virusScan.Result
|
||||
};
|
||||
}
|
||||
|
||||
@ -46,5 +50,6 @@ public class FileInfoManager : IFileInfoManager
|
||||
await _metadataStore.Delete(id);
|
||||
await _paywallStore.Delete(id);
|
||||
await _statsReporter.Delete(id);
|
||||
await _virusScanStore.Delete(id);
|
||||
}
|
||||
}
|
||||
|
38
VoidCat/Services/VirusScanner/ClamAvScanner.cs
Normal file
38
VoidCat/Services/VirusScanner/ClamAvScanner.cs
Normal file
@ -0,0 +1,38 @@
|
||||
using nClam;
|
||||
using VoidCat.Model;
|
||||
using VoidCat.Services.Abstractions;
|
||||
|
||||
namespace VoidCat.Services.VirusScanner;
|
||||
|
||||
public class ClamAvScanner : IVirusScanner
|
||||
{
|
||||
private readonly ILogger<ClamAvScanner> _logger;
|
||||
private readonly IClamClient _clam;
|
||||
private readonly IFileStore _store;
|
||||
|
||||
public ClamAvScanner(ILogger<ClamAvScanner> logger, IClamClient clam, IFileStore store)
|
||||
{
|
||||
_logger = logger;
|
||||
_clam = clam;
|
||||
_store = store;
|
||||
}
|
||||
|
||||
public async ValueTask<VirusScanResult> ScanFile(Guid id, CancellationToken cts)
|
||||
{
|
||||
_logger.LogInformation("Starting scan of {Filename}", id);
|
||||
|
||||
await using var fs = await _store.Open(new(id, Enumerable.Empty<RangeRequest>()), cts);
|
||||
var result = await _clam.SendAndScanFileAsync(fs, cts);
|
||||
|
||||
if (result.Result == ClamScanResults.Error)
|
||||
{
|
||||
_logger.LogError("Failed to scan file {File}", id);
|
||||
}
|
||||
|
||||
return new()
|
||||
{
|
||||
IsVirus = result.Result == ClamScanResults.VirusDetected,
|
||||
VirusNames = result.InfectedFiles?.Select(a => a.VirusName.Trim()).ToList() ?? new()
|
||||
};
|
||||
}
|
||||
}
|
14
VoidCat/Services/VirusScanner/VirusScanStore.cs
Normal file
14
VoidCat/Services/VirusScanner/VirusScanStore.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using VoidCat.Model;
|
||||
using VoidCat.Services.Abstractions;
|
||||
|
||||
namespace VoidCat.Services.VirusScanner;
|
||||
|
||||
public class VirusScanStore : BasicCacheStore<VirusScanResult>, IVirusScanStore
|
||||
{
|
||||
public VirusScanStore(ICache cache) : base(cache)
|
||||
{
|
||||
}
|
||||
|
||||
public override string MapKey(Guid id)
|
||||
=> $"virus-scan:{id}";
|
||||
}
|
27
VoidCat/Services/VirusScanner/VirusScannerStartup.cs
Normal file
27
VoidCat/Services/VirusScanner/VirusScannerStartup.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using nClam;
|
||||
using VoidCat.Model;
|
||||
using VoidCat.Services.Abstractions;
|
||||
|
||||
namespace VoidCat.Services.VirusScanner;
|
||||
|
||||
public static class VirusScannerStartup
|
||||
{
|
||||
public static void AddVirusScanner(this IServiceCollection services, VoidSettings settings)
|
||||
{
|
||||
services.AddTransient<IVirusScanStore, VirusScanStore>();
|
||||
|
||||
var avSettings = settings.VirusScanner;
|
||||
if (avSettings != default)
|
||||
{
|
||||
services.AddHostedService<Background.VirusScannerService>();
|
||||
|
||||
// load ClamAV scanner
|
||||
if (avSettings.ClamAV != default)
|
||||
{
|
||||
services.AddTransient<IClamClient>((_) =>
|
||||
new ClamClient(avSettings.ClamAV.Host, avSettings.ClamAV.Port));
|
||||
services.AddTransient<IVirusScanner, ClamAvScanner>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -17,6 +17,7 @@
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.14.0" />
|
||||
<PackageReference Include="NBitcoin" Version="6.0.19" />
|
||||
<PackageReference Include="nClam" Version="7.0.0" />
|
||||
<PackageReference Include="prometheus-net.AspNetCore" Version="5.0.2" />
|
||||
<PackageReference Include="Seq.Extensions.Logging" Version="6.0.0" />
|
||||
<PackageReference Include="StackExchange.Redis" Version="2.5.27-prerelease" />
|
||||
|
Loading…
Reference in New Issue
Block a user