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() .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(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.AddHttpClient() .ConfigureHttpClient((provider, client) => { var optionsMonitor = provider.GetRequiredService>(); var backend = optionsMonitor.CurrentValue.Backend; client.BaseAddress = backend.BaseAddress; client.Timeout = TimeSpan.FromSeconds(Math.Clamp(backend.RequestTimeoutSeconds, 1, 120)); }); services.AddHttpClient() .ConfigureHttpClient((provider, client) => { var optionsMonitor = provider.GetRequiredService>(); var backend = optionsMonitor.CurrentValue.Backend; client.BaseAddress = backend.BaseAddress; client.Timeout = TimeSpan.FromSeconds(Math.Clamp(backend.RequestTimeoutSeconds, 1, 120)); }); services.AddHttpClient() .ConfigureHttpClient((provider, client) => { var optionsMonitor = provider.GetRequiredService>(); var observer = optionsMonitor.CurrentValue; client.Timeout = TimeSpan.FromSeconds(Math.Clamp(observer.Backend.RequestTimeoutSeconds, 1, 120)); }); services.TryAddEnumerable(ServiceDescriptor.Singleton, 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>().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().Settings); services.TryAddEnumerable(ServiceDescriptor.Singleton, SurfaceCacheOptionsConfigurator>()); services.TryAddEnumerable(ServiceDescriptor.Singleton, SurfaceManifestStoreOptionsConfigurator>()); services.TryAddSingleton(); services.TryAddSingleton(); services.AddHostedService(); services.AddHostedService(); services.AddHostedService(); return services; } } internal sealed class ObserverRuntimeOptionsPostConfigure : IPostConfigureOptions { public void PostConfigure(string? name, ZastavaRuntimeOptions options) { if (string.IsNullOrWhiteSpace(options.Component)) { options.Component = "observer"; } } } internal sealed class SurfaceCacheOptionsConfigurator : IConfigureOptions { 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 { 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"); } }