Compare commits
No commits in common. "74ff0c9fbc2ca6055521c61005c5bd934c609913" and "c23ab0cbdca70e29cadaa5e908ed38c9d2605561" have entirely different histories.
74ff0c9fbc
...
c23ab0cbdc
@ -12,11 +12,10 @@ This repository is a modular multi-package platform library set.
|
|||||||
| Core.Blueprint.Redis | Redis integration helpers and extension points | Core.Blueprint.Common |
|
| Core.Blueprint.Redis | Redis integration helpers and extension points | Core.Blueprint.Common |
|
||||||
| Core.Blueprint.SQLServer | SQL Server integration helpers and extension points | Core.Blueprint.Common |
|
| Core.Blueprint.SQLServer | SQL Server integration helpers and extension points | Core.Blueprint.Common |
|
||||||
| Core.Blueprint.Storage | Blob/file storage integration helpers | Core.Blueprint.Common |
|
| Core.Blueprint.Storage | Blob/file storage integration helpers | Core.Blueprint.Common |
|
||||||
| Core.Blueprint.KeyVault | Key vault and provider-agnostic secret contract helpers | Core.Blueprint.Common |
|
| Core.Blueprint.KeyVault | Key vault access integration helpers | Core.Blueprint.Common |
|
||||||
|
|
||||||
## Boundary Rules
|
## Boundary Rules
|
||||||
|
|
||||||
- Blueprint remains library-only.
|
- Blueprint remains library-only.
|
||||||
- Identity abstractions are not owned by this repository.
|
- Identity abstractions are not owned by this repository.
|
||||||
- Downstream repositories consume package contracts from this repo.
|
- Downstream repositories consume package contracts from this repo.
|
||||||
- Secret provider abstractions are provider-agnostic; concrete provider adapters are bound at runtime.
|
|
||||||
|
|||||||
@ -1,34 +0,0 @@
|
|||||||
# Provider-Agnostic Secret Provider Rollout
|
|
||||||
|
|
||||||
This package defines a provider-agnostic contract for secret lookup without binding to Vault, cloud providers, or environment files in core layers.
|
|
||||||
|
|
||||||
## Contract Surface
|
|
||||||
|
|
||||||
- `IBlueprintSecretProvider`
|
|
||||||
- `BlueprintSecretReference`
|
|
||||||
- `BlueprintSecretResolutionResult`
|
|
||||||
|
|
||||||
## Runtime Defaults
|
|
||||||
|
|
||||||
- `AddBlueprintKeyVaultModule(...)` now registers:
|
|
||||||
- `BlueprintKeyVaultRuntimeSettings` with:
|
|
||||||
- `VaultName`
|
|
||||||
- `SecretProviderName`
|
|
||||||
- `NoOpBlueprintSecretProvider` as default fallback.
|
|
||||||
|
|
||||||
The default fallback returns unresolved lookups and never introduces provider-specific behavior.
|
|
||||||
|
|
||||||
## Binding Strategy
|
|
||||||
|
|
||||||
1. Keep domain and application layers dependent only on `IBlueprintSecretProvider`.
|
|
||||||
2. Bind provider implementation at runtime through DI:
|
|
||||||
- Vault adapter
|
|
||||||
- Cloud secret manager adapter
|
|
||||||
- Environment/test adapter
|
|
||||||
3. Keep one active provider per deployment profile.
|
|
||||||
|
|
||||||
## Rollout Notes
|
|
||||||
|
|
||||||
- Stage 33 keeps this contract-only baseline.
|
|
||||||
- Concrete Vault/OIDC provider integration should be implemented in infrastructure/runtime layers only.
|
|
||||||
- Existing identity logic ownership remains in Thalos repositories.
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
namespace Core.Blueprint.KeyVault.Contracts;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Defines a provider-agnostic secret lookup reference.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="Scope">Secret namespace, path, or scope name.</param>
|
|
||||||
/// <param name="Name">Secret key name inside the scope.</param>
|
|
||||||
/// <param name="Version">Optional secret version marker.</param>
|
|
||||||
public sealed record BlueprintSecretReference(
|
|
||||||
string Scope,
|
|
||||||
string Name,
|
|
||||||
string? Version = null);
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
namespace Core.Blueprint.KeyVault.Contracts;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents the outcome of a secret provider lookup.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="IsResolved">True when a secret value was found.</param>
|
|
||||||
/// <param name="Value">Resolved secret value, if found.</param>
|
|
||||||
/// <param name="ProviderName">Name of the active secret provider.</param>
|
|
||||||
/// <param name="Version">Resolved version marker, if available.</param>
|
|
||||||
public sealed record BlueprintSecretResolutionResult(
|
|
||||||
bool IsResolved,
|
|
||||||
string? Value,
|
|
||||||
string ProviderName,
|
|
||||||
string? Version)
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a resolved secret result.
|
|
||||||
/// </summary>
|
|
||||||
public static BlueprintSecretResolutionResult Resolved(
|
|
||||||
string value,
|
|
||||||
string providerName,
|
|
||||||
string? version = null)
|
|
||||||
{
|
|
||||||
return new BlueprintSecretResolutionResult(true, value, providerName, version);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a missing secret result.
|
|
||||||
/// </summary>
|
|
||||||
public static BlueprintSecretResolutionResult Missing(
|
|
||||||
string providerName,
|
|
||||||
string? version = null)
|
|
||||||
{
|
|
||||||
return new BlueprintSecretResolutionResult(false, null, providerName, version);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
namespace Core.Blueprint.KeyVault.Contracts;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Defines provider-agnostic secret retrieval operations.
|
|
||||||
/// </summary>
|
|
||||||
public interface IBlueprintSecretProvider
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Resolves a secret from the configured provider.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="reference">Provider-agnostic secret reference.</param>
|
|
||||||
/// <param name="cancellationToken">Cancellation token.</param>
|
|
||||||
/// <returns>Resolution result describing value and provider metadata.</returns>
|
|
||||||
ValueTask<BlueprintSecretResolutionResult> GetSecretAsync(
|
|
||||||
BlueprintSecretReference reference,
|
|
||||||
CancellationToken cancellationToken = default);
|
|
||||||
}
|
|
||||||
@ -1,5 +1,4 @@
|
|||||||
using Core.Blueprint.Common.DependencyInjection;
|
using Core.Blueprint.Common.DependencyInjection;
|
||||||
using Core.Blueprint.KeyVault.Contracts;
|
|
||||||
using Core.Blueprint.KeyVault.Runtime;
|
using Core.Blueprint.KeyVault.Runtime;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
@ -16,18 +15,13 @@ public static class BlueprintKeyVaultServiceCollectionExtensions
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="services">Service collection.</param>
|
/// <param name="services">Service collection.</param>
|
||||||
/// <param name="vaultName">Target key vault name.</param>
|
/// <param name="vaultName">Target key vault name.</param>
|
||||||
/// <param name="secretProviderName">Active provider name for secret resolution.</param>
|
|
||||||
/// <returns>Service collection for fluent chaining.</returns>
|
/// <returns>Service collection for fluent chaining.</returns>
|
||||||
public static IServiceCollection AddBlueprintKeyVaultModule(
|
public static IServiceCollection AddBlueprintKeyVaultModule(
|
||||||
this IServiceCollection services,
|
this IServiceCollection services,
|
||||||
string vaultName = "default-vault",
|
string vaultName = "default-vault")
|
||||||
string secretProviderName = "unconfigured")
|
|
||||||
{
|
{
|
||||||
services.AddBlueprintRuntimeCore();
|
services.AddBlueprintRuntimeCore();
|
||||||
services.TryAddSingleton(new BlueprintKeyVaultRuntimeSettings(
|
services.TryAddSingleton(new BlueprintKeyVaultRuntimeSettings(ResolveVaultName(vaultName)));
|
||||||
ResolveVaultName(vaultName),
|
|
||||||
ResolveProviderName(secretProviderName)));
|
|
||||||
services.TryAddSingleton<IBlueprintSecretProvider, NoOpBlueprintSecretProvider>();
|
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
@ -41,14 +35,4 @@ public static class BlueprintKeyVaultServiceCollectionExtensions
|
|||||||
|
|
||||||
return "default-vault";
|
return "default-vault";
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string ResolveProviderName(string secretProviderName)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrWhiteSpace(secretProviderName))
|
|
||||||
{
|
|
||||||
return secretProviderName;
|
|
||||||
}
|
|
||||||
|
|
||||||
return "unconfigured";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,4 @@ namespace Core.Blueprint.KeyVault.Runtime;
|
|||||||
/// Defines runtime settings for key vault integration helpers.
|
/// Defines runtime settings for key vault integration helpers.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="VaultName">Target key vault name.</param>
|
/// <param name="VaultName">Target key vault name.</param>
|
||||||
/// <param name="SecretProviderName">Active secret provider name bound through DI.</param>
|
public sealed record BlueprintKeyVaultRuntimeSettings(string VaultName);
|
||||||
public sealed record BlueprintKeyVaultRuntimeSettings(
|
|
||||||
string VaultName,
|
|
||||||
string SecretProviderName);
|
|
||||||
|
|||||||
@ -1,20 +0,0 @@
|
|||||||
using Core.Blueprint.KeyVault.Contracts;
|
|
||||||
|
|
||||||
namespace Core.Blueprint.KeyVault.Runtime;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Default provider used when no concrete secret manager adapter is configured.
|
|
||||||
/// </summary>
|
|
||||||
public sealed class NoOpBlueprintSecretProvider(BlueprintKeyVaultRuntimeSettings settings)
|
|
||||||
: IBlueprintSecretProvider
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public ValueTask<BlueprintSecretResolutionResult> GetSecretAsync(
|
|
||||||
BlueprintSecretReference reference,
|
|
||||||
CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
var providerName = settings.SecretProviderName;
|
|
||||||
var result = BlueprintSecretResolutionResult.Missing(providerName, reference.Version);
|
|
||||||
return ValueTask.FromResult(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,4 +1,3 @@
|
|||||||
using Core.Blueprint.KeyVault.Contracts;
|
|
||||||
using Core.Blueprint.KeyVault.DependencyInjection;
|
using Core.Blueprint.KeyVault.DependencyInjection;
|
||||||
using Core.Blueprint.KeyVault.Runtime;
|
using Core.Blueprint.KeyVault.Runtime;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
@ -12,62 +11,11 @@ public class UnitTest1
|
|||||||
{
|
{
|
||||||
var services = new ServiceCollection();
|
var services = new ServiceCollection();
|
||||||
|
|
||||||
services.AddBlueprintKeyVaultModule("agile-vault", "vault");
|
services.AddBlueprintKeyVaultModule("agile-vault");
|
||||||
|
|
||||||
using var provider = services.BuildServiceProvider();
|
using var provider = services.BuildServiceProvider();
|
||||||
var settings = provider.GetRequiredService<BlueprintKeyVaultRuntimeSettings>();
|
var settings = provider.GetRequiredService<BlueprintKeyVaultRuntimeSettings>();
|
||||||
|
|
||||||
Assert.Equal("agile-vault", settings.VaultName);
|
Assert.Equal("agile-vault", settings.VaultName);
|
||||||
Assert.Equal("vault", settings.SecretProviderName);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task AddBlueprintKeyVaultModule_WhenNoProviderBound_UsesNoOpProvider()
|
|
||||||
{
|
|
||||||
var services = new ServiceCollection();
|
|
||||||
|
|
||||||
services.AddBlueprintKeyVaultModule("agile-vault");
|
|
||||||
|
|
||||||
await using var provider = services.BuildServiceProvider();
|
|
||||||
var secretProvider = provider.GetRequiredService<IBlueprintSecretProvider>();
|
|
||||||
var result = await secretProvider.GetSecretAsync(
|
|
||||||
new BlueprintSecretReference("thalos/oidc", "google-client-secret"));
|
|
||||||
|
|
||||||
Assert.False(result.IsResolved);
|
|
||||||
Assert.Null(result.Value);
|
|
||||||
Assert.Equal("unconfigured", result.ProviderName);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task AddBlueprintKeyVaultModule_WhenProviderAlreadyRegistered_PreservesCustomProvider()
|
|
||||||
{
|
|
||||||
var services = new ServiceCollection();
|
|
||||||
services.AddSingleton<IBlueprintSecretProvider, FakeSecretProvider>();
|
|
||||||
|
|
||||||
services.AddBlueprintKeyVaultModule();
|
|
||||||
|
|
||||||
await using var provider = services.BuildServiceProvider();
|
|
||||||
var secretProvider = provider.GetRequiredService<IBlueprintSecretProvider>();
|
|
||||||
var result = await secretProvider.GetSecretAsync(
|
|
||||||
new BlueprintSecretReference("scope", "name", "v1"));
|
|
||||||
|
|
||||||
Assert.True(result.IsResolved);
|
|
||||||
Assert.Equal("custom-provider", result.ProviderName);
|
|
||||||
Assert.Equal("secret-value", result.Value);
|
|
||||||
Assert.Equal("v1", result.Version);
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class FakeSecretProvider : IBlueprintSecretProvider
|
|
||||||
{
|
|
||||||
public ValueTask<BlueprintSecretResolutionResult> GetSecretAsync(
|
|
||||||
BlueprintSecretReference reference,
|
|
||||||
CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
var result = BlueprintSecretResolutionResult.Resolved(
|
|
||||||
"secret-value",
|
|
||||||
"custom-provider",
|
|
||||||
reference.Version);
|
|
||||||
return ValueTask.FromResult(result);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user