Enhance policy system with global rules and age validation
Some checks failed
Go / build (push) Has been cancelled
Go / release (push) Has been cancelled

- Updated policy configuration to include global rules applicable to all events, allowing for site-wide security policies.
- Introduced age validation features to prevent replay and clock skew attacks, with configurable maximum age limits for events.
- Enhanced example policy and README documentation to reflect new global rules and age validation capabilities.
- Added comprehensive tests for global rule checks and age validation scenarios.
- Bumped version to v0.16.2.
This commit is contained in:
2025-10-16 12:41:32 +01:00
parent 585ce11f71
commit db941a18ea
8 changed files with 677 additions and 119 deletions

View File

@@ -11,7 +11,7 @@ export ORLY_POLICY_ENABLED=true
## Policy Configuration File
The policy configuration is loaded from `$HOME/.config/ORLY/policy.json`. See `example-policy.json` for a complete example.
The policy configuration is loaded from `$HOME/.config/ORLY/policy.json`. See `docs/example-policy.json` for a complete example with global rules and age validation.
### Structure
@@ -21,6 +21,17 @@ The policy configuration is loaded from `$HOME/.config/ORLY/policy.json`. See `e
"whitelist": [1, 3, 5, 7, 9735],
"blacklist": []
},
"global": {
"description": "Global rules applied to all events",
"write_allow": [],
"write_deny": [],
"read_allow": [],
"read_deny": [],
"size_limit": 100000,
"content_limit": 50000,
"max_age_of_event": 86400,
"max_age_event_in_future": 300
},
"rules": {
"1": {
"description": "Text notes - allow all authenticated users",
@@ -29,12 +40,33 @@ The policy configuration is loaded from `$HOME/.config/ORLY/policy.json`. See `e
"read_allow": [],
"read_deny": [],
"size_limit": 32000,
"content_limit": 10000
"content_limit": 10000,
"max_age_of_event": 3600,
"max_age_event_in_future": 60
}
}
}
```
### Policy Evaluation Order
The policy system evaluates events in the following order:
1. **Global Rules** - Applied to all events first
2. **Kinds Filtering** - Whitelist/blacklist by event kind
3. **Kind-specific Rules** - Rules for specific event kinds
4. **Script Rules** - Custom script logic (if enabled)
### Global Rules
The `global` section defines rules that apply to **all events** regardless of their kind. These rules are evaluated **first** and take precedence over kind-specific rules.
Global rules support all the same fields as kind-specific rules, allowing you to:
- Set site-wide size limits
- Block specific pubkeys globally
- Enforce age restrictions on all events
- Apply content limits across all event types
### Kinds Filtering
- `whitelist`: If present, only these event kinds are allowed. All others are denied.
@@ -55,6 +87,50 @@ The policy configuration is loaded from `$HOME/.config/ORLY/policy.json`. See `e
- `content_limit`: Maximum content field size in bytes
- `privileged`: If true, event must be authored by authenticated user or contain authenticated user in p tags
- `rate_limit`: Rate limit in bytes per second (not yet implemented)
- `max_age_of_event`: Maximum age of event in seconds (prevents replay attacks)
- `max_age_event_in_future`: Maximum time event can be in the future in seconds (prevents clock skew attacks)
### Age Validation
The policy system includes built-in timestamp validation to prevent common attacks:
#### MaxAgeOfEvent
- **Purpose**: Prevents replay attacks by rejecting events that are too old
- **Behavior**: Events with `created_at` older than `current_time - max_age_of_event` are rejected
- **Example**: Setting `max_age_of_event: 3600` rejects events older than 1 hour
- **Use Cases**:
- Prevent replay of old events
- Ensure events are recent and relevant
- Reduce storage of stale data
#### MaxAgeEventInFuture
- **Purpose**: Prevents clock skew attacks by rejecting events too far in the future
- **Behavior**: Events with `created_at` newer than `current_time + max_age_event_in_future` are rejected
- **Example**: Setting `max_age_event_in_future: 300` rejects events more than 5 minutes in the future
- **Use Cases**:
- Prevent clock manipulation attacks
- Ensure reasonable timestamp accuracy
- Block events with impossible future timestamps
#### Age Validation Examples
```json
{
"global": {
"max_age_of_event": 86400, // Reject events older than 24 hours
"max_age_event_in_future": 300 // Reject events more than 5 minutes in future
},
"rules": {
"1": {
"max_age_of_event": 3600, // Text notes: reject older than 1 hour
"max_age_event_in_future": 60 // Text notes: reject more than 1 minute in future
},
"4": {
"max_age_of_event": 604800 // Direct messages: reject older than 7 days
}
}
}
```
## Policy Scripts
@@ -76,7 +152,7 @@ The script receives JSON events via stdin and outputs JSON responses via stdout.
### Example Script
See `example-policy.sh` for a complete example showing:
See `docs/example-policy.sh` for a complete example showing:
- IP address blocking
- Content filtering
- Authentication requirements
@@ -86,11 +162,16 @@ See `example-policy.sh` for a complete example showing:
### EVENT Processing
When policy is enabled, every EVENT envelope is checked using `CheckPolicy("write", event, loggedInPubkey, ipAddress)` before being stored.
When policy is enabled, every EVENT envelope is checked using `CheckPolicy("write", event, loggedInPubkey, ipAddress)` before being stored. The policy evaluation follows this order:
1. **Global Rules** - Applied first to all events
2. **Kinds Filtering** - Whitelist/blacklist check
3. **Kind-specific Rules** - Rules for the event's kind
4. **Script Rules** - Custom script logic (if enabled)
### REQ Processing
When policy is enabled, every event returned in REQ responses is filtered using `CheckPolicy("read", event, loggedInPubkey, ipAddress)` before being sent to the client.
When policy is enabled, every event returned in REQ responses is filtered using `CheckPolicy("read", event, loggedInPubkey, ipAddress)` before being sent to the client. The same evaluation order applies for read access.
## Error Handling
@@ -105,9 +186,31 @@ Policy decisions are logged at debug level:
- `policy rejected event <id>`
- `policy filtered out event <id> for read access`
## Best Practices
### Global Rules
- Use global rules for site-wide security policies (size limits, age restrictions)
- Keep global rules simple and broad to avoid unintended side effects
- Test global rules thoroughly as they affect all events
### Age Validation
- Set reasonable age limits based on your use case:
- **Text notes (kind 1)**: 1-24 hours max age, 1-5 minutes future tolerance
- **Direct messages (kind 4)**: 7-30 days max age, 1-5 minutes future tolerance
- **Replaceable events (kind 0, 3)**: Longer max age, shorter future tolerance
- Consider network latency when setting future tolerance
- Monitor rejected events to tune age limits appropriately
### Policy Hierarchy
- Global rules should be broader than kind-specific rules
- Use global rules for security, kind-specific rules for functionality
- Avoid conflicting rules between global and kind-specific policies
## Security Considerations
- Policy scripts run with the same privileges as the relay process
- Scripts should be carefully reviewed and tested
- Consider using read-only filesystems for policy scripts in production
- Monitor script execution time to prevent DoS attacks
- Age validation helps prevent replay and clock skew attacks
- Global rules provide defense-in-depth security