feat: add acl plugin#13349
Open
AlinsRan wants to merge 10 commits into
Open
Conversation
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>
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>
shreemaan-abhishek
previously approved these changes
May 13, 2026
There was a problem hiding this comment.
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.luawith allow/deny label logic and optional extraction/parsing fromctx.external_uservia 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.
- 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>
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>
shreemaan-abhishek
previously approved these changes
May 14, 2026
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>
9eb9081
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
nic-6443
approved these changes
May 18, 2026
membphis
approved these changes
May 18, 2026
shreemaan-abhishek
approved these changes
May 19, 2026
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.
Summary
Add the
aclplugin, 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
aclplugin fills this gap by allowing operators to define allow/deny rules directly on labels — no code required.Description
The
aclplugin checks consumer labels (from APISIX Consumers) or external user attributes (from authentication plugins that setctx.external_user) against configured allow or deny lists.Evaluation order: when both
allow_labelsanddeny_labelsare configured,deny_labelsis 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:
table["cloud","infra"](Lua array)json[cloud,infra](JSON string)segmented_text"cloud,infra"external_user_label_field_separatorScenarios
Scenario 1: Consumer label-based allow list (key-auth)
Restrict an API to consumers belonging to the
platformteam.Plugin config:
{ "acl": { "allow_labels": { "team": ["platform"] } } }Scenario 2: Consumer label-based deny list
Block guest-role consumers while allowing all others.
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.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).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.
Plugin config:
{ "acl": { "allow_labels": { "roles": ["admin"] }, "external_user_label_field": "$..roles", "external_user_label_field_key": "roles" } }Plugin Attributes
allow_labelsallow_labelsordeny_labelsis required.deny_labelsallow_labels.rejected_coderejected_msgexternal_user_label_fieldgroupsctx.external_user.external_user_label_field_keyexternal_user_label_field.external_user_label_field_parsersegmented_text,json, ortable. Auto-detected if not set.external_user_label_field_separatorsegmented_textparser. Required when parser issegmented_text.Changes
apisix/plugins/acl.lua: Plugin implementation (priority 2410)t/plugin/acl.t: 55 test cases for consumer label-based ACLt/plugin/acl2.t: Test cases forctx.external_userbased ACLdocs/en/latest/plugins/acl.md: English documentationdocs/zh/latest/plugins/acl.md: Chinese documentationconf/config.yaml.example: Register plugin at priority 2410apisix/cli/config.lua: Add to default plugin list