mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-03 09:37:19 +01:00
Add Hermes gateway smoke harness
This commit is contained in:
1 parent
6f21eae0a4
commit
61b21059c9
3 files changed
+462
No files matched your search
@@ -0,0 +1,51 @@
|
||||
# syntax=docker/dockerfile:1.6
|
||||
ARG HERMES_VERSION=0.17.0
|
||||
FROM node:22-bookworm-slim
|
||||
|
||||
ARG HERMES_VERSION
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
bash \
|
||||
ca-certificates \
|
||||
curl \
|
||||
git \
|
||||
jq \
|
||||
python3 \
|
||||
python3-pip \
|
||||
tini \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# The npm package is a pinned bridge that exposes the `hermes` binary. Install
|
||||
# the matching Python package globally first so the non-root runtime user can
|
||||
# import the upstream Hermes modules at container start.
|
||||
RUN python3 -m pip install --break-system-packages --no-cache-dir "hermes-agent==${HERMES_VERSION}" \
|
||||
&& npm install -g --ignore-scripts "hermes-agent@${HERMES_VERSION}" \
|
||||
&& python3 -c "import hermes_cli.main" \
|
||||
&& command -v hermes >/dev/null
|
||||
|
||||
RUN useradd --create-home --shell /bin/bash --uid 10001 hermes \
|
||||
&& mkdir -p /home/hermes/.hermes /home/hermes/workspace \
|
||||
&& chown -R hermes:hermes /home/hermes
|
||||
|
||||
COPY entrypoint.sh /usr/local/bin/hermes-gateway-entrypoint
|
||||
RUN chmod 0755 /usr/local/bin/hermes-gateway-entrypoint
|
||||
|
||||
USER hermes
|
||||
WORKDIR /home/hermes/workspace
|
||||
|
||||
ENV HOME=/home/hermes \
|
||||
HERMES_HOME=/home/hermes/.hermes \
|
||||
XDG_CONFIG_HOME=/home/hermes/.config \
|
||||
XDG_CACHE_HOME=/home/hermes/.cache \
|
||||
XDG_DATA_HOME=/home/hermes/.local/share \
|
||||
API_SERVER_ENABLED=true \
|
||||
API_SERVER_HOST=0.0.0.0 \
|
||||
API_SERVER_PORT=8642 \
|
||||
NO_COLOR=1
|
||||
|
||||
EXPOSE 8642
|
||||
|
||||
ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/hermes-gateway-entrypoint"]
|
||||
Executable
+56
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
log() {
|
||||
echo "[hermes-gateway-smoke] $*"
|
||||
}
|
||||
|
||||
hash_prefix() {
|
||||
local value="$1"
|
||||
if command -v sha256sum >/dev/null 2>&1; then
|
||||
printf "%s" "$value" | sha256sum | awk '{print substr($1,1,12)}'
|
||||
elif command -v shasum >/dev/null 2>&1; then
|
||||
printf "%s" "$value" | shasum -a 256 | awk '{print substr($1,1,12)}'
|
||||
else
|
||||
printf "unavailable"
|
||||
fi
|
||||
}
|
||||
|
||||
generate_key() {
|
||||
node -e 'process.stdout.write(require("node:crypto").randomBytes(32).toString("hex"))'
|
||||
}
|
||||
|
||||
export HOME="${HOME:-/home/hermes}"
|
||||
export HERMES_HOME="${HERMES_HOME:-${HOME}/.hermes}"
|
||||
export XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-${HOME}/.config}"
|
||||
export XDG_CACHE_HOME="${XDG_CACHE_HOME:-${HOME}/.cache}"
|
||||
export XDG_DATA_HOME="${XDG_DATA_HOME:-${HOME}/.local/share}"
|
||||
export API_SERVER_ENABLED="${API_SERVER_ENABLED:-true}"
|
||||
export API_SERVER_HOST="${API_SERVER_HOST:-0.0.0.0}"
|
||||
export API_SERVER_PORT="${API_SERVER_PORT:-8642}"
|
||||
export NO_COLOR="${NO_COLOR:-1}"
|
||||
|
||||
if [[ "${API_SERVER_ENABLED}" != "true" ]]; then
|
||||
log "forcing API_SERVER_ENABLED=true for gateway smoke"
|
||||
export API_SERVER_ENABLED=true
|
||||
fi
|
||||
|
||||
if [[ -z "${API_SERVER_KEY:-}" ]]; then
|
||||
API_SERVER_KEY="$(generate_key)"
|
||||
export API_SERVER_KEY
|
||||
log "generated API_SERVER_KEY sha256=$(hash_prefix "$API_SERVER_KEY") len=${#API_SERVER_KEY}"
|
||||
else
|
||||
log "using provided API_SERVER_KEY sha256=$(hash_prefix "$API_SERVER_KEY") len=${#API_SERVER_KEY}"
|
||||
fi
|
||||
|
||||
mkdir -p "$HERMES_HOME" "$XDG_CONFIG_HOME" "$XDG_CACHE_HOME" "$XDG_DATA_HOME" "$HOME/workspace"
|
||||
chmod 0700 "$HERMES_HOME" "$XDG_CONFIG_HOME" "$XDG_CACHE_HOME" "$XDG_DATA_HOME" || true
|
||||
|
||||
log "HOME=${HOME}"
|
||||
log "HERMES_HOME=${HERMES_HOME}"
|
||||
log "workspace=$(pwd)"
|
||||
log "API_SERVER_HOST=${API_SERVER_HOST}"
|
||||
log "API_SERVER_PORT=${API_SERVER_PORT}"
|
||||
log "state listing hash=$(find "$HERMES_HOME" -maxdepth 2 -type f -print 2>/dev/null | sort | sha256sum | awk '{print substr($1,1,12)}')"
|
||||
|
||||
exec hermes gateway run --replace --accept-hooks "$@"
|
||||
Executable
+355
@@ -0,0 +1,355 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
log() {
|
||||
echo "[hermes-gateway-join] $*"
|
||||
}
|
||||
|
||||
warn() {
|
||||
echo "[hermes-gateway-join] WARN: $*" >&2
|
||||
}
|
||||
|
||||
fail() {
|
||||
echo "[hermes-gateway-join] ERROR: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
require_cmd() {
|
||||
local cmd="$1"
|
||||
command -v "$cmd" >/dev/null 2>&1 || fail "missing required command: ${cmd}"
|
||||
}
|
||||
|
||||
require_cmd curl
|
||||
require_cmd jq
|
||||
|
||||
PAPERCLIP_API_URL="${PAPERCLIP_API_URL:-http://localhost:3100}"
|
||||
API_BASE="${PAPERCLIP_API_URL%/}/api"
|
||||
COMPANY_ID="${COMPANY_ID:-${PAPERCLIP_COMPANY_ID:-}}"
|
||||
COMPANY_SELECTOR="${COMPANY_SELECTOR:-}"
|
||||
|
||||
HERMES_AGENT_NAME="${HERMES_AGENT_NAME:-Hermes Gateway Smoke Agent}"
|
||||
HERMES_GATEWAY_API_BASE_URL="${HERMES_GATEWAY_API_BASE_URL:-http://127.0.0.1:${HERMES_GATEWAY_PORT:-8642}}"
|
||||
HERMES_GATEWAY_API_KEY="${HERMES_GATEWAY_API_KEY:-${API_SERVER_KEY:-}}"
|
||||
HERMES_GATEWAY_ALLOW_INSECURE_HTTP="${HERMES_GATEWAY_ALLOW_INSECURE_HTTP:-0}"
|
||||
HERMES_GATEWAY_SESSION_KEY_STRATEGY="${HERMES_GATEWAY_SESSION_KEY_STRATEGY:-issue}"
|
||||
HERMES_GATEWAY_TIMEOUT_SEC="${HERMES_GATEWAY_TIMEOUT_SEC:-180}"
|
||||
PAPERCLIP_API_URL_FOR_HERMES="${PAPERCLIP_API_URL_FOR_HERMES:-}"
|
||||
GATEWAY_PROBE_TIMEOUT_SEC="${GATEWAY_PROBE_TIMEOUT_SEC:-4}"
|
||||
|
||||
AUTH_HEADERS=()
|
||||
if [[ -n "${PAPERCLIP_AUTH_HEADER:-}" ]]; then
|
||||
AUTH_HEADERS+=(-H "Authorization: ${PAPERCLIP_AUTH_HEADER}")
|
||||
elif [[ -n "${PAPERCLIP_API_KEY:-}" ]]; then
|
||||
AUTH_HEADERS+=(-H "Authorization: Bearer ${PAPERCLIP_API_KEY}")
|
||||
fi
|
||||
if [[ -n "${PAPERCLIP_COOKIE:-}" ]]; then
|
||||
AUTH_HEADERS+=(-H "Cookie: ${PAPERCLIP_COOKIE}")
|
||||
fi
|
||||
|
||||
RESPONSE_CODE=""
|
||||
RESPONSE_BODY=""
|
||||
|
||||
hash_prefix() {
|
||||
local value="$1"
|
||||
if command -v sha256sum >/dev/null 2>&1; then
|
||||
printf "%s" "$value" | sha256sum | awk '{print substr($1,1,12)}'
|
||||
elif command -v shasum >/dev/null 2>&1; then
|
||||
printf "%s" "$value" | shasum -a 256 | awk '{print substr($1,1,12)}'
|
||||
else
|
||||
printf "unavailable"
|
||||
fi
|
||||
}
|
||||
|
||||
redact_text() {
|
||||
local text="$1"
|
||||
local secret
|
||||
for secret in "${HERMES_GATEWAY_API_KEY:-}" "${CLAIM_SECRET:-}" "${AGENT_API_KEY:-}" "${PAPERCLIP_AUTH_HEADER:-}" "${PAPERCLIP_COOKIE:-}" "${PAPERCLIP_API_KEY:-}"; do
|
||||
if [[ -n "$secret" ]]; then
|
||||
text="${text//$secret/[redacted len=${#secret}]}"
|
||||
fi
|
||||
done
|
||||
printf "%s" "$text"
|
||||
}
|
||||
|
||||
print_response_error() {
|
||||
redact_text "$RESPONSE_BODY" >&2
|
||||
echo >&2
|
||||
}
|
||||
|
||||
api_request() {
|
||||
local method="$1"
|
||||
local path="$2"
|
||||
local data="${3-}"
|
||||
local tmp
|
||||
tmp="$(mktemp)"
|
||||
|
||||
local url
|
||||
if [[ "$path" == http://* || "$path" == https://* ]]; then
|
||||
url="$path"
|
||||
elif [[ "$path" == /api/* ]]; then
|
||||
url="${PAPERCLIP_API_URL%/}${path}"
|
||||
else
|
||||
url="${API_BASE}${path}"
|
||||
fi
|
||||
|
||||
if [[ -n "$data" ]]; then
|
||||
RESPONSE_CODE="$(curl -sS -o "$tmp" -w "%{http_code}" -X "$method" "${AUTH_HEADERS[@]}" -H "Content-Type: application/json" "$url" --data "$data")"
|
||||
else
|
||||
RESPONSE_CODE="$(curl -sS -o "$tmp" -w "%{http_code}" -X "$method" "${AUTH_HEADERS[@]}" "$url")"
|
||||
fi
|
||||
RESPONSE_BODY="$(cat "$tmp")"
|
||||
rm -f "$tmp"
|
||||
}
|
||||
|
||||
assert_status() {
|
||||
local expected="$1"
|
||||
if [[ "$RESPONSE_CODE" != "$expected" ]]; then
|
||||
print_response_error
|
||||
fail "expected HTTP ${expected}, got HTTP ${RESPONSE_CODE}"
|
||||
fi
|
||||
}
|
||||
|
||||
assert_json_has_string() {
|
||||
local jq_expr="$1"
|
||||
local value
|
||||
value="$(jq -r "$jq_expr // empty" <<<"$RESPONSE_BODY")"
|
||||
if [[ -z "$value" ]]; then
|
||||
print_response_error
|
||||
fail "expected JSON string at ${jq_expr}"
|
||||
fi
|
||||
echo "$value"
|
||||
}
|
||||
|
||||
fail_board_auth_required() {
|
||||
local operation="$1"
|
||||
print_response_error
|
||||
cat >&2 <<EOF
|
||||
[hermes-gateway-join] ERROR: ${operation} requires board/operator auth.
|
||||
|
||||
Provide one of:
|
||||
PAPERCLIP_AUTH_HEADER="Bearer <board-token>"
|
||||
PAPERCLIP_COOKIE="<board-session-cookie>"
|
||||
|
||||
Current auth context appears insufficient (HTTP ${RESPONSE_CODE}).
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
is_remote_plain_http() {
|
||||
local url="$1"
|
||||
[[ "$url" == http://* ]] || return 1
|
||||
case "$url" in
|
||||
http://localhost|http://localhost/*|http://localhost:*) return 1 ;;
|
||||
http://127.*|http://0.0.0.0|http://0.0.0.0/*|http://0.0.0.0:*) return 1 ;;
|
||||
http://\[::1\]|http://\[::1\]/*|http://\[::1\]:*) return 1 ;;
|
||||
esac
|
||||
return 0
|
||||
}
|
||||
|
||||
strip_trailing_slash() {
|
||||
local value="$1"
|
||||
while [[ "$value" == */ && "$value" != "http://" && "$value" != "https://" ]]; do
|
||||
value="${value%/}"
|
||||
done
|
||||
printf "%s" "$value"
|
||||
}
|
||||
|
||||
resolve_company_id() {
|
||||
if [[ -n "$COMPANY_ID" ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
log "resolving company id"
|
||||
api_request "GET" "/companies"
|
||||
if [[ "$RESPONSE_CODE" == "401" || "$RESPONSE_CODE" == "403" ]]; then
|
||||
fail_board_auth_required "Company resolution"
|
||||
fi
|
||||
assert_status "200"
|
||||
|
||||
if [[ -n "$COMPANY_SELECTOR" ]]; then
|
||||
COMPANY_ID="$(jq -r --arg selector "$COMPANY_SELECTOR" '
|
||||
map(select(
|
||||
(.id == $selector)
|
||||
or ((.issuePrefix // "") == $selector)
|
||||
or ((.name // "") == $selector)
|
||||
)) | .[0].id // empty
|
||||
' <<<"$RESPONSE_BODY")"
|
||||
[[ -n "$COMPANY_ID" ]] || fail "no company matched COMPANY_SELECTOR=${COMPANY_SELECTOR}"
|
||||
else
|
||||
COMPANY_ID="$(jq -r '.[0].id // empty' <<<"$RESPONSE_BODY")"
|
||||
[[ -n "$COMPANY_ID" ]] || fail "no companies found; create one before running smoke test"
|
||||
fi
|
||||
}
|
||||
|
||||
assert_onboarding_contains() {
|
||||
local body="$1"
|
||||
local needle="$2"
|
||||
if ! grep -Fq "$needle" <<<"$body"; then
|
||||
echo "$body" >&2
|
||||
fail "onboarding response missing expected text: ${needle}"
|
||||
fi
|
||||
}
|
||||
|
||||
probe_hermes_gateway() {
|
||||
[[ -n "$HERMES_GATEWAY_API_BASE_URL" ]] || fail "HERMES_GATEWAY_API_BASE_URL is required"
|
||||
[[ -n "$HERMES_GATEWAY_API_KEY" ]] || fail "HERMES_GATEWAY_API_KEY or API_SERVER_KEY is required before any Paperclip state is mutated"
|
||||
|
||||
if is_remote_plain_http "$HERMES_GATEWAY_API_BASE_URL" && [[ "$HERMES_GATEWAY_ALLOW_INSECURE_HTTP" != "1" ]]; then
|
||||
fail "HERMES_GATEWAY_API_BASE_URL uses non-loopback http. Set HERMES_GATEWAY_ALLOW_INSECURE_HTTP=1 for local-only unsafe HTTP, or use HTTPS."
|
||||
fi
|
||||
|
||||
local health_url="${HERMES_GATEWAY_API_BASE_URL%/}/health"
|
||||
log "probing Hermes gateway health at ${health_url} with apiKey sha256=$(hash_prefix "$HERMES_GATEWAY_API_KEY") len=${#HERMES_GATEWAY_API_KEY}"
|
||||
local code
|
||||
code="$(curl -sS -o /dev/null -w "%{http_code}" --max-time "$GATEWAY_PROBE_TIMEOUT_SEC" -H "Authorization: Bearer ${HERMES_GATEWAY_API_KEY}" "$health_url" || true)"
|
||||
if [[ "$code" != "200" ]]; then
|
||||
fail "Hermes gateway health probe failed before mutating Paperclip state: ${health_url} returned HTTP ${code}. Start Hermes with API_SERVER_ENABLED=true API_SERVER_KEY=<key> hermes gateway run --replace --accept-hooks, or set HERMES_GATEWAY_API_BASE_URL/HERMES_GATEWAY_API_KEY."
|
||||
fi
|
||||
}
|
||||
|
||||
log "checking Paperclip health"
|
||||
api_request "GET" "/health"
|
||||
assert_status "200"
|
||||
log "deployment mode=$(jq -r '.deploymentMode // "unknown"' <<<"$RESPONSE_BODY") exposure=$(jq -r '.deploymentExposure // "unknown"' <<<"$RESPONSE_BODY")"
|
||||
|
||||
resolve_company_id
|
||||
probe_hermes_gateway
|
||||
|
||||
log "creating agent-only invite for company ${COMPANY_ID}"
|
||||
INVITE_PAYLOAD="$(jq -nc '{allowedJoinTypes:"agent"}')"
|
||||
api_request "POST" "/companies/${COMPANY_ID}/invites" "$INVITE_PAYLOAD"
|
||||
if [[ "$RESPONSE_CODE" == "401" || "$RESPONSE_CODE" == "403" ]]; then
|
||||
fail_board_auth_required "Invite creation"
|
||||
fi
|
||||
assert_status "201"
|
||||
INVITE_TOKEN="$(assert_json_has_string '.token')"
|
||||
INVITE_ID="$(assert_json_has_string '.id')"
|
||||
log "created invite ${INVITE_ID}"
|
||||
|
||||
log "verifying onboarding JSON and text endpoints"
|
||||
api_request "GET" "/invites/${INVITE_TOKEN}/onboarding"
|
||||
assert_status "200"
|
||||
ONBOARDING_JSON="$RESPONSE_BODY"
|
||||
ONBOARDING_TEXT_PATH="$(jq -r '.invite.onboardingTextPath // empty' <<<"$ONBOARDING_JSON")"
|
||||
[[ -n "$ONBOARDING_TEXT_PATH" ]] || fail "onboarding manifest missing invite.onboardingTextPath"
|
||||
assert_onboarding_contains "$ONBOARDING_JSON" "hermes_gateway"
|
||||
assert_onboarding_contains "$ONBOARDING_JSON" "API_SERVER_ENABLED=true"
|
||||
assert_onboarding_contains "$ONBOARDING_JSON" "API_SERVER_KEY"
|
||||
assert_onboarding_contains "$ONBOARDING_JSON" "agentDefaultsPayload"
|
||||
|
||||
api_request "GET" "/invites/${INVITE_TOKEN}/onboarding.txt"
|
||||
assert_status "200"
|
||||
ONBOARDING_TEXT="$RESPONSE_BODY"
|
||||
assert_onboarding_contains "$ONBOARDING_TEXT" 'adapterType: "hermes_gateway"'
|
||||
assert_onboarding_contains "$ONBOARDING_TEXT" "API_SERVER_ENABLED=true"
|
||||
assert_onboarding_contains "$ONBOARDING_TEXT" "API_SERVER_KEY"
|
||||
assert_onboarding_contains "$ONBOARDING_TEXT" "hermes gateway run --replace --accept-hooks"
|
||||
assert_onboarding_contains "$ONBOARDING_TEXT" "agentDefaultsPayload.apiBaseUrl"
|
||||
|
||||
JOIN_PAYLOAD="$(jq -nc \
|
||||
--arg name "$HERMES_AGENT_NAME" \
|
||||
--arg apiBaseUrl "$HERMES_GATEWAY_API_BASE_URL" \
|
||||
--arg apiKey "$HERMES_GATEWAY_API_KEY" \
|
||||
--arg paperclipApiUrl "$PAPERCLIP_API_URL_FOR_HERMES" \
|
||||
--arg sessionKeyStrategy "$HERMES_GATEWAY_SESSION_KEY_STRATEGY" \
|
||||
--argjson timeoutSec "$HERMES_GATEWAY_TIMEOUT_SEC" \
|
||||
--argjson allowInsecure "$(if [[ "$HERMES_GATEWAY_ALLOW_INSECURE_HTTP" == "1" ]]; then echo true; else echo false; fi)" \
|
||||
'{
|
||||
requestType: "agent",
|
||||
agentName: $name,
|
||||
adapterType: "hermes_gateway",
|
||||
capabilities: "Hermes gateway Docker smoke harness",
|
||||
agentDefaultsPayload: {
|
||||
apiBaseUrl: $apiBaseUrl,
|
||||
apiKey: $apiKey,
|
||||
sessionKeyStrategy: $sessionKeyStrategy,
|
||||
timeoutSec: $timeoutSec
|
||||
}
|
||||
}
|
||||
| if $paperclipApiUrl != "" then .agentDefaultsPayload.paperclipApiUrl = $paperclipApiUrl else . end
|
||||
| if $allowInsecure then .agentDefaultsPayload.dangerouslyAllowInsecureRemoteHttp = true else . end')"
|
||||
|
||||
log "submitting Hermes gateway agent join request"
|
||||
api_request "POST" "/invites/${INVITE_TOKEN}/accept" "$JOIN_PAYLOAD"
|
||||
if [[ "$RESPONSE_CODE" != "202" ]]; then
|
||||
print_response_error
|
||||
fi
|
||||
assert_status "202"
|
||||
JOIN_REQUEST_ID="$(assert_json_has_string '.id')"
|
||||
CLAIM_SECRET="$(assert_json_has_string '.claimSecret')"
|
||||
CLAIM_API_PATH="$(assert_json_has_string '.claimApiKeyPath')"
|
||||
DIAGNOSTICS_JSON="$(jq -c '.diagnostics // []' <<<"$RESPONSE_BODY")"
|
||||
if [[ "$DIAGNOSTICS_JSON" != "[]" ]]; then
|
||||
log "join diagnostics: $(redact_text "$DIAGNOSTICS_JSON")"
|
||||
fi
|
||||
|
||||
if is_remote_plain_http "$HERMES_GATEWAY_API_BASE_URL"; then
|
||||
if ! jq -e '[.diagnostics[]? | select(.code == "hermes_gateway_plain_http_remote_unsafe_allowed")] | length > 0' <<<"$RESPONSE_BODY" >/dev/null; then
|
||||
fail "expected hermes_gateway_plain_http_remote_unsafe_allowed diagnostic for non-loopback HTTP join"
|
||||
fi
|
||||
fi
|
||||
|
||||
log "approving join request ${JOIN_REQUEST_ID}"
|
||||
api_request "POST" "/companies/${COMPANY_ID}/join-requests/${JOIN_REQUEST_ID}/approve" "{}"
|
||||
if [[ "$RESPONSE_CODE" == "401" || "$RESPONSE_CODE" == "403" ]]; then
|
||||
fail_board_auth_required "Join approval"
|
||||
fi
|
||||
assert_status "200"
|
||||
CREATED_AGENT_ID="$(assert_json_has_string '.createdAgentId')"
|
||||
|
||||
log "verifying invalid claim secret is rejected"
|
||||
api_request "POST" "/join-requests/${JOIN_REQUEST_ID}/claim-api-key" '{"claimSecret":"invalid-smoke-secret-value"}'
|
||||
if [[ "$RESPONSE_CODE" == "201" ]]; then
|
||||
fail "invalid claim secret unexpectedly succeeded"
|
||||
fi
|
||||
|
||||
log "claiming API key with one-time claim secret"
|
||||
CLAIM_PAYLOAD="$(jq -nc --arg secret "$CLAIM_SECRET" '{claimSecret:$secret}')"
|
||||
api_request "POST" "$CLAIM_API_PATH" "$CLAIM_PAYLOAD"
|
||||
assert_status "201"
|
||||
AGENT_API_KEY="$(assert_json_has_string '.token')"
|
||||
KEY_ID="$(assert_json_has_string '.keyId')"
|
||||
|
||||
log "verifying replay claim is rejected"
|
||||
api_request "POST" "$CLAIM_API_PATH" "$CLAIM_PAYLOAD"
|
||||
if [[ "$RESPONSE_CODE" == "201" ]]; then
|
||||
fail "claim secret replay unexpectedly succeeded"
|
||||
fi
|
||||
|
||||
log "verifying stored Hermes gateway agent config"
|
||||
api_request "GET" "/agents/${CREATED_AGENT_ID}"
|
||||
assert_status "200"
|
||||
|
||||
AGENT_ADAPTER_TYPE="$(jq -r '.adapterType // empty' <<<"$RESPONSE_BODY")"
|
||||
[[ "$AGENT_ADAPTER_TYPE" == "hermes_gateway" ]] || fail "expected adapterType=hermes_gateway, got ${AGENT_ADAPTER_TYPE}"
|
||||
|
||||
STORED_API_BASE_URL="$(jq -r '.adapterConfig.apiBaseUrl // empty' <<<"$RESPONSE_BODY")"
|
||||
[[ -n "$STORED_API_BASE_URL" ]] || fail "stored adapterConfig.apiBaseUrl is missing"
|
||||
if [[ "$(strip_trailing_slash "$STORED_API_BASE_URL")" != "$(strip_trailing_slash "$HERMES_GATEWAY_API_BASE_URL")" ]]; then
|
||||
fail "stored apiBaseUrl mismatch: expected $(strip_trailing_slash "$HERMES_GATEWAY_API_BASE_URL"), got $(strip_trailing_slash "$STORED_API_BASE_URL")"
|
||||
fi
|
||||
|
||||
if jq -e --arg raw "$HERMES_GATEWAY_API_KEY" '.adapterConfig.apiKey == $raw' <<<"$RESPONSE_BODY" >/dev/null; then
|
||||
fail "stored adapterConfig.apiKey leaked the raw Hermes API key"
|
||||
fi
|
||||
if ! jq -e '(.adapterConfig.apiKey.type // "") == "secret_ref"' <<<"$RESPONSE_BODY" >/dev/null; then
|
||||
warn "stored adapterConfig.apiKey is not a visible secret_ref; response shape may redact it entirely"
|
||||
fi
|
||||
|
||||
STORED_SESSION_STRATEGY="$(jq -r '.adapterConfig.sessionKeyStrategy // empty' <<<"$RESPONSE_BODY")"
|
||||
[[ "$STORED_SESSION_STRATEGY" == "$HERMES_GATEWAY_SESSION_KEY_STRATEGY" ]] || fail "stored sessionKeyStrategy mismatch: expected ${HERMES_GATEWAY_SESSION_KEY_STRATEGY}, got ${STORED_SESSION_STRATEGY:-<empty>}"
|
||||
|
||||
if [[ -n "$PAPERCLIP_API_URL_FOR_HERMES" ]]; then
|
||||
STORED_PAPERCLIP_API_URL="$(jq -r '.adapterConfig.paperclipApiUrl // empty' <<<"$RESPONSE_BODY")"
|
||||
[[ "$STORED_PAPERCLIP_API_URL" == "$PAPERCLIP_API_URL_FOR_HERMES" || "$(strip_trailing_slash "$STORED_PAPERCLIP_API_URL")" == "$(strip_trailing_slash "$PAPERCLIP_API_URL_FOR_HERMES")" ]] \
|
||||
|| fail "stored paperclipApiUrl mismatch"
|
||||
fi
|
||||
|
||||
log "success"
|
||||
log "companyId=${COMPANY_ID}"
|
||||
log "inviteId=${INVITE_ID}"
|
||||
log "joinRequestId=${JOIN_REQUEST_ID}"
|
||||
log "agentId=${CREATED_AGENT_ID}"
|
||||
log "keyId=${KEY_ID}"
|
||||
log "hermesGatewayApiKeySha256=$(hash_prefix "$HERMES_GATEWAY_API_KEY") len=${#HERMES_GATEWAY_API_KEY}"
|
||||
log "agentApiKeySha256=$(hash_prefix "$AGENT_API_KEY") len=${#AGENT_API_KEY}"
|
||||
Reference in new issue
Block a user