Remove legacy AttackTracer repo content
Some checks failed
OfficeCom Sentinel Client / build-client (push) Has been cancelled

This commit is contained in:
OfficeCom Codex
2026-07-17 00:55:16 +02:00
parent 85e394f647
commit b5e06b885a
57 changed files with 98 additions and 3602 deletions

View File

@@ -0,0 +1,41 @@
namespace OCSentinelCli.Commands;
internal static class ScanAndUploadCommand
{
public static int Execute(string[] args)
{
string outputPath = @"C:\ProgramData\OCSentinel\reports\latest.json";
bool hasOutput = false;
for (int i = 0; i < args.Length; i++)
{
if (string.Equals(args[i], "--output", StringComparison.OrdinalIgnoreCase) && i + 1 < args.Length)
{
outputPath = args[i + 1];
hasOutput = true;
break;
}
}
List<string> scanArgs = [.. args];
if (!hasOutput)
{
scanArgs.Add("--output");
scanArgs.Add(outputPath);
}
int scanExitCode = ScanCommand.Execute([.. scanArgs]);
if (scanExitCode != 0)
{
return scanExitCode;
}
var uploadArgs = new List<string>
{
"--report",
outputPath
};
return UploadCommand.Execute([.. uploadArgs]);
}
}

View File

@@ -0,0 +1,138 @@
using System.Text.Json;
using System.Runtime.Versioning;
namespace OCSentinelCli.Commands;
[SupportedOSPlatform("windows")]
internal static class ScanCommand
{
public static int Execute(string[] args)
{
ScanOptions options;
try
{
options = ScanOptions.Parse(args);
}
catch (ArgumentException ex)
{
Console.Error.WriteLine(ex.Message);
Console.Error.WriteLine();
Console.Error.WriteLine(ScanOptions.Usage);
return 2;
}
if (options.ShowHelp)
{
Console.WriteLine(ScanOptions.Usage);
return 0;
}
try
{
var scanner = new AttackScanner();
ScanResult result = scanner.Run(options);
string json = JsonSerializer.Serialize(result, JsonOptions.Default);
if (!string.IsNullOrWhiteSpace(options.OutputPath))
{
string outputPath = Path.GetFullPath(options.OutputPath);
string? outputDirectory = Path.GetDirectoryName(outputPath);
if (!string.IsNullOrWhiteSpace(outputDirectory))
{
Directory.CreateDirectory(outputDirectory);
}
File.WriteAllText(outputPath, json);
}
WriteConsoleOutput(result, options, json);
if (options.FailOnThreshold && !string.Equals(result.AlertState, "ok", StringComparison.OrdinalIgnoreCase))
{
return 1;
}
return options.FailOnAttacks && result.TotalEvents > 0 ? 1 : 0;
}
catch (Exception ex)
{
Console.Error.WriteLine($"Scanner failed: {ex}");
return 1;
}
}
private static void WriteConsoleOutput(ScanResult result, ScanOptions options, string json)
{
if (!options.JsonOnly)
{
Console.WriteLine($"OfficeCom Sentinel summary for {result.MachineName}");
Console.WriteLine($"Generated: {result.GeneratedAtLocal:yyyy-MM-dd HH:mm:ss}");
Console.WriteLine($"Events: {result.TotalEvents}");
Console.WriteLine($"Unique IPs: {result.UniqueIpCount}");
Console.WriteLine($"Status: {result.AlertState}");
Console.WriteLine($"Reason: {result.AlertReason}");
Console.WriteLine($"Base status: {result.BaseAlertState}");
Console.WriteLine($"Scan errors: {result.Errors.Count}");
Console.WriteLine();
if (result.TopSources.Count == 0)
{
Console.WriteLine("No attacks found in the selected lookback window.");
}
else
{
Console.WriteLine("Top source IPs:");
for (int i = 0; i < result.TopSources.Count; i++)
{
AggregatedAttack aggregate = result.TopSources[i];
Console.WriteLine($"{i + 1}. {aggregate.SourceIp} | {aggregate.Count} hits | {aggregate.FirstSeenLocal:yyyy-MM-dd HH:mm:ss} -> {aggregate.LastSeenLocal:yyyy-MM-dd HH:mm:ss} | {aggregate.RateLabel}");
Console.WriteLine($" Targets: {string.Join(", ", aggregate.Targets)} | Users: {string.Join(", ", aggregate.Usernames)}");
}
}
if (result.VulnerabilityCorrelation.TotalCount > 0)
{
Console.WriteLine();
Console.WriteLine("Vulnerability correlation:");
Console.WriteLine($"CVE findings for this host: {result.VulnerabilityCorrelation.TotalCount}");
Console.WriteLine($"Critical/High findings: {result.VulnerabilityCorrelation.CriticalCount}");
Console.WriteLine($"CVSS >= 8 findings: {result.VulnerabilityCorrelation.HighCvssCount}");
}
if (!string.IsNullOrWhiteSpace(options.OutputPath))
{
Console.WriteLine();
Console.WriteLine($"JSON report written to {Path.GetFullPath(options.OutputPath)}");
}
if (result.Errors.Count > 0)
{
Console.WriteLine();
Console.WriteLine("Warnings:");
foreach (string error in result.Errors)
{
Console.WriteLine($"- {error}");
}
}
}
else
{
Console.WriteLine(json);
}
if (options.NinjaOutput)
{
Console.WriteLine();
Console.WriteLine($"ATTACKTRACER_STATUS={result.AlertState}");
Console.WriteLine($"ATTACKTRACER_REASON={result.AlertReason}");
Console.WriteLine($"ATTACKTRACER_BASE_STATUS={result.BaseAlertState}");
Console.WriteLine($"ATTACKTRACER_EVENTS={result.TotalEvents}");
Console.WriteLine($"ATTACKTRACER_UNIQUE_IPS={result.UniqueIpCount}");
Console.WriteLine($"ATTACKTRACER_ERRORS={result.Errors.Count}");
Console.WriteLine($"ATTACKTRACER_CVE_TOTAL={result.VulnerabilityCorrelation.TotalCount}");
Console.WriteLine($"ATTACKTRACER_CVE_CRITICAL={result.VulnerabilityCorrelation.CriticalCount}");
Console.WriteLine($"ATTACKTRACER_CVE_HIGH_CVSS={result.VulnerabilityCorrelation.HighCvssCount}");
}
}
}

