up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Build Test Deploy / build-test (push) Has been cancelled
Build Test Deploy / authority-container (push) Has been cancelled
Build Test Deploy / docs (push) Has been cancelled
Build Test Deploy / deploy (push) Has been cancelled

This commit is contained in:
2025-10-19 10:38:55 +03:00
parent c4980d9625
commit daa6a4ae8c
250 changed files with 17967 additions and 66 deletions

View File

@@ -0,0 +1,136 @@
using System.Globalization;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using StellaOps.Scanner.Core.Contracts;
namespace StellaOps.Scanner.Core.Utility;
public static class ScannerIdentifiers
{
private static readonly Guid ScanJobNamespace = new("d985aa76-8c2b-4cba-bac0-c98c90674f04");
private static readonly Guid CorrelationNamespace = new("7cde18f5-729e-4ea1-be3d-46fda4c55e38");
public static ScanJobId CreateJobId(
string imageReference,
string? imageDigest = null,
string? tenantId = null,
string? salt = null)
{
ArgumentException.ThrowIfNullOrWhiteSpace(imageReference);
var normalizedReference = NormalizeImageReference(imageReference);
var normalizedDigest = NormalizeDigest(imageDigest) ?? "none";
var normalizedTenant = string.IsNullOrWhiteSpace(tenantId) ? "global" : tenantId.Trim().ToLowerInvariant();
var normalizedSalt = (salt?.Trim() ?? string.Empty).ToLowerInvariant();
using var sha256 = SHA256.Create();
var payload = $"{normalizedReference}|{normalizedDigest}|{normalizedTenant}|{normalizedSalt}";
var hashed = sha256.ComputeHash(Encoding.UTF8.GetBytes(payload));
return new ScanJobId(CreateGuidFromHash(ScanJobNamespace, hashed));
}
public static string CreateCorrelationId(ScanJobId jobId, string? stage = null, string? suffix = null)
{
var normalizedStage = string.IsNullOrWhiteSpace(stage)
? "scan"
: stage.Trim().ToLowerInvariant().Replace(' ', '-');
var normalizedSuffix = string.IsNullOrWhiteSpace(suffix)
? string.Empty
: "-" + suffix.Trim().ToLowerInvariant().Replace(' ', '-');
return $"scan-{normalizedStage}-{jobId}{normalizedSuffix}";
}
public static string CreateDeterministicHash(params string[] segments)
{
if (segments is null || segments.Length == 0)
{
throw new ArgumentException("At least one segment must be provided.", nameof(segments));
}
using var sha256 = SHA256.Create();
var joined = string.Join('|', segments.Select(static s => s?.Trim() ?? string.Empty));
var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(joined));
return Convert.ToHexString(hash).ToLowerInvariant();
}
public static Guid CreateDeterministicGuid(Guid namespaceId, ReadOnlySpan<byte> nameBytes)
{
Span<byte> namespaceBytes = stackalloc byte[16];
namespaceId.TryWriteBytes(namespaceBytes);
Span<byte> buffer = stackalloc byte[namespaceBytes.Length + nameBytes.Length];
namespaceBytes.CopyTo(buffer);
nameBytes.CopyTo(buffer[namespaceBytes.Length..]);
Span<byte> hash = stackalloc byte[32];
SHA256.TryHashData(buffer, hash, out _);
Span<byte> guidBytes = stackalloc byte[16];
hash[..16].CopyTo(guidBytes);
guidBytes[6] = (byte)((guidBytes[6] & 0x0F) | 0x50);
guidBytes[8] = (byte)((guidBytes[8] & 0x3F) | 0x80);
return new Guid(guidBytes);
}
public static string NormalizeImageReference(string reference)
{
ArgumentException.ThrowIfNullOrWhiteSpace(reference);
var trimmed = reference.Trim();
var atIndex = trimmed.IndexOf('@');
if (atIndex > 0)
{
var prefix = trimmed[..atIndex].ToLowerInvariant();
return $"{prefix}{trimmed[atIndex..]}";
}
var colonIndex = trimmed.IndexOf(':');
if (colonIndex > 0)
{
var name = trimmed[..colonIndex].ToLowerInvariant();
var tag = trimmed[(colonIndex + 1)..];
return $"{name}:{tag}";
}
return trimmed.ToLowerInvariant();
}
public static string? NormalizeDigest(string? digest)
{
if (string.IsNullOrWhiteSpace(digest))
{
return null;
}
var trimmed = digest.Trim();
var parts = trimmed.Split(':', 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
if (parts.Length != 2)
{
return trimmed.ToLowerInvariant();
}
return $"{parts[0].ToLowerInvariant()}:{parts[1].ToLowerInvariant()}";
}
public static string CreateDeterministicCorrelation(string audience, ScanJobId jobId, string? component = null)
{
using var sha256 = SHA256.Create();
var payload = $"{audience.Trim().ToLowerInvariant()}|{jobId}|{component?.Trim().ToLowerInvariant() ?? string.Empty}";
var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(payload));
var guid = CreateGuidFromHash(CorrelationNamespace, hash);
return $"corr-{guid.ToString("n", CultureInfo.InvariantCulture)}";
}
private static Guid CreateGuidFromHash(Guid namespaceId, ReadOnlySpan<byte> hash)
{
Span<byte> guidBytes = stackalloc byte[16];
hash[..16].CopyTo(guidBytes);
guidBytes[6] = (byte)((guidBytes[6] & 0x0F) | 0x50);
guidBytes[8] = (byte)((guidBytes[8] & 0x3F) | 0x80);
return new Guid(guidBytes);
}
}