Skip to content

feat(access_control): Implement scope validation for per-resource authorization - #13708

Open
eveniota wants to merge 8 commits into
apache:masterfrom
eveniota:access-control/scope-claim-validation
Open

eveniota wants to merge 8 commits into
apache:masterfrom
eveniota:access-control/scope-claim-validation

Conversation

@eveniota

Copy link
Copy Markdown

Fixes #13607

Summary

The access_control plugin previously parsed and exposed a scope claim and supported the --invalid-scope-status-code configuration option, but AccessToken::validate() never evaluated the scope against incoming request paths. Consequently, any valid token authorized access to every resource covered by a remap rule, and setting --invalid-scope-status-code produced no effect.
This PR finishes the feature by implementing path-prefix scope validation on normalized path segments, providing per-resource granularity while remaining fully backward compatible with existing tokens.

Design Approach (per #13607 suggestion)

This PR implements the minimal backward-compatible approach:

  1. Path-prefix matching on normalized segments
  2. Absent/empty scope = unrestricted (backward compatible)
  3. Scope check in separate function after token validation

These choices are open to feedback; see Design Decisions section below.

Key Requirements & Design Decisions

  1. Clean Separation of Concerns (Plumbing):

    • Kept AccessToken::validate() focused purely on token cryptographic integrity, semantics, and timing.
    • Implemented scope comparison as a separate helper function, validateScope(requestPath, scope), called in enforceAccessControl() immediately after token->validate() succeeds.
    • When out of scope, the transaction state is set to OUT_OF_SCOPE, correctly triggering --invalid-scope-status-code (default: 403) and suppressing subject header extraction.
  2. Matching Semantics (Normalized Path Segments):

    • Matching is performed as a path-prefix on normalized segments.
    • Handles trailing slashes, redundant slashes, and ATS's TSUrlPathGet() format (which omits leading slashes).
    • Enforces directory segment boundaries so that a token scoped to /reports/ (or /reports) authorizes /reports/2026/ but strictly rejects sibling paths like /reports2/ or /reports_backup.
  3. Backward Compatibility (Absent / Empty Scope):

    • An absent or empty scope claim is treated as unrestricted. Existing tokens in the field continue to function without disruption.
  4. Resource Granularity vs Target Audience (sub claim):

    • Retains the existing model where sub represents target audience (e.g. frogs-in-a-well), while adding the missing per-resource granularity (e.g. scope="/reports/2026/").

What Changed

  • Core Matching Logic (access_control.cc, access_control.h):

    • Declared and implemented validateScope(StringView requestPath, StringView scope).
    • Added helper normalizePath() to ensure leading slashes, collapse consecutive slashes, and normalize trailing slashes.
    • Updated @todo comments regarding scope validation.
  • Transaction Enforcement (plugin.cc):

    • Inside enforceAccessControl(), extracted the request path via TSUrlPathGet() and validated it against token->getScope().
    • On failure, sets data->_vaState = OUT_OF_SCOPE and invokes handleInvalidToken().
  • Unit Tests (test_access_control.cc):

    • Added Catch2 test cases covering:
      • Empty scope (unrestricted / backward compatibility)
      • Exact match (/reports $\rightarrow$ /reports)
      • Subpath matches (/reports $\rightarrow$ /reports/2026/annual.pdf)
      • Sibling segment boundary enforcement (/reports vs /reports2 $\rightarrow$ rejected)
      • Inverted hierarchy / parent path requests with child scope $\rightarrow$ rejected
      • Normalization edge cases (trailing slashes, redundant slashes, root scope "/")
      • Token builder & parser integration with addScope() / getScope()
    • Documentation (access_control.en.rst):
      • Replaced the note stating scope is "ignored by the current version of the plugin" with full documentation of the matching semantics, segment boundary rules, and status code behavior.

Future Work

  • Pattern matching (e.g., /reports/{year}/)
  • Scope lists or disjunctions
  • Host-qualified scopes (e.g., example.com:/reports/)
  • Query parameter scoping

These can be added in follow-ups once the core prefix matching is proven.

Verification

  • Catch2 unit tests pass: ./build/plugins/experimental/access_control/unit_tests/test_access_control
  • Code formatted via cmake --build build -t format

Copilot AI lite review requested due to automatic review settings September 19, 2026 09:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Documentation and API wording review comments remain unresolved.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 2 Low severity

Open (2)
What changed in this PR

Implements per-resource path-scope validation for the experimental access_control plugin.

Changes:

  • Adds normalized path-prefix matching with backward-compatible empty scopes.
  • Enforces scope checks during authorization.
  • Adds unit tests and updates documentation.
File Description
plugins/​experimental/​access_control/​unit_tests/​test_access_control.cc Scope matching and integration tests
plugins/​experimental/​access_control/​plugin.cc Scope enforcement integration
plugins/​experimental/​access_control/​access_control.h Scope validation API
plugins/​experimental/​access_control/​access_control.cc Path normalization and validation
doc/​admin-guide/​plugins/​access_control.en.rst Scope behavior documentation

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread doc/admin-guide/plugins/access_control.en.rst Outdated
Comment on lines +111 to +113
* Validates whether a request path fails within the scope claim of an access token.
* Matching is performed on normalized path segments. An empty or absent scope is
* treated as unrestricted (returns true).
Clarify scope usage in access control documentation.

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings September 21, 2026 11:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Address the traversal vulnerability and add the requested enforcement-path test coverage.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 High severity · 1 Low severity

Open (2)
Resolved since last review (1)

Comment thread plugins/experimental/access_control/access_control.cc Outdated
@JosiahWI JosiahWI added this to the 11.0.0 milestone Sep 21, 2026
Copilot AI review requested due to automatic review settings September 21, 2026 14:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Copilot review overview

Review effort: Lite
Findings: 2 High severity · 1 Low severity

Open (3)

Comment thread plugins/experimental/access_control/access_control.cc Outdated
Copilot AI review requested due to automatic review settings September 21, 2026 16:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Copilot review overview

Review effort: Lite
Findings: 3 High severity · 1 Low severity

Open (4)

Comment thread plugins/experimental/access_control/access_control.cc Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings September 22, 2026 02:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Critical path-decoding and scope-canonicalization issues remain unresolved.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 3 High severity · 1 Low severity

Open (4)
Resolved since last review (3)
Previously missed (1)

In code that hasn't changed since last review

Low severity Add integration coverage for scoped access enforcement

plugins/​experimental/​access_control/​plugin.cc:539

The new enforcement branch is only exercised through direct validateScope() unit calls; the existing access-control AuTest does not send a scoped token through enforceAccessControl() or verify the configured out-of-scope status and subject-header suppression. Add an integration case for an in-scope and out-of-scope request so this transaction-level plumbing is protected.

}

String decoded(path.size(), '\0');
size_t decodedLen = urlDecode(path.data(), path.size(), decoded.data(), decoded.size());
}

String decoded(path.size(), '\0');
size_t decodedLen = urlDecode(path.data(), path.size(), decoded.data(), decoded.size());
Comment on lines +525 to +528
String normRequestPath = normalizePath(requestPath);
String normScope = normalizePath(scope);
if (normScope == "/") {
return true;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

access_control: implement the scope claim so a token authorizes only its own resources

3 participants