< Summary - Heimdall API

Information
Class: ArturRios.Heimdall.WebApi.Monitoring.SecurityMonitoringService
Assembly: ArturRios.Heimdall.WebApi
File(s): /home/ci/agent/workspace/heimdall-api-docs_main/app/src/Presentation/ArturRios.Heimdall.WebApi/Monitoring/SecurityMonitoringService.cs
Line coverage
22%
Covered lines: 4
Uncovered lines: 14
Coverable lines: 18
Total lines: 79
Line coverage: 22.2%
Branch coverage
0%
Covered branches: 0
Total branches: 12
Branch coverage: 0%
Method coverage
50%
Covered methods: 2
Fully covered methods: 2
Total methods: 4
Method coverage: 50%
Full method coverage: 50%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
get_PassName()100%210%
get_StartupDescription()100%11100%
RunAsync()0%156120%

File(s)

/home/ci/agent/workspace/heimdall-api-docs_main/app/src/Presentation/ArturRios.Heimdall.WebApi/Monitoring/SecurityMonitoringService.cs

#LineLine coverage
 1using ArturRios.Heimdall.Query.Handlers;
 2using ArturRios.Heimdall.Query.Input;
 3using ArturRios.Heimdall.Query.Output;
 4using ArturRios.Heimdall.Shared.Retention;
 5using ArturRios.Mediator.Query;
 6using ArturRios.Heimdall.WebApi.Retention;
 7
 8namespace ArturRios.Heimdall.WebApi.Monitoring;
 9
 10/// <summary>
 11///     Reads the security signals the system already produces and writes the ones worth acting on
 12///     to the log, at warning level, with a stable marker an alerting rule can match (NFR-26).
 13/// </summary>
 14/// <remarks>
 15///     <para>
 16///         <b>The log is the destination, deliberately.</b> This API has no opinion about where an
 17///         operator's alerts go — a mail relay, a pager, a SIEM — and building an integration for
 18///         one would be guessing. What it can do is make the signal unmistakable and machine-matchable
 19///         where the operator is already collecting: the logs, which #98 gave a retention period and
 20///         which ship off the host.
 21///     </para>
 22///     <para>
 23///         <b>Every signal is logged on every run it persists.</b> There is no suppression or
 24///         deduplication, which would be the obvious refinement and is the wrong one here: an
 25///         attack that continues is more interesting on its tenth tick than its first, and a
 26///         detector that goes quiet while the thing it detects is still happening is worse than no
 27///         detector.
 28///     </para>
 29/// </remarks>
 30public class SecurityMonitoringService(
 31    IServiceScopeFactory scopeFactory,
 32    DataRetentionOptions options,
 33    ILogger<SecurityMonitoringService> logger)
 96634    : ScheduledPassService(scopeFactory, options.MonitoringWindow, logger)
 35{
 36    /// <summary>
 37    ///     The marker every signal line carries. Stable, greppable, and the thing an alerting rule
 38    ///     should match on rather than the prose, which is written for a human reading afterwards.
 39    /// </summary>
 40    public const string Marker = "SECURITY_SIGNAL";
 41
 042    public override string PassName => "security-monitoring";
 43
 44    protected override string StartupDescription =>
 96645        $"Security signal detection scheduled every {options.MonitoringWindow}, alerting at "
 96646        + $"{options.RefusalThreshold} refusals by one identity or {options.LockoutThreshold} "
 96647        + "accounts locked out at once";
 48
 49    /// <remarks>
 50    ///     Resolves the query mediator rather than the command one every other pass uses: this pass
 51    ///     reads, and making it a command would put an audit entry on every tick saying "looked, saw
 52    ///     nothing", burying the writes NFR-09 exists to record.
 53    /// </remarks>
 54    protected override async Task<ScheduledPassResult> RunAsync(IServiceProvider scope)
 55    {
 056        var result = await scope.GetRequiredService<QueryMediator>()
 057            .ExecuteQueryAsync<DetectSecuritySignalsQuery, SecuritySignalsOutput>(
 058                new DetectSecuritySignalsQuery());
 59
 060        if (!result.Success)
 61        {
 062            return ScheduledPassResult.Failure("Security signal detection", result.Errors);
 63        }
 64
 065        var signals = result.Data?.Signals.ToList() ?? [];
 66
 067        foreach (var signal in signals)
 68        {
 069            logger.LogWarning(
 070                "{Marker} {Kind}: {Description} (count {Count}, actor {ActorId})",
 071                Marker, signal.Kind, signal.Description, signal.Count,
 072                signal.ActorId?.ToString() ?? "n/a");
 73        }
 74
 75        // Nothing extra for the caller to log: each signal has already been written above, and a
 76        // run that found nothing is not news.
 077        return ScheduledPassResult.Quiet;
 078    }
 79}