Skip to content

feat: add acl plugin#13349

Open
AlinsRan wants to merge 10 commits into
apache:masterfrom
AlinsRan:feat/acl-plugin
Open

feat: add acl plugin#13349
AlinsRan wants to merge 10 commits into
apache:masterfrom
AlinsRan:feat/acl-plugin

Conversation

@AlinsRan
Copy link
Copy Markdown
Contributor

@AlinsRan AlinsRan commented May 9, 2026

Summary

Add the acl plugin, which provides label-based access control for API routes.

Motivation

APISIX currently lacks a native mechanism to restrict API access based on consumer labels or external user attributes. Teams often use labels to represent organizational roles (e.g. team: platform, role: admin) or subscription tiers, and need a simple way to enforce access policies without writing custom plugins.

The acl plugin fills this gap by allowing operators to define allow/deny rules directly on labels — no code required.

Description

The acl plugin checks consumer labels (from APISIX Consumers) or external user attributes (from authentication plugins that set ctx.external_user) against configured allow or deny lists.

Evaluation order: when both allow_labels and deny_labels are configured, deny_labels is checked first. A request is rejected if any deny rule matches, or if no allow rule matches.

Authentication requirement: the request must be authenticated (consumer or external user). Unauthenticated requests return 401.

Label value formats

The plugin supports three formats for label values, with automatic detection when no parser is configured:

Format Example value Description
table ["cloud","infra"] (Lua array) Native APISIX consumer labels
json [cloud,infra] (JSON string) JWT/OIDC claims encoded as JSON arrays
segmented_text "cloud,infra" Delimiter-separated string, requires external_user_label_field_separator

Scenarios

Scenario 1: Consumer label-based allow list (key-auth)

Restrict an API to consumers belonging to the platform team.

Consumer alice  labels: {team: platform}  → allowed
Consumer bob    labels: {team: sales}     → 403

Plugin config:

{
  "acl": {
    "allow_labels": { "team": ["platform"] }
  }
}

Scenario 2: Consumer label-based deny list

Block guest-role consumers while allowing all others.

Consumer carol  labels: {role: guest}  → 403
Consumer dave   labels: {role: admin}  → allowed

Plugin config:

{
  "acl": {
    "deny_labels": { "role": ["guest"] }
  }
}

Scenario 3: External user (JWT/OIDC) with JSON array claim

JWT payload contains "groups": "[\"cloud\",\"infra\"]" (JSON-encoded string). Extract and match against allow list.

JWT claim: { "groups": "[\"cloud\",\"infra\"]" }

Plugin config:

{
  "acl": {
    "allow_labels": { "groups": ["cloud"] },
    "external_user_label_field": "groups",
    "external_user_label_field_parser": "json"
  }
}

Scenario 4: External user with delimiter-separated claim

JWT payload contains "groups": "cloud,infra" (comma-separated text).

JWT claim: { "groups": "cloud,infra" }

Plugin config:

{
  "acl": {
    "allow_labels": { "groups": ["cloud"] },
    "external_user_label_field": "groups",
    "external_user_label_field_parser": "segmented_text",
    "external_user_label_field_separator": ","
  }
}

Scenario 5: Nested field extraction with JSONPath

JWT payload has a nested structure; use a JSONPath expression to extract the value.

JWT payload: { "user": { "roles": ["admin", "editor"] } }

Plugin config:

{
  "acl": {
    "allow_labels": { "roles": ["admin"] },
    "external_user_label_field": "$..roles",
    "external_user_label_field_key": "roles"
  }
}

Plugin Attributes

Name Type Required Default Description
allow_labels object False* Labels to allow. At least one of allow_labels or deny_labels is required.
deny_labels object False* Labels to deny. Evaluated before allow_labels.
rejected_code integer False 403 HTTP status code on rejection.
rejected_msg string False Custom rejection message.
external_user_label_field string False groups JSONPath or plain field name to extract from ctx.external_user.
external_user_label_field_key string False Label key name for the extracted value. Defaults to external_user_label_field.
external_user_label_field_parser string False segmented_text, json, or table. Auto-detected if not set.
external_user_label_field_separator string False Regex separator for segmented_text parser. Required when parser is segmented_text.

Changes

  • apisix/plugins/acl.lua: Plugin implementation (priority 2410)
  • t/plugin/acl.t: 55 test cases for consumer label-based ACL
  • t/plugin/acl2.t: Test cases for ctx.external_user based ACL
  • docs/en/latest/plugins/acl.md: English documentation
  • docs/zh/latest/plugins/acl.md: Chinese documentation
  • conf/config.yaml.example: Register plugin at priority 2410
  • apisix/cli/config.lua: Add to default plugin list

