Version: 1.0
Status: Design Document
Last Updated: 2025-01-17
MUXI Server uses HMAC-based authentication (like AWS) with key/secret pairs. Authentication is two-layered:
- Layer 1: Server Management API - Protected with server admin credentials
- Layer 2: Formation API - Proxied transparently, formations handle their own auth
- ✅ Simple: Just key + secret (like AWS credentials)
- ✅ Secure: HMAC signing, no secrets transmitted
- ✅ Separation: Server management ≠ Formation usage
- ✅ File-based: No environment variables, only config files
- ✅ CLI-only: SDKs talk to formations, not server management
┌─────────────────────────────────────────────────────┐
│ CLI (Admin) │
│ │
│ ~/.muxi/profiles.yaml │
│ key: "MUXI_abc123" │
│ secret: "sk_xyz789" │
└──────────────┬──────────────────────────────────────┘
│
│ HMAC-signed request
│ Authorization: MUXI-HMAC key=..., timestamp=..., signature=...
↓
┌─────────────────────────────────────────────────────┐
│ MUXI Server │
│ │
│ ~/.muxi/server/config.yaml │
│ auth: │
│ enabled: true │
│ key: "MUXI_abc123" │
│ secret: "sk_xyz789" │
│ │
│ Layer 1: Management API (Auth Required) │
│ POST /formations/deploy 🔒 │
│ GET /formations 🔒 │
│ DELETE /formations/{id} 🔒 │
│ │
│ Layer 2: Proxy API (Transparent) │
│ /{formation_id}/* → Formation runtime │
│ - No auth (pass-through) │
│ - Forward all headers │
└─────────────────────────────────────────────────────┘
│
│ Transparent proxy
↓
┌─────────────────────────────────────────────────────┐
│ Formation Runtime (Port 8001) │
│ │
│ Formation handles its own auth: │
│ - Admin key: "fa_admin_123" │
│ - Client key: "fc_client_456" │
│ │
│ Endpoints: │
│ POST /chat │
│ POST /workflow │
│ GET /health │
└─────────────────────────────────────────────────────┘
Location: ~/.muxi/server/config.yaml
server:
port: 3000
host: "0.0.0.0"
auth:
enabled: true
key: "MUXI_abc123def456"
secret: "sk_xyz789abc012def345"
formations:
port_range_start: 8000
port_range_end: 9000
# ... other settingsKey Format:
key: Public identifier, prefixMUXI_secret: Secret key, prefixsk_
Generation:
$ muxi-server init
🔐 Generating authentication credentials...
Key: MUXI_e8f3a9b2c4d1
Secret: sk_9f2e8d7c6b5a4f3e2d1c0b9a8f7e6d5c
📝 Saved to: ~/.muxi/server/config.yaml
⚠️ Keep your secret secure!
Add to CLI profile: muxi config add-profileManual Editing:
Users can edit ~/.muxi/server/config.yaml directly to set/change credentials.
Location: ~/.muxi/profiles.yaml
default:
key: "MUXI_abc123def456"
secret: "sk_xyz789abc012"
servers:
- "https://api.myserver.com"
production:
key: "MUXI_prod_key_123"
secret: "sk_prod_secret_456"
servers:
- "https://prod1.myserver.com"
- "https://prod2.myserver.com"
staging:
key: "MUXI_staging_789"
secret: "sk_staging_012"
servers:
- "https://staging.myserver.com"CLI Usage:
# Use default profile
muxi formation deploy app.yaml
# Use specific profile
muxi formation deploy app.yaml --profile=production
# Deploy to all servers in profile
muxi formation deploy app.yaml --profile=production --all-serversConfiguration Commands:
# Add profile
muxi config add-profile production \
--key=MUXI_prod_123 \
--secret=sk_prod_456 \
--server=https://prod.myserver.com
# List profiles
muxi config list-profiles
# Show profile
muxi config show-profile production
# Delete profile
muxi config delete-profile staging┌─────────┐ ┌──────────┐
│ CLI │ │ Server │
└────┬────┘ └────┬─────┘
│ │
│ 1. Load credentials from profile │
│ key = "MUXI_abc123" │
│ secret = "sk_xyz789" │
│ │
│ 2. Prepare request │
│ method = "POST" │
│ path = "/formations/deploy" │
│ timestamp = unix_timestamp() │
│ body = {...} │
│ │
│ 3. Create signing string │
│ message = "timestamp;method;path" │
│ message = "1705484123;POST;/formations/deploy"
│ │
│ 4. Sign with HMAC-SHA256 │
│ signature = HMAC-SHA256(secret, message)
│ signature = base64(signature) │
│ │
│ 5. Send request │
├───────────────────────────────────────────>│
│ Authorization: MUXI-HMAC key=MUXI_abc123, │
│ timestamp=1705484123, │
│ signature=base64(...) │
│ │
│ 6. Validate │
│ - Extract params │
│ - Check key │
│ - Recompute sig │
│ - Compare │
│ - Check time │
│ │
│ 7. ✅ or ❌ │
│<────────────────────────────────────────────│
│ Response (200 or 401) │
Authorization: MUXI-HMAC key=<KEY>, timestamp=<TIMESTAMP>, signature=<SIGNATURE>
Example:
Authorization: MUXI-HMAC key=MUXI_abc123, timestamp=1705484123, signature=YWJjZGVmZ2hpamtsbW5vcA==
Parameters:
key: Public key identifier (e.g.,MUXI_abc123)timestamp: Unix timestamp in seconds (e.g.,1705484123)signature: Base64-encoded HMAC-SHA256 signature
{timestamp};{method};{path}
Examples:
1705484123;POST;/formations/deploy
1705484200;GET;/formations
1705484300;DELETE;/formations/my-api
Pseudocode:
def generate_signature(secret, timestamp, method, path):
message = f"{timestamp};{method};{path}"
signature = hmac.new(
secret.encode('utf-8'),
message.encode('utf-8'),
hashlib.sha256
).digest()
return base64.b64encode(signature).decode('utf-8')Go Implementation:
func computeHMAC(secret, timestamp, method, path string) string {
message := fmt.Sprintf("%s;%s;%s", timestamp, method, path)
h := hmac.New(sha256.New, []byte(secret))
h.Write([]byte(message))
signature := h.Sum(nil)
return base64.StdEncoding.EncodeToString(signature)
}-
Extract Authorization Header
- Parse
MUXI-HMACformat - Extract
key,timestamp,signature
- Parse
-
Validate Key
- Check if
keymatchesconfig.auth.key - Constant-time comparison to prevent timing attacks
- Check if
-
Check Timestamp
- Parse timestamp to Unix time
- Verify timestamp is within 5 minutes of current time
- Prevents replay attacks
-
Recompute Signature
- Create signing string:
{timestamp};{method};{path} - Compute HMAC-SHA256 with
config.auth.secret - Base64 encode result
- Create signing string:
-
Compare Signatures
- Use constant-time comparison
- If match → ✅ authenticated
- If mismatch → ❌ 401 Unauthorized
Missing Authorization Header:
{
"error": "Unauthorized",
"message": "Missing authorization header",
"code": 401
}Invalid Key:
{
"error": "Unauthorized",
"message": "Invalid key",
"code": 401
}Expired Timestamp:
{
"error": "Unauthorized",
"message": "Request expired (timestamp too old)",
"code": 401
}Invalid Signature:
{
"error": "Unauthorized",
"message": "Invalid signature",
"code": 401
}All server management endpoints require authentication:
POST /formations/deploy- Deploy new formationGET /formations- List all formationsGET /formations/{id}- Get formation detailsPOST /formations/{id}/restart- Restart formationPOST /formations/{id}/stop- Stop formationDELETE /formations/{id}- Delete formationGET /formations/{id}/logs- Get formation logs
Formation proxy endpoints are transparent (no server auth):
/{formation_id}/*- All requests proxied to formation
Why? The formation handles its own authentication. Server just proxies.
Example:
# Server doesn't check auth, just forwards
curl https://api.myserver.com/my-formation/chat \
-H "Authorization: Bearer fc_formation_client_key" \
-d '{"message": "Hello"}'
# Formation receives:
# POST /chat
# Authorization: Bearer fc_formation_client_key
# Formation validates its own key-
Secret Never Transmitted
- Only signature is sent over network
- Even if HTTPS is compromised, secret is safe
-
Replay Attack Prevention
- Timestamp expires in 5 minutes
- Old requests automatically rejected
-
Tamper Detection
- Any modification to request invalidates signature
- Method, path changes detected
-
No Shared Secrets in Transit
- Unlike Bearer tokens, secret stays on both ends
- Similar to AWS SigV4
Default: 5 minutes
auth:
timestamp_tolerance: 300 # 5 minutes in secondsWhy 5 minutes?
- Allows for clock skew between client/server
- Short enough to prevent replay attacks
- Long enough to handle network delays
To rotate credentials:
-
Generate new key/secret:
muxi-server init --rotate
-
Update server config:
auth: key: "MUXI_new_key_789" secret: "sk_new_secret_012"
-
Update CLI profiles:
muxi config add-profile production \ --key=MUXI_new_key_789 \ --secret=sk_new_secret_012 \ --server=https://api.myserver.com
-
Restart server:
systemctl restart muxi-server
For local development, authentication can be disabled:
# ~/.muxi/server/config.yaml
auth:
enabled: falseWhen disabled:
- All management endpoints are open
- No authentication required
⚠️ Only use on localhost!
Use case: Local testing, development environments
# Default profile
muxi formation deploy app.yaml
# Specific profile
muxi formation deploy app.yaml --profile=production
# Override server
muxi formation deploy app.yaml --server=https://custom.commuxi formation list
muxi formation list --profile=productionmuxi formation delete my-api
muxi formation delete my-api --profile=productionSDKs talk to formations, not the server management API.
from muxi import MuxiClient
# SDK connects to formation (proxied through server)
client = MuxiClient(
server="https://api.myserver.com/my-formation", # Formation endpoint
admin_key="fa_admin_formation_key", # Formation's admin key
client_key="fc_client_formation_key" # Formation's client key
)
# These go to the formation, not server management
response = client.chat("Hello!", user_id="user_123")
workflow = client.run_workflow("onboarding", params={...})
client.close()Key Point: SDK uses formation's credentials, NOT server credentials!
- Add auth config to
pkg/config/config.go - Implement HMAC signature generation
- Implement HMAC signature validation
- Create auth middleware for management API
- Apply middleware to protected endpoints
- Exclude proxy routes from auth
- Add
muxi-server initcommand (generate credentials) - Add auth enable/disable config option
- Add timestamp tolerance config
- Add audit logging for auth failures
- Add tests for auth logic
- Profile management commands
- Load credentials from
~/.muxi/profiles.yaml - HMAC signature generation in HTTP client
- Send
Authorizationheader with requests - Handle 401 errors gracefully
- Profile switching (
--profileflag) - Multi-server deployment support
-
Valid Request
- Correct key, secret, timestamp
- Should return 200
-
Invalid Key
- Wrong key
- Should return 401
-
Invalid Signature
- Tampered signature
- Should return 401
-
Expired Timestamp
- Timestamp > 5 minutes old
- Should return 401
-
Auth Disabled
- Config:
auth.enabled: false - Should allow all requests
- Config:
-
Proxy Routes
/{formation_id}/*- Should not require server auth
# 1. Start server with auth
muxi-server start
# 2. Try without auth (should fail)
curl -X POST http://localhost:3000/formations/deploy
# Expected: 401 Unauthorized
# 3. Try with valid auth (should succeed)
# Generate signature with key/secret
curl -X POST http://localhost:3000/formations/deploy \
-H "Authorization: MUXI-HMAC key=MUXI_abc123, timestamp=..., signature=..."
# Expected: 201 Created
# 4. Try proxy route (should work without auth)
curl http://localhost:3000/my-formation/health
# Expected: 200 OK (from formation)- AWS Signature Version 4: https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html
- HMAC RFC 2104: https://www.rfc-editor.org/rfc/rfc2104
- HTTP Authentication RFC 7235: https://www.rfc-editor.org/rfc/rfc7235
Document Version: 1.0
Last Updated: 2025-01-17
Status: Ready for Implementation