Compare commits
2 Commits
feature/th
...
developmen
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31e09b2050 | ||
|
|
85796336c6 |
@ -26,6 +26,8 @@ Session refresh token signing is bound to `IIdentitySecretMaterialProvider`.
|
|||||||
- Runtime binding is configuration-based by default.
|
- Runtime binding is configuration-based by default.
|
||||||
- Vault/cloud/env adapters can be swapped at DI boundaries without changing use-case code.
|
- Vault/cloud/env adapters can be swapped at DI boundaries without changing use-case code.
|
||||||
- OIDC provider material uses the same boundary (no provider SDK coupling in use-case logic).
|
- OIDC provider material uses the same boundary (no provider SDK coupling in use-case logic).
|
||||||
|
- Missing secrets fail explicitly at runtime; the service no longer falls back to a baked-in signing secret.
|
||||||
|
- `AddThalosServiceRuntime()` only provides a local in-memory session-signing default when no host configuration is present, so isolated tests and developer runs stay deterministic without changing production behavior.
|
||||||
|
|
||||||
## Configuration Keys
|
## Configuration Keys
|
||||||
|
|
||||||
@ -33,3 +35,9 @@ Session refresh token signing is bound to `IIdentitySecretMaterialProvider`.
|
|||||||
- `ThalosIdentity:Secrets:Oidc:Google:ClientId`
|
- `ThalosIdentity:Secrets:Oidc:Google:ClientId`
|
||||||
- `ThalosIdentity:Secrets:Oidc:Google:Issuer` (optional, defaults to `https://accounts.google.com`)
|
- `ThalosIdentity:Secrets:Oidc:Google:Issuer` (optional, defaults to `https://accounts.google.com`)
|
||||||
- `ThalosIdentity:Secrets:Default` (fallback)
|
- `ThalosIdentity:Secrets:Default` (fallback)
|
||||||
|
|
||||||
|
## Production Expectation
|
||||||
|
|
||||||
|
- Production hosts must provide `ThalosIdentity:Secrets:SessionSigning`.
|
||||||
|
- Google OIDC validation must provide `ThalosIdentity:Secrets:Oidc:Google:ClientId`.
|
||||||
|
- The optional `Default` key is intended for non-sensitive shared local/test values, not as a production substitute for explicit signing material.
|
||||||
|
|||||||
@ -28,3 +28,5 @@ docker run --rm -p 8080:8080 \
|
|||||||
|
|
||||||
- Exposes internal identity runtime endpoint set and gRPC service.
|
- Exposes internal identity runtime endpoint set and gRPC service.
|
||||||
- Google OIDC claim validation requires `ThalosIdentity:Secrets:Oidc:Google:ClientId`.
|
- Google OIDC claim validation requires `ThalosIdentity:Secrets:Oidc:Google:ClientId`.
|
||||||
|
- Session refresh signing requires `ThalosIdentity:Secrets:SessionSigning`; there is no baked-in production fallback secret.
|
||||||
|
- If the host does not provide configuration, `AddThalosServiceRuntime()` supplies a local in-memory session-signing default strictly for isolated tests and developer-only runtime wiring.
|
||||||
|
|||||||
@ -18,6 +18,12 @@ namespace Thalos.Service.Application.DependencyInjection;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class ThalosServiceRuntimeServiceCollectionExtensions
|
public static class ThalosServiceRuntimeServiceCollectionExtensions
|
||||||
{
|
{
|
||||||
|
private static readonly IReadOnlyDictionary<string, string?> LocalRuntimeDefaults =
|
||||||
|
new Dictionary<string, string?>(StringComparer.Ordinal)
|
||||||
|
{
|
||||||
|
["ThalosIdentity:Secrets:SessionSigning"] = "thalos-local-session-signing-secret"
|
||||||
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds thalos-service runtime wiring aligned with blueprint runtime and thalos-dal runtime.
|
/// Adds thalos-service runtime wiring aligned with blueprint runtime and thalos-dal runtime.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -28,7 +34,8 @@ public static class ThalosServiceRuntimeServiceCollectionExtensions
|
|||||||
services.AddBlueprintRuntimeCore();
|
services.AddBlueprintRuntimeCore();
|
||||||
services.AddThalosDalRuntime();
|
services.AddThalosDalRuntime();
|
||||||
services.TryAddSingleton<IConfiguration>(_ =>
|
services.TryAddSingleton<IConfiguration>(_ =>
|
||||||
new ConfigurationBuilder().AddInMemoryCollection().Build());
|
// Local-only defaults keep isolated tests and developer runs deterministic.
|
||||||
|
new ConfigurationBuilder().AddInMemoryCollection(LocalRuntimeDefaults).Build());
|
||||||
services.TryAddSingleton<IIdentityPolicyDecisionService, IdentityPolicyDecisionService>();
|
services.TryAddSingleton<IIdentityPolicyDecisionService, IdentityPolicyDecisionService>();
|
||||||
services.TryAddSingleton<IIdentityTokenDecisionService, IdentityTokenDecisionService>();
|
services.TryAddSingleton<IIdentityTokenDecisionService, IdentityTokenDecisionService>();
|
||||||
|
|
||||||
|
|||||||
@ -8,8 +8,6 @@ namespace Thalos.Service.Application.Secrets;
|
|||||||
public sealed class ConfigurationIdentitySecretMaterialProvider(IConfiguration configuration)
|
public sealed class ConfigurationIdentitySecretMaterialProvider(IConfiguration configuration)
|
||||||
: IIdentitySecretMaterialProvider
|
: IIdentitySecretMaterialProvider
|
||||||
{
|
{
|
||||||
private const string FallbackSecret = "thalos-dev-secret";
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public bool TryGetSecret(string secretKey, out string secretValue)
|
public bool TryGetSecret(string secretKey, out string secretValue)
|
||||||
{
|
{
|
||||||
@ -39,7 +37,8 @@ public sealed class ConfigurationIdentitySecretMaterialProvider(IConfiguration c
|
|||||||
{
|
{
|
||||||
return secretValue;
|
return secretValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
return FallbackSecret;
|
throw new InvalidOperationException(
|
||||||
|
$"Identity secret '{secretKey}' is not configured for the current runtime.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,5 +18,6 @@ public interface IIdentitySecretMaterialProvider
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="secretKey">Logical secret key.</param>
|
/// <param name="secretKey">Logical secret key.</param>
|
||||||
/// <returns>Secret material value.</returns>
|
/// <returns>Secret material value.</returns>
|
||||||
|
/// <exception cref="InvalidOperationException">Thrown when the secret is not available in the active runtime.</exception>
|
||||||
string GetSecret(string secretKey);
|
string GetSecret(string secretKey);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,56 @@
|
|||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Thalos.Service.Application.Secrets;
|
||||||
|
|
||||||
|
namespace Thalos.Service.Application.UnitTests;
|
||||||
|
|
||||||
|
public class ConfigurationIdentitySecretMaterialProviderTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void TryGetSecret_WhenScopedSecretConfigured_ReturnsScopedValue()
|
||||||
|
{
|
||||||
|
var provider = CreateProvider(new Dictionary<string, string?>
|
||||||
|
{
|
||||||
|
["ThalosIdentity:Secrets:SessionSigning"] = "scoped-secret",
|
||||||
|
["ThalosIdentity:Secrets:Default"] = "default-secret"
|
||||||
|
});
|
||||||
|
|
||||||
|
var ok = provider.TryGetSecret("SessionSigning", out var secretValue);
|
||||||
|
|
||||||
|
Assert.True(ok);
|
||||||
|
Assert.Equal("scoped-secret", secretValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TryGetSecret_WhenScopedSecretMissing_UsesDefaultSecret()
|
||||||
|
{
|
||||||
|
var provider = CreateProvider(new Dictionary<string, string?>
|
||||||
|
{
|
||||||
|
["ThalosIdentity:Secrets:Default"] = "default-secret"
|
||||||
|
});
|
||||||
|
|
||||||
|
var ok = provider.TryGetSecret("MissingSecret", out var secretValue);
|
||||||
|
|
||||||
|
Assert.True(ok);
|
||||||
|
Assert.Equal("default-secret", secretValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetSecret_WhenSecretMissing_ThrowsExplicitRuntimeError()
|
||||||
|
{
|
||||||
|
var provider = CreateProvider(new Dictionary<string, string?>());
|
||||||
|
|
||||||
|
var error = Assert.Throws<InvalidOperationException>(() => provider.GetSecret("SessionSigning"));
|
||||||
|
|
||||||
|
Assert.Contains("SessionSigning", error.Message, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ConfigurationIdentitySecretMaterialProvider CreateProvider(
|
||||||
|
IReadOnlyDictionary<string, string?> configurationValues)
|
||||||
|
{
|
||||||
|
var configuration = new ConfigurationBuilder()
|
||||||
|
.AddInMemoryCollection(configurationValues)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
return new ConfigurationIdentitySecretMaterialProvider(configuration);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -42,6 +42,21 @@ public class HmacIdentitySessionTokenCodecTests
|
|||||||
Assert.False(ok);
|
Assert.False(ok);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Encode_WhenSigningSecretUnavailable_ThrowsExplicitRuntimeError()
|
||||||
|
{
|
||||||
|
var codec = new HmacIdentitySessionTokenCodec(new MissingSecretMaterialProvider());
|
||||||
|
var descriptor = new IdentitySessionDescriptor(
|
||||||
|
"user-9",
|
||||||
|
"tenant-9",
|
||||||
|
IdentityAuthProvider.InternalJwt,
|
||||||
|
DateTimeOffset.UtcNow.AddMinutes(5));
|
||||||
|
|
||||||
|
var error = Assert.Throws<InvalidOperationException>(() => codec.Encode(descriptor));
|
||||||
|
|
||||||
|
Assert.Contains("SessionSigning", error.Message, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
private sealed class FakeSecretMaterialProvider : IIdentitySecretMaterialProvider
|
private sealed class FakeSecretMaterialProvider : IIdentitySecretMaterialProvider
|
||||||
{
|
{
|
||||||
public bool TryGetSecret(string secretKey, out string secretValue)
|
public bool TryGetSecret(string secretKey, out string secretValue)
|
||||||
@ -52,4 +67,19 @@ public class HmacIdentitySessionTokenCodecTests
|
|||||||
|
|
||||||
public string GetSecret(string secretKey) => "unit-test-secret";
|
public string GetSecret(string secretKey) => "unit-test-secret";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed class MissingSecretMaterialProvider : IIdentitySecretMaterialProvider
|
||||||
|
{
|
||||||
|
public bool TryGetSecret(string secretKey, out string secretValue)
|
||||||
|
{
|
||||||
|
secretValue = string.Empty;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetSecret(string secretKey)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Identity secret '{secretKey}' is not configured for the current runtime.");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user