The acl plugin provides label-based access control for API routes. It works
with APISIX consumers by checking their labels, supporting three label value
formats: table, JSON array, and separator-delimited text.

Access can be controlled using allow_labels (allowlist) or deny_labels
(denylist), with customizable rejection codes and messages.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dosubot dosubot Bot added size:XXL This PR changes 1000+ lines, ignoring generated files. doc Documentation things enhancement New feature or request labels May 9, 2026
AlinsRan and others added 4 commits May 9, 2026 17:42
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The upstream core/log.lua does not add function name prefix to log messages
(unlike EE which uses debug.getinfo). Remove the 'extra_values_with_parser():'
and 'extra_values_without_parser():' prefixes from error_log assertions.

Also convert TEST 36's --- error_log eval + qr/\Q...\E/ back to plain
--- error_log since Test::Nginx already applies \Q\E quoting internally
for literal string matching.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@AlinsRan AlinsRan requested a review from Copilot May 13, 2026 08:06
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds a new acl plugin to enforce label-based access control on routes, supporting both APISIX consumer labels and externally-provided user attributes (ctx.external_user).

Changes:

  • Implement apisix/plugins/acl.lua with allow/deny label logic and optional extraction/parsing from ctx.external_user via JSONPath.
  • Add extensive test coverage for consumer-label ACL (t/plugin/acl.t) and a smaller suite for route behavior (t/plugin/acl2.t).
  • Register and document the plugin across configs and docs (EN/ZH), including default plugin lists.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
t/plugin/acl2.t Adds basic route-level ACL validation tests (auth + allow list).
t/plugin/acl.t Adds comprehensive ACL test matrix for consumer labels + external_user parsing/schema cases.
t/admin/plugins.t Registers acl in plugin listing tests.
docs/zh/latest/plugins/acl.md Introduces Chinese documentation for acl plugin usage and config.
docs/zh/latest/config.json Adds plugins/acl to Chinese docs nav/config.
docs/en/latest/plugins/acl.md Introduces English documentation for acl plugin usage and config.
docs/en/latest/config.json Adds plugins/acl to English docs nav/config.
conf/config.yaml.example Registers acl plugin at priority 2410 in example config.
apisix/plugins/acl.lua New plugin implementation (schema, label matching, external_user extraction).
apisix/cli/config.lua Adds acl to the default plugin list.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread apisix/plugins/acl.lua
Comment thread apisix/plugins/acl.lua Outdated
Comment thread apisix/plugins/acl.lua
Comment thread apisix/plugins/acl.lua
Comment thread t/plugin/acl.t
Comment thread t/plugin/acl.t Outdated
Comment thread t/plugin/acl.t
Comment thread t/plugin/acl.t
Comment thread t/plugin/acl.t Outdated
Comment thread docs/en/latest/plugins/acl.md Outdated
- Fix trailing whitespace in t/plugin/acl.t line 877 (eclint failure)
- Fix TEST 36 error_log pattern: use eval qr// to properly escape regex
  metacharacters ([, (, )) in the expected PCRE error message
- Rename extra_values_* -> extract_values_* for clarity
- Change core.log.info -> core.log.debug for label logging to avoid
  leaking sensitive user/tenant attributes
- Only apply external_user parser/sep config for ctx.external_user,
  not ctx.consumer, to prevent consumer label matching breakage
- Fix TEST 51/52: change external_user_label_field_key to
  external_user_label_field_separator in test configs and expected
  response bodies so they actually test the separator field validation
- Fix typo in comment: 'dose' -> 'does'
- Clarify docs: external_user_label_field accepts JSONPath or plain
  field name (both English and Chinese docs)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
AlinsRan and others added 2 commits May 14, 2026 03:45
The PCRE error message format differs between PCRE1 ('pcre_compile() failed:
missing ) in ...') and PCRE2 ('pcre2_compile() failed: missing closing
parenthesis'). Match only the code-generated prefix which is stable across
all versions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
nic-6443
nic-6443 previously approved these changes May 14, 2026
…_schema

Invalid JSONPath expressions are now rejected at config time via
jp.parse(). This prevents misconfigured routes from silently allowing
all requests through when using deny-only ACL policies.

Add TEST 53: schema rejects invalid JSONPath in external_user_label_field.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@AlinsRan AlinsRan dismissed stale reviews from nic-6443 and shreemaan-abhishek via 9eb9081 May 15, 2026 07:17
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

doc Documentation things enhancement New feature or request size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants