236 lines
8.5 KiB
C#
236 lines
8.5 KiB
C#
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<string, SurfaceSecretHandle>(StringComparer.OrdinalIgnoreCase)
|
|
{
|
|
["cas-access"] = handle
|
|
});
|
|
var environment = new StubSurfaceEnvironment();
|
|
var options = new ScannerWebServiceOptions();
|
|
|
|
var configurator = new ScannerSurfaceSecretConfigurator(
|
|
secretProvider,
|
|
environment,
|
|
NullLogger<ScannerSurfaceSecretConfigurator>.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<ScannerWebServiceOptions>(webOptions),
|
|
NullLogger<ScannerStorageOptionsPostConfigurator>.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<string, SurfaceSecretHandle>(StringComparer.OrdinalIgnoreCase)
|
|
{
|
|
["attestation"] = handle
|
|
});
|
|
var environment = new StubSurfaceEnvironment();
|
|
var options = new ScannerWebServiceOptions();
|
|
|
|
var configurator = new ScannerSurfaceSecretConfigurator(
|
|
secretProvider,
|
|
environment,
|
|
NullLogger<ScannerSurfaceSecretConfigurator>.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<string, SurfaceSecretHandle>(StringComparer.OrdinalIgnoreCase)
|
|
{
|
|
["registry"] = handle
|
|
});
|
|
var environment = new StubSurfaceEnvironment();
|
|
var options = new ScannerWebServiceOptions();
|
|
|
|
var configurator = new ScannerSurfaceSecretConfigurator(
|
|
secretProvider,
|
|
environment,
|
|
NullLogger<ScannerSurfaceSecretConfigurator>.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<string, SurfaceSecretHandle> _handles;
|
|
|
|
public StubSecretProvider(IDictionary<string, SurfaceSecretHandle> handles)
|
|
{
|
|
_handles = handles ?? throw new ArgumentNullException(nameof(handles));
|
|
}
|
|
|
|
public ValueTask<SurfaceSecretHandle> 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<string>(),
|
|
new SurfaceSecretsConfiguration("inline", "tenant", null, null, null, true),
|
|
"tenant",
|
|
new SurfaceTlsConfiguration(null, null, null));
|
|
RawVariables = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
|
}
|
|
|
|
public SurfaceEnvironmentSettings Settings { get; }
|
|
|
|
public IReadOnlyDictionary<string, string> RawVariables { get; }
|
|
}
|
|
|
|
private sealed class OptionsMonitorStub<T> : IOptionsMonitor<T> where T : class
|
|
{
|
|
private readonly IOptions<T> _options;
|
|
|
|
public OptionsMonitorStub(IOptions<T> options)
|
|
{
|
|
_options = options;
|
|
}
|
|
|
|
public T CurrentValue => _options.Value;
|
|
|
|
public T Get(string? name) => _options.Value;
|
|
|
|
public IDisposable OnChange(Action<T, string?> listener) => NullDisposable.Instance;
|
|
|
|
private sealed class NullDisposable : IDisposable
|
|
{
|
|
public static readonly NullDisposable Instance = new();
|
|
public void Dispose() { }
|
|
}
|
|
}
|
|
}
|