View File

@@ -0,0 +1,100 @@
using OCSentinelCli.Configuration;
using System.Text.Json;
using OCSentinelCli.Security;
using OCSentinelCli.Transport;
namespace OCSentinelCli.Commands;
internal static class UploadCommand
{
public static int Execute(string[] args)
{
string? reportPath = null;
string? configPath = null;
string? secretPath = null;
for (int i = 0; i < args.Length; i++)
{
switch (args[i])
{
case "--report":
reportPath = ReadValue(args, ref i, "--report");
break;
case "--client-config":
configPath = ReadValue(args, ref i, "--client-config");
break;
case "--secret-path":
secretPath = ReadValue(args, ref i, "--secret-path");
break;
case "--help":
case "-h":
Console.WriteLine("Usage: OCSentinelCli upload --report <path> [--client-config <path>] [--secret-path <path>]");
return 0;
default:
throw new ArgumentException($"Unknown argument: {args[i]}");
}
}
if (string.IsNullOrWhiteSpace(reportPath))
{
throw new ArgumentException("Missing required argument: --report");
}
string resolvedConfigPath = Path.GetFullPath(configPath ?? @"C:\ProgramData\OCSentinel\config\ocsentinel-client.json");
string resolvedSecretPath = Path.GetFullPath(secretPath ?? @"C:\ProgramData\OCSentinel\secrets\ocsentinel-upload-secret.dat");
ClientConfiguration config = ClientConfiguration.Load(resolvedConfigPath);
if (string.IsNullOrWhiteSpace(config.N8nWebhookUrl))
{
throw new InvalidOperationException("Client configuration does not define n8nWebhookUrl.");
}
if (!File.Exists(resolvedSecretPath))
{
throw new FileNotFoundException($"Protected upload secret file not found: {resolvedSecretPath}");
}
string reportFullPath = Path.GetFullPath(reportPath);
string json = File.ReadAllText(reportFullPath);
ScanResult? parsedReport = JsonSerializer.Deserialize<ScanResult>(json, JsonOptions.Default);
if (parsedReport is not null)
{
parsedReport = parsedReport with
{
Runtime = parsedReport.Runtime with
{
UploadAttempted = true
}
};
json = JsonSerializer.Serialize(parsedReport, JsonOptions.Default);
File.WriteAllText(reportFullPath, json);
}
string secret = ProtectedSecretStore.LoadSecret(resolvedSecretPath);
var client = new N8nUploadClient();
var result = client.UploadJson(config.N8nWebhookUrl, Environment.MachineName, BuildMetadata.Version, json, secret, config.UploadTimeoutSeconds);
if (!result.Success)
{
Console.Error.WriteLine($"Upload failed ({result.StatusCode}): {result.Message}");
return 1;
}
Console.WriteLine($"Upload succeeded ({result.StatusCode})");
Console.WriteLine($"Nonce: {result.Nonce}");
Console.WriteLine($"Payload SHA256: {result.PayloadSha256}");
return 0;
}
private static string ReadValue(string[] args, ref int index, string argName)
{
if (index + 1 >= args.Length)
{
throw new ArgumentException($"Missing value for {argName}");
}
index++;
return args[index];
}
}

View File

@@ -0,0 +1,10 @@
namespace OCSentinelCli.Commands;
internal static class VersionCommand
{
public static int Execute()
{
Console.WriteLine(BuildMetadata.Version);
return 0;
}
}