[rust-server] Restrict from_headers matches to the intended auth scheme - #24607
[rust-server] Restrict from_headers matches to the intended auth scheme#24607twistali wants to merge 2 commits into
Conversation
|
This fixes the issue here, but the untyped from_headers still seems like a footgun for anyone hand-rolling swagger-rs 7.x. Worth an issue against Metaswitch/swagger-rs too? |
|
@wing328 circle CI looks unrelated. Are there issues occurring on that CI system? |
|
Two potential testing gaps to consider. There's no fixture proving Basic+Bearer coexist without swallowing each other (only OAuth+Basic is tested), and the assertions are string-exact rather than runtime tested. A request-level test through AddContext::call would prove the actual fallthrough behavior as |
|
for circleci failures, please ignore those for the time being |
Add a fixture pairing HTTP Basic with Bearer (the untested isBasicBearer section) ahead of an apiKey scheme, plus a runtime test through AddContext::call in the petstore sample.
There was a problem hiding this comment.
1 issue found across 3 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="modules/openapi-generator/src/test/java/org/openapitools/codegen/rust/RustServerCodegenTest.java">
<violation number="1" location="modules/openapi-generator/src/test/java/org/openapitools/codegen/rust/RustServerCodegenTest.java:300">
P2: The Basic+Bearer coexistence case is only validated by string-exact assertions in this Java test; there is no runtime regression test proving the `isBasicBasic` and `isBasicBearer` blocks (the two HTTP schemes that both read the Authorization header) fall through to each other's credentials. The added petstore runtime tests cover OAuth(Bearer)+Basic where the bearer block is generated first, so they don't exercise a Basic block preceding a Bearer block. Since this is precisely the pairing the PR fixes, adding a request-level runtime test for it (mirroring the fallthrough assertions already used here) would close the gap.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
|
|
||
| // Each Authorization-based block must be restricted to its own scheme... | ||
| TestUtils.assertFileContains(contextPath, basicBlock); | ||
| TestUtils.assertFileContains(contextPath, bearerBlock); |
There was a problem hiding this comment.
P2: The Basic+Bearer coexistence case is only validated by string-exact assertions in this Java test; there is no runtime regression test proving the isBasicBasic and isBasicBearer blocks (the two HTTP schemes that both read the Authorization header) fall through to each other's credentials. The added petstore runtime tests cover OAuth(Bearer)+Basic where the bearer block is generated first, so they don't exercise a Basic block preceding a Bearer block. Since this is precisely the pairing the PR fixes, adding a request-level runtime test for it (mirroring the fallthrough assertions already used here) would close the gap.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/test/java/org/openapitools/codegen/rust/RustServerCodegenTest.java, line 300:
<comment>The Basic+Bearer coexistence case is only validated by string-exact assertions in this Java test; there is no runtime regression test proving the `isBasicBasic` and `isBasicBearer` blocks (the two HTTP schemes that both read the Authorization header) fall through to each other's credentials. The added petstore runtime tests cover OAuth(Bearer)+Basic where the bearer block is generated first, so they don't exercise a Basic block preceding a Bearer block. Since this is precisely the pairing the PR fixes, adding a request-level runtime test for it (mirroring the fallthrough assertions already used here) would close the gap.</comment>
<file context>
@@ -259,4 +260,60 @@ public void testAuthSchemeBlocksOnlyMatchTheirOwnScheme() throws IOException {
+
+ // Each Authorization-based block must be restricted to its own scheme...
+ TestUtils.assertFileContains(contextPath, basicBlock);
+ TestUtils.assertFileContains(contextPath, bearerBlock);
+ TestUtils.assertFileNotContains(contextPath,
+ "if let Some(auth) = swagger::auth::from_headers(headers) {");
</file context>
Fixes #24095
Problem
The
rust-servercontext.mustachetemplate emits one block per security scheme, each doing an earlyreturnon match. TheAuthorization-header blocks callswagger::auth::from_headers(headers)and bind the result unconditionally:Since swagger-rs 7,
from_headersis no longer scheme-typed. It returnsOption<AuthData>and matches either scheme (swagger-7.0.1src/auth.rs:216):So a
Basic-only block swallowsBearerrequests (and vice versa) and returns immediately, making every later security scheme block unreachable — including in-headerapiKeyblocks.Impact: for any spec where an
Authorization-header scheme precedes an in-headerapiKeyscheme, a request carryingAuthorization: Bearer …is authorized via the wrong scheme and the API key / client certificate is never evaluated. Where the mismatched path has a permissive fallback (e.g.AllowAllAuthenticatorwhen OAuth is unconfigured) this is a silent authorization bypass. This is a regression from the swagger 5/6 typedfrom_headers::<Basic>(headers)form.Fix
Option B from the bug ticket — restrict each block's pattern to the variant it was generated for, so a non-matching header falls through to the next block instead of being consumed:
AuthDatais already imported by the template, so no new imports are needed, and no change to swagger-rs is required — this fixes every 7.x consumer immediately.Note this fixes
isOAuthandisBasicBeareras well as the reportedisBasicBasic: aBasicheader could equally be swallowed by a Bearer/OAuth block.Behaviour change
Worth calling out explicitly: on an API declaring only Basic auth, a request with
Authorization: Bearer …previously reached the authenticator asAuthData::Bearer; it now falls through tocontext.push(None::<AuthData>)and is treated as unauthenticated (and symmetrically for aBasicheader on a Bearer/OAuth-only API). That is the intended security fix, but it is a semantic change for anyone relying on the permissive behaviour. Happy to retarget if maintainers consider this breaking.Not addressed (pre-existing, out of scope): an in-header
apiKeyscheme whose header name is literallyAuthorizationwould still collide.Changes
modules/openapi-generator/src/main/resources/rust-server/context.mustache— scheme-restricted patterns for theisBasicBasic,isBasicBearerandisOAuthblocks.RustServerCodegenTest.testAuthSchemeBlocksOnlyMatchTheirOwnScheme— new regression test asserting both the correct forms and, viaassertFileNotContains, the absence of the unrestricted forms; also asserts the in-headerapiKeyblock is still generated.openapi-v3,petstore-with-fake-endpoints-models-for-testing,ping-bearer-auth.Testing
./mvnw clean package— BUILD SUCCESS, full test suite green../bin/generate-samples.sh bin/configs/rust-server*.yaml— 7/7 generators succeeded, no sample drift.mvn -pl modules/openapi-generator -am test -Dtest=RustServerCodegenTest— 7/7 pass.context.mustachealone makes it fail withdoes not contain line [if let Some(bearer @ AuthData::Bearer(..)) = …].cargo check --all-featureson the regeneratedpetstore-with-fake-endpoints-models-for-testingsample compiles clean.rust-serversample retains an unrestricted= swagger::auth::from_headers(headers), and thatcontext.mustacheis the only template referencingfrom_headers.CC @frol @farcaller @richardwhiuk @paladinzh @jacob-pro @dsteeley
PR checklist
Commit all changed files.
This is important, as CI jobs will verify all generator outputs of your HEAD commit as it would merge with master.
These must match the expectations made by your contribution.
You may regenerate an individual generator by passing the relevant config(s) as an argument to the script, for example
./bin/generate-samples.sh bin/configs/java*.IMPORTANT: Do NOT purge/delete any folders/files (e.g. tests) when regenerating the samples as manually written tests may be removed.
Summary by cubic
Restricts
rust-serverauth handling to match only the intended scheme (Basic vs Bearer) so cross-scheme matches no longer short-circuit later auth blocks like headerapiKey(fixes #24095).Bug Fixes
context.mustache:AuthData::Basic(..)for Basic andAuthData::Bearer(..)for Bearer/OAuth.apiKeyreachable.apiKey, and a runtimeAddContexttest validating scheme precedence andapiKeyreachability; regenerate Rust server samples.Migration
Authorizationheaders on single-scheme APIs now fall through as unauthenticated.swagger-rs7.x generated servers.Written for commit caa2250. Summary will update on new commits.