Files
git.stella-ops.org/src/Zastava/StellaOps.Zastava.Observer/DependencyInjection/ObserverServiceCollectionExtensions.cs
StellaOps Bot e2e404e705
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
console-runner-image / build-runner-image (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
up
2025-12-14 16:24:16 +02:00

184 lines
8.1 KiB
C#

using System;
using System.IO;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using StellaOps.Cryptography.DependencyInjection;
using StellaOps.Scanner.Surface.Env;
using StellaOps.Scanner.Surface.FS;
using StellaOps.Scanner.Surface.Secrets;
using StellaOps.Scanner.Surface.Validation;
using StellaOps.Zastava.Core.Configuration;
using StellaOps.Zastava.Observer.Backend;
using StellaOps.Zastava.Observer.Configuration;
using StellaOps.Zastava.Observer.ContainerRuntime;
using StellaOps.Zastava.Observer.ContainerRuntime.Cri;
using StellaOps.Zastava.Observer.Posture;
using StellaOps.Zastava.Observer.Runtime;
using StellaOps.Zastava.Observer.Runtime.ProcSnapshot;
using StellaOps.Zastava.Observer.Secrets;
using StellaOps.Zastava.Observer.Surface;
using StellaOps.Zastava.Observer.Worker;
namespace Microsoft.Extensions.DependencyInjection;
public static class ObserverServiceCollectionExtensions
{
public static IServiceCollection AddZastavaObserver(this IServiceCollection services, IConfiguration configuration)
{
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(configuration);
services.AddZastavaRuntimeCore(configuration, componentName: "observer");
services.AddStellaOpsCrypto();
services.AddOptions<ZastavaObserverOptions>()
.Bind(configuration.GetSection(ZastavaObserverOptions.SectionName))
.ValidateDataAnnotations()
.PostConfigure(options =>
{
if (options.Backoff.Initial <= TimeSpan.Zero)
{
options.Backoff.Initial = TimeSpan.FromSeconds(1);
}
if (options.Backoff.Max < options.Backoff.Initial)
{
options.Backoff.Max = options.Backoff.Initial;
}
if (!options.Backend.AllowInsecureHttp && !string.Equals(options.Backend.BaseAddress.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException("Observer backend baseAddress must use HTTPS unless allowInsecureHttp is explicitly enabled.");
}
if (!options.Backend.PolicyPath.StartsWith("/", StringComparison.Ordinal))
{
throw new InvalidOperationException("Observer backend policyPath must be absolute (start with '/').");
}
if (!options.Backend.EventsPath.StartsWith("/", StringComparison.Ordinal))
{
throw new InvalidOperationException("Observer backend eventsPath must be absolute (start with '/').");
}
})
.ValidateOnStart();
services.TryAddSingleton(TimeProvider.System);
services.TryAddSingleton<ICriRuntimeClientFactory, CriRuntimeClientFactory>();
services.TryAddSingleton<IRuntimeEventBuffer, RuntimeEventBuffer>();
services.TryAddSingleton<IRuntimeProcessCollector, RuntimeProcessCollector>();
services.TryAddSingleton<IProcSnapshotCollector, ProcSnapshotCollector>();
services.TryAddSingleton<IRuntimePostureCache, RuntimePostureCache>();
services.TryAddSingleton<IRuntimePostureEvaluator, RuntimePostureEvaluator>();
services.TryAddSingleton<ContainerStateTrackerFactory>();
services.TryAddSingleton<ContainerRuntimePoller>();
services.AddHttpClient<IRuntimePolicyClient, RuntimePolicyClient>()
.ConfigureHttpClient((provider, client) =>
{
var optionsMonitor = provider.GetRequiredService<IOptionsMonitor<ZastavaObserverOptions>>();
var backend = optionsMonitor.CurrentValue.Backend;
client.BaseAddress = backend.BaseAddress;
client.Timeout = TimeSpan.FromSeconds(Math.Clamp(backend.RequestTimeoutSeconds, 1, 120));
});
services.AddHttpClient<IRuntimeEventsClient, RuntimeEventsClient>()
.ConfigureHttpClient((provider, client) =>
{
var optionsMonitor = provider.GetRequiredService<IOptionsMonitor<ZastavaObserverOptions>>();
var backend = optionsMonitor.CurrentValue.Backend;
client.BaseAddress = backend.BaseAddress;
client.Timeout = TimeSpan.FromSeconds(Math.Clamp(backend.RequestTimeoutSeconds, 1, 120));
});
services.AddHttpClient<IRuntimeFactsClient, RuntimeFactsClient>()
.ConfigureHttpClient((provider, client) =>
{
var optionsMonitor = provider.GetRequiredService<IOptionsMonitor<ZastavaObserverOptions>>();
var observer = optionsMonitor.CurrentValue;
client.Timeout = TimeSpan.FromSeconds(Math.Clamp(observer.Backend.RequestTimeoutSeconds, 1, 120));
});
services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<ZastavaRuntimeOptions>, ObserverRuntimeOptionsPostConfigure>());
// Surface environment + cache/manifest/secrets wiring
services.AddSurfaceEnvironment(options =>
{
options.ComponentName = "Zastava.Observer";
options.AddPrefix("ZASTAVA_OBSERVER");
options.AddPrefix("ZASTAVA");
options.KnownFeatureFlags.Add("drift");
options.KnownFeatureFlags.Add("prefetch");
options.TenantResolver = sp => sp.GetRequiredService<IOptions<ZastavaRuntimeOptions>>().Value.Tenant;
});
services.AddSurfaceFileCache();
services.AddSurfaceManifestStore();
services.AddSurfaceSecrets(options =>
{
options.ComponentName = "Zastava.Observer";
options.RequiredSecretTypes.Add("cas-access");
options.RequiredSecretTypes.Add("attestation");
});
// Surface validation for preflight checks
services.AddSurfaceValidation();
services.TryAddSingleton(sp => sp.GetRequiredService<ISurfaceEnvironment>().Settings);
services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<SurfaceCacheOptions>, SurfaceCacheOptionsConfigurator>());
services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<SurfaceManifestStoreOptions>, SurfaceManifestStoreOptionsConfigurator>());
services.TryAddSingleton<IObserverSurfaceSecrets, ObserverSurfaceSecrets>();
services.TryAddSingleton<IRuntimeSurfaceFsClient, RuntimeSurfaceFsClient>();
services.AddHostedService<ObserverBootstrapService>();
services.AddHostedService<ContainerLifecycleHostedService>();
services.AddHostedService<RuntimeEventDispatchService>();
return services;
}
}
internal sealed class ObserverRuntimeOptionsPostConfigure : IPostConfigureOptions<ZastavaRuntimeOptions>
{
public void PostConfigure(string? name, ZastavaRuntimeOptions options)
{
if (string.IsNullOrWhiteSpace(options.Component))
{
options.Component = "observer";
}
}
}
internal sealed class SurfaceCacheOptionsConfigurator : IConfigureOptions<SurfaceCacheOptions>
{
private readonly SurfaceEnvironmentSettings settings;
public SurfaceCacheOptionsConfigurator(SurfaceEnvironmentSettings settings)
{
this.settings = settings ?? throw new ArgumentNullException(nameof(settings));
}
public void Configure(SurfaceCacheOptions options)
{
options.RootDirectory ??= settings.CacheRoot.FullName;
}
}
internal sealed class SurfaceManifestStoreOptionsConfigurator : IConfigureOptions<SurfaceManifestStoreOptions>
{
private readonly SurfaceEnvironmentSettings settings;
public SurfaceManifestStoreOptionsConfigurator(SurfaceEnvironmentSettings settings)
{
this.settings = settings ?? throw new ArgumentNullException(nameof(settings));
}
public void Configure(SurfaceManifestStoreOptions options)
{
options.Bucket = string.IsNullOrWhiteSpace(settings.SurfaceFsBucket) ? options.Bucket : settings.SurfaceFsBucket;
options.RootDirectory ??= Path.Combine(settings.CacheRoot.FullName, "manifests");
}
}