basic structure and initial scan request service

This commit is contained in:
Volodymyr Smirnov 2020-10-23 11:11:19 +03:00
parent 46d696cdb1
commit 96695664e0
23 changed files with 242 additions and 168 deletions

View File

@ -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<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet]
public IEnumerable<WeatherForecast> 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();
}
}
}

View File

@ -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; }
}
}

View File

@ -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<string, ScanRequestEntry> Results { get; set; } =
new Dictionary<string, ScanRequestEntry>();
}
}

View File

@ -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; }
}
}

View File

@ -4,5 +4,25 @@
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<Folder Include="Controllers" />
</ItemGroup>
<ItemGroup>
<None Update="backends.yaml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="MongoDB.Driver" Version="2.11.3" />
<PackageReference Include="MongoDB.Driver.GridFS" Version="2.11.3" />
<PackageReference Include="YamlDotNet" Version="8.1.2" />
</ItemGroup>
<ItemGroup>
<_ContentIncludedByDefault Remove="Properties\launchSettings.json" />
</ItemGroup>
</Project> </Project>

View File

@ -1,23 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace MalwareMultiScan.Api namespace MalwareMultiScan.Api
{ {
public class Program public static class Program
{ {
public static void Main(string[] args) public static void Main(string[] args)
{ {
CreateHostBuilder(args).Build().Run(); CreateHostBuilder(args).Build().Run();
} }
public static IHostBuilder CreateHostBuilder(string[] args) => private static IHostBuilder CreateHostBuilder(string[] args)
Host.CreateDefaultBuilder(args) {
return Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }); .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });
}
} }
} }

View File

@ -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"
}
}
}
}

View File

@ -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<string>("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<BackendConfiguration[]>(configurationContent);
}
public BackendConfiguration[] Backends { get; }
}
}

View File

@ -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<ScanRequest> _collection;
private readonly BackendConfigurationReader _configurationReader;
private readonly IHttpClientFactory _httpClientFactory;
private readonly ILogger<ScanRequestService> _logger;
public ScanRequestService(IMongoDatabase db, BackendConfigurationReader configurationReader,
IHttpClientFactory httpClientFactory, ILogger<ScanRequestService> logger)
{
_configurationReader = configurationReader;
_httpClientFactory = httpClientFactory;
_logger = logger;
_bucket = new GridFSBucket(db);
_collection = db.GetCollection<ScanRequest>(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<string> 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;
}
}
}

View File

@ -1,47 +1,43 @@
using System; using MalwareMultiScan.Api.Services;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using MongoDB.Driver;
using Microsoft.Extensions.Logging;
namespace MalwareMultiScan.Api namespace MalwareMultiScan.Api
{ {
public class Startup public class Startup
{ {
private readonly IConfiguration _configuration;
public Startup(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) public void ConfigureServices(IServiceCollection services)
{ {
services.AddSingleton<BackendConfigurationReader>();
services.AddSingleton<ScanRequestService>();
services.AddSingleton(
serviceProvider =>
{
var db = new MongoClient(_configuration.GetConnectionString("Mongo"));
return db.GetDatabase(
_configuration.GetValue<string>("DatabaseName"));
});
services.AddControllers(); 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) public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{ {
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting(); app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
} }
} }

View File

@ -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; }
}
}

View File

@ -6,5 +6,13 @@
"Microsoft.Hosting.Lifetime": "Information" "Microsoft.Hosting.Lifetime": "Information"
} }
}, },
"AllowedHosts": "*" "AllowedHosts": "*",
"ConnectionStrings": {
"Mongo": "mongodb://localhost:27017"
},
"DatabaseName": "MalwareMultiScan",
"BackendsConfiguration": "backends.yaml"
} }

View File

@ -0,0 +1,7 @@
- id: windows-defender
name: Windows Defender
endpoint: http://localhost:9902
- id: clamav
name: ClamAV
endpoint: http://localhost:9901

View File

@ -14,4 +14,8 @@
<PackageReference Include="Hangfire.MemoryStorage" Version="1.7.0" /> <PackageReference Include="Hangfire.MemoryStorage" Version="1.7.0" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<_ContentIncludedByDefault Remove="Properties\launchSettings.json" />
</ItemGroup>
</Project> </Project>

View File

@ -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"
}
}
}
}

View File

@ -27,7 +27,8 @@ namespace MalwareMultiScan.Worker
{ {
app.UseRouting(); app.UseRouting();
app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); app.UseEndpoints(
endpoints => endpoints.MapControllers());
app.UseHangfireServer(); app.UseHangfireServer();
} }