perf(auth): allocation-free token parsing; single partition-key build per RPC

This commit is contained in:
Joseph Doherty
2026-08-15 12:23:25 -04:00
parent 3ff073d1ea
commit 88d38bb900
5 changed files with 221 additions and 25 deletions
@@ -196,6 +196,55 @@ public sealed class CachingApiKeyVerifierTests
Assert.Equal(2, inner.MarkUsedCount);
}
/// <summary>
/// Pins the header parse that arms the revoke-vs-in-flight generation guard. It runs on every
/// cache miss and is written to allocate only the returned key id; these cases hold it to the
/// rules the original <c>Split('_')</c> form applied. Note this parse is deliberately laxer than
/// the interceptor's partition parse — it applies no key-id length cap and does not require a
/// non-empty third segment — because a wrong <em>id</em> here would disarm the guard, whereas
/// an over-long one merely fails to match any generation.
/// </summary>
/// <param name="authorizationHeader">The presented header value.</param>
/// <param name="expected">The key id the parse must yield, or <see langword="null"/>.</param>
[Theory]
[InlineData(null, null)]
[InlineData("", null)]
[InlineData(" ", null)]
[InlineData("Bearer", null)]
[InlineData("Bearer ", null)]
[InlineData("Bearer mxgw_operator01_super-secret", "operator01")]
[InlineData("bearer mxgw_operator01_super-secret", "operator01")]
[InlineData(" Bearer mxgw_operator01_super-secret ", "operator01")]
[InlineData("mxgw_operator01_super-secret", "operator01")]
[InlineData("Bearer mxgw_abc_sec_ret", "abc")]
[InlineData("Bearer mxgw_a_b_c", "a")]
// Laxer than the interceptor: an empty or absent third segment still yields the key id.
[InlineData("Bearer mxgw_abc_", "abc")]
[InlineData("Bearer mxgw_abc__secret", "abc")]
[InlineData("Bearer mxgw_abc", null)]
[InlineData("Bearer mxgwabcsecret", null)]
[InlineData("Bearer mxgw__secret", null)]
[InlineData("Bearer _mxgw_abc_secret", null)]
[InlineData("Bearer MXGW_abc_secret", null)]
[InlineData("Bearer xmxgw_abc_secret", null)]
[InlineData("Bearer mxgw", null)]
[InlineData("Bearer mxgw_", null)]
[InlineData("Bearer ___", null)]
public void TryParseKeyId_MatchesTokenShapeRules(string? authorizationHeader, string? expected)
{
Assert.Equal(expected, CachingApiKeyVerifier.TryParseKeyId(authorizationHeader));
}
/// <summary>The guard parse applies no key-id length cap: an over-long id is still returned whole.</summary>
[Fact]
public void TryParseKeyId_LongKeyId_ReturnedWhole()
{
string keyId = new('a', 65);
Assert.Equal(keyId, CachingApiKeyVerifier.TryParseKeyId($"Bearer mxgw_{keyId}_secret"));
}
private static MemoryCache NewCache() => new(new MemoryCacheOptions());
private static ApiKeyVerification Success(string keyId) => new(
@@ -659,6 +659,64 @@ public sealed class GatewayGrpcAuthorizationInterceptorTests
}
}
/// <summary>
/// Pins the token shape the limiter partition is minted from. The parser is on every
/// authenticated request, so it is written to allocate only the returned key id; these cases
/// hold it to the rules the original <c>Split('_')</c> form applied — literal <c>mxgw</c> first
/// segment, non-empty second and third segments, and a bounded key id — including the ones a
/// hand-rolled scanner is most likely to drift on (a key id read from the second segment even
/// when the secret itself contains separators).
/// </summary>
/// <param name="authorizationHeader">The presented header value.</param>
/// <param name="expected">The key id the parse must yield, or <see langword="null"/>.</param>
[Theory]
[InlineData(null, null)]
[InlineData("", null)]
[InlineData(" ", null)]
[InlineData("Bearer", null)]
[InlineData("Bearer ", null)]
[InlineData("Bearer mxgw_abc_secret", "abc")]
[InlineData("bearer mxgw_abc_secret", "abc")]
[InlineData(" Bearer mxgw_abc_secret ", "abc")]
[InlineData("mxgw_abc_secret", "abc")]
// The secret may carry separators of its own; the key id is still the second segment.
[InlineData("Bearer mxgw_abc_sec_ret", "abc")]
[InlineData("Bearer mxgw_a_b_c", "a")]
[InlineData("Bearer mxgw_abc_secret_", "abc")]
// Shape failures: no separators, too few segments, empty segments, wrong prefix.
[InlineData("Bearer mxgwabcsecret", null)]
[InlineData("Bearer mxgw_abc", null)]
[InlineData("Bearer mxgw_abc_", null)]
[InlineData("Bearer mxgw_abc__secret", null)]
[InlineData("Bearer mxgw__secret", null)]
[InlineData("Bearer _mxgw_abc_secret", null)]
[InlineData("Bearer _abc_secret", null)]
[InlineData("Bearer MXGW_abc_secret", null)]
[InlineData("Bearer xmxgw_abc_secret", null)]
[InlineData("Bearer mxgw", null)]
[InlineData("Bearer mxgw_", null)]
[InlineData("Bearer ___", null)]
public void TryResolveKeyId_MatchesTokenShapeRules(string? authorizationHeader, string? expected)
{
Assert.Equal(expected, GatewayGrpcAuthorizationInterceptor.TryResolveKeyId(authorizationHeader));
}
/// <summary>
/// The key-id length cap is what stops an invented id of arbitrary length from becoming a
/// limiter partition, so the boundary is pinned on both sides.
/// </summary>
[Fact]
public void TryResolveKeyId_HonoursKeyIdLengthCap()
{
string atCap = new('a', 64);
string overCap = new('a', 65);
Assert.Equal(atCap, GatewayGrpcAuthorizationInterceptor.TryResolveKeyId($"Bearer mxgw_{atCap}_secret"));
Assert.Null(GatewayGrpcAuthorizationInterceptor.TryResolveKeyId($"Bearer mxgw_{overCap}_secret"));
}
private static MxAccessGatewayService CreateService(
ISessionManager sessionManager,
IGatewayRequestIdentityAccessor identityAccessor)