Files
mrambossekandClaude Fable 5 ad85f3bfcd
server-release / image (push) Successful in 15s
server-test / test (push) Successful in 29s
server-release / release (push) Successful in 31s
enrollment: the server mints the §2.1 bootstrap link, the app consumes it
POST /admin/enroll-tokens now returns the whole link, not just the token:

  echolot://enroll?v=1&u=<control URL>&p=pin-sha256:<b64>&t=<token>

The server is the only party that knows all three parts at once, and the part
an operator gets wrong by hand is the base64 pin — which does not fail loudly,
it just never matches, surfacing days later as an inscrutable TLS error. The
app takes the link from a paste or from an echolot:// deep link (QR scan), and
writes URL, pin and credential together or not at all.

One trap the tests pin: an unencoded "+" in a query string decodes to a space,
so a hand-assembled link arrives with a pin wrong by one character. Base64 has
no spaces, so they are restored — unambiguous, and it cannot damage a correctly
encoded pin.

Also fixes a spec divergence: §2.1 names the field device_credential and the
first implementation shipped "credential". Both are sent now and the client
prefers the spec's; the alias goes once nothing reads it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-01 12:06:22 +02:00

59 lines
2.9 KiB
Bash

#!/usr/bin/env bash
# SPDX-FileCopyrightText: 2026 Echolot contributors
# SPDX-License-Identifier: GPL-3.0-or-later
#
# Runs core-protocol's LiveServerTest against the deployed fmr server: mints an
# enrollment token over SSH (admin is localhost-only), enrolls over the public
# control plane, computes the SPKI pin from the served cert, and hands the
# whole lot to the Gradle test. Proves the Kotlin client talks to the real
# server over the wire.
#
# Usage: JAVA_HOME=... echolot-app/scripts/test-fmr.sh [gradle-task] [test-filter]
# e.g. ... test-fmr.sh :core-engine:test '*LiveGrantedTest*'
set -euo pipefail
SSH_HOST="${ECHOLOT_SSH:-claude-echolot}"
CTL_HOST="${ECHOLOT_CTL_HOST:-fmr-1.echo-lot.app}"
CTL_PORT="${ECHOLOT_CTL_PORT:-8443}"
UDP_PORT="${ECHOLOT_UDP_PORT:-8442}"
CTL_URL="https://${CTL_HOST}:${CTL_PORT}"
echo "· minting enrollment token on ${SSH_HOST} ..."
MINTED=$(ssh -o BatchMode=yes "$SSH_HOST" \
'curl -s -X POST http://127.0.0.1:8444/admin/enroll-tokens')
TOKEN=$(printf '%s' "$MINTED" | python -c 'import json,sys;print(json.load(sys.stdin)["token"])')
# The server also returns the whole §2.1 bootstrap link. LiveEnrollmentTest redeems that link,
# which is what proves the Go side and the Kotlin side agree on its encoding — a disagreement
# there yields a pin wrong by one character, which fails much later and looks like anything but.
ENROLL_URI=$(printf '%s' "$MINTED" \
| python -c 'import json,sys;print(json.load(sys.stdin).get("enroll_uri",""))')
echo "· enrolling over ${CTL_URL} ..."
# A second token, because the one above is single-use and may be spent by LiveEnrollmentTest.
TOKEN2=$(ssh -o BatchMode=yes "$SSH_HOST" \
'curl -s -X POST http://127.0.0.1:8444/admin/enroll-tokens' \
| python -c 'import json,sys;print(json.load(sys.stdin)["token"])')
CRED=$(curl -sk -X POST "${CTL_URL}/v1/enroll" -H "Authorization: Bearer ${TOKEN2}" \
-H "X-Echolot-App-Version: 0.2.0" \
| python -c 'import json,sys;d=json.load(sys.stdin);print(d.get("device_credential") or d["credential"])')
echo "· computing SPKI pin from served cert ..."
PIN=$(echo | openssl s_client -connect "${CTL_HOST}:${CTL_PORT}" 2>/dev/null \
| openssl x509 -pubkey -noout \
| openssl pkey -pubin -outform der 2>/dev/null \
| openssl dgst -sha256 -binary | openssl base64)
echo "· pin=${PIN}"
TASK="${1:-:core-protocol:test}"
FILTER="${2:-*LiveServerTest*}"
echo "· running ${TASK} ${FILTER} ..."
cd "$(dirname "$0")/.."
ECHOLOT_LIVE_URL="$CTL_URL" \
ECHOLOT_LIVE_PIN="$PIN" \
ECHOLOT_LIVE_CRED="$CRED" \
ECHOLOT_LIVE_UDP="${CTL_HOST}:${UDP_PORT}" \
ECHOLOT_LIVE_TARGET="${ECHOLOT_LIVE_TARGET:-fmr}" \
ECHOLOT_ENROLL_URI="$ENROLL_URI" \
./gradlew "$TASK" --tests "$FILTER" --info --rerun-tasks --console=plain \
2>&1 | grep -E "profile:|capabilities:|session:|echo |primed|mtu probe|downtrain|big_send|largest|observations bytes|link:|parsed:|enrolled:|refused|Live[A-Za-z]*Test|BUILD|FAIL|PASS|^e:" || true