#!/usr/bin/env bash # Regenerates every file under docs/output/ from a real run. Nothing in docs/output/ is # hand-written; if a claim in the article disagrees with a file here, the file is right. # # ./scripts/run-all.sh # # The whole module is started twice - once loose, once strict - plus a separate mTLS process, # because the difference between those runs IS the content. set -eu cd "$(dirname "$0")/.." OUT=docs/output mkdir -p "$OUT" hdr() { printf '%s\n%s\n%s\n\n' "$(printf '=%.0s' $(seq 1 78))" "$1" "$(printf '=%.0s' $(seq 1 78))"; } scrub() { sed -E \ -e 's/\r$//' \ -e 's/[0-9]{4}-[0-9]{2}-[0-9]{2}[T ][0-9:.]+(Z|\+[0-9:]+)?//g' \ -e 's/"(exp|iat|nbf)": [0-9]+/"\1": /g' \ -e 's/"(jti|kid)": "[0-9a-f-]+"/"\1": ""/g' \ -e 's/(JSESSIONID=)[0-9A-F]+/\1/g' \ -e 's/issuedAt=[^]]*\]/issuedAt=]/g' \ -e 's/ [0-9]+ --- / --- /g' \ -e '/Picked up JAVA_TOOL_OPTIONS/d' \ | cat -s } claims() { ./scripts/claims.sh "$1"; } json() { python3 -m json.tool 2>/dev/null || cat; } cc_token() { # cc_token curl -s -u "$1:$2" -X POST http://127.0.0.1:9000/oauth2/token \ -d grant_type=client_credentials -d scope=orders.read \ | python3 -c 'import json,sys; print(json.load(sys.stdin).get("access_token",""))' } ######################################################################################## # LOOSE RUN ######################################################################################## ./scripts/run.sh > /dev/null 2>&1 USER_TOKEN=$(./scripts/user-token.sh) { hdr "docs/output/01-user-token.txt A complete authorization_code + PKCE flow, driven by curl. No browser, no OIDC library. scripts/user-token.sh, then scripts/claims.sh" claims "$USER_TOKEN" echo echo "# sub is the human. scope is what the human consented to. aud names the service the" echo "# token was minted for - chapter 4 is about whether anybody looks at it." } | scrub > "$OUT/01-user-token.txt" { hdr "docs/output/02-five-strategies.txt The same request into the edge service, five ways of getting a token for the hop to downstream. Read the sub and scope of each downstream response. GET /edge/{naive,relay,client-credentials,exchange,relay-async}" for endpoint in naive relay client-credentials exchange relay-async; do echo "\$ curl -H \"Authorization: Bearer \$TOKEN\" 127.0.0.1:8081/edge/$endpoint" curl -s -H "Authorization: Bearer $USER_TOKEN" "http://127.0.0.1:8081/edge/$endpoint" | json echo done echo "# naive - 401. The control." echo "# relay - sub: alice, scope: [orders.write, orders.read]. The user's own" echo "# token, unchanged, including scopes downstream did not need." echo "# client-creds - sub: edge-service, scope: [orders.read]. Correctly scoped, and" echo "# the user has disappeared from downstream's audit log." echo "# exchange - sub: alice, scope: [orders.read]. Both. This is what RFC 8693 is" echo "# for and it is the one nobody reaches for." echo "# relay-async - 401. The relay interceptor reads SecurityContextHolder, which is" echo "# a ThreadLocal, and the call was made on a different thread." } | scrub > "$OUT/02-five-strategies.txt" { hdr "docs/output/03-audience-ignored.txt A token minted for a DIFFERENT service, presented to the downstream service. Default validators." WRONG=$(cc_token reporting-service reporting-secret) echo "# the token reporting-service was issued:" claims "$WRONG" echo echo "\$ curl -H 'Authorization: Bearer ' 127.0.0.1:8082/orders" curl -s -H "Authorization: Bearer $WRONG" http://127.0.0.1:8082/orders | json echo echo "# HTTP 200. The aud claim says reporting-api. The service is downstream-api." echo "# JwtValidators.createDefault() is a DelegatingOAuth2TokenValidator over three" echo "# validators - JwtTypeValidator, JwtTimestampValidator and" echo "# X509CertificateThumbprintValidator. Structure, expiry, and certificate binding." echo "# No issuer. No audience. Read back by reflection in ValidatorContractTests." } | scrub > "$OUT/03-audience-ignored.txt" { hdr "docs/output/04-gateway-token-relay.txt Spring Cloud Gateway Server MVC with 'filters: - TokenRelay='. GET /edge/relay through the gateway on 8080." echo "\$ curl -H \"Authorization: Bearer \$TOKEN\" 127.0.0.1:8080/edge/relay" curl -s -i -H "Authorization: Bearer $USER_TOKEN" http://127.0.0.1:8080/edge/relay \ | sed -n '1,/^\r$/p' | grep -viE '^(date|keep-alive|connection|content-length|transfer-encoding):' curl -s -H "Authorization: Bearer $USER_TOKEN" http://127.0.0.1:8080/edge/relay | json echo echo "\$ curl 127.0.0.1:8080/edge/relay # no Authorization header at all" curl -s -o /dev/null -w 'status %{http_code}\n' http://127.0.0.1:8080/edge/relay echo echo "# and the identical route with the TokenRelay filter REMOVED:" echo "\$ curl -H \"Authorization: Bearer \$TOKEN\" 127.0.0.1:8080/norelay/x" curl -s -H "Authorization: Bearer $USER_TOKEN" http://127.0.0.1:8080/norelay/x | json echo echo "# TokenRelay relays the access token of the currently authenticated USER - the one" echo "# obtained by oauth2Login(). This gateway has no oauth2Login, so there is no" echo "# authorized client to read a token from, and the filter contributes nothing. What" echo "# reaches the edge service is whatever Authorization header the caller sent, because" echo "# the gateway proxied it. TokenRelay is not 'forward the incoming bearer token'." } | scrub > "$OUT/04-gateway-token-relay.txt" ######################################################################################## # STRICT RUN ######################################################################################## STRICT=true ./scripts/run.sh > /dev/null 2>&1 { hdr "docs/output/05-strict-validation.txt The same tokens against JwtValidators.createAtJwtValidator().issuer(..).audience(..), with the authorization server emitting RFC 9068 tokens (typ: at+jwt, client_id claim). STRICT=true ./scripts/run.sh" WRONG=$(cc_token reporting-service reporting-secret) RIGHT=$(cc_token edge-service edge-secret) echo "# the wrong-audience token that was accepted in 03:" curl -s -i -H "Authorization: Bearer $WRONG" http://127.0.0.1:8082/orders \ | grep -iE '^(HTTP|WWW-Authenticate)' echo echo "# a token minted for this service:" curl -s -H "Authorization: Bearer $RIGHT" http://127.0.0.1:8082/orders | json echo echo "# and the edge service, which was NOT updated - it still uses Boot's" echo "# auto-configured decoder:" STRICT_USER=$(./scripts/user-token.sh) curl -s -i -H "Authorization: Bearer $STRICT_USER" http://127.0.0.1:8081/edge/relay \ | grep -iE '^(HTTP|WWW-Authenticate)' echo echo "# Turning on RFC 9068 at the authorization server broke every resource server that" echo "# still has NimbusJwtDecoder's default JOSE type verifier, and the error message" echo "# mentions neither RFC 9068 nor the authorization server." } | scrub > "$OUT/05-strict-validation.txt" ./scripts/stop.sh ######################################################################################## # mTLS ######################################################################################## ./scripts/certs.sh > /dev/null CP="target/classes:$(cat target/cp.txt)" setsid nohup java -Xmx160m -cp "$CP" com.ankurm.s2s.mtls.MtlsApplication \ > /tmp/s2s-Mtls.log 2>&1 < /dev/null & for _ in $(seq 1 60); do curl -s -o /dev/null -m 2 --cacert target/certs/internal-ca.crt \ --cert target/certs/edge.crt --key target/certs/edge.key \ https://localhost:8443/mtls/whoami && break sleep 1 done { hdr "docs/output/06-mtls.txt Client-certificate authentication on port 8443, server.ssl.client-auth=need. Certificates from scripts/certs.sh. edge.crt and rogue.crt have IDENTICAL subjects and different issuers." echo "\$ openssl x509 -in target/certs/edge.crt -noout -subject -issuer" openssl x509 -in target/certs/edge.crt -noout -subject -issuer echo "\$ openssl x509 -in target/certs/rogue.crt -noout -subject -issuer" openssl x509 -in target/certs/rogue.crt -noout -subject -issuer echo echo "\$ curl --cert edge.crt --key edge.key https://localhost:8443/mtls/whoami" curl -s --cacert target/certs/internal-ca.crt --cert target/certs/edge.crt \ --key target/certs/edge.key https://localhost:8443/mtls/whoami | json echo echo "\$ curl --cert rogue.crt --key rogue.key https://localhost:8443/mtls/whoami" curl -s --cacert target/certs/internal-ca.crt --cert target/certs/rogue.crt \ --key target/certs/rogue.key https://localhost:8443/mtls/whoami \ -w '[curl exit %{exitcode}, http %{http_code}]\n' 2>&1 | tail -1 echo echo "\$ curl https://localhost:8443/mtls/trusted-header # a permitAll() endpoint" curl -sk https://localhost:8443/mtls/trusted-header \ -w '[curl exit %{exitcode}, http %{http_code}]\n' 2>&1 | tail -1 echo echo "\$ curl --cert edge.crt --key edge.key -H 'X-Client-Cert-Subject: CN=payments-service' \\" echo " https://localhost:8443/mtls/trusted-header" curl -s --cacert target/certs/internal-ca.crt --cert target/certs/edge.crt \ --key target/certs/edge.key -H 'X-Client-Cert-Subject: CN=payments-service' \ https://localhost:8443/mtls/trusted-header echo echo echo "# Three things worth reading twice." echo "# 1. The rogue certificate fails with curl exit 56 and NO http status. The handshake" echo "# is rejected; the application never sees a request and logs nothing at INFO." echo "# 2. So does the permitAll() endpoint. client-auth=need is a property of the" echo "# CONNECTOR, not of a path. You cannot expose a public endpoint on that port." echo "# 3. The last call is what a mesh deployment usually looks like from inside the" echo "# application: an identity taken from a header, verified by nothing." } | scrub > "$OUT/06-mtls.txt" for pid in $(ps -eo pid,ppid,comm,args | awk '$3 ~ /^java/ && $0 ~ /com\.ankurm\.s2s\.mtls/ {print $1}'); do kill -9 "$pid" 2>/dev/null || true done ######################################################################################## # TESTS ######################################################################################## { hdr "docs/output/07-tests.txt mvn -B test" mvn -B test 2>&1 | grep -E 'Tests run|ERROR|BUILD' | head -30 } | scrub > "$OUT/07-tests.txt" echo "regenerated $(ls "$OUT" | wc -l) files under $OUT"