Repository navigation
test(ci): isolate the login rate-limit bucket per test host - #876
Merged
Merged
Conversation
#840's collision is now measurable without being in the way. The probe's child trusts loopback as a proxy and the burst carries an X-Forwarded-For of its own, so the counter it reads is the one its own fourteen requests wrote instead of one shared with every other class on the machine. IsolatedLoginBucket is the helper. It goes through configuration, not PostConfigure<ForwardedHeadersOptions>, because Program.cs reads RateLimiting:TrustedProxies eagerly into a registration record and hands that snapshot to AddCluckworkEdgeSecurity - a PostConfigure runs after and is silently overwritten. It refuses a host that already declares its own trusted proxies: such a host is exercising #143's anti-spoof boundary, and overwriting its proxy list would delete the thing under test. Two things this turned up that are worth the commit message: A first cut minted buckets as 198.51.100.<a>.<b>. Five octets is not a valid IPv4 literal, ForwardedHeadersMiddleware rejects it and falls back to the socket peer, and every supposedly independent bucket silently collapsed onto loopback. Buckets are now TEST-NET-3 /32s. The burst bucket moved by 18 on 14 requests, in a Redis container private to this class. Nothing external could have written it, so the probe's own fixture spent in its own bucket before the burst ran - the same collision, one level down. That is why the assertion is >= and not ==.
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
ConfigureIsolatedLoginBucket, CreateIsolatedClient, DefaultClientIp and SynthesizedProxy had zero callers: the probe is a serving subprocess, so it sets RateLimiting__TrustedProxies__0 in its own environment and adds the header itself. Only ClientIpFor is used. Kept the guard and the two findings that cost the debugging cycles - the five-octet address that silently collapsed every bucket onto loopback, and the PostConfigure that Program.cs's eager snapshot overwrites - and dropped the comments restating them.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Gives the login rate-limit counter a bucket the probe can measure without the suite's own traffic in it. Follow-up to #866, which proved the collision but did not remove it from the way.
IsolatedLoginBucketmakes loopback a trusted proxy for one test host, soForwardedHeadersMiddlewarehonoursX-Forwarded-Forand the limiter keys on an address the test names instead of on the socket peer.Two findings from building it
A first cut minted buckets as
198.51.100.<a>.<b>. Five octets is not a valid IPv4 literal.ForwardedHeadersMiddlewarerejects it and falls back to the socket peer, so every "independent" bucket silently collapsed onto loopback — where the test's own exhaustion turned the next client's first request into a 429. It looked like isolation not working, not like a malformed address. Buckets are TEST-NET-3/32s now, and the helper says why that matters.The probe's own fixture spends against the probe's own bucket. The burst moved the counter by 18 on 14 requests, in a Redis container private to that class — nothing external could have written it.
CluckworkWebApplicationFactorybuilds its host in the constructor, before any test body runs, and the suite logs in constantly from loopback. So #840's collision reproduces one level down, inside a single class. That is why the assertion is>=and not==: an exact assertion here would have made the file red on a working mechanism.Why configuration and not
PostConfigure<ForwardedHeadersOptions>Program.cs:63readsRateLimiting:TrustedProxieseagerly into a registration record and hands that snapshot toAddCluckworkEdgeSecurity, which clearsKnownIPNetworksand rebuilds them from it. APostConfigureruns afterwards and is silently overwritten — a helper written that way looks correct and does nothing at runtime.Guard
A host that already sets
RateLimiting:TrustedProxies:0is refused, not merged. Such a host is exercising #143's anti-spoof boundary; overwriting its proxy list would delete the thing under test while leaving the test green.Verification
Executed, 32/32 across every class that touches the login limiter or the forwarded-header trust decision:
RateLimitingTests,AuthRateLimitLoggingTests,DistributedRateLimiterWiringTests,MultiInstanceRateLimitTests,LoginCounterKeyProbeTests,SchemaDocsTests— 20ForwardedHeaderTrustKestrelTests,SecurityHeadersTests,KestrelRequestBodyLimitTests— 12The second group matters most: those are the anti-spoof tests, and they still pass because they configure their own proxies and therefore never enter this helper.
Not in this PR
The collision is still there for the ~102 classes that log in through the shared factory. This isolates the probe. Extending it to the suite is the next step and should be its own PR, because it means every factory opting in and each one is a place to get the trust decision wrong.
Does not close #840. No flake has been shown to stop flaking yet — that needs repetition, not reasoning.