// ----------------------------------------------------------------------------- // VerdictServiceCollectionExtensions.cs // Sprint: SPRINT_20251229_001_001_BE_cgs_infrastructure (CGS-007) // Task: Wire Fulcio keyless signing for VerdictBuilder // Description: DI extensions for VerdictBuilder with optional keyless signing // ----------------------------------------------------------------------------- using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using StellaOps.Signer.Core; using StellaOps.Signer.Keyless; namespace StellaOps.Verdict; /// /// Dependency injection extensions for the Verdict Builder service. /// public static class VerdictServiceCollectionExtensions { /// /// Adds the Verdict Builder service to the DI container. /// Signing mode is determined from configuration (VerdictBuilder:SigningMode). /// /// Service collection /// Configuration root /// Service collection for chaining public static IServiceCollection AddVerdictBuilder( this IServiceCollection services, IConfiguration configuration) { // Bind configuration services.Configure(configuration.GetSection("VerdictBuilder")); // Validate options on startup services.AddSingleton, VerdictBuilderOptionsValidator>(); // Register VerdictBuilder with conditional signer based on mode services.AddSingleton(sp => { var options = sp.GetRequiredService>().Value; var logger = sp.GetRequiredService>(); IDsseSigner? signer = null; if (options.SigningMode == VerdictSigningMode.Keyless) { logger.LogInformation( "VerdictBuilder configured for Keyless signing (Fulcio: {FulcioUrl}, OIDC: {OidcIssuer})", options.FulcioUrl, options.OidcIssuerUrl); signer = sp.GetRequiredService(); } else if (options.SigningMode == VerdictSigningMode.AirGap) { logger.LogInformation("VerdictBuilder configured for AirGap mode (unsigned verdicts)"); } else { logger.LogWarning("VerdictBuilder SigningMode={Mode} not supported, falling back to AirGap", options.SigningMode); } return new VerdictBuilderService(logger, signer); }); return services; } /// /// Adds the Verdict Builder service with keyless signing support. /// Requires Fulcio and OIDC configuration. /// /// Service collection /// Configuration root /// Service collection for chaining public static IServiceCollection AddVerdictBuilderWithKeylessSigning( this IServiceCollection services, IConfiguration configuration) { // Add keyless signing infrastructure services.AddKeylessSigning(configuration); // Add verdict builder services.AddVerdictBuilder(configuration); return services; } /// /// Adds the Verdict Builder service for air-gapped deployments (no signing). /// /// Service collection /// Service collection for chaining public static IServiceCollection AddVerdictBuilderAirGap(this IServiceCollection services) { // Configure for air-gap mode services.Configure(options => { options.SigningMode = VerdictSigningMode.AirGap; }); // Register VerdictBuilder without signer services.AddSingleton(sp => { var logger = sp.GetRequiredService>(); return new VerdictBuilderService(logger, signer: null); }); return services; } /// /// Adds keyless signing infrastructure (Fulcio client, OIDC token provider). /// private static IServiceCollection AddKeylessSigning( this IServiceCollection services, IConfiguration configuration) { // Use existing Signer.Keyless infrastructure services.AddKeylessSigning(configuration.GetSection("VerdictBuilder:Keyless")); // Register OIDC token provider for ambient tokens (CI runners, workload identity) // This is environment-specific and needs to be configured based on deployment services.AddSingleton(sp => { var logger = sp.GetRequiredService>(); // Check for Gitea/GitHub Actions token var giteaToken = Environment.GetEnvironmentVariable("ACTIONS_ID_TOKEN_REQUEST_TOKEN"); var githubToken = Environment.GetEnvironmentVariable("ACTIONS_ID_TOKEN_REQUEST_TOKEN"); // Check for Google Cloud workload identity var googleToken = Environment.GetEnvironmentVariable("GOOGLE_APPLICATION_CREDENTIALS"); // Default to file-based ambient token var tokenPath = Environment.GetEnvironmentVariable("OIDC_TOKEN_FILE") ?? "/var/run/sigstore/cosign/oidc-token"; var config = new OidcAmbientConfig { Issuer = configuration["VerdictBuilder:OidcIssuerUrl"] ?? "https://token.actions.githubusercontent.com", TokenPath = tokenPath, WatchForChanges = true }; return new AmbientOidcTokenProvider(config, logger); }); // Register KeylessDsseSigner services.AddSingleton(); return services; } } /// /// Validates VerdictBuilderOptions on startup. /// internal sealed class VerdictBuilderOptionsValidator : IValidateOptions { public ValidateOptionsResult Validate(string? name, VerdictBuilderOptions options) { try { options.Validate(); return ValidateOptionsResult.Success; } catch (InvalidOperationException ex) { return ValidateOptionsResult.Fail(ex.Message); } } }