Files
oc-sentinel/src/OCSentinelCli/Models.cs
OfficeCom Codex 2ca50a4ee9
All checks were successful
OfficeCom Sentinel Client / validate-client (push) Successful in 23s
OfficeCom Sentinel Client / build-client-windows (push) Successful in 49s
Limit uploaded event details
2026-08-03 01:18:35 +02:00

318 lines
9.3 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;
public string Service { get; init; } = string.Empty;
public int? DestinationPort { get; init; }
public string Endpoint { 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 List<string> Services { get; init; } = [];
public List<int> DestinationPorts { get; init; } = [];
public List<string> Endpoints { 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(),
Services = ordered.Select(static attack => attack.Service).Where(static service => !string.IsNullOrWhiteSpace(service)).Distinct(StringComparer.OrdinalIgnoreCase).Order().ToList(),
DestinationPorts = ordered.Select(static attack => attack.DestinationPort).Where(static port => port.HasValue).Select(static port => port!.Value).Distinct().Order().ToList(),
Endpoints = ordered.Select(static attack => attack.Endpoint).Where(static endpoint => !string.IsNullOrWhiteSpace(endpoint)).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;
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 ReportedEventCount { get; init; }
public bool EventsTruncated { 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 RansomwareBetaSummary RansomwareBeta { 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 RansomwareBetaSummary
{
public bool Enabled { get; init; }
public bool AlertingEnabled { get; init; }
public string State { get; init; } = "disabled";
public string Reason { get; init; } = "Ransomware beta is disabled.";
public int LookbackMinutes { get; init; }
public List<RansomwareSignal> Signals { get; init; } = [];
public List<RansomwareSensorStatus> Sensors { get; init; } = [];
public List<RansomwareSmbSession> SmbSessions { get; init; } = [];
public RansomwareFileChurnSummary FileChurn { get; init; } = new();
}
internal sealed record RansomwareSensorStatus
{
public string Name { get; init; } = string.Empty;
public bool Enabled { get; init; }
public bool Available { get; init; }
public string State { get; init; } = "unknown";
public int EventCount { get; init; }
}
internal sealed record RansomwareFileChurnSummary
{
public bool Enabled { get; init; }
public bool DataAvailable { get; init; }
public bool IsTruncated { get; init; }
public int WindowMinutes { get; init; }
public int FileOperationCount { get; init; }
public int DeleteOperationCount { get; init; }
public int WriteOperationCount { get; init; }
public int DistinctProcessCount { get; init; }
public List<RansomwareFileChurnProcess> TopProcesses { get; init; } = [];
}
internal sealed record RansomwareFileChurnProcess
{
public string Process { get; init; } = string.Empty;
public int DeleteOperationCount { get; init; }
public int WriteOperationCount { get; init; }
}
internal sealed record RansomwareSmbSession
{
public string ClientComputerName { get; init; } = string.Empty;
public string ClientUserName { get; init; } = string.Empty;
public long SessionId { get; init; }
public long OpenFileCount { get; init; }
}
internal sealed record RansomwareSignal
{
public DateTimeOffset Timestamp { get; init; }
public string Category { get; init; } = string.Empty;
public string Confidence { get; init; } = string.Empty;
public string Process { get; init; } = string.Empty;
public string Source { get; init; } = string.Empty;
public long EventId { get; init; }
public string Evidence { get; init; } = string.Empty;
}
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.";
}