Skip to content

Add ACL key expression templates bound to client identity (${cert_common_name}, ${username}) - #2662

Open
cmeng-gao wants to merge 1 commit into
eclipse-zenoh:mainfrom
opkit:feat/acl-keyexpr-templates
Open

Add ACL key expression templates bound to client identity (${cert_common_name}, ${username})#2662
cmeng-gao wants to merge 1 commit into
eclipse-zenoh:mainfrom
opkit:feat/acl-keyexpr-templates

Conversation

@cmeng-gao

@cmeng-gao cmeng-gao commented Jul 3, 2026

Copy link
Copy Markdown

Description

What does this PR do?

Allows ACL rules to define key_expr_templates: plain-string key expressions containing placeholders that are expanded per connection from the authenticated identity of the remote instance, at transport establishment. Supported placeholders:

  • ${cert_common_name} — the TLS/QUIC certificate common name
  • ${username} — the user/password-authenticated username

Subjects additionally gain cert_common_name_prefixes for prefix-based matching.

With a single static rule, every client is confined to its own key space:

access_control: {
  enabled: true,
  default_permission: "deny",
  rules: [
    {
      id: "tenant-own-namespace",
      permission: "allow",
      flows: ["ingress", "egress"],
      messages: ["put", "delete", "declare_subscriber", "declare_queryable", "query", "reply"],
      key_expr_templates: ["tenant/${cert_common_name}/**"],
    },
  ],
  subjects: [
    { id: "tenants", cert_common_name_prefixes: ["t"] },
  ],
  policies: [
    { rules: ["tenant-own-namespace"], subjects: ["tenants"] },
  ],
}

