Access Control
Relevant source files
The following files were used as context for generating this wiki page:
Access Control in kamal-proxy comprises a multi-layered security stack designed to protect upstream services from unauthorized access and abuse. It operates at the service level, allowing for granular configuration of IP allow/deny lists, User-Agent filtering, Basic Authentication, and Rate Limiting. These controls are integrated directly into the request handling pipeline to ensure that malicious or unauthorized traffic is rejected before reaching the backend targets.
The architecture prioritizes security by placing access checks early in the request lifecycle, specifically before sensitive operations like TLS redirection (in the case of IP filtering) or password challenges. This prevents cleartext credential leakage and preserves system resources by dropping blocked traffic as early as possible.
Security Pipeline Architecture
Access control logic is executed within serviceRequestWithTarget in a specific, non-negotiable order. This sequence ensures that cheaper, more restrictive checks (like IP blocking) happen before more expensive or interactive ones (like Basic Auth).
Execution Flow
The following diagram illustrates the order of security checks when a request reaches a service:
Logic Rationale
- Deny List First: Prevents known abusers from spending rate-limit budgets or seeing auth challenges. Sources: internal/server/deny_list.go:171-174
- Redirects before Auth: Ensures Basic Auth credentials are only solicited over encrypted HTTPS connections. Sources: internal/server/basic_auth_test.go:210-220
- Internal Exemption: Proxy-internal requests (like ACME probes) and health checks are exempt from these filters to prevent service deadlocks. Sources: internal/server/service.go:984, internal/server/deny_list.go:178-181
IP Access Control (Allow & Deny Lists)
kamal-proxy supports both explicit denial of CIDR ranges and restriction to an allow-only list. These components use a forwardedResolver to correctly identify client IPs when operating behind trusted proxies.
Configuration Options
| Flag | Purpose | Logic |
|---|---|---|
--allow-ip | List of permitted CIDR ranges. | If set, only these IPs are allowed. |
--deny-ip | List of blocked CIDR ranges. | Checked before Allow List; beats Allow List. |
--trusted-proxy | Trusted upstream proxy IPs. | Required to trust X-Forwarded-For or custom IP headers. |
Sources: internal/cmd/deploy.go:102-115, internal/server/deny_list_service_test.go:82-90
IP Resolution and Trust
For access control to function behind load balancers, the proxy must resolve the true client IP. If --trusted-proxy is configured, the proxy inspects the X-Forwarded-For chain. Without it, the proxy strictly uses the connecting peer's address to prevent header spoofing.
Sources: internal/server/deny_list.go:27-35, internal/server/ip_allow_list_service_test.go:70-80
User-Agent Filtering
The Deny List includes a User-Agent filter that uses RE2 regular expressions to block specific clients (e.g., aggressive crawlers or scrapers).
- Anchoring: Patterns are automatically anchored to the start (
\A) and end (\z) of the header value. Sources: internal/server/deny_list.go:102-106 - Missing Agents: An absent User-Agent header is only blocked if the explicit pattern
^$is provided. Sources: internal/server/deny_list.go:74-80 - Performance: Address checks (IP) are performed before User-Agent regex matching because they are computationally cheaper. Sources: internal/server/deny_list.go:189-192
Basic Authentication
Basic Authentication provides a simple credential challenge for protected services. To maximize security, credentials are never handled in plaintext by the proxy server.
Credential Hashing
The CLI hashes the <username>:<password> string using a salted SHA-256 digest before sending it over the RPC socket to the proxy. The stored state only contains the scheme, salt, and digest.
Sources: internal/cmd/deploy.go:170-185, internal/server/service.go:167-172
Implementation Details
- Challenge Header: Rejects unauthorized requests with
401 Unauthorizedand aWWW-Authenticateheader. Sources: internal/server/basic_auth_test.go:150-160 - Credential Stripping: Once authenticated, the proxy strips the
Authorizationheader before forwarding the request to the target to avoid leaking proxy credentials to the application. Sources: internal/server/basic_auth_test.go:248-260
Rate Limiting
The rate limiter uses a token bucket algorithm to enforce per-client request caps.
- Granularity: Limits are applied per-service. Sources: ROADMAP.md:85-88
- IPv6 Handling: IPv6 clients are counted per
/64prefix to prevent clients from bypassing limits by cycling through addresses in their assigned block. Sources: internal/server/service.go:180-183 - Exemptions: Specific CIDR ranges (e.g., internal monitoring) can be exempted via the
--rate-limit-exemptflag. Sources: internal/cmd/deploy.go:120-125
Resiliency and Failure Modes
The Access Control system is designed to "fail closed" for stored state to ensure security is not accidentally bypassed during proxy restarts or upgrades.
- Unreadable Rules: If stored Deny or Allow list rules cannot be parsed during service initialization (e.g., due to state file corruption), the service defaults to
denyAll, refusing all traffic until a valid redeployment occurs. Sources: internal/server/deny_list.go:149-155, internal/server/ip_allow_list_service_test.go:230-240 - State Integrity: The state file is saved with restricted permissions (
0600) to protect credential digests from other host processes. Sources: internal/server/basic_auth_test.go:420-430
Access Control provides the necessary infrastructure to manage traffic exposure and protect upstream applications from common network-level threats and unauthorized access attempts.