diff --git a/MalwareMultiScan.Api/Controllers/WeatherForecastController.cs b/MalwareMultiScan.Api/Controllers/WeatherForecastController.cs deleted file mode 100644 index 8bf3c3c..0000000 --- a/MalwareMultiScan.Api/Controllers/WeatherForecastController.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; - -namespace MalwareMultiScan.Api.Controllers -{ - [ApiController] - [Route("[controller]")] - public class WeatherForecastController : ControllerBase - { - private static readonly string[] Summaries = new[] - { - "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" - }; - - private readonly ILogger _logger; - - public WeatherForecastController(ILogger logger) - { - _logger = logger; - } - - [HttpGet] - public IEnumerable Get() - { - var rng = new Random(); - return Enumerable.Range(1, 5).Select(index => new WeatherForecast - { - Date = DateTime.Now.AddDays(index), - TemperatureC = rng.Next(-20, 55), - Summary = Summaries[rng.Next(Summaries.Length)] - }) - .ToArray(); - } - } -} \ No newline at end of file diff --git a/MalwareMultiScan.Api/Data/Configuration/BackendConfiguration.cs b/MalwareMultiScan.Api/Data/Configuration/BackendConfiguration.cs new file mode 100644 index 0000000..3524440 --- /dev/null +++ b/MalwareMultiScan.Api/Data/Configuration/BackendConfiguration.cs @@ -0,0 +1,9 @@ +namespace MalwareMultiScan.Api.Data.Configuration +{ + public class BackendConfiguration + { + 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/Data/Models/ScanRequest.cs b/MalwareMultiScan.Api/Data/Models/ScanRequest.cs new file mode 100644 index 0000000..23c9af3 --- /dev/null +++ b/MalwareMultiScan.Api/Data/Models/ScanRequest.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; + +namespace MalwareMultiScan.Api.Data.Models +{ + public class ScanRequest + { + [BsonId] + [BsonRepresentation(BsonType.ObjectId)] + public string Id { get; set; } + + [BsonRepresentation(BsonType.ObjectId)] + public string FileId { get; set; } + + public Dictionary Results { get; set; } = + new Dictionary(); + } +} \ No newline at end of file diff --git a/MalwareMultiScan.Api/Data/Models/ScanRequestEntry.cs b/MalwareMultiScan.Api/Data/Models/ScanRequestEntry.cs new file mode 100644 index 0000000..87dccd8 --- /dev/null +++ b/MalwareMultiScan.Api/Data/Models/ScanRequestEntry.cs @@ -0,0 +1,9 @@ +namespace MalwareMultiScan.Api.Data.Models +{ + public class ScanRequestEntry + { + public bool Completed { get; set; } + public bool Succeeded { get; set; } + public string[] Threats { get; set; } + } +} \ No newline at end of file diff --git a/MalwareMultiScan.Api/MalwareMultiScan.Api.csproj b/MalwareMultiScan.Api/MalwareMultiScan.Api.csproj index 68ded6d..8e7a9c3 100644 --- a/MalwareMultiScan.Api/MalwareMultiScan.Api.csproj +++ b/MalwareMultiScan.Api/MalwareMultiScan.Api.csproj @@ -4,5 +4,25 @@ netcoreapp3.1 + + + + + + + Always + + + + + + + + + + + <_ContentIncludedByDefault Remove="Properties\launchSettings.json" /> + + diff --git a/MalwareMultiScan.Api/Program.cs b/MalwareMultiScan.Api/Program.cs index c1e647c..13d0b5b 100644 --- a/MalwareMultiScan.Api/Program.cs +++ b/MalwareMultiScan.Api/Program.cs @@ -1,23 +1,19 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; namespace MalwareMultiScan.Api { - public class Program + public static class Program { public static void Main(string[] args) { CreateHostBuilder(args).Build().Run(); } - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) + private static IHostBuilder CreateHostBuilder(string[] args) + { + return Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); + } } } \ No newline at end of file diff --git a/MalwareMultiScan.Api/Properties/launchSettings.json b/MalwareMultiScan.Api/Properties/launchSettings.json deleted file mode 100644 index e2cb472..0000000 --- a/MalwareMultiScan.Api/Properties/launchSettings.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "$schema": "http://json.schemastore.org/launchsettings.json", - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:45236", - "sslPort": 44367 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "weatherforecast", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "MalwareMultiScan.Api": { - "commandName": "Project", - "launchBrowser": true, - "launchUrl": "weatherforecast", - "applicationUrl": "https://localhost:5001;http://localhost:5000", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} diff --git a/MalwareMultiScan.Api/Services/BackendConfigurationReader.cs b/MalwareMultiScan.Api/Services/BackendConfigurationReader.cs new file mode 100644 index 0000000..b6a9b0a --- /dev/null +++ b/MalwareMultiScan.Api/Services/BackendConfigurationReader.cs @@ -0,0 +1,29 @@ +using System.IO; +using MalwareMultiScan.Api.Data.Configuration; +using Microsoft.Extensions.Configuration; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace MalwareMultiScan.Api.Services +{ + public class BackendConfigurationReader + { + public BackendConfigurationReader(IConfiguration configuration) + { + var configurationPath = configuration.GetValue("BackendsConfiguration"); + + if (!File.Exists(configurationPath)) + throw new FileNotFoundException("Missing BackendsConfiguration YAML file", configurationPath); + + var configurationContent = File.ReadAllText(configurationPath); + + var deserializer = new DeserializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .Build(); + + Backends = deserializer.Deserialize(configurationContent); + } + + public BackendConfiguration[] Backends { get; } + } +} \ No newline at end of file diff --git a/MalwareMultiScan.Api/Services/ScanRequestService.cs b/MalwareMultiScan.Api/Services/ScanRequestService.cs new file mode 100644 index 0000000..6813342 --- /dev/null +++ b/MalwareMultiScan.Api/Services/ScanRequestService.cs @@ -0,0 +1,90 @@ +using System; +using System.IO; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using MalwareMultiScan.Api.Data.Models; +using Microsoft.Extensions.Logging; +using MongoDB.Driver; +using MongoDB.Driver.GridFS; + +namespace MalwareMultiScan.Api.Services +{ + public class ScanRequestService + { + private const string CollectionName = "ScanRequests"; + + private readonly GridFSBucket _bucket; + private readonly IMongoCollection _collection; + private readonly BackendConfigurationReader _configurationReader; + private readonly IHttpClientFactory _httpClientFactory; + private readonly ILogger _logger; + + public ScanRequestService(IMongoDatabase db, BackendConfigurationReader configurationReader, + IHttpClientFactory httpClientFactory, ILogger logger) + { + _configurationReader = configurationReader; + _httpClientFactory = httpClientFactory; + _logger = logger; + + _bucket = new GridFSBucket(db); + _collection = db.GetCollection(CollectionName); + } + + private async Task QueueScans(string requestId, string fileName, Stream fileStream) + { + foreach (var backend in _configurationReader.Backends) + { + var cancellationTokenSource = new CancellationTokenSource( + TimeSpan.FromSeconds(5)); + + using var httpClient = _httpClientFactory.CreateClient(); + + try + { + var endpointUri = new Uri( + new Uri(backend.Endpoint), "/scan/file"); + + var formContent = new MultipartFormDataContent + { + {new StringContent("value1"), "callbackUrl"}, + {new StreamContent(fileStream), "inputFile", fileName} + }; + + var response = await httpClient.PostAsync( + endpointUri, formContent, cancellationTokenSource.Token); + + response.EnsureSuccessStatusCode(); + + // TODO: update scan request + } + catch (Exception e) + { + _logger.LogError(e, + $"Failed to queue scanning job on {backend.Id} ({backend.Endpoint})"); + } + } + } + + public async Task InitializeNewRequest(string fileName, Stream fileStream, + CancellationToken cancellationToken = default) + { + var fileId = await _bucket.UploadFromStreamAsync(fileName, fileStream, + cancellationToken: cancellationToken); + + var scanRequest = new ScanRequest + { + FileId = fileId.ToString() + }; + + await _collection.InsertOneAsync(scanRequest, new InsertOneOptions + { + BypassDocumentValidation = false + }, cancellationToken); + + await QueueScans(scanRequest.Id, fileName, fileStream); + + return scanRequest.Id; + } + } +} \ No newline at end of file diff --git a/MalwareMultiScan.Api/Startup.cs b/MalwareMultiScan.Api/Startup.cs index a61cb51..7ed32be 100644 --- a/MalwareMultiScan.Api/Startup.cs +++ b/MalwareMultiScan.Api/Startup.cs @@ -1,47 +1,43 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using MalwareMultiScan.Api.Services; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.HttpsPolicy; -using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; +using MongoDB.Driver; namespace MalwareMultiScan.Api { public class Startup { + private readonly IConfiguration _configuration; + public Startup(IConfiguration configuration) { - Configuration = configuration; + _configuration = configuration; } - - public IConfiguration Configuration { get; } - - // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) { + services.AddSingleton(); + services.AddSingleton(); + + services.AddSingleton( + serviceProvider => + { + var db = new MongoClient(_configuration.GetConnectionString("Mongo")); + + return db.GetDatabase( + _configuration.GetValue("DatabaseName")); + }); + services.AddControllers(); + services.AddHttpClient(); } - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - - app.UseHttpsRedirection(); - app.UseRouting(); - app.UseAuthorization(); - app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); } } diff --git a/MalwareMultiScan.Api/WeatherForecast.cs b/MalwareMultiScan.Api/WeatherForecast.cs deleted file mode 100644 index 19f8687..0000000 --- a/MalwareMultiScan.Api/WeatherForecast.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace MalwareMultiScan.Api -{ - public class WeatherForecast - { - public DateTime Date { get; set; } - - public int TemperatureC { get; set; } - - public int TemperatureF => 32 + (int) (TemperatureC / 0.5556); - - public string Summary { get; set; } - } -} \ No newline at end of file diff --git a/MalwareMultiScan.Api/appsettings.json b/MalwareMultiScan.Api/appsettings.json index d9d9a9b..62ff795 100644 --- a/MalwareMultiScan.Api/appsettings.json +++ b/MalwareMultiScan.Api/appsettings.json @@ -6,5 +6,13 @@ "Microsoft.Hosting.Lifetime": "Information" } }, - "AllowedHosts": "*" + "AllowedHosts": "*", + + "ConnectionStrings": { + "Mongo": "mongodb://localhost:27017" + }, + + "DatabaseName": "MalwareMultiScan", + + "BackendsConfiguration": "backends.yaml" } diff --git a/MalwareMultiScan.Api/backends.yaml b/MalwareMultiScan.Api/backends.yaml new file mode 100644 index 0000000..068828c --- /dev/null +++ b/MalwareMultiScan.Api/backends.yaml @@ -0,0 +1,7 @@ +- id: windows-defender + name: Windows Defender + endpoint: http://localhost:9902 + +- id: clamav + name: ClamAV + endpoint: http://localhost:9901 \ No newline at end of file diff --git a/MalwareMultiScan.Backends/Backends/Abstracts/AbstractLocalProcessScanBackend.cs b/MalwareMultiScan.Backends/Backends/Abstracts/AbstractLocalProcessScanBackend.cs index f46606b..5e4c344 100644 --- a/MalwareMultiScan.Backends/Backends/Abstracts/AbstractLocalProcessScanBackend.cs +++ b/MalwareMultiScan.Backends/Backends/Abstracts/AbstractLocalProcessScanBackend.cs @@ -56,13 +56,13 @@ namespace MalwareMultiScan.Backends.Backends.Abstracts process.WaitForExit(); _logger.LogInformation($"Process has exited with code {process.ExitCode}"); - + var standardOutput = await process.StandardOutput.ReadToEndAsync(); var standardError = await process.StandardError.ReadToEndAsync(); _logger.LogDebug($"Process standard output: {standardOutput}"); _logger.LogDebug($"Process standard error: {standardError}"); - + if (ThrowOnNonZeroExitCode && process.ExitCode != 0) throw new ApplicationException($"Process has terminated with an exit code {process.ExitCode}"); diff --git a/MalwareMultiScan.Backends/Backends/Implementations/ClamavScanBackend.cs b/MalwareMultiScan.Backends/Backends/Implementations/ClamavScanBackend.cs index 2aa6e69..2d2de61 100644 --- a/MalwareMultiScan.Backends/Backends/Implementations/ClamavScanBackend.cs +++ b/MalwareMultiScan.Backends/Backends/Implementations/ClamavScanBackend.cs @@ -11,14 +11,14 @@ namespace MalwareMultiScan.Backends.Backends.Implementations public ClamavScanBackend(ILogger logger) : base(logger) { } - + public override string Id { get; } = "clamav"; - + public override DateTime DatabaseLastUpdate => File.GetLastWriteTime("/var/lib/clamav/daily.cvd"); protected override string BackendPath { get; } = "/usr/bin/clamscan"; - + protected override Regex MatchRegex { get; } = new Regex(@"(\S+): (?[\S]+) FOUND", RegexOptions.Compiled | RegexOptions.Multiline); diff --git a/MalwareMultiScan.Backends/Backends/Implementations/ComodoScanBackend.cs b/MalwareMultiScan.Backends/Backends/Implementations/ComodoScanBackend.cs index 2d40818..6ef2bde 100644 --- a/MalwareMultiScan.Backends/Backends/Implementations/ComodoScanBackend.cs +++ b/MalwareMultiScan.Backends/Backends/Implementations/ComodoScanBackend.cs @@ -11,16 +11,16 @@ namespace MalwareMultiScan.Backends.Backends.Implementations public ComodoScanBackend(ILogger logger) : base(logger) { } - + public override string Id { get; } = "comodo"; - + public override DateTime DatabaseLastUpdate => File.GetLastWriteTime("/opt/COMODO/scanners/bases.cav"); protected override string BackendPath { get; } = "/opt/COMODO/cmdscan"; - + protected override Regex MatchRegex { get; } = - new Regex(@".* ---> Found Virus, Malware Name is (?.*)", + new Regex(@".* ---> Found Virus, Malware Name is (?.*)", RegexOptions.Compiled | RegexOptions.Multiline); protected override string GetBackendArguments(string path) diff --git a/MalwareMultiScan.Backends/Backends/Implementations/DrWebScanBackend.cs b/MalwareMultiScan.Backends/Backends/Implementations/DrWebScanBackend.cs index ba8d11b..b042b52 100644 --- a/MalwareMultiScan.Backends/Backends/Implementations/DrWebScanBackend.cs +++ b/MalwareMultiScan.Backends/Backends/Implementations/DrWebScanBackend.cs @@ -11,14 +11,14 @@ namespace MalwareMultiScan.Backends.Backends.Implementations public DrWebScanBackend(ILogger logger) : base(logger) { } - + public override string Id { get; } = "drweb"; - + public override DateTime DatabaseLastUpdate => File.GetLastWriteTime("/var/opt/drweb.com/version/version.ini"); protected override string BackendPath { get; } = "/usr/bin/drweb-ctl"; - + protected override Regex MatchRegex { get; } = new Regex(@".* - infected with (?[\S ]+)", RegexOptions.Compiled | RegexOptions.Multiline); diff --git a/MalwareMultiScan.Backends/Backends/Implementations/KesScanBackend.cs b/MalwareMultiScan.Backends/Backends/Implementations/KesScanBackend.cs index 89b9b3b..6127780 100644 --- a/MalwareMultiScan.Backends/Backends/Implementations/KesScanBackend.cs +++ b/MalwareMultiScan.Backends/Backends/Implementations/KesScanBackend.cs @@ -11,14 +11,14 @@ namespace MalwareMultiScan.Backends.Backends.Implementations public KesScanBackend(ILogger logger) : base(logger) { } - + public override string Id { get; } = "kes"; - + public override DateTime DatabaseLastUpdate => File.GetLastWriteTime("/var/opt/kaspersky/kesl/common/updates/avbases/klsrl.dat"); protected override string BackendPath { get; } = "/bin/bash"; - + protected override Regex MatchRegex { get; } = new Regex(@"[ +]DetectName.*: (?.*)", RegexOptions.Compiled | RegexOptions.Multiline); diff --git a/MalwareMultiScan.Backends/Backends/Implementations/McAffeeScanBackend.cs b/MalwareMultiScan.Backends/Backends/Implementations/McAffeeScanBackend.cs index 1970951..1791d8a 100644 --- a/MalwareMultiScan.Backends/Backends/Implementations/McAffeeScanBackend.cs +++ b/MalwareMultiScan.Backends/Backends/Implementations/McAffeeScanBackend.cs @@ -11,14 +11,14 @@ namespace MalwareMultiScan.Backends.Backends.Implementations public McAfeeScanBackend(ILogger logger) : base(logger) { } - + public override string Id { get; } = "mcafeee"; - + public override DateTime DatabaseLastUpdate => File.GetLastWriteTime("/usr/local/uvscan/avvscan.dat"); protected override string BackendPath { get; } = "/usr/local/uvscan/uvscan"; - + protected override bool ThrowOnNonZeroExitCode { get; } = false; protected override Regex MatchRegex { get; } = diff --git a/MalwareMultiScan.Backends/Backends/Implementations/SophosScanBackend.cs b/MalwareMultiScan.Backends/Backends/Implementations/SophosScanBackend.cs index d363a77..8c9cfe9 100644 --- a/MalwareMultiScan.Backends/Backends/Implementations/SophosScanBackend.cs +++ b/MalwareMultiScan.Backends/Backends/Implementations/SophosScanBackend.cs @@ -11,9 +11,9 @@ namespace MalwareMultiScan.Backends.Backends.Implementations public SophosScanBackend(ILogger logger) : base(logger) { } - + public override string Id { get; } = "sophos"; - + public override DateTime DatabaseLastUpdate => File.GetLastWriteTime("/opt/sophos-av/lib/sav/vdlsync.upd"); diff --git a/MalwareMultiScan.Worker/MalwareMultiScan.Worker.csproj b/MalwareMultiScan.Worker/MalwareMultiScan.Worker.csproj index 3f7bdca..ded602c 100644 --- a/MalwareMultiScan.Worker/MalwareMultiScan.Worker.csproj +++ b/MalwareMultiScan.Worker/MalwareMultiScan.Worker.csproj @@ -13,5 +13,9 @@ + + + <_ContentIncludedByDefault Remove="Properties\launchSettings.json" /> + diff --git a/MalwareMultiScan.Worker/Properties/launchSettings.json b/MalwareMultiScan.Worker/Properties/launchSettings.json deleted file mode 100644 index 2c9b6ab..0000000 --- a/MalwareMultiScan.Worker/Properties/launchSettings.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "$schema": "http://json.schemastore.org/launchsettings.json", - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:53549", - "sslPort": 44380 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "weatherforecast", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "MalwareMultiScan.Worker": { - "commandName": "Project", - "launchBrowser": true, - "launchUrl": "weatherforecast", - "applicationUrl": "https://localhost:5001;http://localhost:5000", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} diff --git a/MalwareMultiScan.Worker/Startup.cs b/MalwareMultiScan.Worker/Startup.cs index bd3ba6f..a4e1b19 100644 --- a/MalwareMultiScan.Worker/Startup.cs +++ b/MalwareMultiScan.Worker/Startup.cs @@ -27,7 +27,8 @@ namespace MalwareMultiScan.Worker { app.UseRouting(); - app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); + app.UseEndpoints( + endpoints => endpoints.MapControllers()); app.UseHangfireServer(); }