diff --git a/MalwareMultiScan.Api/Controllers/DownloadController.cs b/MalwareMultiScan.Api/Controllers/DownloadController.cs index 8f4414b..ead47f3 100644 --- a/MalwareMultiScan.Api/Controllers/DownloadController.cs +++ b/MalwareMultiScan.Api/Controllers/DownloadController.cs @@ -8,11 +8,11 @@ namespace MalwareMultiScan.Api.Controllers [Route("download")] public class DownloadController : Controller { - private readonly ScanResultsService _scanResultsService; + private readonly ScanResultService _scanResultService; - public DownloadController(ScanResultsService scanResultsService) + public DownloadController(ScanResultService scanResultService) { - _scanResultsService = scanResultsService; + _scanResultService = scanResultService; } [HttpGet("{id}")] @@ -20,7 +20,7 @@ namespace MalwareMultiScan.Api.Controllers [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task Index(string id) { - var fileStream = await _scanResultsService.ObtainFile(id); + var fileStream = await _scanResultService.ObtainFile(id); if (fileStream == null) return NotFound(); diff --git a/MalwareMultiScan.Api/Controllers/QueueController.cs b/MalwareMultiScan.Api/Controllers/QueueController.cs index 908751e..de4e9c4 100644 --- a/MalwareMultiScan.Api/Controllers/QueueController.cs +++ b/MalwareMultiScan.Api/Controllers/QueueController.cs @@ -1,3 +1,10 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using MalwareMultiScan.Api.Services; +using MalwareMultiScan.Shared.Attributes; +using MalwareMultiScan.Shared.Data.Responses; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace MalwareMultiScan.Api.Controllers @@ -7,9 +14,45 @@ namespace MalwareMultiScan.Api.Controllers [Produces("application/json")] public class QueueController : Controller { - public IActionResult Index() + private readonly ScanResultService _scanResultService; + + public QueueController( ScanResultService scanResultService) { - return Ok(); + _scanResultService = scanResultService; + } + + [HttpPost("file")] + [ProducesResponseType(typeof(ResultResponse), StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task ScanFile([FromForm] IFormFile file) + { + var result = await _scanResultService.CreateScanResult(); + + string storedFileId; + + await using (var uploadFileStream = file.OpenReadStream()) + storedFileId = await _scanResultService.StoreFile(file.Name, uploadFileStream); + + await _scanResultService.QueueUrlScan(result, Url.Action("Index", "Download", new {id = storedFileId}, + Request.Scheme, Request.Host.Value)); + + return Created(Url.Action("Index", "ScanResults", new {id = result.Id}, + Request.Scheme, Request.Host.Value), result); + } + + [HttpPost("url")] + [ProducesResponseType(typeof(ResultResponse), StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task ScanUrl([FromForm] [UrlValidation] Uri url) + { + var result = await _scanResultService.CreateScanResult(); + + var resultUrl = Url.Action("Index", "ScanResults", new {id = result.Id}, + Request.Scheme, Request.Host.Value); + + await _scanResultService.QueueUrlScan(result, url.ToString()); + + return Created(resultUrl, result); } } } \ No newline at end of file diff --git a/MalwareMultiScan.Api/Controllers/ScanBackendsController.cs b/MalwareMultiScan.Api/Controllers/ScanBackendsController.cs deleted file mode 100644 index c65e430..0000000 --- a/MalwareMultiScan.Api/Controllers/ScanBackendsController.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Linq; -using System.Threading.Tasks; -using MalwareMultiScan.Api.Data.Configuration; -using MalwareMultiScan.Api.Data.Response; -using MalwareMultiScan.Api.Services; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; - -namespace MalwareMultiScan.Api.Controllers -{ - [ApiController] - [Route("backends")] - [Produces("application/json")] - public class ScanBackendsController : Controller - { - private readonly ScanBackendService _scanBackendService; - - public ScanBackendsController(ScanBackendService scanBackendService) - { - _scanBackendService = scanBackendService; - } - - private async Task GetScanBackendResponse(ScanBackend backend) - { - return new ScanBackendResponse - { - Id = backend.Id, - Name = backend.Name, - Online = await _scanBackendService.Ping(backend) - }; - } - - [HttpGet] - [ProducesResponseType(typeof(ScanBackendResponse[]), StatusCodes.Status200OK)] - public async Task Index() - { - return Ok(await Task.WhenAll( - _scanBackendService.List.Select(GetScanBackendResponse).ToArray())); - } - } -} \ No newline at end of file diff --git a/MalwareMultiScan.Api/Controllers/ScanResultsController.cs b/MalwareMultiScan.Api/Controllers/ScanResultsController.cs index 2d8885a..c2aca1f 100644 --- a/MalwareMultiScan.Api/Controllers/ScanResultsController.cs +++ b/MalwareMultiScan.Api/Controllers/ScanResultsController.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using MalwareMultiScan.Api.Data.Models; using MalwareMultiScan.Api.Services; using MalwareMultiScan.Shared.Data.Responses; using Microsoft.AspNetCore.Http; @@ -11,21 +12,23 @@ namespace MalwareMultiScan.Api.Controllers [Produces("application/json")] public class ScanResultsController : Controller { - private readonly ScanResultsService _scanResultsService; + private readonly ScanResultService _scanResultService; - public ScanResultsController(ScanResultsService scanResultsService) + public ScanResultsController(ScanResultService scanResultService) { - _scanResultsService = scanResultsService; + _scanResultService = scanResultService; } - - [HttpPost("{id}")] - [ProducesResponseType(StatusCodes.Status204NoContent)] - public async Task Index(string id, [FromBody] ResultResponse result) + + [HttpGet("{id}")] + [ProducesResponseType(typeof(ScanResult), StatusCodes.Status200OK)] + public async Task Index(string id) { - await _scanResultsService.UpdateScanResultForBackend( - id, result.Backend, true, result.Success, result.Threats); - - return NoContent(); + var scanResult = await _scanResultService.GetScanResult(id); + + if (scanResult == null) + return NotFound(); + + return Ok(scanResult); } } } \ No newline at end of file diff --git a/MalwareMultiScan.Api/Data/Configuration/ScanBackend.cs b/MalwareMultiScan.Api/Data/Configuration/ScanBackend.cs index a2cb9ad..a230136 100644 --- a/MalwareMultiScan.Api/Data/Configuration/ScanBackend.cs +++ b/MalwareMultiScan.Api/Data/Configuration/ScanBackend.cs @@ -4,6 +4,5 @@ namespace MalwareMultiScan.Api.Data.Configuration { public string Id { get; set; } public string Name { get; set; } - public string Endpoint { get; set; } } } \ No newline at end of file diff --git a/MalwareMultiScan.Api/MalwareMultiScan.Api.csproj b/MalwareMultiScan.Api/MalwareMultiScan.Api.csproj index b4895c0..bb6c35a 100644 --- a/MalwareMultiScan.Api/MalwareMultiScan.Api.csproj +++ b/MalwareMultiScan.Api/MalwareMultiScan.Api.csproj @@ -11,8 +11,10 @@ + + diff --git a/MalwareMultiScan.Api/Services/ReceiverHostedService.cs b/MalwareMultiScan.Api/Services/ReceiverHostedService.cs new file mode 100644 index 0000000..61a2402 --- /dev/null +++ b/MalwareMultiScan.Api/Services/ReceiverHostedService.cs @@ -0,0 +1,41 @@ +using System.Threading; +using System.Threading.Tasks; +using EasyNetQ; +using MalwareMultiScan.Shared.Data.Messages; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; + +namespace MalwareMultiScan.Api.Services +{ + public class ReceiverHostedService : IHostedService + { + private readonly IBus _bus; + private readonly IConfiguration _configuration; + private readonly ScanResultService _scanResultService; + + public ReceiverHostedService(IBus bus, IConfiguration configuration, ScanResultService scanResultService) + { + _bus = bus; + _configuration = configuration; + _scanResultService = scanResultService; + } + + public Task StartAsync(CancellationToken cancellationToken) + { + _bus.Receive(_configuration.GetValue("ResultsSubscriptionId"), async message => + { + await _scanResultService.UpdateScanResultForBackend( + message.Id, message.Backend, true, true, message.Threats); + }); + + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + _bus.Dispose(); + + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/MalwareMultiScan.Api/Services/ScanBackendService.cs b/MalwareMultiScan.Api/Services/ScanBackendService.cs index f1ab944..81bc744 100644 --- a/MalwareMultiScan.Api/Services/ScanBackendService.cs +++ b/MalwareMultiScan.Api/Services/ScanBackendService.cs @@ -1,12 +1,11 @@ using System; using System.IO; -using System.Net.Http; -using System.Threading; using System.Threading.Tasks; +using EasyNetQ; using MalwareMultiScan.Api.Data.Configuration; -using MalwareMultiScan.Shared.Data.Requests; +using MalwareMultiScan.Api.Data.Models; +using MalwareMultiScan.Shared.Data.Messages; using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; @@ -14,14 +13,12 @@ namespace MalwareMultiScan.Api.Services { public class ScanBackendService { - private readonly IHttpClientFactory _httpClientFactory; - private readonly ILogger _logger; + private readonly IBus _bus; - public ScanBackendService(IConfiguration configuration, IHttpClientFactory httpClientFactory, - ILogger logger) + public ScanBackendService(IConfiguration configuration, IBus bus) { - _httpClientFactory = httpClientFactory; - _logger = logger; + _bus = bus; + var configurationPath = configuration.GetValue("BackendsConfiguration"); if (!File.Exists(configurationPath)) @@ -37,74 +34,13 @@ namespace MalwareMultiScan.Api.Services } public ScanBackend[] List { get; } - - public async Task Ping(ScanBackend backend) + + public async Task QueueUrlScan(ScanResult result, ScanBackend backend, string fileUrl) { - var cancellationTokenSource = new CancellationTokenSource( - TimeSpan.FromSeconds(1)); - - try + await _bus.SendAsync(backend.Id, new ScanRequestMessage { - using var httpClient = _httpClientFactory.CreateClient(); - - var pingResponse = await httpClient.GetAsync( - new Uri(new Uri(backend.Endpoint), "/ping"), cancellationTokenSource.Token); - - pingResponse.EnsureSuccessStatusCode(); - } - catch (Exception exception) - { - _logger.LogError( - exception, $"Failed to ping {backend.Id}"); - - return false; - } - - return true; - } - - private async Task QueueScan(ScanBackend backend, string uri, HttpContent content) - { - var cancellationTokenSource = new CancellationTokenSource( - TimeSpan.FromSeconds(5)); - - using var httpClient = _httpClientFactory.CreateClient(); - - try - { - var response = await httpClient.PostAsync( - new Uri(new Uri(backend.Endpoint), uri), content, cancellationTokenSource.Token); - - response.EnsureSuccessStatusCode(); - } - catch (Exception exception) - { - _logger.LogError( - exception, $"Failed to initiate scan against {backend.Id}"); - - return false; - } - - return true; - } - - public async Task QueueFileScan( - ScanBackend backend, string fileName, Stream fileStream, string callbackUrl) - { - return await QueueScan(backend, "/scan/file", new MultipartFormDataContent - { - {new StringContent(callbackUrl), nameof(FileRequest.CallbackUrl)}, - {new StreamContent(fileStream), nameof(FileRequest.InputFile), fileName} - }); - } - - public async Task QueueUrlScan( - ScanBackend backend, string url, string callbackUrl) - { - return await QueueScan(backend, "/scan/url", new MultipartFormDataContent - { - {new StringContent(callbackUrl), nameof(UrlRequest.CallbackUrl)}, - {new StringContent(url), nameof(UrlRequest.InputUrl)} + Id = result.Id, + Uri = new Uri(fileUrl) }); } } diff --git a/MalwareMultiScan.Api/Services/ScanResultsService.cs b/MalwareMultiScan.Api/Services/ScanResultService.cs similarity index 65% rename from MalwareMultiScan.Api/Services/ScanResultsService.cs rename to MalwareMultiScan.Api/Services/ScanResultService.cs index 83de671..6e85f8c 100644 --- a/MalwareMultiScan.Api/Services/ScanResultsService.cs +++ b/MalwareMultiScan.Api/Services/ScanResultService.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; @@ -13,7 +14,7 @@ using MongoDB.Driver.GridFS; namespace MalwareMultiScan.Api.Services { - public class ScanResultsService + public class ScanResultService { private const string CollectionName = "ScanResults"; @@ -22,7 +23,7 @@ namespace MalwareMultiScan.Api.Services private readonly ScanBackendService _scanBackendService; - public ScanResultsService(IMongoDatabase db, ScanBackendService scanBackendService) + public ScanResultService(IMongoDatabase db, ScanBackendService scanBackendService) { _scanBackendService = scanBackendService; @@ -32,15 +33,27 @@ namespace MalwareMultiScan.Api.Services public async Task CreateScanResult() { - var scanResult = new ScanResult(); + var scanResult = new ScanResult + { + Results = _scanBackendService.List.ToDictionary( + k => k.Id, v => new ScanResultEntry()) + }; await _collection.InsertOneAsync(scanResult); return scanResult; } + + public async Task GetScanResult(string id) + { + var result = await _collection.FindAsync( + Builders.Filter.Where(r => r.Id == id)); + + return await result.FirstOrDefaultAsync(); + } - public async Task UpdateScanResultForBackend( - string resultId, string backendId, bool completed, bool succeeded, string[] threats = null) + public async Task UpdateScanResultForBackend(string resultId, string backendId, + bool completed = false, bool succeeded = false, string[] threats = null) { var filterScanResult = Builders.Filter.Where(r => r.Id == resultId); @@ -54,31 +67,16 @@ namespace MalwareMultiScan.Api.Services await _collection.UpdateOneAsync(filterScanResult, updateScanResult); } - public async Task QueueFileScan(ScanResult result, string fileName, Stream fileStream, string callbackUrl) + public async Task QueueUrlScan(ScanResult result, string fileUrl) { foreach (var backend in _scanBackendService.List) - { - var queueResult = await _scanBackendService.QueueFileScan( - backend, fileName, fileStream, callbackUrl); - - await UpdateScanResultForBackend(result.Id, backend.Id, !queueResult, queueResult); - } - } - - public async Task QueueUrlScan(ScanResult result, string url, string callbackUrl) - { - foreach (var backend in _scanBackendService.List) - { - var queueResult = await _scanBackendService.QueueUrlScan( - backend, url, callbackUrl); - - await UpdateScanResultForBackend(result.Id, backend.Id, !queueResult, queueResult); - } + await _scanBackendService.QueueUrlScan(result, backend, fileUrl); } public async Task StoreFile(string fileName, Stream fileStream) { - var objectId = await _bucket.UploadFromStreamAsync(fileName, fileStream); + var objectId = await _bucket.UploadFromStreamAsync( + fileName, fileStream); return objectId.ToString(); } diff --git a/MalwareMultiScan.Api/Startup.cs b/MalwareMultiScan.Api/Startup.cs index 12d3f37..3685a83 100644 --- a/MalwareMultiScan.Api/Startup.cs +++ b/MalwareMultiScan.Api/Startup.cs @@ -1,8 +1,11 @@ +using EasyNetQ; using MalwareMultiScan.Api.Services; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.HttpOverrides; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.OpenApi.Models; using MongoDB.Driver; namespace MalwareMultiScan.Api @@ -18,8 +21,11 @@ namespace MalwareMultiScan.Api public void ConfigureServices(IServiceCollection services) { + services.AddSingleton(x => + RabbitHutch.CreateBus(_configuration.GetConnectionString("RabbitMQ"))); + services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); services.AddSingleton( serviceProvider => @@ -32,13 +38,45 @@ namespace MalwareMultiScan.Api services.AddControllers(); services.AddHttpClient(); + + services.AddSwaggerGen(options => + { + options.SwaggerDoc("MalwareMultiScan", + new OpenApiInfo + { + Title = "MalwareMultiScan", + Version = "1.0.0" + }); + }); + + services.AddHostedService(); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.UseRouting(); + + var forwardingOptions = new ForwardedHeadersOptions + { + ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto + }; - app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); + forwardingOptions.KnownNetworks.Clear(); + forwardingOptions.KnownProxies.Clear(); + + app.UseForwardedHeaders(forwardingOptions); + + app.UseEndpoints(endpoints => endpoints.MapControllers()); + + app.UseSwagger(); + + app.UseSwaggerUI(options => + { + options.DocumentTitle = "MalwareMultiScan"; + + options.SwaggerEndpoint( + $"/swagger/{options.DocumentTitle}/swagger.json", options.DocumentTitle); + }); } } } \ No newline at end of file diff --git a/MalwareMultiScan.Api/appsettings.json b/MalwareMultiScan.Api/appsettings.json index 4ff2c96..1f83738 100644 --- a/MalwareMultiScan.Api/appsettings.json +++ b/MalwareMultiScan.Api/appsettings.json @@ -9,11 +9,13 @@ "AllowedHosts": "*", "ConnectionStrings": { - "Mongo": "mongodb://localhost:27017" + "Mongo": "mongodb://localhost:27017", + "RabbitMQ": "host=localhost" }, "DatabaseName": "MalwareMultiScan", - "BackendsConfiguration": "backends.yaml", - "StoreFileUploads": true + "ResultsSubscriptionId": "mms.results", + + "BackendsConfiguration": "backends.yaml" } diff --git a/MalwareMultiScan.Api/backends.yaml b/MalwareMultiScan.Api/backends.yaml index 068828c..2c35e07 100644 --- a/MalwareMultiScan.Api/backends.yaml +++ b/MalwareMultiScan.Api/backends.yaml @@ -1,7 +1,2 @@ -- id: windows-defender - name: Windows Defender - endpoint: http://localhost:9902 - -- id: clamav - name: ClamAV - endpoint: http://localhost:9901 \ No newline at end of file +- id: dummy + name: Dummy Backend \ No newline at end of file diff --git a/MalwareMultiScan.Backends/Backends/Implementations/ClamavScanBackend.cs b/MalwareMultiScan.Backends/Backends/Implementations/ClamavScanBackend.cs index 0ccb4d5..8798d8c 100644 --- a/MalwareMultiScan.Backends/Backends/Implementations/ClamavScanBackend.cs +++ b/MalwareMultiScan.Backends/Backends/Implementations/ClamavScanBackend.cs @@ -17,6 +17,8 @@ namespace MalwareMultiScan.Backends.Backends.Implementations protected override Regex MatchRegex { get; } = new Regex(@"(\S+): (?[\S]+) FOUND", RegexOptions.Compiled | RegexOptions.Multiline); + protected override bool ThrowOnNonZeroExitCode { get; } = false; + protected override string GetBackendArguments(string path) { return $"--no-summary {path}"; diff --git a/MalwareMultiScan.Backends/Backends/Implementations/DummyScanBackend.cs b/MalwareMultiScan.Backends/Backends/Implementations/DummyScanBackend.cs new file mode 100644 index 0000000..42338db --- /dev/null +++ b/MalwareMultiScan.Backends/Backends/Implementations/DummyScanBackend.cs @@ -0,0 +1,39 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using MalwareMultiScan.Shared.Interfaces; +using Microsoft.AspNetCore.Http; + +namespace MalwareMultiScan.Backends.Backends.Implementations +{ + public class DummyScanBackend : IScanBackend + { + public string Id { get; } = "dummy"; + + public Task ScanAsync(string path, CancellationToken cancellationToken) + { + return Scan(); + } + + public Task ScanAsync(Uri uri, CancellationToken cancellationToken) + { + return Scan(); + } + + public Task ScanAsync(IFormFile file, CancellationToken cancellationToken) + { + return Scan(); + } + + public Task ScanAsync(Stream stream, CancellationToken cancellationToken) + { + return Scan(); + } + + private static Task Scan() + { + return Task.FromResult(new[] {"Malware.Dummy.Result"}); + } + } +} \ No newline at end of file diff --git a/MalwareMultiScan.Scanner/Extensions/ServiceCollectionExtensions.cs b/MalwareMultiScan.Scanner/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..da1d90b --- /dev/null +++ b/MalwareMultiScan.Scanner/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,32 @@ +using System; +using MalwareMultiScan.Backends.Backends.Implementations; +using MalwareMultiScan.Shared.Data.Enums; +using MalwareMultiScan.Shared.Interfaces; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace MalwareMultiScan.Scanner.Extensions +{ + public static class ServiceCollectionExtensions + { + public static void AddScanningBackend(this IServiceCollection services, BackendType type) + { + using var provider = services.BuildServiceProvider(); + + var logger = provider.GetService(); + + services.AddSingleton(type switch + { + BackendType.Dummy => new DummyScanBackend(), + BackendType.Defender => new WindowsDefenderScanBackend(logger), + BackendType.Clamav => new ClamavScanBackend(logger), + BackendType.DrWeb => new DrWebScanBackend(logger), + BackendType.Kes => new KesScanBackend(logger), + BackendType.Comodo => new ComodoScanBackend(logger), + BackendType.Sophos => new SophosScanBackend(logger), + BackendType.McAfee => new McAfeeScanBackend(logger), + _ => throw new NotImplementedException() + }); + } + } +} \ No newline at end of file diff --git a/MalwareMultiScan.Scanner/MalwareMultiScan.Scanner.csproj b/MalwareMultiScan.Scanner/MalwareMultiScan.Scanner.csproj new file mode 100644 index 0000000..ef10cce --- /dev/null +++ b/MalwareMultiScan.Scanner/MalwareMultiScan.Scanner.csproj @@ -0,0 +1,16 @@ + + + + netcoreapp3.1 + + + + + + + + + + + + diff --git a/MalwareMultiScan.Scanner/Program.cs b/MalwareMultiScan.Scanner/Program.cs new file mode 100644 index 0000000..034996d --- /dev/null +++ b/MalwareMultiScan.Scanner/Program.cs @@ -0,0 +1,31 @@ +using System.Threading.Tasks; +using MalwareMultiScan.Scanner.Extensions; +using MalwareMultiScan.Shared.Data.Enums; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace MalwareMultiScan.Scanner +{ + public static class Program + { + public static async Task Main(string[] args) + { + await Host.CreateDefaultBuilder(args) + .ConfigureAppConfiguration(configure => + { + configure.AddJsonFile("settings.json"); + configure.AddEnvironmentVariables(); + }) + .ConfigureServices((context, services) => + { + services.AddLogging(); + + services.AddScanningBackend( + context.Configuration.GetValue("BackendType")); + + services.AddHostedService(); + }).RunConsoleAsync(); + } + } +} \ No newline at end of file diff --git a/MalwareMultiScan.Scanner/Properties/launchSettings.json b/MalwareMultiScan.Scanner/Properties/launchSettings.json new file mode 100644 index 0000000..f97ddfe --- /dev/null +++ b/MalwareMultiScan.Scanner/Properties/launchSettings.json @@ -0,0 +1,10 @@ +{ + "profiles": { + "MalwareMultiScan.Scanner": { + "commandName": "Project", + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development" + } + } + } +} diff --git a/MalwareMultiScan.Scanner/ScanHostedService.cs b/MalwareMultiScan.Scanner/ScanHostedService.cs new file mode 100644 index 0000000..214ab5b --- /dev/null +++ b/MalwareMultiScan.Scanner/ScanHostedService.cs @@ -0,0 +1,73 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using EasyNetQ; +using MalwareMultiScan.Shared.Data.Messages; +using MalwareMultiScan.Shared.Interfaces; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace MalwareMultiScan.Scanner +{ + public class ScanHostedService : IHostedService + { + private readonly IConfiguration _configuration; + private readonly ILogger _logger; + private readonly IScanBackend _scanBackend; + + private IBus _bus; + + public ScanHostedService(ILogger logger, IConfiguration configuration, IScanBackend scanBackend) + { + _logger = logger; + _configuration = configuration; + _scanBackend = scanBackend; + } + + public Task StartAsync(CancellationToken cancellationToken) + { + _bus = RabbitHutch.CreateBus( + _configuration.GetConnectionString("RabbitMQ")); + + _bus.Receive( + _scanBackend.Id, Scan); + + return Task.CompletedTask; + } + + + public Task StopAsync(CancellationToken cancellationToken) + { + _bus.Dispose(); + + return Task.CompletedTask; + } + + private async Task Scan(ScanRequestMessage message) + { + _logger.LogInformation( + $"Starting scan of {message.Uri} via backend {_scanBackend.Id}"); + + var cancellationTokenSource = new CancellationTokenSource( + TimeSpan.FromSeconds(_configuration.GetValue("MaxScanningTime"))); + + try + { + await _bus.SendAsync(_configuration.GetValue("ResultsSubscriptionId"), new ScanResultMessage + { + Id = message.Id, + Backend = _scanBackend.Id, + + Threats = await _scanBackend.ScanAsync( + message.Uri, cancellationTokenSource.Token) + }); + } + catch (Exception exception) + { + _logger.LogError( + exception, "Scanning failed with exception"); + } + } + } +} \ No newline at end of file diff --git a/MalwareMultiScan.Scanner/settings.json b/MalwareMultiScan.Scanner/settings.json new file mode 100644 index 0000000..18e14f3 --- /dev/null +++ b/MalwareMultiScan.Scanner/settings.json @@ -0,0 +1,20 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + + "BackendType": "Dummy", + + "MaxWorkers": 5, + "MaxScanningTime": 60, + + "ResultsSubscriptionId": "mms.results", + + "ConnectionStrings": { + "RabbitMQ": "host=localhost;prefetchcount=1" + } +} diff --git a/MalwareMultiScan.Shared/Attributes/UrlValidationAttribute.cs b/MalwareMultiScan.Shared/Attributes/UrlValidationAttribute.cs index ba6f232..3bd58e0 100644 --- a/MalwareMultiScan.Shared/Attributes/UrlValidationAttribute.cs +++ b/MalwareMultiScan.Shared/Attributes/UrlValidationAttribute.cs @@ -9,8 +9,8 @@ namespace MalwareMultiScan.Shared.Attributes { var uri = (Uri) value; - if (uri == null || uri.Scheme != "http" && uri.Scheme != "https") - return new ValidationResult("Only http(s) URLs are supported"); + if (uri == null || !uri.IsAbsoluteUri || uri.Scheme != "http" && uri.Scheme != "https") + return new ValidationResult("Only http(s) absolute URLs are supported"); return ValidationResult.Success; } diff --git a/MalwareMultiScan.Shared/Data/Enums/BackendType.cs b/MalwareMultiScan.Shared/Data/Enums/BackendType.cs index 31e4fea..7bdad0c 100644 --- a/MalwareMultiScan.Shared/Data/Enums/BackendType.cs +++ b/MalwareMultiScan.Shared/Data/Enums/BackendType.cs @@ -2,6 +2,7 @@ namespace MalwareMultiScan.Shared.Data.Enums { public enum BackendType { + Dummy, Defender, Clamav, DrWeb, diff --git a/MalwareMultiScan.Shared/Data/Messages/ScanRequestMessage.cs b/MalwareMultiScan.Shared/Data/Messages/ScanRequestMessage.cs new file mode 100644 index 0000000..c5b857e --- /dev/null +++ b/MalwareMultiScan.Shared/Data/Messages/ScanRequestMessage.cs @@ -0,0 +1,11 @@ +using System; + +namespace MalwareMultiScan.Shared.Data.Messages +{ + public class ScanRequestMessage + { + public string Id { get; set; } + + public Uri Uri { get; set; } + } +} \ No newline at end of file diff --git a/MalwareMultiScan.Shared/Data/Messages/ScanResultMessage.cs b/MalwareMultiScan.Shared/Data/Messages/ScanResultMessage.cs new file mode 100644 index 0000000..f288000 --- /dev/null +++ b/MalwareMultiScan.Shared/Data/Messages/ScanResultMessage.cs @@ -0,0 +1,9 @@ +namespace MalwareMultiScan.Shared.Data.Messages +{ + public class ScanResultMessage + { + public string Id { get; set; } + public string Backend { get; set; } + public string[] Threats { get; set; } + } +} \ No newline at end of file diff --git a/MalwareMultiScan.sln b/MalwareMultiScan.sln index 4bdf84b..5bf071d 100644 --- a/MalwareMultiScan.sln +++ b/MalwareMultiScan.sln @@ -8,6 +8,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MalwareMultiScan.Backends", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MalwareMultiScan.Api", "MalwareMultiScan.Api\MalwareMultiScan.Api.csproj", "{7B63B897-D390-4617-821F-F96799CBA2F4}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MalwareMultiScan.Scanner", "MalwareMultiScan.Scanner\MalwareMultiScan.Scanner.csproj", "{8A16A3C4-2AE3-4F63-8280-635FF7878080}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -30,5 +32,9 @@ Global {7B63B897-D390-4617-821F-F96799CBA2F4}.Debug|Any CPU.Build.0 = Debug|Any CPU {7B63B897-D390-4617-821F-F96799CBA2F4}.Release|Any CPU.ActiveCfg = Release|Any CPU {7B63B897-D390-4617-821F-F96799CBA2F4}.Release|Any CPU.Build.0 = Release|Any CPU + {8A16A3C4-2AE3-4F63-8280-635FF7878080}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8A16A3C4-2AE3-4F63-8280-635FF7878080}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8A16A3C4-2AE3-4F63-8280-635FF7878080}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8A16A3C4-2AE3-4F63-8280-635FF7878080}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal