Policies
The policies middleware provides comprehensive policy-based access control, rate limiting, and request filtering through a flexible rule system.
Overview
Policies middleware enables you to:
- Control access based on IP addresses, paths, headers, or geographic location
- Implement rate limiting per client IP
- Apply time-based restrictions (business hours, maintenance windows)
- Filter by HTTP method, User-Agent, Content-Type, or query parameters
- Combine multiple security rules with weight-based priority
Key Features
- Multi-layered security: Combine 13 different rule types in a single policy
- Performance optimized: Pre-compiled IP networks and path routers
- Hot-reload compatible: Configuration changes apply without restart
- Thread-safe rate limiting: In-memory state tracking with RwLock
- Flexible evaluation: Weight-based priority with complete rule evaluation
Evaluation Logic
A request is ACCEPTED only if:
- At least ONE allow rule matches (across all enabled policies/rules)
- AND zero deny rules match
Key Points
- All rules evaluated: Not first-match-wins - every enabled rule is checked
- Weight determines order: Higher weight = evaluated first (but doesn't stop evaluation)
- Implicit deny: If no allow rules match, request is denied (403 Forbidden)
- Deny overrides all: A single matching deny rule blocks the request, regardless of allow rules
Evaluation Flow
1. Filter to enabled policies only
2. For each policy:
a. Filter to enabled rules only
b. Sort rules by weight (descending)
c. Evaluate ALL rules in order
d. Track: has_allow, has_deny
3. Final decision:
- If has_deny = true → DENY (403)
- Else if has_allow = false → DENY (implicit, 403)
- Else → ALLOW (continue to backend)
Configuration Structure
[middleware.my_policy]
type = "policies"
# Policy 1
[[middleware.my_policy.options.policies]]
id = "access_control"
name = "Access Control Policy"
enabled = true
# Rule 1
[[middleware.my_policy.options.policies.rules]]
id = "rule1"
name = "Allow Internal Networks"
type = "ip_allow"
weight = 100
enabled = true
[middleware.my_policy.options.policies.rules.options]
ip_addresses = ["10.0.0.0/8", "192.168.0.0/16"]
# Rule 2
[[middleware.my_policy.options.policies.rules]]
id = "rule2"
name = "Deny Blocklist"
type = "ip_deny"
weight = 90
enabled = true
[middleware.my_policy.options.policies.rules.options]
ip_addresses = ["203.0.113.0/24"]
Configuration Fields
Policy fields:
id(string, optional): Unique identifiername(string, optional): Human-readable nameenabled(boolean, default: true): Whether policy is activerules(array, required): Array of rules within this policy
Rule fields:
id(string, optional): Unique identifiername(string, optional): Human-readable nametype(string, required): Rule type (see Rule Types)weight(integer, default: 0): Priority for evaluation orderenabled(boolean, default: true): Whether rule is activeoptions(table, varies): Rule-specific configuration
Rule Types
1. IP Allow (ip_allow)
Allow requests from specific IP addresses or CIDR ranges.
Options:
ip_addresses(array of strings, required): IP addresses or CIDR notation
Example:
[[middleware.security.options.policies.rules]]
type = "ip_allow"
weight = 100
enabled = true
[middleware.security.options.policies.rules.options]
ip_addresses = [
"10.0.0.0/8", # Private network - Class A
"172.16.0.0/12", # Private network - Class B
"192.168.0.0/16", # Private network - Class C
"127.0.0.1/32" # Localhost
]
Behavior:
- Extracts client IP from
remote_addrorclient_ipmetadata - Matches against pre-compiled CIDR networks (O(log n))
- On match: Rule evaluates to ALLOW
- On no match: Rule evaluates to NO_MATCH (implicit deny if no other allows)
Use Cases:
- Internal network allowlisting
- VPN-only access
- Specific office IP ranges
2. IP Deny (ip_deny)
Block requests from specific IP addresses or CIDR ranges.
Options:
ip_addresses(array of strings, required): IP addresses or CIDR notation
Example:
[[middleware.security.options.policies.rules]]
type = "ip_deny"
weight = 90
enabled = true
[middleware.security.options.policies.rules.options]
ip_addresses = [
"203.0.113.0/24", # TEST-NET-3 (example blocklist)
"198.51.100.0/24" # TEST-NET-2 (example blocklist)
]
Behavior:
- Same IP matching logic as
ip_allow - On match: Rule evaluates to DENY (request blocked)
- On no match: Rule evaluates to NO_MATCH
Use Cases:
- Blocklist known malicious IPs
- Prevent access from specific regions/networks
- Emergency blocking of compromised IPs
3. Rate Limit (rate_limit)
Throttle requests based on count within a time window (per client IP).
Options:
max_requests(integer, required): Maximum requests allowedwindow_seconds(integer, required): Time window in seconds
Example:
[[middleware.api_limiter.options.policies.rules]]
type = "rate_limit"
weight = 50
enabled = true
[middleware.api_limiter.options.policies.rules.options]
max_requests = 100
window_seconds = 60 # 100 requests per minute
Behavior:
- Tracks request count per client IP using thread-safe RwLock
- Sliding window: resets when
window_secondsexpires - Within limit: Rule evaluates to NO_MATCH
- Limit exceeded: Rule evaluates to DENY
- First request in new window: count = 1, starts new timer
Important: Rate limiting acts as a DENY rule when exceeded. You MUST have an allow rule (typically allow_all) for rate limiting to work correctly.
Use Cases:
- API rate limiting (100 req/min, 1000 req/hour)
- DDoS protection
- Fair usage enforcement
- Prevent abuse
4. Path Match (path)
Allow or deny based on URL path patterns.
Options:
paths(array of strings, required): Path patterns (matchit syntax)mode(string, required): "allow" or "deny"
Example:
[[middleware.api_filter.options.policies.rules]]
type = "path"
weight = 80
enabled = true
[middleware.api_filter.options.policies.rules.options]
paths = [
"/api/public/{*path}", # Catch-all under /api/public
"/health",
"/metrics"
]
mode = "allow"
Pattern Syntax (matchit):
- Exact:
/users,/api/health - Wildcards:
/api/*(single segment) - Catch-all:
/api/{*path}(multiple segments) - Parameters:
/users/{id}
Behavior:
- Pre-compiles paths into matchit router at config load
- Extracts path from
pathmetadata field - Normalizes paths (adds leading
/, removes trailing/) - On match + mode="allow": Rule evaluates to ALLOW
- On match + mode="deny": Rule evaluates to DENY
- On no match: Rule evaluates to NO_MATCH
Use Cases:
- Protect admin endpoints (
/admin/{*path}deny) - Allow only specific API paths
- Public vs private API segregation
5. Geographic (geo)
Allow or deny based on client's country code.
Options:
country_codes(array of strings, required): ISO 3166-1 alpha-2 codesmode(string, required): "allow" or "deny"
Example:
[[middleware.geo_filter.options.policies.rules]]
type = "geo"
weight = 70
enabled = true
[middleware.geo_filter.options.policies.rules.options]
country_codes = ["US", "GB", "CA", "AU"]
mode = "allow"
Behavior:
- Extracts country from
geo_countryorcountry_codemetadata - Normalizes codes to uppercase
- On match + mode="allow": Rule evaluates to ALLOW
- On match + mode="deny": Rule evaluates to DENY
- On no match: Rule evaluates to NO_MATCH
Requirements:
geo_countryorcountry_codemust be populated in request metadata- Typically set by geolocation middleware or protocol adapter
- Country codes must be valid ISO 3166-1 alpha-2 (2 letters)
Use Cases:
- GDPR compliance (EU countries only)
- Regional service restrictions
- Country-based routing
- Sanctions compliance
6. Header Match (header)
Allow or deny based on HTTP header values.
Options:
mode(string, required): "allow" or "deny"headers(array of objects, required): Header match configurationsname(string): Header namematch_type(string): "exact", "contains", or "regex"value(string): Value or pattern to match
Example:
[[middleware.header_filter.options.policies.rules]]
type = "header"
weight = 60
enabled = true
[middleware.header_filter.options.policies.rules.options]
mode = "allow"
headers = [
{ name = "User-Agent", match_type = "regex", value = "^Mozilla.*" },
{ name = "X-API-Key", match_type = "exact", value = "secret-key" }
]
Match Types:
exact: Case-insensitive exact matchcontains: Case-insensitive substring matchregex: Regex pattern match (compiled at runtime)
Behavior:
- Checks headers from metadata (
header_<name>or direct name lookup) - ALL headers must match for rule to trigger
- Invalid regex patterns log warning and return false
- On all match + mode="allow": Rule evaluates to ALLOW
- On all match + mode="deny": Rule evaluates to DENY
- On any no match: Rule evaluates to NO_MATCH
Use Cases:
- API key validation
- User-Agent filtering (block bots)
- Custom authentication headers
- Version-based access control
7. Allow All (allow_all)
Blanket allow for all requests (no configuration required).
Example:
[[middleware.open_api.options.policies.rules]]
type = "allow_all"
weight = 100
enabled = true
Behavior:
- Always evaluates to ALLOW
- No options required
- Useful as base rule for rate limiting
Use Cases:
- Testing and development
- Public endpoints
- Base allow rule for rate limiting policies
- Default allow (with specific denies)
8. Deny All (deny_all)
Blanket deny for all requests (no configuration required).
Example:
[[middleware.maintenance.options.policies.rules]]
type = "deny_all"
weight = 0
enabled = true
Behavior:
- Always evaluates to DENY
- No options required
Use Cases:
- Maintenance mode
- Emergency blocking
- Default-deny security stance
- Placeholder for future rules
9. Time Based (time_based)
Allow or deny requests based on time windows, days of week, and date ranges.
Options:
allow_during_window(boolean, default: true): If true, allow during window; if false, deny during windowtimezone(string, default: "UTC"): IANA timezone (e.g., "America/New_York", "Europe/London")start_time(string, optional): Start time in HH:MM format (24-hour)end_time(string, optional): End time in HH:MM format (24-hour)days_of_week(array of strings, optional): Allowed days ("monday", "tuesday", etc., lowercase)start_date(string, optional): Start date in YYYY-MM-DD formatend_date(string, optional): End date in YYYY-MM-DD format
Example 1: Business Hours
[[middleware.hours.options.policies.rules]]
type = "time_based"
weight = 100
enabled = true
[middleware.hours.options.policies.rules.options]
allow_during_window = true
timezone = "America/New_York"
start_time = "09:00"
end_time = "17:00"
days_of_week = ["monday", "tuesday", "wednesday", "thursday", "friday"]
Example 2: Maintenance Window
[[middleware.maint.options.policies.rules]]
type = "time_based"
weight = 100
enabled = true
[middleware.maint.options.policies.rules.options]
allow_during_window = false # Deny during window
timezone = "UTC"
start_time = "02:00"
end_time = "04:00"
Example 3: Date Range Restriction
[[middleware.event.options.policies.rules]]
type = "time_based"
weight = 100
enabled = true
[middleware.event.options.policies.rules.options]
allow_during_window = true
timezone = "UTC"
start_date = "2025-01-01"
end_date = "2025-12-31"
Behavior:
- Evaluates current time in specified timezone
- Checks time window (supports wrap-around midnight: 22:00-06:00)
- Checks day of week (all specified days must match)
- Checks date range (inclusive)
- If all conditions pass:
allow_during_window=true→ Rule evaluates to ALLOWallow_during_window=false→ Rule evaluates to DENY
- If any condition fails:
allow_during_window=true→ Rule evaluates to DENYallow_during_window=false→ Rule evaluates to ALLOW
Use Cases:
- Business hours enforcement
- Maintenance windows (deny during specific times)
- Weekend-only access
- Holiday schedule restrictions
- Time-limited promotions or features
Notes:
- Invalid timezone falls back to UTC with warning
- Invalid time/date formats return NO_MATCH with warning
- Time windows that wrap midnight (22:00-06:00) are supported
- All time comparisons use the current time in the specified timezone
10. HTTP Method (method)
Allow or deny requests based on HTTP method (GET, POST, PUT, DELETE, etc.).
Options:
mode(string, required): "allow" or "deny"methods(array of strings, required): HTTP methods to match
Example:
[[middleware.api_methods.options.policies.rules]]
type = "method"
weight = 85
enabled = true
[middleware.api_methods.options.policies.rules.options]
mode = "allow"
methods = ["GET", "POST"]
Supported Methods:
GET- Retrieve dataPOST- Submit dataPUT- Update/replace dataPATCH- Partial updateDELETE- Remove dataOPTIONS- Get supported methodsHEAD- Get headers only
Behavior:
- Extracts HTTP method from request metadata
- Matches against configured methods list
- On match + mode="allow": Rule evaluates to ALLOW
- On match + mode="deny": Rule evaluates to DENY
- On no match: Rule evaluates to NO_MATCH
Use Cases:
- Read-only API endpoints (GET only)
- Restrict destructive operations (deny DELETE)
- Separate read/write permissions
- Method-based routing control
11. User Agent (user_agent)
Allow or deny requests based on User-Agent header patterns using regex.
Options:
mode(string, required): "allow" or "deny"patterns(array of objects, required): Pattern match configurationslabel(string, optional): Friendly name for the patternpattern(string, required): Regex pattern to match
Example:
[[middleware.bot_filter.options.policies.rules]]
type = "user_agent"
weight = 75
enabled = true
[middleware.bot_filter.options.policies.rules.options]
mode = "deny"
patterns = [
{ label = "Common Bots", pattern = "/bot|crawler|spider/i" },
{ label = "Scrapers", pattern = "/scrapy|selenium/i" },
{ pattern = "/^python-requests/i" }
]
Pattern Syntax:
- Standard regex with delimiters:
/pattern/flags - Common flags:
i- Case-insensitive matchingm- Multiline matching
- Examples:
/Mozilla.*Chrome/- Match Chrome browsers/bot|crawler/i- Match any bot or crawler (case-insensitive)/^Mobile|Android|iPhone/- Match mobile devices
Behavior:
- Extracts User-Agent from request headers/metadata
- Tests against all configured patterns (any match triggers rule)
- Invalid regex patterns log warning and are skipped
- On any pattern match + mode="allow": Rule evaluates to ALLOW
- On any pattern match + mode="deny": Rule evaluates to DENY
- On no match: Rule evaluates to NO_MATCH
Use Cases:
- Block bots and scrapers
- Allow only specific browsers
- Filter by device type (mobile vs desktop)
- Detect and block automated tools
- API client version enforcement
12. Content Type (content_type)
Allow or deny requests based on Content-Type header.
Options:
mode(string, required): "allow" or "deny"content_types(array of strings, required): MIME types to match (supports wildcards)
Example:
[[middleware.content_filter.options.policies.rules]]
type = "content_type"
weight = 70
enabled = true
[middleware.content_filter.options.policies.rules.options]
mode = "allow"
content_types = [
"application/json",
"application/x-www-form-urlencoded",
"multipart/form-data"
]
Example with Wildcards:
[[middleware.content_filter.options.policies.rules]]
type = "content_type"
weight = 70
enabled = true
[middleware.content_filter.options.policies.rules.options]
mode = "deny"
content_types = [
"application/xml", # Specific type
"text/*" # All text types
]
Wildcard Support:
application/*- Matches any application typetext/*- Matches any text typeimage/*- Matches any image type- Exact matches also supported:
application/json
Common MIME Types:
application/json- JSON dataapplication/xml- XML datatext/html- HTML documentstext/plain- Plain textmultipart/form-data- File uploadsapplication/x-www-form-urlencoded- Form submissions
Behavior:
- Extracts Content-Type from request headers
- Supports wildcard matching (e.g.,
application/*) - On match + mode="allow": Rule evaluates to ALLOW
- On match + mode="deny": Rule evaluates to DENY
- On no match: Rule evaluates to NO_MATCH
Use Cases:
- Accept only JSON API requests
- Block XML uploads for security
- Restrict file upload types
- Content-type based routing
- API versioning by content type
13. Query Parameter (query_parameter)
Allow or deny requests based on query parameter values with flexible matching.
Options:
mode(string, required): "allow" or "deny"parameters(array of objects, required): Parameter match configurationsname(string, required): Query parameter namematch_type(string, required): "exists", "exact", "contains", or "regex"value(string, optional): Value or pattern to match (not required for "exists")
Example 1: Require API Key
[[middleware.param_filter.options.policies.rules]]
type = "query_parameter"
weight = 90
enabled = true
[middleware.param_filter.options.policies.rules.options]
mode = "allow"
parameters = [
{ name = "api_key", match_type = "exists" }
]
Example 2: Exact Value Match
[[middleware.param_filter.options.policies.rules]]
type = "query_parameter"
weight = 85
enabled = true
[middleware.param_filter.options.policies.rules.options]
mode = "allow"
parameters = [
{ name = "version", match_type = "exact", value = "v2" },
{ name = "format", match_type = "exact", value = "json" }
]
Example 3: Regex Pattern
[[middleware.param_filter.options.policies.rules]]
type = "query_parameter"
weight = 80
enabled = true
[middleware.param_filter.options.policies.rules.options]
mode = "allow"
parameters = [
{ name = "id", match_type = "regex", value = "/^[0-9]+$/" },
{ name = "timestamp", match_type = "regex", value = "/^[0-9]{10,}$/" }
]
Example 4: Block Admin Access
[[middleware.param_filter.options.policies.rules]]
type = "query_parameter"
weight = 95
enabled = true
[middleware.param_filter.options.policies.rules.options]
mode = "deny"
parameters = [
{ name = "role", match_type = "contains", value = "admin" }
]
Match Types:
exists: Parameter must be present (any value, including empty)exact: Parameter value must match exactly (case-sensitive)contains: Parameter value must contain the specified text (case-sensitive)regex: Parameter value must match regex pattern
Behavior:
- Extracts query parameters from request
- ALL configured parameters must match for rule to trigger
- Invalid regex patterns log warning and return false
- On all match + mode="allow": Rule evaluates to ALLOW
- On all match + mode="deny": Rule evaluates to DENY
- On any no match: Rule evaluates to NO_MATCH
Use Cases:
- Require API keys or tokens
- Validate request parameters
- Filter by version or format
- Block requests with suspicious parameters
- Enforce parameter patterns (numeric IDs, dates, etc.)
- API access control based on flags
Weight and Priority
Rules are evaluated in weight order (higher weight = evaluated first):
Weight Range Purpose Example
----------- --------- -------
-1000 to -100 Critical overrides Emergency blocks
-99 to -1 High priority rules Specific allows/denies
0 Default priority Most rules
1 to 99 Lower priority Catch-all rules
100 to 1000 Fallback rules Default deny-all
Important: Weight only affects evaluation ORDER. All enabled rules are still evaluated - it's not first-match-wins.
Common Patterns
Pattern 1: IP Allowlist with Blocklist
[[middleware.security.options.policies]]
id = "ip_security"
enabled = true
# Allow internal networks (highest priority)
[[middleware.security.options.policies.rules]]
type = "ip_allow"
weight = 100
[middleware.security.options.policies.rules.options]
ip_addresses = ["10.0.0.0/8", "192.168.0.0/16"]
# Deny known bad IPs (medium priority)
[[middleware.security.options.policies.rules]]
type = "ip_deny"
weight = 90
[middleware.security.options.policies.rules.options]
ip_addresses = ["203.0.113.0/24"]
Result:
- Internal IPs: ✅ ALLOW (matches allow, no deny)
- Blocked IPs: ❌ DENY (matches deny)
- External IPs: ❌ DENY (implicit - no allow match)
Pattern 2: Rate Limiting
[[middleware.rate_limiter.options.policies]]
id = "api_limits"
enabled = true
# Allow all (required base)
[[middleware.rate_limiter.options.policies.rules]]
type = "allow_all"
weight = 100
# Rate limit (acts as deny when exceeded)
[[middleware.rate_limiter.options.policies.rules]]
type = "rate_limit"
weight = 50
[middleware.rate_limiter.options.policies.rules.options]
max_requests = 100
window_seconds = 60
Result:
- Under limit: ✅ ALLOW (allow_all + rate_limit NO_MATCH)
- Over limit: ❌ DENY (allow_all + rate_limit DENY)
Pattern 3: Path-Based Access Control
[[middleware.api_access.options.policies]]
id = "path_control"
enabled = true
# Allow public API
[[middleware.api_access.options.policies.rules]]
type = "path"
weight = 100
[middleware.api_access.options.policies.rules.options]
paths = ["/api/public/{*path}"]
mode = "allow"
# Deny admin paths
[[middleware.api_access.options.policies.rules]]
type = "path"
weight = 90
[middleware.api_access.options.policies.rules.options]
paths = ["/admin/{*path}"]
mode = "deny"
Result:
/api/public/users: ✅ ALLOW/admin/users: ❌ DENY/api/internal/users: ❌ DENY (implicit - no allow)
Pattern 4: Layered Security
[[middleware.layered.options.policies]]
id = "multi_layer"
enabled = true
# Geographic restriction
[[middleware.layered.options.policies.rules]]
type = "geo"
weight = 100
[middleware.layered.options.policies.rules.options]
country_codes = ["US", "GB"]
mode = "allow"
# IP restriction
[[middleware.layered.options.policies.rules]]
type = "ip_allow"
weight = 90
[middleware.layered.options.policies.rules.options]
ip_addresses = ["192.168.0.0/16"]
# Path restriction
[[middleware.layered.options.policies.rules]]
type = "path"
weight = 80
[middleware.layered.options.policies.rules.options]
paths = ["/admin/{*path}"]
mode = "deny"
Result:
- Must be from US/GB AND from internal IP AND not accessing /admin
- All conditions must be met (geo allow + IP allow + path not deny)
Best Practices
1. Always Have an Allow Rule
Without any allow rules, all requests are implicitly denied:
# ❌ Bad: No allow rules
[[middleware.bad.options.policies.rules]]
type = "ip_deny"
[middleware.bad.options.policies.rules.options]
ip_addresses = ["1.2.3.4"]
# ✅ Good: Has allow rule
[[middleware.good.options.policies.rules]]
type = "ip_allow"
[middleware.good.options.policies.rules.options]
ip_addresses = ["10.0.0.0/8"]
2. Use Descriptive IDs and Names
Makes debugging and log analysis much easier:
[[middleware.security.options.policies]]
id = "corporate_network_access"
name = "Corporate Network Access Control"
[[middleware.security.options.policies.rules]]
id = "allow_hq_office"
name = "Allow HQ Office Network"
type = "ip_allow"
3. Higher Weight for Security-Critical Rules
Emergency blocks should have very high weights:
# Emergency block (weight -1000)
[[middleware.security.options.policies.rules]]
id = "emergency_block"
type = "ip_deny"
weight = -1000
[middleware.security.options.policies.rules.options]
ip_addresses = ["1.2.3.4"] # Compromised IP
# Normal rules (weight 0-100)
[[middleware.security.options.policies.rules]]
type = "ip_allow"
weight = 100
[middleware.security.options.policies.rules.options]
ip_addresses = ["10.0.0.0/8"]
4. Test in Development First
Policy misconfiguration can block legitimate traffic:
# Start with liberal policy, then tighten
1. Start: allow_all
2. Add: specific deny rules (test each)
3. Replace: allow_all with specific allows (test thoroughly)
4. Deploy: to production
5. Monitor Logs for Violations
Use debug logging to see rule evaluations:
RUST_LOG=harmony=debug cargo run -- --config config.toml
# Look for:
# "Evaluating policy: <name>"
# "Rule <name> matched - ALLOW/DENY"
# "Request ALLOWED/DENIED - <reason>"
6. Combine with Other Security Measures
Policies work best alongside:
- JWT authentication (verify tokens)
- TLS/mTLS (encrypted transport)
- WAF (SQL injection, XSS protection)
- API gateways (additional rate limiting)
Performance
Pre-Compilation Optimizations
- IP rules: CIDR networks compiled at config load → O(log n) lookup
- Path rules: matchit router compiled at config load → O(log n) lookup
- Rate limiting: In-memory HashMap with RwLock → O(1) lookup
- Automatic cleanup: Background task runs every 60 seconds
- Removes entries older than 5 minutes (300 seconds)
- Prevents unbounded memory growth from unique IPs
- Debug logging when entries are removed
- Header/Geo: Simple string matching → O(1) per header
Typical Performance
- Overhead: < 1ms per request with moderate rule counts (< 50 rules)
- Memory: ~1KB per rule (pre-compiled state)
- Scalability: Tested up to 1000 concurrent requests without degradation
Hot Reload Support
Policies middleware supports hot configuration reload:
- Changes to policies/rules applied without restart
- Pre-compiled caches updated atomically (Arc swap)
- Rate limit state preserved across reloads
- Zero downtime for configuration changes
Troubleshooting
All Requests Denied
Symptoms: All requests return 403 Forbidden
Solutions:
- Check that at least one allow rule is configured
- Verify the allow rule actually matches your requests:
RUST_LOG=harmony=debug cargo run
# Look for: "Rule <name> did not match" - Check rule
enabledflags (both policy and rule level) - Verify metadata fields are populated (
remote_addr,path, etc.)
Rate Limiting Not Working
Symptoms: Requests not being rate limited, or all denied
Solutions:
- Ensure you have an
allow_allrule:[[middleware.limiter.options.policies.rules]]
type = "allow_all"
weight = 100
[[middleware.limiter.options.policies.rules]]
type = "rate_limit"
weight = 50
[middleware.limiter.options.policies.rules.options]
max_requests = 100
window_seconds = 60 - Verify
remote_addrorclient_ipis set in request metadata - Check rate limit window hasn't already expired
- Note: Rate limit state is automatically cleaned up after 5 minutes of inactivity to prevent memory leaks. This means inactive IPs will have their counters reset.
Geographic Rules Not Matching
Symptoms: Geo rules don't seem to evaluate
Solutions:
- Ensure
geo_countryorcountry_codeis populated in metadata - Verify country codes are uppercase ISO 3166-1 alpha-2 (2 letters)
- Check that geolocation middleware/adapter runs before policies
- Test with known country code:
# Add debug logging to see metadata
RUST_LOG=harmony=debug cargo run
Path Rules Not Matching
Symptoms: Path-based allow/deny not working
Solutions:
- Check path syntax (must start with
/) - Verify
pathmetadata is populated - Test patterns individually:
# Start simple
paths = ["/api"]
# Then add complexity
paths = ["/api/{*path}"] - Remember: path filter uses request path AFTER endpoint prefix
Debug Logging
Enable detailed logging to see rule evaluation:
# Full debug output
RUST_LOG=harmony=debug cargo run -- --config config.toml
# Policies only
RUST_LOG=harmony::models::middleware::types::policies=debug cargo run
# Look for these log messages:
# INFO harmony::models::middleware::types::policies > PoliciesMiddleware initialized with N enabled policies
# DEBUG harmony::models::middleware::types::policies > Evaluating policy: <name>
# DEBUG harmony::models::middleware::types::policies > Evaluating rule: type=<type>, name=<name>, weight=<weight>
# DEBUG harmony::models::middleware::types::policies > IP rule evaluation: client_ip=X, matches=Y
# DEBUG harmony::models::middleware::types::policies > Rule <name> matched - ALLOW
# WARN harmony::models::middleware::types::policies > Rule <name> matched - DENY
# WARN harmony::models::middleware::types::policies > Request DENIED - at least one deny rule matched
# WARN harmony::models::middleware::types::policies > Request DENIED - no allow rule matched (implicit deny)
# DEBUG harmony::models::middleware::types::policies > Request ALLOWED - has allow rule(s) and no deny rules