fix(auth): aggregate bulk constraint denials into one audit event; ValueTask constraint checks
A bulk command with N denied items wrote N audit events into the bounded, drop-on-full audit channel, so a low-privilege key could evict every other pending audit record. Bulk filters now accumulate a ConstraintDenialSummary (count, constraint names, <=20 sample targets) and write one aggregated constraint-denied event per command. The per-item constraint checks are synchronous, so IConstraintEnforcer's Check* methods now return ValueTask instead of allocating a Task per item.
This commit is contained in:
@@ -488,6 +488,7 @@ public sealed class MxAccessGatewayService(
|
||||
|
||||
Dictionary<int, SubscribeResult> denied = [];
|
||||
List<string> allowed = [];
|
||||
ConstraintDenialSummary? denialSummary = null;
|
||||
for (int index = 0; index < tagAddresses.Count; index++)
|
||||
{
|
||||
string tagAddress = tagAddresses[index];
|
||||
@@ -500,8 +501,7 @@ public sealed class MxAccessGatewayService(
|
||||
continue;
|
||||
}
|
||||
|
||||
await constraintEnforcer.RecordDenialAsync(identity, command.Kind.ToString(), tagAddress, failure, correlationId, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
(denialSummary ??= new ConstraintDenialSummary(tagAddresses.Count)).Add(tagAddress, failure);
|
||||
denied[index] = new SubscribeResult
|
||||
{
|
||||
ServerHandle = serverHandle,
|
||||
@@ -511,11 +511,17 @@ public sealed class MxAccessGatewayService(
|
||||
};
|
||||
}
|
||||
|
||||
if (denied.Count == 0)
|
||||
if (denialSummary is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// One aggregated audit event per bulk command, not one per denied item (see
|
||||
// ConstraintDenialSummary for why).
|
||||
await constraintEnforcer
|
||||
.RecordBulkDenialAsync(identity, command.Kind.ToString(), denialSummary, correlationId, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
// Build the filtered command directly instead of cloning the original and clearing it:
|
||||
// the clone deep-copied every denied address only to drop it. The payload's other fields
|
||||
// (server_handle) are copied across explicitly. Nothing aliases the request here — these
|
||||
@@ -556,6 +562,7 @@ public sealed class MxAccessGatewayService(
|
||||
|
||||
Dictionary<int, BulkReadResult> denied = [];
|
||||
List<string> allowed = [];
|
||||
ConstraintDenialSummary? denialSummary = null;
|
||||
for (int index = 0; index < tagAddresses.Count; index++)
|
||||
{
|
||||
string tagAddress = tagAddresses[index];
|
||||
@@ -568,8 +575,7 @@ public sealed class MxAccessGatewayService(
|
||||
continue;
|
||||
}
|
||||
|
||||
await constraintEnforcer.RecordDenialAsync(identity, command.Kind.ToString(), tagAddress, failure, correlationId, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
(denialSummary ??= new ConstraintDenialSummary(tagAddresses.Count)).Add(tagAddress, failure);
|
||||
denied[index] = new BulkReadResult
|
||||
{
|
||||
ServerHandle = serverHandle,
|
||||
@@ -580,11 +586,17 @@ public sealed class MxAccessGatewayService(
|
||||
};
|
||||
}
|
||||
|
||||
if (denied.Count == 0)
|
||||
if (denialSummary is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// One aggregated audit event per bulk command, not one per denied item (see
|
||||
// ConstraintDenialSummary for why).
|
||||
await constraintEnforcer
|
||||
.RecordBulkDenialAsync(identity, command.Kind.ToString(), denialSummary, correlationId, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
MxCommand filtered = new() { Kind = command.Kind };
|
||||
ReadBulkCommand payload = new()
|
||||
{
|
||||
@@ -621,6 +633,7 @@ public sealed class MxAccessGatewayService(
|
||||
|
||||
Dictionary<int, BulkWriteResult> denied = [];
|
||||
List<TEntry> allowed = [];
|
||||
ConstraintDenialSummary? denialSummary = null;
|
||||
for (int index = 0; index < entries.Count; index++)
|
||||
{
|
||||
TEntry entry = entries[index];
|
||||
@@ -634,14 +647,7 @@ public sealed class MxAccessGatewayService(
|
||||
continue;
|
||||
}
|
||||
|
||||
await constraintEnforcer.RecordDenialAsync(
|
||||
identity,
|
||||
command.Kind.ToString(),
|
||||
itemHandle.ToString(System.Globalization.CultureInfo.InvariantCulture),
|
||||
failure,
|
||||
correlationId,
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
(denialSummary ??= new ConstraintDenialSummary(entries.Count)).Add(itemHandle, failure);
|
||||
denied[index] = new BulkWriteResult
|
||||
{
|
||||
ServerHandle = serverHandle,
|
||||
@@ -651,11 +657,17 @@ public sealed class MxAccessGatewayService(
|
||||
};
|
||||
}
|
||||
|
||||
if (denied.Count == 0)
|
||||
if (denialSummary is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// One aggregated audit event per bulk command, not one per denied item (see
|
||||
// ConstraintDenialSummary for why).
|
||||
await constraintEnforcer
|
||||
.RecordBulkDenialAsync(identity, command.Kind.ToString(), denialSummary, correlationId, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return new WriteBulkConstraintPlan(
|
||||
BuildFilteredWriteBulkCommand(command, allowed),
|
||||
entries.Count,
|
||||
@@ -742,6 +754,7 @@ public sealed class MxAccessGatewayService(
|
||||
|
||||
Dictionary<int, SubscribeResult> denied = [];
|
||||
List<int> allowed = [];
|
||||
ConstraintDenialSummary? denialSummary = null;
|
||||
for (int index = 0; index < itemHandles.Count; index++)
|
||||
{
|
||||
int itemHandle = itemHandles[index];
|
||||
@@ -754,8 +767,7 @@ public sealed class MxAccessGatewayService(
|
||||
continue;
|
||||
}
|
||||
|
||||
await constraintEnforcer.RecordDenialAsync(identity, command.Kind.ToString(), itemHandle.ToString(System.Globalization.CultureInfo.InvariantCulture), failure, correlationId, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
(denialSummary ??= new ConstraintDenialSummary(itemHandles.Count)).Add(itemHandle, failure);
|
||||
denied[index] = new SubscribeResult
|
||||
{
|
||||
ServerHandle = serverHandle,
|
||||
@@ -765,11 +777,17 @@ public sealed class MxAccessGatewayService(
|
||||
};
|
||||
}
|
||||
|
||||
if (denied.Count == 0)
|
||||
if (denialSummary is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// One aggregated audit event per bulk command, not one per denied item (see
|
||||
// ConstraintDenialSummary for why).
|
||||
await constraintEnforcer
|
||||
.RecordBulkDenialAsync(identity, command.Kind.ToString(), denialSummary, correlationId, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
MxCommand filtered = new() { Kind = command.Kind };
|
||||
AdviseItemBulkCommand payload = new() { ServerHandle = command.AdviseItemBulk.ServerHandle };
|
||||
payload.ItemHandles.Add(allowed);
|
||||
|
||||
Reference in New Issue
Block a user