fix: simplify external authentication secret IDs

Use a compact namespaced GUID for newly managed secrets while preserving unique staging and opaque existing references.
This commit is contained in:
Sipke Schoorstra 2026-08-02 00:22:16 +02:00
parent 7e82a55a9f
commit 119f49f7a2
No known key found for this signature in database
GPG key ID: 5C10502B28A4268F
2 changed files with 7 additions and 5 deletions

View file

@ -22,14 +22,13 @@ public sealed class ElsaSecretBindingResolver(
{
ArgumentException.ThrowIfNullOrWhiteSpace(request.ConnectionId);
ArgumentException.ThrowIfNullOrWhiteSpace(request.FieldName);
var fieldName = string.Concat(request.FieldName.Where(char.IsLetterOrDigit)).ToLowerInvariant();
if (fieldName.Length == 0)
if (!request.FieldName.Any(char.IsLetterOrDigit))
throw new ArgumentException("The secret field name must contain a letter or digit.", nameof(request));
// Stage every replacement under a new reference. The caller publishes
// that reference with the connection CAS and removes it on CAS failure,
// so a stale request can never rotate material used by the live binding.
var name = $"external-authentication-{request.ConnectionId.ToLowerInvariant()}-{fieldName}-{Guid.NewGuid():N}";
var name = $"external-authentication:{Guid.NewGuid():N}";
var secret = await secretManager.CreateAsync(new()
{
Name = name,

View file

@ -88,12 +88,15 @@ public class ElsaSecretBindingResolverTests
using var value = new SensitiveString("super-secret");
var binding = await resolver.StageAsync(new ManagedSecretBindingWriteRequest("connection-a", "clientSecret", value));
var replacementBinding = await resolver.StageAsync(new ManagedSecretBindingWriteRequest("connection-a", "clientSecret", value));
Assert.Equal(SecretBindingOwnership.Managed, binding.Ownership);
Assert.Equal(ElsaSecretBindingResolver.ResolverType, binding.ResolverType);
Assert.StartsWith("external-authentication-connection-a-clientsecret-", binding.Reference, StringComparison.Ordinal);
Assert.StartsWith("external-authentication:", binding.Reference, StringComparison.Ordinal);
Assert.True(Guid.TryParseExact(binding.Reference["external-authentication:".Length..], "N", out _));
Assert.NotEqual(binding.Reference, replacementBinding.Reference);
Assert.DoesNotContain("super-secret", binding.Reference, StringComparison.Ordinal);
await manager.Received(1).CreateAsync(Arg.Is<CreateSecretRequest>(x => x.Value == "super-secret"), Arg.Any<CancellationToken>());
await manager.Received(2).CreateAsync(Arg.Is<CreateSecretRequest>(x => x.Value == "super-secret"), Arg.Any<CancellationToken>());
await manager.DidNotReceive().RotateAsync(Arg.Any<string>(), Arg.Any<RotateSecretRequest>(), Arg.Any<CancellationToken>());
}