35 KiB
Ginxsom Management System Design
Executive Summary
This document outlines the design for a secure management interface for ginxsom, a Blossom media storage server. The design adapts proven patterns from c-relay's management system while addressing ginxsom's specific requirements for blob storage, authentication, and FastCGI architecture.
Key Design Principles:
- Security First: NIP-17 gift wrap encryption for all admin commands
- Database-Centric: Configuration and state stored in SQLite
- Unified Handler: Tag-based routing similar to c-relay
- Two-Step Confirmation: Critical operations require explicit confirmation
- Backwards Compatible: Integrates with existing admin API without breaking changes
Table of Contents
- System Architecture
- Database Schema
- Keypair Management
- Authentication Architecture
- Management Commands
- API Design
- File Structure
- Implementation Plan
- Security Considerations
- Migration Strategy
1. System Architecture
1.1 Component Overview
flowchart TB
subgraph Client["Admin Client"]
CLI[CLI Tool / nak]
WEB[Web Interface]
end
subgraph Gateway["nginx Gateway"]
NGINX[nginx]
end
subgraph FastCGI["FastCGI Application"]
MAIN[main.c]
VALIDATOR[request_validator.c]
ADMIN[admin_api.c]
MGMT[management_handler.c<br/>NEW]
DM[dm_admin.c<br/>NEW]
end
subgraph Storage["Data Layer"]
DB[(SQLite Database)]
BLOBS[Blob Storage]
KEYS[Key Storage<br/>Memory Only]
end
CLI -->|NIP-17 Gift Wrap| NGINX
WEB -->|NIP-17 Gift Wrap| NGINX
NGINX -->|FastCGI| MAIN
MAIN --> VALIDATOR
VALIDATOR --> ADMIN
ADMIN --> MGMT
MGMT --> DM
DM --> DB
DM --> KEYS
MGMT --> BLOBS
style MGMT fill:#ff9
style DM fill:#ff9
style KEYS fill:#f99
1.2 Data Flow for Admin Commands
sequenceDiagram
participant Admin
participant nginx
participant FastCGI
participant Validator
participant DM Handler
participant Database
Admin->>nginx: POST /admin<br/>NIP-17 Gift Wrap
nginx->>FastCGI: Forward Request
FastCGI->>Validator: Validate Auth
Validator->>Validator: Decrypt Gift Wrap
Validator->>Validator: Verify Admin Pubkey
Validator-->>FastCGI: Auth Result
alt Auth Failed
FastCGI-->>Admin: 401/403 Error
else Auth Success
FastCGI->>DM Handler: Parse Command
DM Handler->>DM Handler: Extract Tags
DM Handler->>DM Handler: Route to Handler
alt Critical Operation
DM Handler->>Database: Store Pending Change
DM Handler-->>Admin: Confirmation Required
Admin->>nginx: POST /admin<br/>Confirmation Event
nginx->>FastCGI: Forward Confirmation
FastCGI->>DM Handler: Execute Pending
DM Handler->>Database: Apply Changes
else Non-Critical
DM Handler->>Database: Execute Directly
end
DM Handler->>DM Handler: Encrypt Response (NIP-44)
DM Handler-->>Admin: Encrypted Result
end
1.3 Integration with Existing System
The management system integrates with ginxsom's existing architecture:
Existing Components (Keep):
src/main.c: FastCGI request loop and routingsrc/request_validator.c: Centralized authenticationsrc/admin_api.c: Current admin API endpointsdb/schema.sql: Base database schema
New Components (Add):
src/management_handler.c: Unified command handlersrc/dm_admin.c: NIP-17 gift wrap processingsrc/keypair_manager.c: Server and admin key managementsrc/pending_changes.c: Two-step confirmation system
Modified Components:
src/main.c: Add/adminendpoint routingsrc/request_validator.c: Add NIP-17 validationdb/schema.sql: Add management tables
2. Database Schema
2.1 New Tables
-- Server keypair (relay identity)
CREATE TABLE IF NOT EXISTS server_keys (
id INTEGER PRIMARY KEY CHECK (id = 1), -- Singleton table
public_key TEXT NOT NULL CHECK (length(public_key) = 64),
private_key_encrypted TEXT NOT NULL, -- Encrypted with system key
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
last_rotated INTEGER,
UNIQUE(public_key)
);
-- Admin public keys (authorized administrators)
CREATE TABLE IF NOT EXISTS admin_keys (
id INTEGER PRIMARY KEY AUTOINCREMENT,
public_key TEXT NOT NULL UNIQUE CHECK (length(public_key) = 64),
name TEXT, -- Human-readable identifier
permissions TEXT NOT NULL DEFAULT 'full', -- JSON array of permissions
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
created_by TEXT, -- Admin pubkey who added this key
last_used INTEGER,
enabled INTEGER NOT NULL DEFAULT 1 CHECK (enabled IN (0, 1))
);
-- Pending changes requiring confirmation
CREATE TABLE IF NOT EXISTS pending_changes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
change_type TEXT NOT NULL, -- 'delete_blob', 'update_config', 'add_admin', etc.
change_data TEXT NOT NULL, -- JSON with change details
requested_by TEXT NOT NULL, -- Admin pubkey
requested_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
expires_at INTEGER NOT NULL, -- Confirmation deadline
confirmed INTEGER NOT NULL DEFAULT 0 CHECK (confirmed IN (0, 1)),
confirmed_at INTEGER,
executed INTEGER NOT NULL DEFAULT 0 CHECK (executed IN (0, 1)),
executed_at INTEGER,
result TEXT -- Execution result or error
);
-- Admin command audit log
CREATE TABLE IF NOT EXISTS admin_audit_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
admin_pubkey TEXT NOT NULL,
command TEXT NOT NULL, -- Command tag from event
parameters TEXT, -- JSON with command parameters
success INTEGER NOT NULL CHECK (success IN (0, 1)),
error_message TEXT,
timestamp INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
client_ip TEXT,
event_id TEXT -- Nostr event ID for traceability
);
-- NIP-17 gift wrap tracking (prevent replay)
CREATE TABLE IF NOT EXISTS gift_wrap_tracking (
event_id TEXT PRIMARY KEY,
admin_pubkey TEXT NOT NULL,
processed_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
expires_at INTEGER NOT NULL
);
-- Create indexes for performance
CREATE INDEX IF NOT EXISTS idx_admin_keys_pubkey ON admin_keys(public_key);
CREATE INDEX IF NOT EXISTS idx_admin_keys_enabled ON admin_keys(enabled);
CREATE INDEX IF NOT EXISTS idx_pending_changes_expires ON pending_changes(expires_at);
CREATE INDEX IF NOT EXISTS idx_pending_changes_requested_by ON pending_changes(requested_by);
CREATE INDEX IF NOT EXISTS idx_audit_log_timestamp ON admin_audit_log(timestamp);
CREATE INDEX IF NOT EXISTS idx_audit_log_admin ON admin_audit_log(admin_pubkey);
CREATE INDEX IF NOT EXISTS idx_gift_wrap_expires ON gift_wrap_tracking(expires_at);
2.2 Schema Extensions to Existing Tables
-- Add management-related config keys
INSERT OR IGNORE INTO config (key, value, description) VALUES
('server_pubkey', '', 'Server public key (relay identity)'),
('admin_dm_enabled', 'true', 'Enable NIP-17 DM-based admin commands'),
('admin_confirmation_timeout', '300', 'Seconds to confirm critical operations'),
('admin_session_timeout', '3600', 'Admin session timeout in seconds'),
('admin_rate_limit', '100', 'Max admin commands per hour per admin'),
('admin_audit_retention', '2592000', 'Audit log retention in seconds (30 days)');
2.3 Migration from Current Schema
The current schema already has:
configtable (unified configuration)blobstable (blob metadata)auth_rulestable (authentication rules)
Migration Strategy:
- Add new tables without modifying existing ones
- Populate
admin_keysfrom existingconfig.admin_pubkey - Generate server keypair on first startup if not exists
- Maintain backwards compatibility with existing admin API
3. Keypair Management
3.1 Server Keypair (Relay Identity)
Purpose: Identifies the ginxsom server in Nostr ecosystem
Generation:
// On first startup or explicit generation
int generate_server_keypair(void) {
unsigned char privkey[32];
unsigned char pubkey[32];
// Generate using nostr_core_lib
if (nostr_generate_keypair(privkey, pubkey) != NOSTR_SUCCESS) {
return -1;
}
// Store in database (encrypted)
char pubkey_hex[65];
char privkey_hex[65];
nostr_bytes_to_hex(pubkey, 32, pubkey_hex);
nostr_bytes_to_hex(privkey, 32, privkey_hex);
// Encrypt private key before storage
char encrypted_privkey[256];
encrypt_with_system_key(privkey_hex, encrypted_privkey);
// Store in database
return store_server_keypair(pubkey_hex, encrypted_privkey);
}
Storage:
- Public key: Stored in
server_keys.public_keyandconfig.server_pubkey - Private key: NEVER stored in database - kept in process memory only
- On startup: Load from database, decrypt, keep in memory
- On shutdown: Clear from memory securely
Command-Line Options:
# Generate new server keypair
ginxsom-fcgi --generate-server-key
# Import existing keypair
ginxsom-fcgi --import-server-key <hex_private_key>
# Show server public key
ginxsom-fcgi --show-server-pubkey
3.2 Admin Keypair Management
Purpose: Authorize administrators to manage the server
Initial Setup:
# Generate admin keypair (done by admin, not server)
ADMIN_PRIVKEY=$(nak key generate)
ADMIN_PUBKEY=$(echo "$ADMIN_PRIVKEY" | nak key public)
# Add admin to server (requires existing admin or direct DB access)
sqlite3 db/ginxsom.db << EOF
INSERT INTO admin_keys (public_key, name, permissions, created_by)
VALUES ('$ADMIN_PUBKEY', 'Primary Admin', '["full"]', 'system');
EOF
Runtime Management:
# Add new admin (via DM command)
nak event -k 14 --envelope --sec $ADMIN_PRIVKEY \
--tag command add_admin \
--tag pubkey $NEW_ADMIN_PUBKEY \
--tag name "Secondary Admin" \
--tag permissions '["read","write"]' \
| curl -X POST http://localhost:9001/admin -d @-
# List admins
nak event -k 14 --envelope --sec $ADMIN_PRIVKEY \
--tag command list_admins \
| curl -X POST http://localhost:9001/admin -d @-
# Revoke admin
nak event -k 14 --envelope --sec $ADMIN_PRIVKEY \
--tag command revoke_admin \
--tag pubkey $ADMIN_TO_REVOKE \
| curl -X POST http://localhost:9001/admin -d @-
3.3 Key Rotation
Server Key Rotation:
- Generate new keypair
- Update database
- Announce new pubkey via NIP-11 relay info
- Keep old key for 30 days for transition
Admin Key Rotation:
- Admin generates new keypair
- Uses old key to authorize new key
- Old key remains valid during transition period
- Explicit revocation of old key after confirmation
4. Authentication Architecture
4.1 NIP-17 Gift Wrap Overview
Why NIP-17?
- End-to-end encryption between admin and server
- Prevents eavesdropping on admin commands
- Provides forward secrecy
- Standard Nostr protocol (interoperable)
Gift Wrap Structure:
{
"kind": 1059,
"content": "<encrypted_seal>",
"tags": [
["p", "<server_pubkey>"]
],
"pubkey": "<random_ephemeral_key>",
"created_at": 1234567890,
"sig": "<signature>"
}
Seal Structure (encrypted in gift wrap):
{
"kind": 13,
"content": "<encrypted_rumor>",
"tags": [
["p", "<admin_pubkey>"]
],
"pubkey": "<admin_pubkey>",
"created_at": 1234567890
}
Rumor Structure (encrypted in seal):
{
"kind": 14,
"content": "",
"tags": [
["command", "list_blobs"],
["limit", "50"],
["offset", "0"]
],
"pubkey": "<admin_pubkey>",
"created_at": 1234567890
}
4.2 Authentication Flow
sequenceDiagram
participant Admin
participant Server
participant DB
Admin->>Admin: Create Kind 14 Rumor<br/>(command + params)
Admin->>Admin: Wrap in Kind 13 Seal<br/>(encrypt with server pubkey)
Admin->>Admin: Wrap in Kind 1059 Gift<br/>(encrypt with ephemeral key)
Admin->>Server: POST /admin<br/>Gift Wrap Event
Server->>Server: Unwrap Gift<br/>(decrypt with server privkey)
Server->>Server: Unwrap Seal<br/>(decrypt with admin pubkey)
Server->>Server: Extract Rumor<br/>(Kind 14 command)
Server->>DB: Check admin_pubkey authorized
alt Not Authorized
Server-->>Admin: 403 Forbidden
else Authorized
Server->>DB: Check gift wrap not replayed
alt Replayed
Server-->>Admin: 409 Conflict (Replay)
else Fresh
Server->>DB: Store gift wrap ID
Server->>Server: Parse command tags
Server->>Server: Execute command
Server->>DB: Log to audit_log
Server->>Server: Encrypt response (NIP-44)
Server-->>Admin: Encrypted Response
end
end
4.3 Validation Rules
Gift Wrap Validation:
- Event kind must be 1059
- Must have
ptag with server pubkey - Signature must be valid
- Created_at within acceptable time window (±5 minutes)
Seal Validation:
- Decryption must succeed with server private key
- Event kind must be 13
- Must have
ptag with admin pubkey
Rumor Validation:
- Decryption must succeed with admin public key
- Event kind must be 14
- Admin pubkey must be in
admin_keystable withenabled=1 - Must have
commandtag - Event ID not in
gift_wrap_tracking(prevent replay) - Created_at within acceptable time window
Integration with Existing Validator:
Add to src/request_validator.c:
// Add to event kind routing (around line 438)
else if (event_kind == 1059) {
// NIP-17 Gift Wrap for admin commands
int gift_wrap_result = validate_gift_wrap_admin(event);
if (gift_wrap_result != NOSTR_SUCCESS) {
result->valid = 0;
result->error_code = gift_wrap_result;
strcpy(result->reason, "Gift wrap validation failed");
cJSON_Delete(event);
return NOSTR_SUCCESS;
}
}
5. Management Commands
5.1 Command Categories
Blob Operations:
list_blobs- List blobs with filtersget_blob_info- Get detailed blob metadatadelete_blob- Delete blob (requires confirmation)mirror_blob- Mirror blob from another serververify_blob- Verify blob integrity
Storage Management:
get_storage_stats- Get storage usage statisticsget_disk_usage- Get disk space informationcleanup_orphans- Remove orphaned filesset_quota- Set storage quota for user
Configuration:
get_config- Get configuration valuesset_config- Set configuration value (requires confirmation)list_auth_rules- List authentication rulesadd_auth_rule- Add authentication ruleremove_auth_rule- Remove authentication rule (requires confirmation)
Statistics:
get_stats- Get server statisticsget_upload_stats- Get upload statistics by time periodget_user_stats- Get per-user statisticsget_bandwidth_stats- Get bandwidth usage
System:
get_health- Get system health statusget_version- Get server version infobackup_database- Create database backuprestore_database- Restore from backup (requires confirmation)rotate_logs- Rotate log files
Admin Management:
list_admins- List admin keysadd_admin- Add new admin key (requires confirmation)revoke_admin- Revoke admin key (requires confirmation)get_audit_log- Get admin audit log
5.2 Command Structure
Request Format (Kind 14 Rumor):
{
"kind": 14,
"content": "",
"tags": [
["command", "list_blobs"],
["limit", "50"],
["offset", "0"],
["filter_type", "image/*"],
["since", "1234567890"]
],
"pubkey": "<admin_pubkey>",
"created_at": 1234567890
}
Response Format (NIP-44 Encrypted):
{
"status": "success",
"command": "list_blobs",
"data": {
"blobs": [...],
"total": 1234,
"limit": 50,
"offset": 0
},
"timestamp": 1234567890
}
Error Response:
{
"status": "error",
"command": "list_blobs",
"error": {
"code": "INVALID_PARAMETER",
"message": "Invalid limit value",
"details": "Limit must be between 1 and 1000"
},
"timestamp": 1234567890
}
5.3 Two-Step Confirmation
Critical Operations Requiring Confirmation:
delete_blob- Permanent data lossset_config- System behavior changesadd_admin- Security implicationsrevoke_admin- Access control changesrestore_database- Data integrity risk
Confirmation Flow:
Step 1: Request
{
"kind": 14,
"tags": [
["command", "delete_blob"],
["sha256", "abc123..."]
]
}
Step 1: Response
{
"status": "confirmation_required",
"command": "delete_blob",
"pending_id": "42",
"details": {
"sha256": "abc123...",
"size": 1048576,
"type": "image/jpeg",
"uploaded_at": 1234567890
},
"expires_at": 1234568190,
"message": "This will permanently delete the blob. Send confirmation within 5 minutes."
}
Step 2: Confirmation
{
"kind": 14,
"tags": [
["command", "confirm"],
["pending_id", "42"]
]
}
Step 2: Response
{
"status": "success",
"command": "delete_blob",
"data": {
"sha256": "abc123...",
"deleted": true
},
"timestamp": 1234567900
}
6. API Design
6.1 Endpoint Routing
New Endpoint:
POST /admin- Unified admin command endpoint (NIP-17 gift wrap)
Existing Endpoints (Keep):
GET /api/stats- Statistics (existing admin API)GET /api/config- Configuration (existing admin API)PUT /api/config- Update config (existing admin API)GET /api/files- File listing (existing admin API)GET /api/health- Health check (existing admin API)
Integration Strategy:
- New
/adminendpoint for NIP-17 DM-based commands - Existing
/api/*endpoints remain for backwards compatibility - Both systems share same database and authentication
- Gradual migration path for clients
6.2 Request Handling
Handler Registration in src/main.c:
// Add to main request loop (around line 1640)
else if (strcmp(request_method, "POST") == 0 &&
strcmp(request_uri, "/admin") == 0) {
// Handle NIP-17 gift wrap admin commands
handle_admin_dm_request();
}
Unified Handler Pattern:
// src/management_handler.c
void handle_admin_dm_request(void) {
// 1. Read gift wrap event from request body
char *event_json = read_request_body();
// 2. Validate and unwrap gift wrap
admin_command_t cmd;
int result = unwrap_admin_gift_wrap(event_json, &cmd);
if (result != SUCCESS) {
send_admin_error(result, "Failed to unwrap gift wrap");
return;
}
// 3. Check admin authorization
if (!is_admin_authorized(cmd.admin_pubkey)) {
send_admin_error(ERROR_UNAUTHORIZED, "Not authorized");
return;
}
// 4. Check for replay
if (is_gift_wrap_replayed(cmd.event_id)) {
send_admin_error(ERROR_REPLAY, "Gift wrap already processed");
return;
}
// 5. Route to command handler
admin_response_t response;
result = route_admin_command(&cmd, &response);
// 6. Log to audit log
log_admin_command(&cmd, result);
// 7. Encrypt and send response
send_admin_response(&response, cmd.admin_pubkey);
}
6.3 Tag-Based Routing
Command Router:
int route_admin_command(admin_command_t *cmd, admin_response_t *response) {
const char *command = get_tag_value(cmd->tags, "command");
if (!command) {
return ERROR_MISSING_COMMAND;
}
// Blob operations
if (strcmp(command, "list_blobs") == 0) {
return handle_list_blobs(cmd, response);
} else if (strcmp(command, "get_blob_info") == 0) {
return handle_get_blob_info(cmd, response);
} else if (strcmp(command, "delete_blob") == 0) {
return handle_delete_blob(cmd, response);
}
// Storage management
else if (strcmp(command, "get_storage_stats") == 0) {
return handle_get_storage_stats(cmd, response);
} else if (strcmp(command, "cleanup_orphans") == 0) {
return handle_cleanup_orphans(cmd, response);
}
// Configuration
else if (strcmp(command, "get_config") == 0) {
return handle_get_config(cmd, response);
} else if (strcmp(command, "set_config") == 0) {
return handle_set_config(cmd, response);
}
// Admin management
else if (strcmp(command, "list_admins") == 0) {
return handle_list_admins(cmd, response);
} else if (strcmp(command, "add_admin") == 0) {
return handle_add_admin(cmd, response);
}
// Confirmation
else if (strcmp(command, "confirm") == 0) {
return handle_confirm_pending(cmd, response);
}
else {
return ERROR_UNKNOWN_COMMAND;
}
}
7. File Structure
7.1 New Files
Core Management:
src/management_handler.c - Unified command handler and routing
src/management_handler.h - Handler interface definitions
src/dm_admin.c - NIP-17 gift wrap processing
src/dm_admin.h - Gift wrap interface
src/keypair_manager.c - Server and admin key management
src/keypair_manager.h - Key management interface
src/pending_changes.c - Two-step confirmation system
src/pending_changes.h - Pending changes interface
Command Handlers:
src/commands/blob_commands.c - Blob operation handlers
src/commands/storage_commands.c - Storage management handlers
src/commands/config_commands.c - Configuration handlers
src/commands/stats_commands.c - Statistics handlers
src/commands/admin_commands.c - Admin management handlers
src/commands/system_commands.c - System operation handlers
Utilities:
src/utils/encryption.c - NIP-44 encryption utilities
src/utils/audit_log.c - Audit logging utilities
src/utils/response_builder.c - Response formatting
7.2 Modified Files
- Add
/adminendpoint routing (line ~1640) - Initialize management system on startup
- Add cleanup on shutdown
- Add Kind 1059 (gift wrap) validation (line ~438)
- Add gift wrap unwrapping logic
- Add replay prevention checks
- Keep existing endpoints for backwards compatibility
- Add integration hooks for new management system
- Share database access with new handlers
- Add new tables (server_keys, admin_keys, pending_changes, etc.)
- Add indexes for performance
- Add default configuration values
- Add new source files to build
- Add dependencies for new modules
- Update clean targets
7.3 Integration Points
With Existing Admin API:
// src/admin_api.c - Add integration function
void admin_api_notify_config_change(const char *key, const char *value) {
// Called by management system when config changes
// Invalidates cache, triggers reload
nostr_request_validator_force_cache_refresh();
}
With Request Validator:
// src/request_validator.c - Add gift wrap validation
int validate_gift_wrap_admin(cJSON *event) {
// Validate Kind 1059 structure
// Unwrap and validate seal
// Unwrap and validate rumor
// Check admin authorization
// Check replay prevention
return NOSTR_SUCCESS;
}
With Database:
// All handlers use shared database connection
// Consistent error handling
// Transaction support for atomic operations
8. Implementation Plan
8.1 Phase 1: Foundation (Week 1-2)
Goals:
- Database schema extensions
- Keypair management
- Basic gift wrap processing
Tasks:
- Create database migration script
- Implement
keypair_manager.c- Server keypair generation
- Admin keypair storage
- Key loading on startup
- Implement
dm_admin.c- Gift wrap unwrapping
- Seal decryption
- Rumor extraction
- Add gift wrap validation to
request_validator.c - Create test suite for gift wrap processing
Deliverables:
- Working keypair management
- Gift wrap unwrap/validation
- Database schema updated
- Unit tests passing
8.2 Phase 2: Command Infrastructure (Week 3-4)
Goals:
- Unified command handler
- Tag-based routing
- Response encryption
Tasks:
- Implement
management_handler.c- Request parsing
- Command routing
- Response building
- Implement
pending_changes.c- Two-step confirmation
- Timeout handling
- Execution tracking
- Add
/adminendpoint tomain.c - Implement audit logging
- Create command handler templates
Deliverables:
- Working command routing
- Two-step confirmation system
- Audit logging functional
- Integration tests passing
8.3 Phase 3: Core Commands (Week 5-6)
Goals:
- Implement essential management commands
- Integration with existing systems
Tasks:
- Implement blob commands
list_blobsget_blob_infodelete_blob(with confirmation)
- Implement storage commands
get_storage_statsget_disk_usagecleanup_orphans
- Implement config commands
get_configset_config(with confirmation)
- Implement admin commands
list_adminsadd_admin(with confirmation)revoke_admin(with confirmation)
- Integration testing with existing admin API
Deliverables:
- Core commands functional
- Integration with existing API
- End-to-end tests passing
8.4 Phase 4: Advanced Features (Week 7-8)
Goals:
- Statistics and monitoring
- System operations
- CLI tooling
Tasks:
- Implement stats commands
get_statsget_upload_statsget_user_stats
- Implement system commands
backup_databaserestore_databaserotate_logs
- Create CLI tool for admin operations
- Create web interface (optional)
- Performance optimization
- Security audit
Deliverables:
- All commands implemented
- CLI tool functional
- Performance benchmarks
- Security review complete
8.5 Phase 5: Documentation & Deployment (Week 9-10)
Goals:
- Complete documentation
- Deployment guides
- Migration tools
Tasks:
- Write admin documentation
- Create setup guides
- Create migration scripts
- Write troubleshooting guide
- Create example scripts
- Production deployment testing
Deliverables:
- Complete documentation
- Migration tools
- Deployment guides
- Production-ready release
9. Security Considerations
9.1 Threat Model
Threats:
- Unauthorized Access: Attacker gains admin privileges
- Replay Attacks: Reuse of captured gift wrap events
- Man-in-the-Middle: Interception of admin commands
- Privilege Escalation: Regular user gains admin access
- Data Exfiltration: Unauthorized access to blob data
- Denial of Service: Resource exhaustion via admin commands
Mitigations:
- NIP-17 Encryption: End-to-end encryption prevents MITM
- Gift Wrap Tracking: Prevents replay attacks
- Admin Key Management: Strict authorization checks
- Two-Step Confirmation: Prevents accidental critical operations
- Audit Logging: Tracks all admin actions
- Rate Limiting: Prevents DoS via admin commands
9.2 Key Security
Server Private Key:
- NEVER stored in database
- Loaded into memory on startup
- Cleared on shutdown
- Protected by OS memory protection
- Consider using secure enclave if available
Admin Private Keys:
- NEVER stored on server
- Managed by admin clients only
- Rotated regularly
- Revoked immediately if compromised
Database Encryption:
- Consider encrypting sensitive config values
- Use system keyring for encryption keys
- Implement key rotation mechanism
9.3 Access Control
Permission Levels:
{
"full": ["*"],
"read": ["list_*", "get_*"],
"write": ["list_*", "get_*", "set_*", "add_*"],
"admin": ["list_*", "get_*", "set_*", "add_*", "delete_*", "revoke_*"]
}
Permission Checks:
int check_admin_permission(const char *admin_pubkey, const char *command) {
// Load admin permissions from database
cJSON *permissions = get_admin_permissions(admin_pubkey);
// Check if command is allowed
if (has_permission(permissions, command)) {
return 1;
}
// Check wildcard permissions
if (has_permission(permissions, "*")) {
return 1;
}
return 0;
}
9.4 Audit Trail
What to Log:
- All admin commands (success and failure)
- Admin key additions/revocations
- Configuration changes
- Critical operations (delete, restore, etc.)
- Authentication failures
- Suspicious activity
Log Format:
{
"timestamp": 1234567890,
"admin_pubkey": "abc123...",
"command": "delete_blob",
"parameters": {"sha256": "def456..."},
"success": true,
"client_ip": "192.168.1.100",
"event_id": "ghi789..."
}
Log Retention:
- Keep audit logs for 30 days minimum
- Archive older logs to separate storage
- Implement log rotation
- Protect logs from tampering
10. Migration Strategy
10.1 Backwards Compatibility
Existing Admin API:
- Keep all existing
/api/*endpoints - No breaking changes to current API
- Gradual deprecation over 6 months
- Clear migration documentation
Database:
- Additive schema changes only
- No modifications to existing tables
- Migration script handles upgrades
- Rollback capability
Configuration:
- Existing config keys remain valid
- New config keys added with defaults
- No changes to config file format
10.2 Migration Steps
Step 1: Database Migration
# Backup existing database
cp db/ginxsom.db db/ginxsom.db.backup
# Run migration script
sqlite3 db/ginxsom.db < db/migrations/002_add_management_system.sql
# Verify migration
sqlite3 db/ginxsom.db "SELECT name FROM sqlite_master WHERE type='table';"
Step 2: Generate Server Keypair
# Generate server keypair
./build/ginxsom-fcgi --generate-server-key
# Verify keypair
./build/ginxsom-fcgi --show-server-pubkey
Step 3: Add Initial Admin
# Generate admin keypair
ADMIN_PRIVKEY=$(nak key generate)
ADMIN_PUBKEY=$(echo "$ADMIN_PRIVKEY" | nak key public)
# Add to database
sqlite3 db/ginxsom.db << EOF
INSERT INTO admin_keys (public_key, name, permissions, created_by)
VALUES ('$ADMIN_PUBKEY', 'Primary Admin', '["full"]', 'system');
EOF
# Save admin private key securely
echo "$ADMIN_PRIVKEY" > ~/.config/ginxsom/admin.key
chmod 600 ~/.config/ginxsom/admin.key
Step 4: Test New System
# Test gift wrap command
nak event -k 14 --envelope --sec $ADMIN_PRIVKEY \
--tag command list_admins \
| curl -X POST http://localhost:9001/admin -d @-
# Verify response
Step 5: Update Clients
- Update admin scripts to use new
/adminendpoint - Migrate from
/api/*to NIP-17 commands - Test thoroughly before production
10.3 Rollback Plan
If Issues Occur:
- Stop ginxsom service
- Restore database backup
- Revert to previous version
- Investigate issues
- Fix and retry migration
Database Rollback:
# Stop service
systemctl stop ginxsom
# Restore backup
cp db/ginxsom.db.backup db/ginxsom.db
# Restart service
systemctl start ginxsom
Appendix A: Command Reference
Blob Commands
list_blobs
{
"command": "list_blobs",
"limit": "50",
"offset": "0",
"filter_type": "image/*",
"since": "1234567890"
}
get_blob_info
{
"command": "get_blob_info",
"sha256": "abc123..."
}
delete_blob (requires confirmation)
{
"command": "delete_blob",
"sha256": "abc123..."
}
Storage Commands
get_storage_stats
{
"command": "get_storage_stats"
}
cleanup_orphans
{
"command": "cleanup_orphans",
"dry_run": "true"
}
Configuration Commands
get_config
{
"command": "get_config",
"key": "max_file_size"
}
set_config (requires confirmation)
{
"command": "set_config",
"key": "max_file_size",
"value": "209715200"
}
Admin Commands
list_admins
{
"command": "list_admins"
}
add_admin (requires confirmation)
{
"command": "add_admin",
"pubkey": "def456...",
"name": "Secondary Admin",
"permissions": "[\"read\",\"write\"]"
}
revoke_admin (requires confirmation)
{
"command": "revoke_admin",
"pubkey": "def456..."
}
Appendix B: Example Scripts
Setup Script
#!/bin/bash
# setup_admin.sh - Initial admin setup
set -e
echo "Ginxsom Admin Setup"
echo "==================="
# Generate admin keypair
echo "Generating admin keypair..."
ADMIN_PRIVKEY=$(nak key generate)
ADMIN_PUBKEY=$(echo "$ADMIN_PRIVKEY" | nak key public)
echo "Admin Public Key: $ADMIN_PUBKEY"
# Save private key
mkdir -p ~/.config/ginxsom
echo "$ADMIN_PRIVKEY" > ~/.config/ginxsom/admin.key
chmod 600 ~/.config/ginxsom/admin.key
echo "Private key saved to ~/.config/ginxsom/admin.key"
# Add to database
echo "Adding admin to database..."
sqlite3 db/ginxsom.db << EOF
INSERT INTO admin_keys (public_key, name, permissions, created_by)
VALUES ('$ADMIN_PUBKEY', 'Primary Admin', '["full"]', 'system');
EOF
echo "Admin setup complete!"
echo "Test with: ./admin_command.sh list_admins"
Admin Command Script
#!/bin/bash
# admin_command.sh - Send admin command
ADMIN_PRIVKEY=$(cat ~/.config/ginxsom/admin.key)
COMMAND=$1
shift
# Build tags
TAGS=""
for arg in "$@"; do
KEY=$(echo "$arg" | cut -d= -f1)
VALUE=$(echo "$arg" | cut -d= -f2-)
TAGS="$TAGS --tag $KEY \"$VALUE\""
done
# Send command
eval nak event -k 14 --envelope --sec "$ADMIN_PRIVKEY" \
--tag command "$COMMAND" \
$TAGS \
| curl -s -X POST http://localhost:9001/admin -d @- \
| jq .
Usage Examples
# List admins
./admin_command.sh list_admins
# Get storage stats
./admin_command.sh get_storage_stats
# List blobs
./admin_command.sh list_blobs limit=10 offset=0
# Delete blob (with confirmation)
./admin_command.sh delete_blob sha256=abc123...
# Then confirm
./admin_command.sh confirm pending_id=42
Conclusion
This design provides a secure, scalable management system for ginxsom that:
- Leverages Proven Patterns: Adapts c-relay's successful architecture
- Maintains Security: NIP-17 encryption and two-step confirmation
- Ensures Compatibility: Integrates with existing admin API
- Enables Growth: Extensible command system for future features
- Provides Auditability: Complete audit trail of admin actions
The phased implementation plan allows for incremental development and testing, while the migration strategy ensures smooth transition from the current system.
Next Steps:
- Review and approve design
- Begin Phase 1 implementation
- Set up development environment
- Create test infrastructure
- Start coding!