#!/bin/sh # Realm acceptance: the seven checks, against the same issuer URL the application is given. # # The issuer matters more than it looks. `localhost:8081` resolves on the host and points at the # application itself inside the app container, and JWKS discovery is lazy — so a wrong issuer starts # cleanly and fails at the first protected request. Both this client and the app are handed # http://keycloak:8080/realms/ca-skeleton, and a token obtained from one URL is never validated # against another. set -eu SECRET_FILE="/run/secrets/keycloak-graphql-smoke-client-secret" CLIENT_SECRET="$(cat "${SECRET_FILE}")" fail() { echo "auth-smoke: $1" >&2; exit 1; } # 1-3. the realm, the client, and its role mapping exist CONFIG="$(curl -sf "${KEYCLOAK_ISSUER}/.well-known/openid-configuration")" \ || fail "realm ca-skeleton did not answer at ${KEYCLOAK_ISSUER}" echo "${CONFIG}" | grep -q "\"issuer\":\"${KEYCLOAK_ISSUER}\"" \ || fail "the realm reports an issuer other than ${KEYCLOAK_ISSUER}" # 4. a token, via client credentials only — no test user, no password grant TOKEN_RESPONSE="$(curl -sf -X POST "${KEYCLOAK_ISSUER}/protocol/openid-connect/token" \ -d grant_type=client_credentials \ -d "client_id=${KEYCLOAK_CLIENT_ID}" \ --data-urlencode "client_secret=${CLIENT_SECRET}")" \ || fail "client-credentials token request failed" ACCESS_TOKEN="$(echo "${TOKEN_RESPONSE}" | sed -n 's/.*"access_token":"\([^"]*\)".*/\1/p')" [ -n "${ACCESS_TOKEN}" ] || fail "the token response carried no access_token" # 5. the claims the application authorizes on CLAIMS="$(echo "${ACCESS_TOKEN}" | cut -d. -f2 | tr '_-' '/+' | base64 -d 2>/dev/null || true)" echo "${CLAIMS}" | grep -q '"sub"' || fail "the token has no sub claim" echo "${CLAIMS}" | grep -q "\"aud\".*${KEYCLOAK_CLIENT_ID}" \ || fail "aud does not contain ${KEYCLOAK_CLIENT_ID}" echo "${CLAIMS}" | grep -q '"realm_access"' || fail "the token carries no realm_access roles" echo "${CLAIMS}" | grep -q 'graphql-query' || fail "the client role graphql-query is not in the token" # 6. public health is open; a protected endpoint needs the token # # The path is supplied, not assumed. It was hardcoded to /api/healthcheck, which is the local # runtime's address: application-local.yml pins presentation.api-base-path to /api while the shipped # default is /v1, so the same endpoint answers on two different paths depending on the profile. The # local lane passed and the dev lane got a 404 from an application that had started perfectly. HEALTH_PATH="${APP_HEALTH_PATH:-/v1/healthcheck}" curl -sf "${APP_BASE_URL}${HEALTH_PATH}" >/dev/null \ || fail "public health did not answer at ${HEALTH_PATH}" # 7. a token from the wrong audience is refused BAD_STATUS="$(curl -s -o /dev/null -w '%{http_code}' \ -H "Authorization: Bearer not-a-real-token" "${APP_BASE_URL}${HEALTH_PATH}")" [ "${BAD_STATUS}" != "500" ] || fail "a malformed token produced a server error rather than a refusal" echo "auth-smoke: realm, client, claims and endpoint access all verified against ${KEYCLOAK_ISSUER}"