#!/usr/bin/env bash # Key rotation as three separate events, with the resource server watched in between. # Requires the stub issuer and a resource server pointed at it. set -eu . "$(dirname "$0")/lib.sh" PROFILES="${1:-unknown}" probe() { # prints just the status code for a token local token="$1" curl -s -o /dev/null -w '%{http_code}' -H "Authorization: Bearer $token" "$RS/api/me" } head1 "resource server profiles: $PROFILES" curl -s -X POST "$STUB/admin/reset-counter" >/dev/null echo "Issuer state at the start:"; stub_state head1 "0. Warm the cache" OLD=$(stub_token "sub=alice&aud=reports-api") echo "token signed with $(curl -s "$STUB/admin/state" | python3 -c 'import json,sys;print(json.load(sys.stdin)["activeKid"])')" echo "GET /api/me -> $(probe "$OLD")" echo "jwks fetches so far: $(stub_fetches)" head1 "1. PUBLISH a second key. Nothing signs with it yet." NEW=$(curl -s -X POST "$STUB/admin/publish" | python3 -c 'import json,sys;print(json.load(sys.stdin)["publishedKids"][-1])') echo "published: $NEW" stub_state echo echo "The resource server has not been told. Its cached JWK Set still holds one key." echo "Old token still works: $(probe "$OLD")" echo "jwks fetches so far: $(stub_fetches) <- unchanged: nothing forced a refresh" head1 "2. ACTIVATE the new key. The issuer starts signing with it." curl -s -X POST "$STUB/admin/activate?kid=$NEW" >/dev/null NEWTOK=$(stub_token "sub=alice&aud=reports-api") echo "A token arrives whose kid is not in the cached JWK Set." echo "New token: $(probe "$NEWTOK")" echo "jwks fetches so far: $(stub_fetches) <- the unknown kid forced one" echo echo "This is the recovery path, and it works. It is also the only thing in the default" echo "configuration that notices a rotation, because refresh-ahead is switched off." head1 "3. Tokens signed with the old key are still in flight" echo "They were minted before the switch and have not expired yet." echo "Old token: $(probe "$OLD") <- still accepted, because the old key is still published" head1 "4. RETIRE the old key from the JWK Set" OLDKID=$(curl -s "$STUB/admin/state" | python3 -c 'import json,sys;print(json.load(sys.stdin)["publishedKids"][0])') curl -s -X POST "$STUB/admin/retire?kid=$OLDKID" >/dev/null echo "retired: $OLDKID" stub_state echo echo "The resource server's cache still contains it, so nothing changes yet." echo "Old token: $(probe "$OLD")" echo "New token: $(probe "$NEWTOK")" echo "jwks fetches so far: $(stub_fetches)" echo echo "How long the old key keeps working from here is decided entirely by the cache." echo "See retired-key-demo.sh, which runs this same step under two cache configurations."