55 lines
3.0 KiB
Bash
Executable File
55 lines
3.0 KiB
Bash
Executable File
#!/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}"
|