Compare commits

..

No commits in common. "development" and "feature/thalos-service-google-oidc" have entirely different histories.

7 changed files with 5 additions and 108 deletions

View File

@ -26,8 +26,6 @@ Session refresh token signing is bound to `IIdentitySecretMaterialProvider`.
- Runtime binding is configuration-based by default.
- 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).
- 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
@ -35,9 +33,3 @@ Session refresh token signing is bound to `IIdentitySecretMaterialProvider`.
- `ThalosIdentity:Secrets:Oidc:Google:ClientId`
- `ThalosIdentity:Secrets:Oidc:Google:Issuer` (optional, defaults to `https://accounts.google.com`)
- `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.

View File

@ -28,5 +28,3 @@ docker run --rm -p 8080:8080 \
- Exposes internal identity runtime endpoint set and gRPC service.
- 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.

View File

@ -18,12 +18,6 @@ namespace Thalos.Service.Application.DependencyInjection;
/// </summary>
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>
/// Adds thalos-service runtime wiring aligned with blueprint runtime and thalos-dal runtime.
/// </summary>
@ -34,8 +28,7 @@ public static class ThalosServiceRuntimeServiceCollectionExtensions
services.AddBlueprintRuntimeCore();
services.AddThalosDalRuntime();
services.TryAddSingleton<IConfiguration>(_ =>
// Local-only defaults keep isolated tests and developer runs deterministic.
new ConfigurationBuilder().AddInMemoryCollection(LocalRuntimeDefaults).Build());
new ConfigurationBuilder().AddInMemoryCollection().Build());
services.TryAddSingleton<IIdentityPolicyDecisionService, IdentityPolicyDecisionService>();
services.TryAddSingleton<IIdentityTokenDecisionService, IdentityTokenDecisionService>();

View File

@ -8,6 +8,8 @@ namespace Thalos.Service.Application.Secrets;
public sealed class ConfigurationIdentitySecretMaterialProvider(IConfiguration configuration)
: IIdentitySecretMaterialProvider
{
private const string FallbackSecret = "thalos-dev-secret";
/// <inheritdoc />
public bool TryGetSecret(string secretKey, out string secretValue)
{
@ -37,8 +39,7 @@ public sealed class ConfigurationIdentitySecretMaterialProvider(IConfiguration c
{
return secretValue;
}
throw new InvalidOperationException(
$"Identity secret '{secretKey}' is not configured for the current runtime.");
return FallbackSecret;
}
}

View File

@ -18,6 +18,5 @@ public interface IIdentitySecretMaterialProvider
/// </summary>
/// <param name="secretKey">Logical secret key.</param>
/// <returns>Secret material value.</returns>
/// <exception cref="InvalidOperationException">Thrown when the secret is not available in the active runtime.</exception>
string GetSecret(string secretKey);
}

View File

@ -1,56 +0,0 @@
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);
}
}

View File

@ -42,21 +42,6 @@ public class HmacIdentitySessionTokenCodecTests
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
{
public bool TryGetSecret(string secretKey, out string secretValue)
@ -67,19 +52,4 @@ public class HmacIdentitySessionTokenCodecTests
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.");
}
}
}