A client presenting a certificate with CN t42 can then only use tenant/t42/**.

Why is this change needed?

In multi-tenant deployments (robot fleets, simulation platforms, sandboxed workloads), tenants are created and destroyed dynamically. Today each tenant requires an explicit rule/subject/policy triple (cert_common_names is exact-match only, and key_exprs cannot reference the connection identity), and since ACL config cannot be updated at runtime, every new tenant means editing the config and restarting the router — disrupting all connected sessions.

With identity-bound templates, one static config covers an unlimited number of dynamically created tenants: no config edits, no restarts. This is in line with what MQTT brokers offer for the same problem (mosquitto's pattern ... %u, EMQX's ${username}/${clientid}).

Related Issues

Closes #2659. #1432 previously proposed trust-based authorization (permissions embedded in certificates/tokens) for similar scalability reasons and was closed without implementation; this is a much lighter alternative that covers the multi-tenant isolation case within the existing ACL model.

Implementation details

  • Expansion point: subject matching already happens per-transport in AclEnforcer::new_transport_unicast, where the peer's authenticated identity is available. Templates are expanded there into per-connection policies, reusing the existing KeBoxTree machinery.
  • Evaluation semantics: template rules are evaluated as part of their subject — a subject's effective policy is the union of its static rules and its expanded template rules, with the usual precedence (explicit deny > explicit allow > default permission). Evaluating the expanded policy as a separate synthetic subject would let a subject's own default fallback override its deny-templates under default_permission: allow; union semantics keep behavior identical to the equivalent static configuration (covered by a dedicated regression test).
  • Validation / failure behavior:
    • substituted values must be usable as (part of) a single key-expression chunk (no /, *, $, ?, #), otherwise the rule is skipped for that connection;
    • a template referencing an identity the connection does not have (e.g. ${username} on a TLS-only connection) is skipped for that connection, falling back to static rules / default permission;
    • malformed templates — including misspelled placeholders — are rejected at config load time via probe expansion ($ is reserved in key expressions, so any unsubstituted ${...} fails validation).
  • Config shape: key_exprs becomes optional on a rule; at least one of key_exprs / key_expr_templates must be provided, and both can coexist. cert_common_name_prefixes is a separate subject field (rather than glob syntax inside cert_common_names) to avoid silently changing the semantics of existing exact-match configs.
  • Backward compatibility / feature gating: all new config fields are optional and off by default; configurations that don't use them behave exactly as before. zenoh's public API is unchanged (AclConfigRule is not re-exported). Happy to gate the new fields behind unstable if preferred.
  • Performance impact: template expansion happens once per transport at establishment (only when template rules are configured and the subject matches); the per-connection policy is a regular KeBoxTree consulted through the same cached decision path as static rules. Configurations without template rules take the exact same code path as before.
  • Documentation: new fields documented in DEFAULT_CONFIG.json5 (with a worked example) and in the zenoh-config doc comments.

Testing

  • Unit tests: template expansion (both placeholders, combined, multiple occurrences), unsafe-substitution rejection, missing-identity rejection, unknown-placeholder rejection at config time, prefix subject matching.
  • New end-to-end suite zenoh/tests/acl_template.rs over TLS, QUIC and user/password transports: tenant isolation in both directions (cross-tenant subscribe and publish blocked at the router), prefix-unmatched fallback to default deny, deny-template under default allow (regression for the union semantics).
  • All existing acl and authentication test suites pass unchanged.

🤖 Generated with Claude Code


🏷️ Label-Based Checklist

Based on the labels applied to this PR, please complete these additional requirements:

Labels: new feature

🆕 New Feature Requirements

Since this PR adds a new feature:

  • Feature scope documented - Clear description of what the feature does and why it's needed
  • Minimum necessary code - Implementation is as simple as possible, doesn't overcomplicate the system
  • New APIs well-designed - Public APIs are intuitive, consistent with existing APIs
  • Comprehensive tests - All functionality is tested (happy path + edge cases + error cases)
  • Examples provided - Usage examples in code comments or separate example files
  • Documentation added - New docs explaining the feature, its use cases, and API
  • Feature flag considered - Can the feature be enabled/disabled for gradual rollout?
  • Performance impact assessed - Memory, CPU, storage implications measured
  • Integration tested - Feature works with existing features

Consider: Can this feature be split into smaller, incremental PRs?

Instructions:

  1. Check off items as you complete them (change - [ ] to - [x])
  2. The PR checklist CI will verify these are completed

This checklist updates automatically when labels change, but preserves your checked boxes.

Allow ACL rules to define key_expr_templates with placeholders
expanded per-connection from the authenticated identity of the remote
instance: ${cert_common_name} (TLS/QUIC certificate common name) and
${username} (user/password authentication). Subjects gain
cert_common_name_prefixes for prefix-based matching. A single static
rule such as "tenant/${cert_common_name}/**" then confines every
client to its own key space, covering dynamically created tenants
without config edits or router restarts.

Templates are expanded in AclEnforcer::new_transport_unicast into
per-connection policies, evaluated as part of their subject: a
subject's effective policy is the union of its static rules and its
expanded template rules, with the usual precedence (explicit deny >
explicit allow > default permission).

Validation and failure behavior:
- substituted values must be usable as (part of) a single key
  expression chunk (no '/', '*', '$', '?', '#'), otherwise the rule
  is skipped for that connection;
- a template referencing an identity the connection does not have is
  skipped for that connection, falling back to static rules and the
  default permission;
- malformed templates, including misspelled placeholders, are
  rejected at config load time via probe expansion.

Fully backward compatible: both new config fields are optional, and
configurations that do not use them behave exactly as before.
key_exprs becomes optional on a rule; at least one of key_exprs and
key_expr_templates must be provided.

Covered by unit tests and end-to-end tests over TLS, QUIC and
user/password transports (tenant isolation in both directions,
prefix-unmatched fallback to default deny, deny-template under
default allow).

Closes eclipse-zenoh#2659

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: chunmeng.gao <gaochunmeng@gmail.com>
@cmeng-gao

Copy link
Copy Markdown
Author

For the label check: this PR is a new feature (implements #2659). Could a maintainer add the new feature label? Thanks!

@diogomatsubara diogomatsubara added the new feature Something new is needed label Jul 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

new feature Something new is needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ACL: templated key expressions bound to client identity (${cert_common_name}, ${username}) for dynamic multi-tenant isolation

2 participants