Skip to content

Commit

Permalink
feat(scan): provide a default scan factory
Browse files Browse the repository at this point in the history
closes #68
  • Loading branch information
ostridm committed Dec 2, 2022
1 parent f8fe51a commit 6c21728
Show file tree
Hide file tree
Showing 28 changed files with 942 additions and 166 deletions.
95 changes: 95 additions & 0 deletions src/SecTester.Scan/DefaultScanFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using SecTester.Core;
using SecTester.Core.Utils;
using SecTester.Scan.Models;
using SecTester.Scan.Target.Har;
using Response = SecTester.Scan.Target.Har.Response;

namespace SecTester.Scan;

public class DefaultScanFactory : ScanFactory
{
private static readonly IEnumerable<Discovery> DefaultDiscoveryTypes = new List<Discovery> { Discovery.Archive };
private readonly Scans _scans;
private readonly ILogger _logger;
private readonly Configuration _configuration;
private readonly SystemTimeProvider _systemTimeProvider;

public DefaultScanFactory(Configuration configuration, Scans scans, SystemTimeProvider systemTimeProvider,
ILogger logger)
{
_scans = scans ?? throw new ArgumentNullException(nameof(scans));
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
_systemTimeProvider = systemTimeProvider ?? throw new ArgumentNullException(nameof(systemTimeProvider));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

public async Task<Scan> CreateScan(ScanSettingsOptions settingsOptions, ScanOptions? options)
{
var scanConfig = await BuildScanConfig(new ScanSettings(settingsOptions)).ConfigureAwait(false);
var scanId = await _scans.CreateScan(scanConfig).ConfigureAwait(false);

return new Scan(scanId, _scans, _logger, options ?? new ScanOptions());
}

private async Task<ScanConfig> BuildScanConfig(ScanSettingsOptions settingsOptions)
{
var target = new Target.Target(settingsOptions.Target);
var fileId = await CreateAndUploadHar(target).ConfigureAwait(false);

return new ScanConfig(settingsOptions.Name!)
{
FileId = fileId,
Smart = settingsOptions.Smart,
PoolSize = settingsOptions.PoolSize,
SkipStaticParams = settingsOptions.SkipStaticParams,
Module = Module.Dast,
DiscoveryTypes = DefaultDiscoveryTypes,
AttackParamLocations = settingsOptions.AttackParamLocations,
Tests = settingsOptions.Tests,
Repeaters = settingsOptions.RepeaterId is null ? default : new List<string> { settingsOptions.RepeaterId },
SlowEpTimeout =
settingsOptions.SlowEpTimeout is null ? default : (int)settingsOptions.SlowEpTimeout.Value.TotalSeconds,
TargetTimeout =
settingsOptions.TargetTimeout is null ? default : (int)settingsOptions.TargetTimeout.Value.TotalSeconds
};
}

private async Task<string> CreateAndUploadHar(Target.Target target)
{
var filename = GenerateFileName(target.Url);
var har = await CreateHar(target).ConfigureAwait(false);

return await _scans.UploadHar(new UploadHarOptions(har, filename, true)).ConfigureAwait(false);
}

private static string GenerateFileName(string url)
{
var host = new Uri(url).Host;

host = host.Length <= HarDefaults.MaxHostLength ? host : host.Substring(0, HarDefaults.MaxHostLength);

return $"{host.TrimEnd('-')}-{Guid.NewGuid().ToString()}.har";
}

private async Task<Entry> CreateHarEntry(Target.Target target)
{
var request = await target.ToHarRequest().ConfigureAwait(false);
return new Entry(_systemTimeProvider.Now, 0, Timings.Default, Cache.Default, request, Response.Default);
}

private async Task<Har> CreateHar(Target.Target target)
{
var entry = await CreateHarEntry(target).ConfigureAwait(false);

return new Har(
new Log(
new Creator(_configuration.Name, _configuration.Version),
new List<Entry> { entry }
)
);
}
}
2 changes: 1 addition & 1 deletion src/SecTester.Scan/Models/ScanConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace SecTester.Scan.Models;

public record ScanConfig(string Name)
{
public string Name { get; init; } = Name ?? throw new ArgumentNullException(nameof(Name));
public string Name { get; } = Name ?? throw new ArgumentNullException(nameof(Name));
public Module? Module { get; init; }
public IEnumerable<TestType>? Tests { get; init; }
public IEnumerable<Discovery>? DiscoveryTypes { get; init; }
Expand Down
9 changes: 7 additions & 2 deletions src/SecTester.Scan/Scan.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using SecTester.Scan.Models;

namespace SecTester.Scan;
Expand All @@ -22,14 +23,18 @@ public class Scan : IDisposable
};

private readonly ScanOptions _options;

private readonly ILogger _logger;
private readonly Scans _scans;
private readonly ScanState _scanState = new(ScanStatus.Pending);

public Scan(Scans scans, ScanOptions options)
public string Id { get; }

public Scan(string id, Scans scans, ILogger logger, ScanOptions options)
{
Id = id ?? throw new ArgumentNullException(nameof(id));
_scans = scans ?? throw new ArgumentNullException(nameof(scans));
_options = options ?? throw new ArgumentNullException(nameof(options));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

public bool Active => ActiveStatuses.Contains(_scanState.Status);
Expand Down
38 changes: 38 additions & 0 deletions src/SecTester.Scan/ScanDefaults.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq;
using SecTester.Scan.Models;

namespace SecTester.Scan;

internal static class ScanDefaults
{
public const int MinNameLength = 1;
public const int MaxNameLength = 200;
public const int MinPoolSize = 1;
public const int MaxPoolSize = 50;

public static readonly TimeSpan MinTargetTimeout = TimeSpan.FromSeconds(1);
public static readonly TimeSpan MaxTargetTimeout = TimeSpan.FromSeconds(120);
public static readonly TimeSpan MinSlowEpTimeout = TimeSpan.FromSeconds(100);

public const bool DefaultSmart = true;
public const bool DefaultSkipStaticParams = true;
public const int DefaultPoolSize = 10;

public static readonly TimeSpan DefaultTargetTimeout = TimeSpan.FromSeconds(5);
public static readonly TimeSpan DefaultSlowEpTimeout = TimeSpan.FromSeconds(1000);

public static readonly IEnumerable<AttackParamLocation> DefaultAttackParamLocations = new List<AttackParamLocation>
{
AttackParamLocation.Body, AttackParamLocation.Query, AttackParamLocation.Fragment
};

public static readonly IEnumerable<TestType> TestTypeWhiteList = Enum
.GetValues(typeof(TestType))
.Cast<TestType>();

public static readonly IEnumerable<AttackParamLocation> AttackParamLocationWhiteList = Enum
.GetValues(typeof(AttackParamLocation))
.Cast<AttackParamLocation>();
}
2 changes: 1 addition & 1 deletion src/SecTester.Scan/ScanFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ namespace SecTester.Scan;

public interface ScanFactory
{
Task<Scan> CreateScan(ScanSettingsOptions settingsOptions, ScanOptions? options);
Task<Scan> CreateScan(ScanSettingsOptions settingsOptions, ScanOptions? options = default);
}
173 changes: 173 additions & 0 deletions src/SecTester.Scan/ScanSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using SecTester.Scan.Models;
using SecTester.Scan.Target;

namespace SecTester.Scan;

public record ScanSettings : ScanSettingsOptions
{
private Target.Target _target = null!;
private IEnumerable<TestType> _tests = null!;

private string? _name;
private TimeSpan? _targetTimeout;
private TimeSpan? _slowEpTimeout;
private int? _poolSize;
private IEnumerable<AttackParamLocation>? _attackParamLocations;

public TargetOptions Target
{
get { return _target; }
set
{
_target = new Target.Target(value);
}
}

public string? Name
{
get { return _name; }
set
{
if (string.IsNullOrWhiteSpace(value) ||
value!.Length is < ScanDefaults.MinNameLength or > ScanDefaults.MaxNameLength)
{
throw new ArgumentException($"Name must be less than {ScanDefaults.MaxNameLength} characters.");
}

_name = value;
}
}

public string? RepeaterId { get; set; }

public bool? Smart { get; set; }

public int? PoolSize
{
get { return _poolSize; }
set
{
if (value is null or < ScanDefaults.MinPoolSize or > ScanDefaults.MaxPoolSize)
{
throw new ArgumentException("Invalid pool size.");
}

_poolSize = value;
}
}

public TimeSpan? SlowEpTimeout
{
get { return _slowEpTimeout; }
set
{
if (value is null || value < ScanDefaults.MinSlowEpTimeout)
{
throw new ArgumentException("Invalid slow entry point timeout.");
}

_slowEpTimeout = value;
}
}

public TimeSpan? TargetTimeout
{
get { return _targetTimeout; }
set
{
if (value is null || (value < ScanDefaults.MinTargetTimeout || value > ScanDefaults.MaxTargetTimeout))
{
throw new ArgumentException("Invalid target connection timeout.");
}

_targetTimeout = value;
}
}

public bool? SkipStaticParams { get; set; }

public IEnumerable<TestType> Tests
{
get { return _tests; }
set
{
Assert(value, ScanDefaults.TestTypeWhiteList,
"Unknown test type supplied.",
"Please provide at least one test.");

_tests = value.Distinct();
}
}

public IEnumerable<AttackParamLocation>? AttackParamLocations
{
get { return _attackParamLocations; }
set
{
Assert(value, ScanDefaults.AttackParamLocationWhiteList,
"Unknown attack param location supplied.",
"Please provide at least one attack parameter location.");

_attackParamLocations = value!.Distinct();
}
}

public ScanSettings(TargetOptions targetOptions, IEnumerable<TestType> tests, string? repeaterId = default,
string? name = default)
: this(targetOptions, tests, repeaterId, name, ScanDefaults.DefaultSmart, ScanDefaults.DefaultPoolSize,
ScanDefaults.DefaultTargetTimeout,
ScanDefaults.DefaultSlowEpTimeout, ScanDefaults.DefaultSkipStaticParams, ScanDefaults.DefaultAttackParamLocations)
{
}

internal ScanSettings(ScanSettingsOptions scanSettingsOptions)
: this(scanSettingsOptions.Target, scanSettingsOptions.Tests, scanSettingsOptions.RepeaterId,
scanSettingsOptions.Name, scanSettingsOptions.Smart, scanSettingsOptions.PoolSize,
scanSettingsOptions.TargetTimeout, scanSettingsOptions.SlowEpTimeout, scanSettingsOptions.SkipStaticParams,
scanSettingsOptions.AttackParamLocations)
{
}

private ScanSettings(TargetOptions targetOptions, IEnumerable<TestType> tests, string? repeaterId,
string? name,
bool? smart, int? poolSize, TimeSpan? targetTimeout,
TimeSpan? slowEpTimeout, bool? skipStaticParams,
IEnumerable<AttackParamLocation>? attackParamLocations)
{
Target = targetOptions;
Tests = tests;
Name = name ?? CreateDefaultName(targetOptions);
RepeaterId = repeaterId;
Smart = smart ?? ScanDefaults.DefaultSmart;
PoolSize = poolSize ?? ScanDefaults.DefaultPoolSize;
TargetTimeout = targetTimeout ?? ScanDefaults.DefaultTargetTimeout;
SlowEpTimeout = slowEpTimeout ?? ScanDefaults.DefaultSlowEpTimeout;
SkipStaticParams = skipStaticParams ?? ScanDefaults.DefaultSkipStaticParams;
AttackParamLocations = attackParamLocations ?? ScanDefaults.DefaultAttackParamLocations;
}

private static string CreateDefaultName(TargetOptions target)
{
var uri = new Uri(target.Url);
var name = $"{target.Method ?? HttpMethod.Get} {uri.Host}";

return name.Length <= ScanDefaults.MaxNameLength ? name : name.Substring(0, ScanDefaults.MaxNameLength);
}

private static void Assert<T>(IEnumerable<T>? value, IEnumerable<T> whiteList,
string unknownEntry, string emptyList)
{
if (value is null || !value.All(whiteList.Contains))
{
throw new ArgumentException(unknownEntry);
}
if (!value.Distinct().Any())
{
throw new ArgumentException(emptyList);
}
}
}
6 changes: 6 additions & 0 deletions src/SecTester.Scan/Target/Har/Cache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace SecTester.Scan.Target.Har;

public record Cache()
{
public static readonly Cache Default = new();
};
6 changes: 6 additions & 0 deletions src/SecTester.Scan/Target/Har/Content.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace SecTester.Scan.Target.Har;

public record Content(int Size, string MimeType)
{
public static readonly Content Default = new(-1, "text/plain");
}
3 changes: 3 additions & 0 deletions src/SecTester.Scan/Target/Har/Creator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace SecTester.Scan.Target.Har;

public record Creator(string Name, string Version);
6 changes: 6 additions & 0 deletions src/SecTester.Scan/Target/Har/Entry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using System;

namespace SecTester.Scan.Target.Har;

public record Entry(DateTime StartedDateTime, int Time, Timings Timings, Cache Cache, Request Request,
Response Response);
2 changes: 1 addition & 1 deletion src/SecTester.Scan/Target/Har/Har.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
namespace SecTester.Scan.Target.Har;

public record Har;
public record Har(Log Log);
6 changes: 6 additions & 0 deletions src/SecTester.Scan/Target/Har/HarDefaults.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace SecTester.Scan.Target.Har;

public static class HarDefaults
{
public const int MaxHostLength = 200;
}
5 changes: 5 additions & 0 deletions src/SecTester.Scan/Target/Har/Log.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using System.Collections.Generic;

namespace SecTester.Scan.Target.Har;

public record Log(Creator Creator, IEnumerable<Entry> Entries, string Version = "1.2");
Loading

0 comments on commit 6c21728

Please sign in to comment.