ACL Configuration Guide¶
Configure access control rules between modules.
1. Overview¶
ACL (Access Control List) is used to control invocation permissions between modules, preventing unauthorized module calls.
Core Concepts:
| Concept | Description |
|---|---|
| Callers | List of caller module ID patterns |
| Targets | List of target module ID patterns |
| Effect | Allow or deny |
| Wildcards | * matches any characters, executor.* matches all modules starting with executor. |
2. Quick Start¶
2.1 Create Configuration File¶
# acl/global_acl.yaml
rules:
# Allow orchestrator.* to call executor.*
- callers: ["orchestrator.*"]
targets: ["executor.*"]
effect: allow
# Allow all calls by default
- callers: ["*"]
targets: ["*"]
effect: allow
2.2 Load and Use¶
from apcore import Registry, Executor, ACL
registry = Registry(extensions_dir="./extensions")
registry.discover()
# Load ACL
acl = ACL.load("./acl/global_acl.yaml")
# Create Executor
executor = Executor(registry=registry, acl=acl)
# Permissions are automatically checked on invocation
result = executor.call(
module_id="executor.email.send_email",
inputs={...},
context=context
)
3. Configuration Format¶
3.1 Basic Structure¶
# acl/global_acl.yaml
version: "1.0" # Optional
# Default behavior (optional, defaults to deny)
default_effect: deny
# Rules list
rules:
- callers: ["<pattern>"]
targets: ["<pattern>"]
effect: allow | deny
description: "Optional rule description"
3.2 Rule Fields¶
| Field | Required | Description |
|---|---|---|
callers |
Yes | List of caller module ID patterns |
targets |
Yes | List of target module ID patterns |
effect |
Yes | allow or deny |
description |
No | Rule description |
4. Pattern Matching¶
4.1 Exact Match¶
rules:
# Only allow specific module to call specific module
- callers: ["orchestrator.user.register"]
targets: ["executor.email.send_email"]
effect: allow
4.2 Prefix Wildcards¶
rules:
# Allow all modules under orchestrator to call all modules under executor
- callers: ["orchestrator.*"]
targets: ["executor.*"]
effect: allow
# Allow calling all modules under common
- callers: ["*"]
targets: ["common.*"]
effect: allow
4.3 Multi-level Wildcards¶
rules:
# Match any depth
- callers: ["api.*"]
targets: ["executor.email.*"]
effect: allow
# api.handler.user_api → executor.email.send_email ✓
# api.v2.handler.user_api → executor.email.send_template ✓
4.4 Special Callers¶
rules:
# Top-level calls (no caller_id)
- callers: ["@external"]
targets: ["api.*"]
effect: allow
# System internal calls
- callers: ["@system"]
targets: ["internal.*"]
effect: allow
5. Common Configuration Patterns¶
5.1 Layered Architecture¶
# Typical three-tier architecture ACL
rules:
# API layer can call Orchestrator layer
- callers: ["api.*"]
targets: ["orchestrator.*"]
effect: allow
# Orchestrator layer can call Executor layer
- callers: ["orchestrator.*"]
targets: ["executor.*"]
effect: allow
# All layers can call Common
- callers: ["*"]
targets: ["common.*"]
effect: allow
# Prohibit cross-layer calls
- callers: ["api.*"]
targets: ["executor.*"]
effect: deny
description: "API cannot call Executor directly"
# Default deny
- callers: ["*"]
targets: ["*"]
effect: deny
5.2 Whitelist Mode¶
default_effect: deny
rules:
# Only allow specific call relationships
- callers: ["orchestrator.user.register"]
targets: ["executor.email.send_email"]
effect: allow
- callers: ["orchestrator.user.register"]
targets: ["executor.database.insert"]
effect: allow
- callers: ["orchestrator.order.create"]
targets: ["executor.payment.charge"]
effect: allow
5.3 Blacklist Mode¶
default_effect: deny
rules:
# Deny calling sensitive modules
- callers: ["*"]
targets: ["internal.admin.*"]
effect: deny
- callers: ["*"]
targets: ["internal.security.*"]
effect: deny
# Only admin modules can call
- callers: ["admin.*"]
targets: ["internal.admin.*"]
effect: allow
5.4 Environment Isolation¶
# Development environment allows more permissive calls
rules:
# Development tools
- callers: ["dev.*"]
targets: ["*"]
effect: allow
description: "Development environment: allow dev modules to call any module"
# Mock modules
- callers: ["*"]
targets: ["mock.*"]
effect: allow
description: "Allow calling mock modules"
6. Rule Priority¶
6.1 Matching Order¶
Rules are matched by priority (higher number first). For rules with the same priority, deny takes precedence over allow; if still tied, the rule defined earlier takes effect:
rules:
# Rule 1: Exact match (priority)
- callers: ["orchestrator.user.register"]
targets: ["executor.email.send_email"]
effect: allow
# Rule 2: Wildcard match
- callers: ["orchestrator.*"]
targets: ["executor.email.*"]
effect: deny
# Rule 3: Default rule
- callers: ["*"]
targets: ["*"]
effect: allow
Call: orchestrator.user.register → executor.email.send_email
Match: Rule 1 (allow, same priority)
Call: orchestrator.order.create → executor.email.send_email
Match: Rule 2 (deny, same priority)
Call: api.handler.test → common.util.format
Match: Rule 3 (allow)
6.2 Best Practices¶
rules:
# 1. Define exceptions first (exact match)
- callers: ["orchestrator.admin.audit"]
targets: ["internal.security.log"]
effect: allow
# 2. Then define general rules
- callers: ["*"]
targets: ["internal.*"]
effect: deny
# 3. Finally define default rule
- callers: ["*"]
targets: ["*"]
effect: allow
6.3 Matching Algorithm Visualization¶
Check: caller="api.handler.user" target="executor.email.send"
Rule 1: callers=["admin.*"] targets=["*"]
caller match: "admin.*" vs "api.handler.user" → ✗ no match
→ skip
Rule 2: callers=["api.*"] targets=["executor.*"]
caller match: "api.*" vs "api.handler.user" → ✓ match
target match: "executor.*" vs "executor.email.send" → ✓ match
→ hit! effect=allow
Result: allow
6.4 Edge Cases¶
| Scenario | Result | Description |
|---|---|---|
| No ACL file | Use default_effect |
Defaults to deny |
| Empty ACL file (no rules) | Use default_effect |
All calls handled by default policy |
| caller_id is null | Treated as @external |
External/top-level call |
| Module calls itself | Normal ACL check | No special handling |
Wildcard *.*.* |
Equivalent to * |
Matches all |
6.5 Performance Impact¶
| Number of Rules | Expected Latency | Recommendation |
|---|---|---|
| < 50 | < 0.1ms | No optimization needed |
| 50-500 | 0.1-1ms | Consider rule ordering optimization |
| > 500 | > 1ms | Consider using prefix tree indexing |
Performance Optimization Recommendations: - Put frequently matched rules at the beginning of the list - Use exact matches instead of wildcards (exact matching is faster) - Cache ACL check results in production (for same caller+target combinations)
7. Runtime Behavior¶
7.1 Check Timing¶
ACL is checked at the following times:
executor.call(module_id, inputs, context)
│
├─ 1. Get caller_id
│ └─ From context.caller_id
│ └─ If None, treated as @external
│
├─ 2. ACL check
│ └─ acl.check(caller_id, target_id)
│ └─ If denied, throw ACLDeniedError
│
└─ 3. Continue execution...
7.2 Error Handling¶
from apcore import ACLDeniedError
try:
result = executor.call(
module_id="internal.secret",
inputs={...},
context=context
)
except ACLDeniedError as e:
print(f"Access denied!")
print(f"Caller: {e.caller_id}")
print(f"Target: {e.target_id}")
print(f"Matched rule: {e.matched_rule}")
7.3 Debug Mode¶
from apcore import ACL
acl = ACL.load("./acl/global_acl.yaml")
# Enable debug mode
acl.debug = True
# All checks will output logs
# [ACL] Check: orchestrator.user.register → executor.email.send_email
# [ACL] Matched rule #1: allow
8. Dynamic ACL¶
8.1 Runtime Modification¶
from apcore import ACL, ACLRule
acl = ACL.load("./acl/global_acl.yaml")
# Add rule
acl.add_rule(ACLRule(
callers=["temp.module"],
targets=["executor.*"],
effect="allow"
))
# Remove rule
acl.remove_rule(callers=["temp.module"], targets=["executor.*"])
# Reload
acl.reload()
8.2 Dynamic Check Based on Context¶
class DynamicACL(ACL):
"""ACL with dynamic permission checking support"""
def check(self, caller_id: str, target_id: str, context: Context) -> bool:
# Check static rules first
if not super().check(caller_id, target_id, context):
return False
# Dynamic check: based on identity roles
if context.identity:
if "admin" in context.identity.roles:
return True # Admins can call any module
# Dynamic check: based on time
from datetime import datetime
hour = datetime.now().hour
if target_id.startswith("maintenance.") and not (2 <= hour <= 6):
return False # Maintenance modules only available at night
return True
9. Configuration Examples¶
9.1 Microservice Architecture¶
# acl/global_acl.yaml
version: "1.0"
default_effect: deny
rules:
# Gateway entry
- callers: ["@external"]
targets: ["gateway.*"]
effect: allow
description: "External can only access Gateway"
# Gateway → Service
- callers: ["gateway.*"]
targets: ["service.*"]
effect: allow
# Service → Repository
- callers: ["service.*"]
targets: ["repository.*"]
effect: allow
# Service → External API
- callers: ["service.*"]
targets: ["external.*"]
effect: allow
# Common utilities
- callers: ["*"]
targets: ["common.*"]
effect: allow
9.2 Multi-tenant Architecture¶
version: "1.0"
default_effect: deny
rules:
# Tenant isolation
- callers: ["tenant.a.*"]
targets: ["tenant.a.*"]
effect: allow
- callers: ["tenant.b.*"]
targets: ["tenant.b.*"]
effect: allow
# Shared services
- callers: ["tenant.*"]
targets: ["shared.*"]
effect: allow
# Admin console (can cross tenants)
- callers: ["admin.*"]
targets: ["*"]
effect: allow
9.3 Security-Sensitive System¶
version: "1.0"
default_effect: deny
rules:
# Public API
- callers: ["@external"]
targets: ["public.*"]
effect: allow
# Require authentication to access
- callers: ["auth.verified.*"]
targets: ["protected.*"]
effect: allow
# Sensitive operations require additional permissions
- callers: ["auth.admin.*"]
targets: ["sensitive.*"]
effect: allow
# Audit log (write-only, no read)
- callers: ["*"]
targets: ["audit.write"]
effect: allow
- callers: ["*"]
targets: ["audit.read"]
effect: deny
- callers: ["compliance.*"]
targets: ["audit.read"]
effect: allow
10. Troubleshooting¶
10.1 Common Issues¶
Issue 1: All calls are denied
# Error: No default allow rule
rules:
- callers: ["orchestrator.*"]
targets: ["executor.*"]
effect: allow
# Missing default rule, all other calls will be denied
# Fix: Add default rule
rules:
- callers: ["orchestrator.*"]
targets: ["executor.*"]
effect: allow
- callers: ["*"]
targets: ["*"]
effect: allow # Or set default_effect: allow
Issue 2: Rule not taking effect
# Error: Rule order problem
rules:
- callers: ["*"]
targets: ["*"]
effect: allow # This will match first
- callers: ["api.*"]
targets: ["internal.*"]
effect: deny # Will never be matched
# Fix: Adjust order
rules:
- callers: ["api.*"]
targets: ["internal.*"]
effect: deny # Match specific rule first
- callers: ["*"]
targets: ["*"]
effect: allow
10.2 Testing ACL¶
from apcore import ACL
acl = ACL.load("./acl/global_acl.yaml")
# Test specific calls
test_cases = [
("orchestrator.user.register", "executor.email.send_email", True),
("api.handler.test", "internal.secret", False),
("admin.panel", "internal.secret", True),
]
for caller, target, expected in test_cases:
result = acl.check(caller, target)
status = "✓" if result == expected else "✗"
print(f"{status} {caller} → {target}: {result}")
Next Steps¶
- Executor API - Learn how ACL integrates with Executor
- Middleware Guide - Extend ACL functionality with middleware
- Architecture - System overall architecture