Files
git.stella-ops.org/src/__Libraries/StellaOps.Verdict/VerdictServiceCollectionExtensions.cs
master a4badc275e UI work to fill SBOM sourcing management gap. UI planning remaining functionality exposure. Work on CI/Tests stabilization
Introduces CGS determinism test runs to CI workflows for Windows, macOS, Linux, Alpine, and Debian, fulfilling CGS-008 cross-platform requirements. Updates local-ci scripts to support new smoke steps, test timeouts, progress intervals, and project slicing for improved test isolation and diagnostics.
2025-12-29 19:12:38 +02:00

176 lines
6.6 KiB
C#

// -----------------------------------------------------------------------------
// 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;
/// <summary>
/// Dependency injection extensions for the Verdict Builder service.
/// </summary>
public static class VerdictServiceCollectionExtensions
{
/// <summary>
/// Adds the Verdict Builder service to the DI container.
/// Signing mode is determined from configuration (VerdictBuilder:SigningMode).
/// </summary>
/// <param name="services">Service collection</param>
/// <param name="configuration">Configuration root</param>
/// <returns>Service collection for chaining</returns>
public static IServiceCollection AddVerdictBuilder(
this IServiceCollection services,
IConfiguration configuration)
{
// Bind configuration
services.Configure<VerdictBuilderOptions>(configuration.GetSection("VerdictBuilder"));
// Validate options on startup
services.AddSingleton<IValidateOptions<VerdictBuilderOptions>, VerdictBuilderOptionsValidator>();
// Register VerdictBuilder with conditional signer based on mode
services.AddSingleton<IVerdictBuilder>(sp =>
{
var options = sp.GetRequiredService<IOptions<VerdictBuilderOptions>>().Value;
var logger = sp.GetRequiredService<Microsoft.Extensions.Logging.ILogger<VerdictBuilderService>>();
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<KeylessDsseSigner>();
}
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;
}
/// <summary>
/// Adds the Verdict Builder service with keyless signing support.
/// Requires Fulcio and OIDC configuration.
/// </summary>
/// <param name="services">Service collection</param>
/// <param name="configuration">Configuration root</param>
/// <returns>Service collection for chaining</returns>
public static IServiceCollection AddVerdictBuilderWithKeylessSigning(
this IServiceCollection services,
IConfiguration configuration)
{
// Add keyless signing infrastructure
services.AddKeylessSigning(configuration);
// Add verdict builder
services.AddVerdictBuilder(configuration);
return services;
}
/// <summary>
/// Adds the Verdict Builder service for air-gapped deployments (no signing).
/// </summary>
/// <param name="services">Service collection</param>
/// <returns>Service collection for chaining</returns>
public static IServiceCollection AddVerdictBuilderAirGap(this IServiceCollection services)
{
// Configure for air-gap mode
services.Configure<VerdictBuilderOptions>(options =>
{
options.SigningMode = VerdictSigningMode.AirGap;
});
// Register VerdictBuilder without signer
services.AddSingleton<IVerdictBuilder>(sp =>
{
var logger = sp.GetRequiredService<Microsoft.Extensions.Logging.ILogger<VerdictBuilderService>>();
return new VerdictBuilderService(logger, signer: null);
});
return services;
}
/// <summary>
/// Adds keyless signing infrastructure (Fulcio client, OIDC token provider).
/// </summary>
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<IOidcTokenProvider>(sp =>
{
var logger = sp.GetRequiredService<Microsoft.Extensions.Logging.ILogger<AmbientOidcTokenProvider>>();
// 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<KeylessDsseSigner>();
return services;
}
}
/// <summary>
/// Validates VerdictBuilderOptions on startup.
/// </summary>
internal sealed class VerdictBuilderOptionsValidator : IValidateOptions<VerdictBuilderOptions>
{
public ValidateOptionsResult Validate(string? name, VerdictBuilderOptions options)
{
try
{
options.Validate();
return ValidateOptionsResult.Success;
}
catch (InvalidOperationException ex)
{
return ValidateOptionsResult.Fail(ex.Message);
}
}
}