using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Threading; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using StellaOps.Scanner.Surface.Env; using StellaOps.Scanner.Surface.Secrets; using StellaOps.Scanner.WebService.Options; using StellaOps.Scanner.Storage; using Xunit; using StellaOps.TestKit; namespace StellaOps.Scanner.WebService.Tests; public sealed class ScannerSurfaceSecretConfiguratorTests { [Trait("Category", TestCategories.Unit)] [Fact] public void Configure_AppliesCasAccessSecretToArtifactStore() { const string json = """ { "driver": "rustfs", "endpoint": "https://surface.api", "bucket": "surface-artifacts", "apiKey": "rust-key", "apiKeyHeader": "X-Surface-Api-Key", "region": "ap-southeast-2" } """; using var handle = SurfaceSecretHandle.FromBytes(Encoding.UTF8.GetBytes(json)); var secretProvider = new StubSecretProvider(new Dictionary(StringComparer.OrdinalIgnoreCase) { ["cas-access"] = handle }); var environment = new StubSurfaceEnvironment(); var options = new ScannerWebServiceOptions(); var configurator = new ScannerSurfaceSecretConfigurator( secretProvider, environment, NullLogger.Instance); configurator.Configure(options); Assert.Equal("rustfs", options.ArtifactStore.Driver); Assert.Equal("https://surface.api", options.ArtifactStore.Endpoint); Assert.Equal("surface-artifacts", options.ArtifactStore.Bucket); Assert.Equal("rust-key", options.ArtifactStore.ApiKey); Assert.Equal("X-Surface-Api-Key", options.ArtifactStore.ApiKeyHeader); Assert.Equal("ap-southeast-2", options.ArtifactStore.Region); } [Trait("Category", TestCategories.Unit)] [Fact] public void PostConfigure_SynchronizesArtifactStoreToScannerStorageOptions() { var webOptions = Microsoft.Extensions.Options.Options.Create(new ScannerWebServiceOptions { ArtifactStore = new ScannerWebServiceOptions.ArtifactStoreOptions { Driver = "rustfs", Endpoint = "https://surface.sync", ApiKey = "sync-key", ApiKeyHeader = "X-Sync", Bucket = "sync-bucket", Region = "us-west-2", RootPrefix = "sync" } }); var configurator = new ScannerStorageOptionsPostConfigurator( new OptionsMonitorStub(webOptions), NullLogger.Instance); var storageOptions = new ScannerStorageOptions(); configurator.PostConfigure(Microsoft.Extensions.Options.Options.DefaultName, storageOptions); Assert.Equal("rustfs", storageOptions.ObjectStore.Driver); Assert.Equal("https://surface.sync", storageOptions.ObjectStore.RustFs.BaseUrl); Assert.Equal("sync-bucket", storageOptions.ObjectStore.BucketName); Assert.Equal("sync", storageOptions.ObjectStore.RootPrefix); Assert.Equal("us-west-2", storageOptions.ObjectStore.Region); Assert.Equal("sync-key", storageOptions.ObjectStore.RustFs.ApiKey); Assert.Equal("X-Sync", storageOptions.ObjectStore.RustFs.ApiKeyHeader); } [Trait("Category", TestCategories.Unit)] [Fact] public void Configure_AppliesAttestationSecretToSigning() { const string json = """ { "keyPem": "-----BEGIN KEY-----\nYWJj\n-----END KEY-----", "certificatePem": "CERT-PEM", "certificateChainPem": "CHAIN-PEM" } """; using var handle = SurfaceSecretHandle.FromBytes(Encoding.UTF8.GetBytes(json)); var secretProvider = new StubSecretProvider(new Dictionary(StringComparer.OrdinalIgnoreCase) { ["attestation"] = handle }); var environment = new StubSurfaceEnvironment(); var options = new ScannerWebServiceOptions(); var configurator = new ScannerSurfaceSecretConfigurator( secretProvider, environment, NullLogger.Instance); configurator.Configure(options); Assert.Equal("-----BEGIN KEY-----\nYWJj\n-----END KEY-----", options.Signing.KeyPem); Assert.Equal("CERT-PEM", options.Signing.CertificatePem); Assert.Equal("CHAIN-PEM", options.Signing.CertificateChainPem); } [Trait("Category", TestCategories.Unit)] [Fact] public void Configure_AppliesRegistrySecretToOptions() { const string json = """ { "defaultRegistry": "registry.example.com", "entries": [ { "registry": "registry.example.com", "username": "demo", "password": "secret", "scopes": ["repo:sample:pull"], "headers": { "X-Test": "value" }, "allowInsecureTls": true, "email": "demo@example.com" } ] } """; using var handle = SurfaceSecretHandle.FromBytes(Encoding.UTF8.GetBytes(json)); var secretProvider = new StubSecretProvider(new Dictionary(StringComparer.OrdinalIgnoreCase) { ["registry"] = handle }); var environment = new StubSurfaceEnvironment(); var options = new ScannerWebServiceOptions(); var configurator = new ScannerSurfaceSecretConfigurator( secretProvider, environment, NullLogger.Instance); configurator.Configure(options); Assert.Equal("registry.example.com", options.Registry.DefaultRegistry); var credential = Assert.Single(options.Registry.Credentials); Assert.Equal("registry.example.com", credential.Registry); Assert.Equal("demo", credential.Username); Assert.Equal("secret", credential.Password); Assert.True(credential.AllowInsecureTls); Assert.Contains("repo:sample:pull", credential.Scopes); Assert.Equal("value", credential.Headers["X-Test"]); Assert.Equal("demo@example.com", credential.Email); } private sealed class StubSecretProvider : ISurfaceSecretProvider { private readonly IDictionary _handles; public StubSecretProvider(IDictionary handles) { _handles = handles ?? throw new ArgumentNullException(nameof(handles)); } public ValueTask GetAsync(SurfaceSecretRequest request, CancellationToken cancellationToken = default) { if (_handles.TryGetValue(request.SecretType, out var handle)) { return ValueTask.FromResult(handle); } throw new SurfaceSecretNotFoundException(request); } } private sealed class StubSurfaceEnvironment : ISurfaceEnvironment { public StubSurfaceEnvironment() { Settings = new SurfaceEnvironmentSettings( new Uri("https://surface"), "bucket", "region", new DirectoryInfo(Path.GetTempPath()), 256, false, Array.Empty(), new SurfaceSecretsConfiguration("inline", "tenant", null, null, null, true), "tenant", new SurfaceTlsConfiguration(null, null, null)); RawVariables = new Dictionary(StringComparer.OrdinalIgnoreCase); } public SurfaceEnvironmentSettings Settings { get; } public IReadOnlyDictionary RawVariables { get; } } private sealed class OptionsMonitorStub : IOptionsMonitor where T : class { private readonly IOptions _options; public OptionsMonitorStub(IOptions options) { _options = options; } public T CurrentValue => _options.Value; public T Get(string? name) => _options.Value; public IDisposable OnChange(Action listener) => NullDisposable.Instance; private sealed class NullDisposable : IDisposable { public static readonly NullDisposable Instance = new(); public void Dispose() { } } } }