feat(hybrid-gateway): integrate muse-cli with Cloudflare netns isolation, symmetric sidechat routing, and 646-pip sync unblock
This commit is contained in:
Executable
+55
@@ -0,0 +1,55 @@
|
||||
#!/bin/bash
|
||||
# exec-sign.sh — call the bl exec-constrained server with SSH-signature auth.
|
||||
# No bearer token, no secret crosses the wire: you sign the request envelope
|
||||
# with your registered fleet key and the server verifies it against the
|
||||
# signers file. A signature is not a secret, so this never trips
|
||||
# secret-handling guardrails.
|
||||
#
|
||||
# Server: exec-constrained.py — named ops ONLY, no arbitrary shell.
|
||||
# Signature namespace: exec-constrained
|
||||
# Envelope: {"op","args","ts","nonce"} — op must be in the server allowlist:
|
||||
# dm.send, dm.thread, dm.read, job.run, chat.messages, chat.send,
|
||||
# health.check, exec.ping
|
||||
# Nonce: 16+ hex chars, replay-protected server-side. ts: unix epoch, ±300s skew.
|
||||
#
|
||||
# Usage: exec-sign.sh <op> '<args-json>' [identity] [keyfile] [url]
|
||||
# op a named op, e.g. exec.ping
|
||||
# args-json JSON object of the op's arguments, e.g. '{}'
|
||||
# identity defaults to operator-646 (must be a principal in the
|
||||
# server's signers file)
|
||||
# keyfile defaults to ~/.ssh/id_frontdoor
|
||||
# url defaults to https://exec.muse-dev.online/exec (cloudflared).
|
||||
# NOTE: the old VM Caddy /exec route to bl:8443 died with
|
||||
# exec-server.py — do not point this at the sslip.io URL.
|
||||
set -euo pipefail
|
||||
OP="${1:?usage: exec-sign.sh <op> '<args-json>' [identity] [keyfile] [url]}"
|
||||
# NOTE: do NOT write this as ${2:-{}} — bash matches the first } as the
|
||||
# expansion's close brace and appends a literal } when $2 is set.
|
||||
ARGS_JSON="${2-}"
|
||||
if [ -z "$ARGS_JSON" ]; then ARGS_JSON='{}'; fi
|
||||
IDENTITY="${3:-operator-646}"
|
||||
KEY="${4:-$HOME/.ssh/id_frontdoor}"
|
||||
URL="${5:-https://exec.muse-dev.online/exec}"
|
||||
|
||||
TS=$(date +%s)
|
||||
NONCE=$(python3 -c "import secrets; print(secrets.token_hex(16))")
|
||||
PAYLOAD=$(python3 -c "
|
||||
import json, sys
|
||||
op, args_json, ts, nonce = sys.argv[1:5]
|
||||
args = json.loads(args_json)
|
||||
if not isinstance(args, dict):
|
||||
raise SystemExit('args-json must be a JSON object')
|
||||
print(json.dumps({'op': op, 'args': args, 'ts': int(ts), 'nonce': nonce}))
|
||||
" "$OP" "$ARGS_JSON" "$TS" "$NONCE")
|
||||
SIG=$(printf '%s' "$PAYLOAD" | ssh-keygen -Y sign -f "$KEY" -n exec-constrained)
|
||||
BODY=$(python3 -c "
|
||||
import json, sys
|
||||
ident, payload, sig = sys.argv[1:4]
|
||||
print(json.dumps({'identity': ident, 'payload': payload, 'signature': sig}))
|
||||
" "$IDENTITY" "$PAYLOAD" "$SIG")
|
||||
# Cloudflare Bot Fight Mode blocks python-urllib POSTs (error 1010):
|
||||
# use curl with a browser User-Agent instead.
|
||||
curl -sS -X POST "$URL" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36' \
|
||||
--data "$BODY"
|
||||
Reference in New Issue
Block a user