1
0

Add the service-to-service module

This commit is contained in:
2026-08-28 10:02:47 +05:30
parent cad813e1ae
commit 0fceb2cd4e
37 changed files with 2566 additions and 4 deletions

View File

@@ -0,0 +1,214 @@
#!/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:]+)?/<timestamp>/g' \
-e 's/"(exp|iat|nbf)": [0-9]+/"\1": <epoch>/g' \
-e 's/"(jti|kid)": "[0-9a-f-]+"/"\1": "<uuid>"/g' \
-e 's/(JSESSIONID=)[0-9A-F]+/\1<session>/g' \
-e 's/issuedAt=[^]]*\]/issuedAt=<timestamp>]/g' \
-e 's/ [0-9]+ --- / <pid> --- /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 <client> <secret>
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 <reporting-service token>' 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"