149 lines
5.3 KiB
C#
149 lines
5.3 KiB
C#
using System.Collections.Generic;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using StellaOps.Zastava.Core.Contracts;
|
|
using StellaOps.Zastava.Observer.Configuration;
|
|
using StellaOps.Zastava.Observer.ContainerRuntime;
|
|
using StellaOps.Zastava.Observer.ContainerRuntime.Cri;
|
|
using StellaOps.Zastava.Observer.Runtime;
|
|
|
|
namespace StellaOps.Zastava.Observer.Worker;
|
|
|
|
internal static class RuntimeEventFactory
|
|
{
|
|
public static RuntimeEventEnvelope Create(
|
|
ContainerLifecycleEvent lifecycleEvent,
|
|
ContainerRuntimeEndpointOptions endpoint,
|
|
CriRuntimeIdentity identity,
|
|
string tenant,
|
|
string nodeName,
|
|
RuntimeProcessCapture? capture = null,
|
|
RuntimePosture? posture = null,
|
|
IReadOnlyList<RuntimeEvidence>? additionalEvidence = null)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(lifecycleEvent);
|
|
ArgumentNullException.ThrowIfNull(endpoint);
|
|
ArgumentNullException.ThrowIfNull(identity);
|
|
ArgumentNullException.ThrowIfNull(tenant);
|
|
ArgumentNullException.ThrowIfNull(nodeName);
|
|
|
|
var snapshot = lifecycleEvent.Snapshot;
|
|
var workloadLabels = snapshot.Labels ?? new Dictionary<string, string>(StringComparer.Ordinal);
|
|
var annotations = snapshot.Annotations is null
|
|
? new Dictionary<string, string>(StringComparer.Ordinal)
|
|
: new Dictionary<string, string>(snapshot.Annotations, StringComparer.Ordinal);
|
|
|
|
var platform = ResolvePlatform(workloadLabels, endpoint);
|
|
var runtimeEvent = new RuntimeEvent
|
|
{
|
|
EventId = ComputeEventId(nodeName, lifecycleEvent),
|
|
When = lifecycleEvent.Timestamp,
|
|
Kind = lifecycleEvent.Kind == ContainerLifecycleEventKind.Start
|
|
? RuntimeEventKind.ContainerStart
|
|
: RuntimeEventKind.ContainerStop,
|
|
Tenant = tenant,
|
|
Node = nodeName,
|
|
Runtime = new RuntimeEngine
|
|
{
|
|
Engine = endpoint.Engine.ToEngineString(),
|
|
Version = identity.RuntimeVersion
|
|
},
|
|
Workload = new RuntimeWorkload
|
|
{
|
|
Platform = platform,
|
|
Namespace = TryGet(workloadLabels, CriLabelKeys.PodNamespace),
|
|
Pod = TryGet(workloadLabels, CriLabelKeys.PodName),
|
|
Container = TryGet(workloadLabels, CriLabelKeys.ContainerName) ?? snapshot.Name,
|
|
ContainerId = $"{endpoint.Engine.ToEngineString()}://{snapshot.Id}",
|
|
ImageRef = ResolveImageRef(snapshot),
|
|
Owner = null
|
|
},
|
|
Process = capture?.Process,
|
|
LoadedLibraries = capture?.Libraries ?? Array.Empty<RuntimeLoadedLibrary>(),
|
|
Posture = posture,
|
|
Evidence = MergeEvidence(capture?.Evidence, additionalEvidence),
|
|
Annotations = annotations.Count == 0 ? null : new SortedDictionary<string, string>(annotations, StringComparer.Ordinal)
|
|
};
|
|
|
|
return RuntimeEventEnvelope.Create(runtimeEvent, ZastavaContractVersions.RuntimeEvent);
|
|
}
|
|
|
|
private static string ResolvePlatform(IReadOnlyDictionary<string, string> labels, ContainerRuntimeEndpointOptions endpoint)
|
|
{
|
|
if (labels.ContainsKey(CriLabelKeys.PodName))
|
|
{
|
|
return "kubernetes";
|
|
}
|
|
|
|
return endpoint.Engine.ToEngineString();
|
|
}
|
|
|
|
private static IReadOnlyList<RuntimeEvidence> MergeEvidence(
|
|
IReadOnlyList<RuntimeEvidence>? primary,
|
|
IReadOnlyList<RuntimeEvidence>? secondary)
|
|
{
|
|
if ((primary is null || primary.Count == 0) && (secondary is null || secondary.Count == 0))
|
|
{
|
|
return Array.Empty<RuntimeEvidence>();
|
|
}
|
|
|
|
if (secondary is null || secondary.Count == 0)
|
|
{
|
|
return primary ?? Array.Empty<RuntimeEvidence>();
|
|
}
|
|
|
|
if (primary is null || primary.Count == 0)
|
|
{
|
|
return secondary;
|
|
}
|
|
|
|
var merged = new List<RuntimeEvidence>(primary.Count + secondary.Count);
|
|
merged.AddRange(primary);
|
|
merged.AddRange(secondary);
|
|
return merged;
|
|
}
|
|
|
|
private static string? ResolveImageRef(CriContainerInfo snapshot)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(snapshot.ImageRef))
|
|
{
|
|
return snapshot.ImageRef;
|
|
}
|
|
|
|
return snapshot.Image;
|
|
}
|
|
|
|
private static string? TryGet(IReadOnlyDictionary<string, string> dictionary, string key)
|
|
{
|
|
if (dictionary.TryGetValue(key, out var value) && !string.IsNullOrWhiteSpace(value))
|
|
{
|
|
return value;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static string ComputeEventId(string nodeName, ContainerLifecycleEvent lifecycleEvent)
|
|
{
|
|
var builder = new StringBuilder()
|
|
.Append(nodeName)
|
|
.Append('|')
|
|
.Append(lifecycleEvent.Snapshot.Id)
|
|
.Append('|')
|
|
.Append(lifecycleEvent.Timestamp.ToUniversalTime().Ticks)
|
|
.Append('|')
|
|
.Append((int)lifecycleEvent.Kind);
|
|
|
|
var bytes = Encoding.UTF8.GetBytes(builder.ToString());
|
|
Span<byte> hash = stackalloc byte[16];
|
|
if (!MD5.TryHashData(bytes, hash, out _))
|
|
{
|
|
using var md5 = MD5.Create();
|
|
hash = md5.ComputeHash(bytes).AsSpan(0, 16);
|
|
}
|
|
|
|
var guid = new Guid(hash);
|
|
return guid.ToString("N");
|
|
}
|
|
}
|