Browse Source
- 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.main
8 changed files with 680 additions and 122 deletions
@ -1,48 +1,152 @@ |
|||||||
#!/bin/bash |
#!/bin/bash |
||||||
|
|
||||||
# Policy script example for ORLY relay |
# ORLY Policy Script Example |
||||||
# This script receives JSON events via stdin and outputs JSON responses via stdout |
# This script demonstrates advanced policy logic including: |
||||||
# Each event includes the original event data plus logged_in_pubkey and ip_address fields |
# - IP address blocking |
||||||
|
# - Content filtering |
||||||
|
# - Authentication requirements |
||||||
|
# - User-specific permissions |
||||||
|
# - Age validation (complementing built-in age checks) |
||||||
|
|
||||||
# Read events from stdin (JSONL format) |
# Configuration |
||||||
|
BLOCKED_IPS=("127.0.0.1" "192.168.1.100") |
||||||
|
BLOCKED_WORDS=("spam" "scam" "phishing") |
||||||
|
TRUSTED_USERS=("746573742d7075626b6579" "abcdef1234567890abcdef1234567890abcdef12") |
||||||
|
ADMIN_USERS=("746573742d7075626b6579") |
||||||
|
|
||||||
|
# Function to check if IP is blocked |
||||||
|
is_ip_blocked() { |
||||||
|
local ip="$1" |
||||||
|
for blocked_ip in "${BLOCKED_IPS[@]}"; do |
||||||
|
if [[ "$ip" == "$blocked_ip" ]]; then |
||||||
|
return 0 |
||||||
|
fi |
||||||
|
done |
||||||
|
return 1 |
||||||
|
} |
||||||
|
|
||||||
|
# Function to check for blocked words |
||||||
|
contains_blocked_words() { |
||||||
|
local content="$1" |
||||||
|
local lower_content=$(echo "$content" | tr '[:upper:]' '[:lower:]') |
||||||
|
|
||||||
|
for word in "${BLOCKED_WORDS[@]}"; do |
||||||
|
if [[ "$lower_content" == *"$word"* ]]; then |
||||||
|
return 0 |
||||||
|
fi |
||||||
|
done |
||||||
|
return 1 |
||||||
|
} |
||||||
|
|
||||||
|
# Function to check if user is trusted |
||||||
|
is_trusted_user() { |
||||||
|
local pubkey="$1" |
||||||
|
for trusted_user in "${TRUSTED_USERS[@]}"; do |
||||||
|
if [[ "$pubkey" == "$trusted_user" ]]; then |
||||||
|
return 0 |
||||||
|
fi |
||||||
|
done |
||||||
|
return 1 |
||||||
|
} |
||||||
|
|
||||||
|
# Function to check if user is admin |
||||||
|
is_admin_user() { |
||||||
|
local pubkey="$1" |
||||||
|
for admin_user in "${ADMIN_USERS[@]}"; do |
||||||
|
if [[ "$pubkey" == "$admin_user" ]]; then |
||||||
|
return 0 |
||||||
|
fi |
||||||
|
done |
||||||
|
return 1 |
||||||
|
} |
||||||
|
|
||||||
|
# Function to validate event age (additional to built-in checks) |
||||||
|
validate_event_age() { |
||||||
|
local created_at="$1" |
||||||
|
local current_time=$(date +%s) |
||||||
|
local age=$((current_time - created_at)) |
||||||
|
|
||||||
|
# Additional age validation beyond built-in checks |
||||||
|
# Reject events older than 7 days for certain kinds |
||||||
|
if [[ $age -gt 604800 ]]; then |
||||||
|
return 1 |
||||||
|
fi |
||||||
|
|
||||||
|
return 0 |
||||||
|
} |
||||||
|
|
||||||
|
# Main policy logic |
||||||
while IFS= read -r line; do |
while IFS= read -r line; do |
||||||
# Parse the JSON event |
# Parse JSON input |
||||||
event_id=$(echo "$line" | jq -r '.id // empty') |
event_id=$(echo "$line" | jq -r '.id // empty') |
||||||
event_kind=$(echo "$line" | jq -r '.kind // empty') |
pubkey=$(echo "$line" | jq -r '.pubkey // empty') |
||||||
event_pubkey=$(echo "$line" | jq -r '.pubkey // empty') |
kind=$(echo "$line" | jq -r '.kind // empty') |
||||||
event_content=$(echo "$line" | jq -r '.content // empty') |
content=$(echo "$line" | jq -r '.content // empty') |
||||||
|
created_at=$(echo "$line" | jq -r '.created_at // empty') |
||||||
logged_in_pubkey=$(echo "$line" | jq -r '.logged_in_pubkey // empty') |
logged_in_pubkey=$(echo "$line" | jq -r '.logged_in_pubkey // empty') |
||||||
ip_address=$(echo "$line" | jq -r '.ip_address // empty') |
ip_address=$(echo "$line" | jq -r '.ip_address // empty') |
||||||
|
|
||||||
# Default action |
# Default to accept |
||||||
action="accept" |
action="accept" |
||||||
message="" |
msg="" |
||||||
|
|
||||||
# Example policy logic: |
# Check IP blocking |
||||||
# 1. Block events from specific IP addresses |
if is_ip_blocked "$ip_address"; then |
||||||
if [[ "$ip_address" == "192.168.1.100" ]]; then |
|
||||||
action="reject" |
action="reject" |
||||||
message="blocked IP address" |
msg="IP address blocked" |
||||||
|
echo "{\"id\":\"$event_id\",\"action\":\"$action\",\"msg\":\"$msg\"}" |
||||||
|
continue |
||||||
fi |
fi |
||||||
|
|
||||||
# 2. Block events with certain content patterns |
# Check for blocked words in content |
||||||
if [[ "$event_content" =~ "spam" ]]; then |
if contains_blocked_words "$content"; then |
||||||
action="reject" |
action="reject" |
||||||
message="spam content detected" |
msg="Content contains blocked words" |
||||||
|
echo "{\"id\":\"$event_id\",\"action\":\"$action\",\"msg\":\"$msg\"}" |
||||||
|
continue |
||||||
fi |
fi |
||||||
|
|
||||||
# 3. Require authentication for certain kinds |
# Additional age validation |
||||||
if [[ "$event_kind" == "3" && -z "$logged_in_pubkey" ]]; then |
if ! validate_event_age "$created_at"; then |
||||||
action="reject" |
action="reject" |
||||||
message="authentication required for kind 3" |
msg="Event too old (additional validation)" |
||||||
|
echo "{\"id\":\"$event_id\",\"action\":\"$action\",\"msg\":\"$msg\"}" |
||||||
|
continue |
||||||
fi |
fi |
||||||
|
|
||||||
# 4. Allow only specific users for kind 3 |
# Kind-specific rules |
||||||
if [[ "$event_kind" == "3" && "$event_pubkey" != "npub1example1" && "$event_pubkey" != "npub1example2" ]]; then |
case "$kind" in |
||||||
|
"4") # Direct messages |
||||||
|
# Require authentication for DMs |
||||||
|
if [[ -z "$logged_in_pubkey" ]]; then |
||||||
action="reject" |
action="reject" |
||||||
message="unauthorized user for kind 3" |
msg="Authentication required for direct messages" |
||||||
fi |
fi |
||||||
|
;; |
||||||
|
"40"|"41"|"42"|"43"|"44") # Channel events |
||||||
|
# Require authentication for channel events |
||||||
|
if [[ -z "$logged_in_pubkey" ]]; then |
||||||
|
action="reject" |
||||||
|
msg="Authentication required for channel events" |
||||||
|
fi |
||||||
|
;; |
||||||
|
"9735") # Zap receipts |
||||||
|
# Only allow trusted users to post zap receipts |
||||||
|
if ! is_trusted_user "$pubkey"; then |
||||||
|
action="reject" |
||||||
|
msg="Only trusted users can post zap receipts" |
||||||
|
fi |
||||||
|
;; |
||||||
|
esac |
||||||
|
|
||||||
|
# Admin bypass for certain operations |
||||||
|
if is_admin_user "$pubkey"; then |
||||||
|
# Admins can bypass most restrictions |
||||||
|
action="accept" |
||||||
|
msg="Admin bypass" |
||||||
|
fi |
||||||
|
|
||||||
|
# Output decision |
||||||
|
echo "{\"id\":\"$event_id\",\"action\":\"$action\",\"msg\":\"$msg\"}" |
||||||
|
|
||||||
# Output JSON response |
|
||||||
echo "{\"id\":\"$event_id\",\"action\":\"$action\",\"msg\":\"$message\"}" |
|
||||||
done |
done |
||||||
Loading…
Reference in new issue