feat(gateway): thread ClientCorrelationId into constraint-denial audit (§1.2)
This commit is contained in:
@@ -69,7 +69,7 @@ public sealed class ConstraintEnforcerTests
|
||||
CancellationToken.None);
|
||||
Assert.NotNull(failure);
|
||||
|
||||
await enforcer.RecordDenialAsync(identity, "Write", "42", failure, CancellationToken.None);
|
||||
await enforcer.RecordDenialAsync(identity, "Write", "42", failure, correlationId: null, CancellationToken.None);
|
||||
|
||||
AuditEvent auditEvent = Assert.Single(auditWriter.Events);
|
||||
Assert.Equal("operator01", auditEvent.Actor);
|
||||
@@ -83,6 +83,43 @@ public sealed class ConstraintEnforcerTests
|
||||
Assert.Null(auditEvent.CorrelationId);
|
||||
}
|
||||
|
||||
/// <summary>A denial carrying a parseable correlation id stores it on the audit record.</summary>
|
||||
[Fact]
|
||||
public async Task RecordDenialAsync_WithGuidCorrelationId_StoresCorrelationId()
|
||||
{
|
||||
ConstraintEnforcer enforcer = CreateEnforcer(out FakeAuditWriter auditWriter);
|
||||
Guid correlationId = Guid.NewGuid();
|
||||
|
||||
await enforcer.RecordDenialAsync(
|
||||
identity: null,
|
||||
"Read",
|
||||
"Secret.Tag",
|
||||
new ConstraintFailure("read_scope", "Tag is outside the API key read scope."),
|
||||
correlationId.ToString(),
|
||||
CancellationToken.None);
|
||||
|
||||
AuditEvent auditEvent = Assert.Single(auditWriter.Events);
|
||||
Assert.Equal(correlationId, auditEvent.CorrelationId);
|
||||
}
|
||||
|
||||
/// <summary>A denial with a non-GUID correlation id leaves the audit correlation id null.</summary>
|
||||
[Fact]
|
||||
public async Task RecordDenialAsync_WithNonGuidCorrelationId_LeavesCorrelationIdNull()
|
||||
{
|
||||
ConstraintEnforcer enforcer = CreateEnforcer(out FakeAuditWriter auditWriter);
|
||||
|
||||
await enforcer.RecordDenialAsync(
|
||||
identity: null,
|
||||
"Read",
|
||||
"Secret.Tag",
|
||||
new ConstraintFailure("read_scope", "Tag is outside the API key read scope."),
|
||||
"cli-xyz",
|
||||
CancellationToken.None);
|
||||
|
||||
AuditEvent auditEvent = Assert.Single(auditWriter.Events);
|
||||
Assert.Null(auditEvent.CorrelationId);
|
||||
}
|
||||
|
||||
/// <summary>A denial with no identity records the canonical "anonymous" actor.</summary>
|
||||
[Fact]
|
||||
public async Task RecordDenialAsync_WithoutIdentity_UsesAnonymousActor()
|
||||
@@ -94,6 +131,7 @@ public sealed class ConstraintEnforcerTests
|
||||
"Read",
|
||||
"Secret.Tag",
|
||||
new ConstraintFailure("read_scope", "Tag is outside the API key read scope."),
|
||||
correlationId: null,
|
||||
CancellationToken.None);
|
||||
|
||||
AuditEvent auditEvent = Assert.Single(auditWriter.Events);
|
||||
|
||||
@@ -38,5 +38,6 @@ public sealed class AllowAllConstraintEnforcer : IConstraintEnforcer
|
||||
string commandKind,
|
||||
string target,
|
||||
ConstraintFailure failure,
|
||||
string? correlationId,
|
||||
CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@ public sealed class PredicateConstraintEnforcer : IConstraintEnforcer
|
||||
/// <summary>Deny predicate keyed on (serverHandle, itemHandle) (returns true to deny).</summary>
|
||||
public Func<int, int, bool> DenyWriteHandle { get; init; } = (_, _) => false;
|
||||
|
||||
/// <summary>Recorded denial messages — (commandKind, target) tuples.</summary>
|
||||
public List<(string CommandKind, string Target)> RecordedDenials { get; } = [];
|
||||
/// <summary>Recorded denial messages — (commandKind, target, correlationId) tuples.</summary>
|
||||
public List<(string CommandKind, string Target, string? CorrelationId)> RecordedDenials { get; } = [];
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ConstraintFailure?> CheckReadTagAsync(
|
||||
@@ -81,9 +81,10 @@ public sealed class PredicateConstraintEnforcer : IConstraintEnforcer
|
||||
string commandKind,
|
||||
string target,
|
||||
ConstraintFailure failure,
|
||||
string? correlationId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
RecordedDenials.Add((commandKind, target));
|
||||
RecordedDenials.Add((commandKind, target, correlationId));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user