206 lines
6.1 KiB
C#
206 lines
6.1 KiB
C#
namespace OCSentinelCli;
|
|
|
|
internal sealed record AttackEvent
|
|
{
|
|
public DateTimeOffset Timestamp { get; init; }
|
|
|
|
public string SourceIp { get; init; } = string.Empty;
|
|
|
|
public long InstanceId { get; init; }
|
|
|
|
public string Target { get; init; } = string.Empty;
|
|
|
|
public string Username { get; init; } = string.Empty;
|
|
|
|
public string Source { get; init; } = string.Empty;
|
|
}
|
|
|
|
internal sealed record AggregatedAttack
|
|
{
|
|
public string SourceIp { get; init; } = string.Empty;
|
|
|
|
public int Count { get; init; }
|
|
|
|
public DateTimeOffset FirstSeenLocal { get; init; }
|
|
|
|
public DateTimeOffset LastSeenLocal { get; init; }
|
|
|
|
public string RateLabel { get; init; } = string.Empty;
|
|
|
|
public List<string> Targets { get; init; } = [];
|
|
|
|
public List<string> Usernames { get; init; } = [];
|
|
|
|
public List<string> Sources { get; init; } = [];
|
|
|
|
public static AggregatedAttack FromGroup(IGrouping<string, AttackEvent> group)
|
|
{
|
|
List<AttackEvent> ordered = group.OrderBy(static attack => attack.Timestamp).ToList();
|
|
DateTimeOffset firstSeen = ordered[0].Timestamp;
|
|
DateTimeOffset lastSeen = ordered[^1].Timestamp;
|
|
|
|
return new AggregatedAttack
|
|
{
|
|
SourceIp = group.Key,
|
|
Count = ordered.Count,
|
|
FirstSeenLocal = firstSeen,
|
|
LastSeenLocal = lastSeen,
|
|
RateLabel = FormatRate(ordered.Count, firstSeen, lastSeen),
|
|
Targets = ordered.Select(static attack => attack.Target).Distinct(StringComparer.OrdinalIgnoreCase).Order().ToList(),
|
|
Usernames = ordered.Select(static attack => attack.Username).Distinct(StringComparer.OrdinalIgnoreCase).Order().ToList(),
|
|
Sources = ordered.Select(static attack => attack.Source).Distinct(StringComparer.OrdinalIgnoreCase).Order().ToList()
|
|
};
|
|
}
|
|
|
|
private static string FormatRate(int count, DateTimeOffset firstSeen, DateTimeOffset lastSeen)
|
|
{
|
|
int seconds = (int)Math.Max(1, (lastSeen - firstSeen).TotalSeconds);
|
|
if (seconds <= 1)
|
|
{
|
|
return $"{count * 60}/min";
|
|
}
|
|
|
|
if (seconds <= 60)
|
|
{
|
|
return $"{count * 60 / seconds}/min";
|
|
}
|
|
|
|
if (seconds <= 3600)
|
|
{
|
|
return $"{count * 3600 / seconds}/hour";
|
|
}
|
|
|
|
return $"{count * 86400 / seconds}/day";
|
|
}
|
|
}
|
|
|
|
internal sealed record ScanResult
|
|
{
|
|
public string SchemaVersion { get; init; } = "2.0";
|
|
|
|
public string MachineName { get; init; } = string.Empty;
|
|
|
|
// Populated only for runs launched by NinjaOne automation.
|
|
public NinjaOneContext NinjaOne { get; init; } = new();
|
|
|
|
public DateTimeOffset GeneratedAtLocal { get; init; }
|
|
|
|
public DateTimeOffset GeneratedAtUtc { get; init; }
|
|
|
|
public string ClientVersion { get; init; } = string.Empty;
|
|
|
|
public int LookbackDays { get; init; }
|
|
|
|
public int TotalEvents { get; init; }
|
|
|
|
public int UniqueIpCount { get; init; }
|
|
|
|
public string AlertState { get; init; } = "ok";
|
|
|
|
public string AlertReason { get; init; } = "No thresholds exceeded.";
|
|
|
|
public string BaseAlertState { get; init; } = "ok";
|
|
|
|
public string BaseAlertReason { get; init; } = "No thresholds exceeded.";
|
|
|
|
public VulnerabilityCorrelationSummary VulnerabilityCorrelation { get; init; } = new();
|
|
|
|
public ScanRuntimeMetadata Runtime { get; init; } = new();
|
|
|
|
public List<AttackEvent> Events { get; init; } = [];
|
|
|
|
public List<AggregatedAttack> TopSources { get; init; } = [];
|
|
|
|
public List<string> Errors { get; init; } = [];
|
|
}
|
|
|
|
internal sealed record NinjaOneContext
|
|
{
|
|
public string OrganizationId { get; init; } = string.Empty;
|
|
|
|
public string OrganizationName { get; init; } = string.Empty;
|
|
|
|
public string MachineId { get; init; } = string.Empty;
|
|
|
|
public string NodeId { get; init; } = string.Empty;
|
|
|
|
public string LocationId { get; init; } = string.Empty;
|
|
|
|
public string LocationName { get; init; } = string.Empty;
|
|
}
|
|
|
|
internal sealed record ScanRuntimeMetadata
|
|
{
|
|
public DateTimeOffset StartedAtUtc { get; init; }
|
|
|
|
public DateTimeOffset FinishedAtUtc { get; init; }
|
|
|
|
public bool UploadAttempted { get; init; }
|
|
|
|
public bool UploadSucceeded { get; init; }
|
|
|
|
public string UploadStatus { get; init; } = "not-attempted";
|
|
|
|
public int QueuedReportCount { get; init; }
|
|
|
|
public DateTimeOffset? LastSuccessfulUploadUtc { get; init; }
|
|
|
|
public string LastUploadError { get; init; } = string.Empty;
|
|
}
|
|
|
|
internal sealed record VulnerabilityFinding
|
|
{
|
|
public string DeviceName { get; init; } = string.Empty;
|
|
|
|
public string CveId { get; init; } = string.Empty;
|
|
|
|
public string Severity { get; init; } = string.Empty;
|
|
|
|
public double? CvssScore { get; init; }
|
|
|
|
public string Remediation { get; init; } = string.Empty;
|
|
}
|
|
|
|
internal sealed record VulnerabilityCorrelationSummary
|
|
{
|
|
public string SourcePath { get; init; } = string.Empty;
|
|
|
|
public int TotalCount { get; init; }
|
|
|
|
public int CriticalCount { get; init; }
|
|
|
|
public int HighCvssCount { get; init; }
|
|
|
|
public List<VulnerabilityFinding> Findings { get; init; } = [];
|
|
|
|
public static VulnerabilityCorrelationSummary Empty(string sourcePath = "")
|
|
{
|
|
return new VulnerabilityCorrelationSummary { SourcePath = sourcePath };
|
|
}
|
|
|
|
public static VulnerabilityCorrelationSummary FromFindings(string sourcePath, List<VulnerabilityFinding> findings)
|
|
{
|
|
int criticalCount = findings.Count(f =>
|
|
string.Equals(f.Severity, "critical", StringComparison.OrdinalIgnoreCase) ||
|
|
string.Equals(f.Severity, "high", StringComparison.OrdinalIgnoreCase));
|
|
|
|
int highCvssCount = findings.Count(f => f.CvssScore.HasValue && f.CvssScore.Value >= 8.0);
|
|
|
|
return new VulnerabilityCorrelationSummary
|
|
{
|
|
SourcePath = sourcePath,
|
|
TotalCount = findings.Count,
|
|
CriticalCount = criticalCount,
|
|
HighCvssCount = highCvssCount,
|
|
Findings = findings
|
|
};
|
|
}
|
|
}
|
|
|
|
internal sealed record CorrelationAssessment
|
|
{
|
|
public string FinalAlertState { get; init; } = "ok";
|
|
|
|
public string CorrelationReason { get; init; } = "No CVE correlation applied.";
|
|
}
|