Add the service-to-service module
This commit is contained in:
214
service-to-service/scripts/run-all.sh
Executable file
214
service-to-service/scripts/run-all.sh
Executable 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"
|
||||
Reference in New Issue
Block a user