Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
6682ca8
feat(mtls): make dev-mode mTLS a one-command toggle
constantine2nd Jul 21, 2026
be9c226
fix(build): stop set -e from swallowing build failures in the run scr…
constantine2nd Jul 21, 2026
3c863d3
fix(psd2): return 401, not 500, when TPP-Signature-Certificate is unu…
constantine2nd Jul 21, 2026
bc08fc0
fix(psd2): identify the TPP by the certificate its API standard actua…
constantine2nd Jul 21, 2026
e8a08af
style(psd2): tidy the leftovers of the certificate Box conversion
constantine2nd Jul 23, 2026
7c22599
docs(mtls): design proposal for terminating mTLS in production
constantine2nd Jul 23, 2026
5977109
docs(mtls): add the implementation plan to the topologies proposal
constantine2nd Jul 23, 2026
6dfe261
feat(mtls): canonicalise PSD2-CERT on ingress
constantine2nd Jul 23, 2026
89c3f74
docs(mtls): record the four design decisions
constantine2nd Jul 23, 2026
5794e6a
feat(mtls): identify the caller by asking whether the TLS peer is a f…
constantine2nd Jul 23, 2026
c6cd204
fix(mtls): drop '(dev-only)' from the boot log
constantine2nd Jul 23, 2026
7d95dbe
docs(mtls): the checked-in dev certificates are enough to test with
constantine2nd Jul 23, 2026
3852014
test(mtls): a development certificate set named for the roles it plays
constantine2nd Jul 23, 2026
61ba74b
test(mtls): keep the dev CA key so the counterpart app can sign again…
constantine2nd Jul 23, 2026
09b8014
fix(mtls): stop the run scripts calling mTLS dev-only
constantine2nd Jul 23, 2026
68ec1bd
fix(mtls): rewrite the hostname scheme without a clear-text URL literal
constantine2nd Jul 24, 2026
ff63f21
style(mtls): clear SonarQube findings on the mTLS work
constantine2nd Jul 24, 2026
567040a
fix(psd2): scope TPP-Signature-Certificate to Berlin Group only
constantine2nd Jul 24, 2026
46a0185
docs(mtls): rename MTLS_DEV_MODE.md to MTLS.md, lead with the trust p…
constantine2nd Jul 24, 2026
f9fd3a7
test(mtls): dev-behind-nginx harness — real nginx over the real middl…
constantine2nd Jul 24, 2026
251407e
docs(mtls): mark §11.4 dev-behind-nginx implemented
constantine2nd Jul 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ obp-api/src/main/resources/*
obp-api/src/test/resources/**
!obp-api/src/test/resources/frozen_type_meta_data
!obp-api/src/test/resources/logback-test.xml
# The development certificate set (scripts/generate_dev_certs.sh) is a test fixture and belongs in
# the repository. Without these two lines a regenerated set is silently untracked and the tests
# that depend on it fail everywhere except the machine that ran the script.
!obp-api/src/test/resources/cert/
!obp-api/src/test/resources/cert/**
*.iml
obp-api/src/main/resources/log4j.properties
obp-api/src/main/scripts/kafka/kafka_*
Expand Down
43 changes: 43 additions & 0 deletions dev/mtls-nginx/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# dev-behind-nginx — OBP-API behind an mTLS reverse proxy

Reproduces the production topology where a reverse proxy terminates the TPP's mutual TLS and
forwards the verified client certificate to OBP-API, which itself terminates a second mTLS hop from
the proxy. See [`docs/MTLS.md`](../../docs/MTLS.md) for the guide and
[`docs/MTLS_TOPOLOGIES.md`](../../docs/MTLS_TOPOLOGIES.md) for the design.

```
TPP client ──mTLS──▶ nginx ──mTLS──▶ OBP-API
tpp-client cert verifies client, forwards it as PSD2-CERT,
presents proxy-client cert to OBP-API
recognises nginx as a trusted forwarder
(mtls.trusted_proxy.*), reads the header as the caller
```

## The two ways to run it

**Automated (the behavioural check):** `NginxForwarderTest` — real nginx (this same image) in front
of the real caller-resolution middleware. It asserts the four things this topology gets wrong in
practice: the URL-encoded-header decode, the forwarder allowlist, spoof overwrite, and the
missed-overwrite misconfiguration. Requires Docker; skipped without it.

```sh
JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64 mvn -pl obp-api -am test \
-DwildcardSuites=bootstrap.http4s.NginxForwarderTest -DfailIfNoTests=false
```

**Manual (eyeball a full OBP-API through the proxy):** `docker-compose.yml` runs just nginx in front
of a separately-started OBP-API — see the header of that file for the exact commands. In short:
start OBP-API with `mtls.enabled=true` and nginx as a trusted forwarder, `docker compose up`, then
call OBP-API through nginx on port 8443 with the TPP client certificate.

## Files

| File | Role |
|---|---|
| `nginx.conf` | The reference proxy config: client-mTLS termination, `PSD2-CERT` = `$ssl_client_escaped_cert` (overwrite), mTLS to the upstream presenting the forwarder identity. |
| `docker-compose.yml` | Runs nginx from `nginx.conf` against a host-run OBP-API, for manual inspection. |

Both use the committed development certificate set (`obp-api/src/test/resources/cert/`): `obp-server`
as the server identity, `dev-ca` as the client-verification CA, `proxy-client` (`CN=nginx-dev-1`) as
nginx's forwarder identity, `tpp-client` as the calling TPP. Regenerate with
`scripts/generate_dev_certs.sh`.
42 changes: 42 additions & 0 deletions dev/mtls-nginx/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Manual reproduction of the "OBP-API behind an mTLS reverse proxy" topology.
#
# nginx terminates the TPP's mTLS and forwards the verified client certificate as PSD2-CERT to a
# running OBP-API over a second mTLS hop. OBP-API must be started separately (this compose only runs
# nginx) with mtls.enabled=true and nginx listed as a trusted forwarder:
#
# OBP_MTLS_ENABLED=true \
# OBP_MTLS_KEYSTORE_PATH=$PWD/obp-api/src/test/resources/cert/obp-server.p12 \
# OBP_MTLS_TRUSTSTORE_PATH=$PWD/obp-api/src/test/resources/cert/dev-truststore.p12 \
# OBP_MTLS_TRUSTED_PROXY_1_ISSUER='CN=OBP Dev CA,O=TESOBE GmbH,C=DE' \
# OBP_MTLS_TRUSTED_PROXY_1_SUBJECT='CN=nginx-dev-1,OU=Edge Proxy,O=TESOBE GmbH,C=DE' \
# OBP_MTLS_TRUST_FORWARDED_HEADER_WITHOUT_TLS=false \
# ./flushall_fast_build_and_run.sh --mtls
#
# Then, from the repo root:
# OBP_UPSTREAM=host.docker.internal:8080 docker compose -f dev/mtls-nginx/docker-compose.yml up
#
# and call OBP-API THROUGH nginx (port 8443), presenting the TPP client certificate:
# CERT=$PWD/obp-api/src/test/resources/cert
# curl --cacert $CERT/dev-ca.crt --cert $CERT/tpp-client.crt --key $CERT/tpp-client.key \
# https://localhost:8443/obp/v5.1.0/root
#
# The automated behavioural check is NginxForwarderTest (same nginx image, real middleware); this
# compose is for eyeballing a full OBP-API through the proxy.

services:
nginx:
image: nginx:1.27-alpine
ports:
- "8443:8443"
environment:
# Where OBP-API's mTLS listener is, from inside the container.
OBP_UPSTREAM: ${OBP_UPSTREAM:-host.docker.internal:8080}
extra_hosts:
- "host.docker.internal:host-gateway"
volumes:
- ./nginx.conf:/etc/nginx/templates/nginx.conf.template:ro
- ../../obp-api/src/test/resources/cert:/certs:ro
# nginx:alpine substitutes ${OBP_UPSTREAM} from the template into the live config on startup.
command: >
/bin/sh -c "envsubst '$$OBP_UPSTREAM' < /etc/nginx/templates/nginx.conf.template > /etc/nginx/nginx.conf
&& exec nginx -g 'daemon off;'"
49 changes: 49 additions & 0 deletions dev/mtls-nginx/nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Reference nginx config for the "OBP-API behind a mTLS reverse proxy" topology.
#
# The client (a TPP) terminates mTLS at nginx; nginx re-establishes mTLS to OBP-API presenting its
# OWN certificate (the forwarder identity), and forwards the client's verified certificate as the
# PSD2-CERT header. OBP-API recognises nginx as a trusted forwarder (mtls.trusted_proxy.*) and reads
# the forwarded header as the caller. See docs/MTLS_TOPOLOGIES.md and docs/MTLS.md.
#
# This is the config a real deployment uses. NginxForwarderTest drives a superset of it (extra
# locations that reproduce the failure modes) against the same nginx image, so the behaviour here
# is what the automated harness verifies.

events {}

http {
upstream obp_api {
# OBP-API's mTLS listener. In the docker-compose here this resolves to the obp-api service;
# for a host-run server use host.docker.internal:8080 (or --network host + 127.0.0.1:8080).
server ${OBP_UPSTREAM};
}

server {
listen 8443 ssl;
server_name localhost;

# --- client side: terminate the TPP's mTLS ---------------------------------------------
ssl_certificate /certs/obp-server.crt; # what nginx presents to the TPP
ssl_certificate_key /certs/obp-server.key;
ssl_client_certificate /certs/dev-ca.crt; # the CA whose clients we accept
ssl_verify_client on; # reject a handshake with no client cert
ssl_verify_depth 2;

# --- upstream side: nginx authenticates itself to OBP-API over mTLS ---------------------
proxy_ssl_certificate /certs/proxy-client.crt; # nginx's forwarder identity (CN=nginx-dev-1)
proxy_ssl_certificate_key /certs/proxy-client.key;
proxy_ssl_verify off; # dev: don't verify OBP's server cert

location / {
proxy_pass https://obp_api;

# THE rule: overwrite PSD2-CERT with the certificate nginx just verified. Overwrite, not
# add — a client-supplied PSD2-CERT is a spoofing attempt and must not survive.
# $ssl_client_escaped_cert is URL-encoded PEM; OBP-API's Psd2CertIngress decodes it.
proxy_set_header PSD2-CERT $ssl_client_escaped_cert;

proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
}
}
}
Loading
Loading