514 lines
20 KiB
Markdown
514 lines
20 KiB
Markdown
# Implementation Plan: Enhanced Admin Event API Structure
|
|
|
|
## Current Issue
|
|
|
|
The current admin event routing at [`main.c:3248-3268`](src/main.c:3248) has a security vulnerability:
|
|
|
|
```c
|
|
if (event_kind == 23455 || event_kind == 23456) {
|
|
// Admin event processing
|
|
int admin_result = process_admin_event_in_config(event, admin_error, sizeof(admin_error), wsi);
|
|
} else {
|
|
// Regular event storage and broadcasting
|
|
}
|
|
```
|
|
|
|
**Problem**: Any event with these kinds gets routed to admin processing, regardless of authorization. This allows unauthorized users to send admin events that could be processed as legitimate admin commands.
|
|
|
|
**Note**: Event kinds 33334 and 33335 are no longer used and have been removed from the admin event routing.
|
|
|
|
## Required Security Enhancement
|
|
|
|
Admin events must be validated for proper authorization BEFORE routing to admin processing:
|
|
|
|
1. **Relay Public Key Check**: Event must have a `p` tag equal to the relay's public key
|
|
2. **Admin Signature Check**: Event must be signed by an authorized admin private key
|
|
3. **Fallback to Regular Processing**: If authorization fails, treat as regular event (not admin event)
|
|
|
|
## Implementation Plan
|
|
|
|
### Phase 1: Add Admin Authorization Validation
|
|
|
|
#### 1.1 Create Consolidated Admin Authorization Function
|
|
**Location**: [`src/main.c`](src/main.c) or [`src/config.c`](src/config.c)
|
|
|
|
```c
|
|
/**
|
|
* Consolidated admin event authorization validator
|
|
* Implements defense-in-depth security for admin events
|
|
*
|
|
* @param event - The event to validate for admin authorization
|
|
* @param error_message - Buffer for detailed error messages
|
|
* @param error_size - Size of error message buffer
|
|
* @return 0 if authorized, -1 if unauthorized, -2 if validation error
|
|
*/
|
|
int is_authorized_admin_event(cJSON* event, char* error_message, size_t error_size) {
|
|
if (!event) {
|
|
snprintf(error_message, error_size, "admin_auth: null event");
|
|
return -2;
|
|
}
|
|
|
|
// Extract event components
|
|
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
|
|
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
|
|
cJSON* tags_obj = cJSON_GetObjectItem(event, "tags");
|
|
|
|
if (!kind_obj || !pubkey_obj || !tags_obj) {
|
|
snprintf(error_message, error_size, "admin_auth: missing required fields");
|
|
return -2;
|
|
}
|
|
|
|
// Validation Layer 1: Kind Check
|
|
int event_kind = (int)cJSON_GetNumberValue(kind_obj);
|
|
if (event_kind != 23455 && event_kind != 23456) {
|
|
snprintf(error_message, error_size, "admin_auth: not an admin event kind");
|
|
return -1;
|
|
}
|
|
|
|
// Validation Layer 2: Relay Targeting Check
|
|
const char* relay_pubkey = get_config_value("relay_pubkey");
|
|
if (!relay_pubkey) {
|
|
snprintf(error_message, error_size, "admin_auth: relay pubkey not configured");
|
|
return -2;
|
|
}
|
|
|
|
// Check for 'p' tag targeting this relay
|
|
int has_relay_target = 0;
|
|
if (cJSON_IsArray(tags_obj)) {
|
|
cJSON* tag = NULL;
|
|
cJSON_ArrayForEach(tag, tags_obj) {
|
|
if (cJSON_IsArray(tag) && cJSON_GetArraySize(tag) >= 2) {
|
|
cJSON* tag_name = cJSON_GetArrayItem(tag, 0);
|
|
cJSON* tag_value = cJSON_GetArrayItem(tag, 1);
|
|
|
|
if (cJSON_IsString(tag_name) && cJSON_IsString(tag_value)) {
|
|
const char* name = cJSON_GetStringValue(tag_name);
|
|
const char* value = cJSON_GetStringValue(tag_value);
|
|
|
|
if (strcmp(name, "p") == 0 && strcmp(value, relay_pubkey) == 0) {
|
|
has_relay_target = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!has_relay_target) {
|
|
// Admin event for different relay - not unauthorized, just not for us
|
|
snprintf(error_message, error_size, "admin_auth: admin event for different relay");
|
|
return -1;
|
|
}
|
|
|
|
// Validation Layer 3: Admin Signature Check (only if targeting this relay)
|
|
const char* event_pubkey = cJSON_GetStringValue(pubkey_obj);
|
|
if (!event_pubkey) {
|
|
snprintf(error_message, error_size, "admin_auth: invalid pubkey format");
|
|
return -2;
|
|
}
|
|
|
|
const char* admin_pubkey = get_config_value("admin_pubkey");
|
|
if (!admin_pubkey || strcmp(event_pubkey, admin_pubkey) != 0) {
|
|
// This is the ONLY case where we log as "Unauthorized admin event attempt"
|
|
// because it's targeting THIS relay but from wrong admin
|
|
snprintf(error_message, error_size, "admin_auth: unauthorized admin for this relay");
|
|
log_warning("SECURITY: Unauthorized admin event attempt for this relay");
|
|
return -1;
|
|
}
|
|
|
|
// All validation layers passed
|
|
log_info("ADMIN: Admin event authorized");
|
|
return 0;
|
|
}
|
|
|
|
```
|
|
|
|
#### 1.2 Update Event Routing Logic
|
|
**Location**: [`main.c:3248`](src/main.c:3248)
|
|
|
|
```c
|
|
// Current problematic code:
|
|
if (event_kind == 23455 || event_kind == 23456) {
|
|
// Admin event processing
|
|
int admin_result = process_admin_event_in_config(event, admin_error, sizeof(admin_error), wsi);
|
|
} else {
|
|
// Regular event storage and broadcasting
|
|
}
|
|
|
|
// Enhanced secure code with consolidated authorization:
|
|
if (result == 0) {
|
|
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
|
|
if (kind_obj && cJSON_IsNumber(kind_obj)) {
|
|
int event_kind = (int)cJSON_GetNumberValue(kind_obj);
|
|
|
|
// Check if this is an admin event
|
|
if (event_kind == 23455 || event_kind == 23456) {
|
|
// Use consolidated authorization check
|
|
char auth_error[512] = {0};
|
|
int auth_result = is_authorized_admin_event(event, auth_error, sizeof(auth_error));
|
|
|
|
if (auth_result == 0) {
|
|
// Authorized admin event - process through admin API
|
|
char admin_error[512] = {0};
|
|
int admin_result = process_admin_event_in_config(event, admin_error, sizeof(admin_error), wsi);
|
|
|
|
if (admin_result != 0) {
|
|
result = -1;
|
|
strncpy(error_message, admin_error, sizeof(error_message) - 1);
|
|
}
|
|
// Admin events are NOT broadcast to subscriptions
|
|
} else {
|
|
// Unauthorized admin event - treat as regular event
|
|
log_warning("Unauthorized admin event treated as regular event");
|
|
if (store_event(event) != 0) {
|
|
result = -1;
|
|
strncpy(error_message, "error: failed to store event", sizeof(error_message) - 1);
|
|
} else {
|
|
broadcast_event_to_subscriptions(event);
|
|
}
|
|
}
|
|
} else {
|
|
// Regular event - normal processing
|
|
if (store_event(event) != 0) {
|
|
result = -1;
|
|
strncpy(error_message, "error: failed to store event", sizeof(error_message) - 1);
|
|
} else {
|
|
broadcast_event_to_subscriptions(event);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Phase 2: Enhanced Admin Event Processing
|
|
|
|
#### 2.1 Admin Event Validation in Config System
|
|
**Location**: [`src/config.c`](src/config.c) - [`process_admin_event_in_config()`](src/config.c:2065)
|
|
|
|
Add additional validation within the admin processing function:
|
|
|
|
```c
|
|
int process_admin_event_in_config(cJSON* event, char* error_buffer, size_t error_buffer_size, struct lws* wsi) {
|
|
// Double-check authorization (defense in depth)
|
|
if (!is_authorized_admin_event(event)) {
|
|
snprintf(error_buffer, error_buffer_size, "unauthorized: not a valid admin event");
|
|
return -1;
|
|
}
|
|
|
|
// Continue with existing admin event processing...
|
|
// ... rest of function unchanged
|
|
}
|
|
```
|
|
|
|
#### 2.2 Logging and Monitoring
|
|
Add comprehensive logging for admin event attempts:
|
|
|
|
```c
|
|
// In the routing logic - enhanced logging
|
|
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
|
|
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
|
|
int event_kind = kind_obj ? cJSON_GetNumberValue(kind_obj) : -1;
|
|
const char* event_pubkey = pubkey_obj ? cJSON_GetStringValue(pubkey_obj) : "unknown";
|
|
|
|
if (is_authorized_admin_event(event)) {
|
|
char log_msg[256];
|
|
snprintf(log_msg, sizeof(log_msg),
|
|
"ADMIN EVENT: Authorized admin event (kind=%d) from pubkey=%.16s...",
|
|
event_kind, event_pubkey);
|
|
log_info(log_msg);
|
|
} else if (event_kind == 23455 || event_kind == 23456) {
|
|
// This catches unauthorized admin event attempts
|
|
char log_msg[256];
|
|
snprintf(log_msg, sizeof(log_msg),
|
|
"SECURITY: Unauthorized admin event attempt (kind=%d) from pubkey=%.16s...",
|
|
event_kind, event_pubkey);
|
|
log_warning(log_msg);
|
|
}
|
|
```
|
|
|
|
## Phase 3: Unified Output Flow Architecture
|
|
|
|
### 3.1 Current Output Flow Analysis
|
|
|
|
After analyzing both [`main.c`](src/main.c) and [`config.c`](src/config.c), the **admin event responses already flow through the standard WebSocket output pipeline**. This is the correct architecture and requires no changes.
|
|
|
|
#### Standard WebSocket Output Pipeline
|
|
|
|
**Regular Events** ([`main.c:2978-2996`](src/main.c:2978)):
|
|
```c
|
|
// Database query responses
|
|
unsigned char* buf = malloc(LWS_PRE + msg_len);
|
|
memcpy(buf + LWS_PRE, msg_str, msg_len);
|
|
lws_write(wsi, buf + LWS_PRE, msg_len, LWS_WRITE_TEXT);
|
|
free(buf);
|
|
```
|
|
|
|
**OK Responses** ([`main.c:3342-3375`](src/main.c:3342)):
|
|
```c
|
|
// Event processing results: ["OK", event_id, success_boolean, message]
|
|
unsigned char *buf = malloc(LWS_PRE + response_len);
|
|
memcpy(buf + LWS_PRE, response_str, response_len);
|
|
lws_write(wsi, buf + LWS_PRE, response_len, LWS_WRITE_TEXT);
|
|
free(buf);
|
|
```
|
|
|
|
#### Admin Event Output Pipeline (Already Unified)
|
|
|
|
**Admin Responses** ([`config.c:2363-2414`](src/config.c:2363)):
|
|
```c
|
|
// Admin query responses use IDENTICAL pattern
|
|
int send_websocket_response_data(struct lws* wsi, cJSON* response_data) {
|
|
unsigned char* buf = malloc(LWS_PRE + response_len);
|
|
memcpy(buf + LWS_PRE, response_str, response_len);
|
|
|
|
// Same lws_write() call as regular events
|
|
int result = lws_write(wsi, buf + LWS_PRE, response_len, LWS_WRITE_TEXT);
|
|
|
|
free(buf);
|
|
return result;
|
|
}
|
|
```
|
|
|
|
### 3.2 Unified Output Flow Confirmation
|
|
|
|
✅ **Admin responses already use the same WebSocket transmission mechanism as regular events**
|
|
|
|
✅ **Both admin and regular events use identical buffer allocation patterns**
|
|
|
|
✅ **Both admin and regular events use the same [`lws_write()`](src/config.c:2393) function**
|
|
|
|
✅ **Both admin and regular events follow the same cleanup patterns**
|
|
|
|
### 3.3 Output Flow Integration Points
|
|
|
|
The admin event processing in [`config.c:2436`](src/config.c:2436) already integrates correctly with the unified output system:
|
|
|
|
1. **Admin Query Processing** ([`config.c:2568-2583`](src/config.c:2568)):
|
|
- Auth queries return structured JSON via [`send_websocket_response_data()`](src/config.c:2571)
|
|
- System commands return status data via [`send_websocket_response_data()`](src/config.c:2631)
|
|
|
|
2. **Response Format Consistency**:
|
|
- Admin responses use standard JSON format
|
|
- Regular events use standard Nostr event format
|
|
- Both transmitted through same WebSocket pipeline
|
|
|
|
3. **Error Handling Consistency**:
|
|
- Admin errors returned via same WebSocket connection
|
|
- Regular event errors returned via OK messages
|
|
- Both use identical transmission mechanism
|
|
|
|
### 3.4 Key Architectural Benefits
|
|
|
|
**No Changes Required**: The output flow is already unified and correctly implemented.
|
|
|
|
**Security Separation**: Admin events are processed separately but responses flow through the same secure WebSocket channel.
|
|
|
|
**Performance Consistency**: Both admin and regular responses use the same optimized transmission path.
|
|
|
|
**Maintenance Simplicity**: Single WebSocket output pipeline reduces complexity and potential bugs.
|
|
|
|
### 3.5 Admin Event Flow Summary
|
|
|
|
```
|
|
Admin Event Input → Authorization Check → Admin Processing → Unified WebSocket Output
|
|
Regular Event Input → Validation → Storage + Broadcast → Unified WebSocket Output
|
|
```
|
|
|
|
Both flows converge at the **Unified WebSocket Output** stage, which is already correctly implemented.
|
|
|
|
## Phase 4: Integration Points for Secure Admin Event Routing
|
|
|
|
### 4.1 Configuration System Integration
|
|
|
|
**Required Configuration Values**:
|
|
- `admin_pubkey` - Public key of authorized administrator
|
|
- `relay_pubkey` - Public key of this relay instance
|
|
|
|
**Integration Points**:
|
|
1. [`get_config_value()`](src/config.c) - Used by authorization function
|
|
2. [`get_relay_pubkey_cached()`](src/config.c) - Used for relay targeting validation
|
|
3. Configuration loading during startup - Must ensure admin/relay pubkeys are available
|
|
|
|
### 4.3 Forward Declarations Required
|
|
|
|
**Location**: [`src/main.c`](src/main.c) - Add near other forward declarations (around line 230)
|
|
|
|
```c
|
|
// Forward declarations for enhanced admin event authorization
|
|
int is_authorized_admin_event(cJSON* event, char* error_message, size_t error_size);
|
|
```
|
|
|
|
### 4.4 Error Handling Integration
|
|
|
|
**Enhanced Error Response System**:
|
|
|
|
```c
|
|
// In main.c event processing - enhanced error handling for admin events
|
|
if (auth_result != 0) {
|
|
// Admin authorization failed - send detailed OK response
|
|
cJSON* event_id = cJSON_GetObjectItem(event, "id");
|
|
if (event_id && cJSON_IsString(event_id)) {
|
|
cJSON* response = cJSON_CreateArray();
|
|
cJSON_AddItemToArray(response, cJSON_CreateString("OK"));
|
|
cJSON_AddItemToArray(response, cJSON_CreateString(cJSON_GetStringValue(event_id)));
|
|
cJSON_AddItemToArray(response, cJSON_CreateBool(0)); // Failed
|
|
cJSON_AddItemToArray(response, cJSON_CreateString(auth_error));
|
|
|
|
// Send via standard WebSocket output pipeline
|
|
char *response_str = cJSON_Print(response);
|
|
if (response_str) {
|
|
size_t response_len = strlen(response_str);
|
|
unsigned char *buf = malloc(LWS_PRE + response_len);
|
|
if (buf) {
|
|
memcpy(buf + LWS_PRE, response_str, response_len);
|
|
lws_write(wsi, buf + LWS_PRE, response_len, LWS_WRITE_TEXT);
|
|
free(buf);
|
|
}
|
|
free(response_str);
|
|
}
|
|
cJSON_Delete(response);
|
|
}
|
|
}
|
|
```
|
|
|
|
### 4.5 Logging Integration Points
|
|
|
|
**Console Logging**: Uses existing [`log_warning()`](src/main.c:993), [`log_info()`](src/main.c:972) functions
|
|
|
|
**Security Event Categories**:
|
|
- Admin authorization success logged via `log_info()`
|
|
- Admin authorization failures logged via `log_warning()`
|
|
- Admin event processing logged via existing admin logging
|
|
|
|
## Phase 5: Detailed Function Specifications
|
|
|
|
### 5.1 Core Authorization Function
|
|
|
|
**Function**: `is_authorized_admin_event()`
|
|
**Location**: [`src/main.c`](src/main.c) or [`src/config.c`](src/config.c)
|
|
**Dependencies**:
|
|
- `get_config_value()` for admin/relay pubkeys
|
|
- `log_warning()` and `log_info()` for logging
|
|
- `cJSON` library for event parsing
|
|
|
|
**Return Values**:
|
|
- `0` - Event is authorized for admin processing
|
|
- `-1` - Event is unauthorized (treat as regular event)
|
|
- `-2` - Validation error (malformed event)
|
|
|
|
**Error Handling**: Detailed error messages in provided buffer for client feedback
|
|
|
|
### 5.2 Enhanced Event Routing
|
|
|
|
**Location**: [`main.c:3248-3340`](src/main.c:3248)
|
|
**Integration**: Replaces existing admin event routing logic
|
|
**Dependencies**:
|
|
- `is_authorized_admin_event()` for authorization
|
|
- `process_admin_event_in_config()` for admin processing
|
|
- `store_event()` and `broadcast_event_to_subscriptions()` for regular events
|
|
|
|
**Security Features**:
|
|
- Graceful degradation for unauthorized admin events
|
|
- Comprehensive logging of authorization attempts
|
|
- No broadcast of admin events to subscriptions
|
|
- Detailed error responses for failed authorization
|
|
|
|
### 5.4 Defense-in-Depth Validation
|
|
|
|
**Primary Validation**: In main event routing logic
|
|
**Secondary Validation**: In `process_admin_event_in_config()` function
|
|
**Tertiary Validation**: In individual admin command handlers
|
|
|
|
**Validation Layers**:
|
|
1. **Kind Check** - Must be admin event kind (23455/23456)
|
|
2. **Relay Targeting Check** - Must have 'p' tag with this relay's pubkey
|
|
3. **Admin Signature Check** - Must be signed by authorized admin (only if targeting this relay)
|
|
4. **Processing Check** - Additional validation in admin handlers
|
|
|
|
**Security Logic**:
|
|
- If no 'p' tag for this relay → Admin event for different relay (not unauthorized)
|
|
- If 'p' tag for this relay + wrong admin signature → "Unauthorized admin event attempt"
|
|
|
|
## Phase 6: Event Flow Documentation
|
|
|
|
### 6.1 Complete Event Processing Flow
|
|
|
|
```
|
|
┌─────────────────┐
|
|
│ WebSocket Input │
|
|
└─────────┬───────┘
|
|
│
|
|
▼
|
|
┌─────────────────┐
|
|
│ Unified │
|
|
│ Validation │ ← nostr_validate_unified_request()
|
|
└─────────┬───────┘
|
|
│
|
|
▼
|
|
┌─────────────────┐
|
|
│ Kind-Based │
|
|
│ Routing Check │ ← Check if kind 23455/23456
|
|
└─────────┬───────┘
|
|
│
|
|
┌────▼────┐
|
|
│ Admin? │
|
|
└────┬────┘
|
|
│
|
|
┌─────▼─────┐ ┌─────────────┐
|
|
│ YES │ │ NO │
|
|
│ │ │ │
|
|
▼ │ ▼ │
|
|
┌─────────────┐ │ ┌─────────────┐ │
|
|
│ Admin │ │ │ Regular │ │
|
|
│ Authorization│ │ │ Event │ │
|
|
│ Check │ │ │ Processing │ │
|
|
└─────┬───────┘ │ └─────┬───────┘ │
|
|
│ │ │ │
|
|
┌────▼────┐ │ ▼ │
|
|
│Authorized?│ │ ┌─────────────┐ │
|
|
└────┬────┘ │ │ store_event()│ │
|
|
│ │ │ + │ │
|
|
┌─────▼─────┐ │ │ broadcast() │ │
|
|
│ YES NO │ │ └─────┬───────┘ │
|
|
│ │ │ │ │ │ │
|
|
│ ▼ ▼ │ │ ▼ │
|
|
│┌─────┐┌───┴┐ │ ┌─────────────┐ │
|
|
││Admin││Treat│ │ │ WebSocket │ │
|
|
││API ││as │ │ │ OK Response │ │
|
|
││ ││Reg │ │ └─────────────┘ │
|
|
│└──┬──┘└───┬┘ │ │
|
|
│ │ │ │ │
|
|
│ ▼ │ │ │
|
|
│┌─────────┐│ │ │
|
|
││WebSocket││ │ │
|
|
││Response ││ │ │
|
|
│└─────────┘│ │ │
|
|
└───────────┴───┘ │
|
|
│ │
|
|
└───────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────┐
|
|
│ Unified │
|
|
│ WebSocket │
|
|
│ Output │
|
|
└─────────────┘
|
|
```
|
|
|
|
### 6.2 Security Decision Points
|
|
|
|
1. **Event Kind Check** - Identifies potential admin events
|
|
2. **Authorization Validation** - Three-layer security check
|
|
3. **Routing Decision** - Admin API vs Regular processing
|
|
4. **Response Generation** - Unified output pipeline
|
|
5. **Audit Logging** - Security event tracking
|
|
|
|
### 6.3 Error Handling Paths
|
|
|
|
**Validation Errors**: Return detailed error messages via OK response
|
|
**Authorization Failures**: Log security event + treat as regular event
|
|
**Processing Errors**: Return admin-specific error responses
|
|
**System Errors**: Fallback to standard error handling
|
|
|
|
This completes the comprehensive implementation plan for the enhanced admin event API structure with unified output flow architecture.
|