8000 GitHub - ICTU/GBO-demo: Demo omgeving voor GBO · GitHub
[go: up one dir, main page]

Skip to content
ICTUPublic

About

Demo omgeving voor GBO

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

1,151 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

GBO — Gemeenschappelijke Bronontsluiting

This repository is a runnable reference demo for GBO. It shows how a consumer can request data from a source, normally over OpenFSC with every request evaluated by an OpenFTV policy before the source returns data. The EUDI demo also contains one deliberately unsecured HTTP source to prove that onboarding is not technically coupled to FSC; that source is not a security example.

The unsecured source is mounted only by the local Compose setup and is not part of the deployable source configuration used by simulation environments.

The demo contains two ways to start that request:

  • DvTP consent — a citizen grants a consumer permission to request a specific data scope.
  • EUDI wallet issuance — a citizen shares a PID from a wallet and receives a source-backed credential.

The production-oriented flows use the same core path:

consumer → FSC Outway → FSC Inway → OpenFTV PDP → source sidecar → GraphQL source

Requirements

For every mode:

  • Docker with the Compose plugin;
  • Git, Make, Bash, curl, jq, OpenSSL and Python 3.9 or newer;
  • a local .env copied from .env.example;
  • non-empty FTV_POSTGRES_PASSWORD, FTV_ADL_READER_PASSWORD, EUDI_POSTGRES_PASSWORD, SOURCE_REGISTRY_PASSWORD and SOURCE_REGISTRY_READER_PASSWORD values in .env.

The complete EUDI flow additionally requires:

  • the pinned nl-wallet v0.5.0 submodule;
  • two public HTTPS URLs: one for the issuance server and one for the EUDI adapter;
  • a compatible test wallet with a valid urn:eudi:pid:nl:1 credential;
  • issuer and reader trust anchors already trusted by that wallet.

The test persona that works with all included wallet offers has mock BSN 999991772. The repository configures the issuance side; it does not install the wallet app or issue its PID.

Quick start

Clone the repository with its nl-wallet submodule and create the local configuration:

git clone --recurse-submodules https://github.com/ICTU/GBO-demo.git
cd GBO-demo
cp .env.example .env

Every mode needs these five values in .env. Any non-empty random value works on the local demo network, for example from openssl rand -hex 16; use a different value for each:

FTV_POSTGRES_PASSWORD=<local-random-password>
FTV_ADL_READER_PASSWORD=<local-random-reader-password>
EUDI_POSTGRES_PASSWORD=<local-random-password>
SOURCE_REGISTRY_PASSWORD=<local-random-writer-password>
SOURCE_REGISTRY_READER_PASSWORD=<local-random-reader-password>

Start the DvTP demo. It needs no public URLs, trust anchors or wallet:

make demo

The first run builds every image and takes a few minutes. Open the demo at:

Entry point URL
Demo landing page http://localhost:9000
Consumer mock http://localhost:9001
Consent portal http://localhost:9002
Developer portal http://localhost:9003
Jaeger (traces) http://localhost:9686

Then follow the DvTP consent walkthrough.

The complete demo, with EUDI

make demo-full adds the EUDI wallet flow to DvTP. It additionally needs the public URLs in .env:

EUDI_PUBLIC_URL=https://eudi-is.example.org/
EUDI_BRI_URL=https://eudi-bri.example.org/

Before starting an EUDI demo, provide the issuer and reader CA material that the test wallet already trusts. The setup deliberately never generates trust anchors. By default it loads these four files from .local/secrets/development-ca/:

issuer-ca-key.pem
issuer-ca-cert.pem
reader-ca-key.pem
reader-ca-cert.pem

Keep the private keys outside version control. To load the same managed CA material from another location, set ONBOARDING_SECRETS_DIR to the absolute path of that secrets root; both the provisioning command and Compose use it. Setup fails before onboarding when any required CA file is absent.

Those CA private keys are provisioning input, not application runtime input. After the issuer, reader and status leaf certificates have been provisioned, the source reconciler receives only their public certificates and the two public CA certificates. Leaf private keys are mounted exclusively into the issuance materializer. A Kubernetes runtime Secret must not contain issuer-ca-key.pem or reader-ca-key.pem.

EUDI_PUBLIC_URL must route to the issuance server and its hostname must be in the reader-certificate DNS SAN. EUDI_BRI_URL must route to the EUDI adapter. See source onboarding for the local certificate and activation model.

With an existing reverse proxy or externally managed tunnel, start everything with:

make demo-full

To use the bundled Cloudflare connector, also set CLOUDFLARE_TUNNEL_TOKEN and run:

COMPOSE_FILE=docker-compose.yml:docker-compose.cloudflare-tunnel.yml make demo-full

Configure these Cloudflare Public Hostnames:

Public hostname Tunnel service
Host from EUDI_PUBLIC_URL http://eudi-issuance-server:18001
Host from EUDI_BRI_URL http://eudi-adapter:4009

Add a final hostname-wide Bypass cache rule for EUDI_PUBLIC_URL and purge that hostname once. Issuer metadata and QR sessions must not be cached.

The first EUDI build can take several minutes. make demo-full performs the complete idempotent bootstrap: FSC setup, contracts, source onboarding, development certificates, EUDI configuration and application startup.

Other modes

Command Starts
make demo-minimal Core services and observability only
make demo DvTP consent flow over OpenFSC
make demo-eudi EUDI wallet flow over OpenFSC
make demo-full DvTP and EUDI flows together
make demo-manager Core services with OpenFTV Manager policy distribution
make demo-down Stops the application and FSC stacks

make demo-manager needs the additional variables documented in its component README. Prefer the demo-* targets over starting Compose directly; they create certificates, contracts and generated configuration in the required order.

Walkthroughs

DvTP consent

The flow starts at the consumer, not at the consent portal: the portal only shows consents that already exist.

  1. Open the consumer mock and click Inloggen met DigiD. The consumer sends you to the consent portal with its request: service, purpose and the scopes bd:ib:2025 and bd:ib:2024.
  2. Pick any sign-in method. The DigiD mock takes any username and a mock BSN as the password, for example 123456789; the mock citizens are in services/graphql-server/mockdata/citizens.json.
  3. Choose the data sets to share and click Geef toestemming. The portal returns a signed consent token to the consumer mock, which immediately runs the query and shows the income data.
  4. Open the developer portal. History · Replay lists the issuance and the query with their outcome and trace id.
  5. In another tab, open the consent portal, sign in as the same citizen, open the consent and click Toestemming intrekken. Back in the consumer mock, expand Inkomensgegevens and click ↻ Verversen to repeat the query with the same token. The PDP now denies it. The consumer is told only FIELD_NOT_PERMITTED, so the consumer mock offers to consent again without naming a cause. The developer portal's history lists the request as DENY; the field's own reason, CONSENT_WITHDRAWN, is in the PDP decision log (docker compose logs openftv-pdp).

Starting again from the consumer mock's home page begins a new consent; it does not repeat the query, because the token only lives in that result page.

The developer portal injects DVTP_CONSUMER_PEER_ID into the dienstverlener_oin field of its predefined DvTP issuance scenarios. It uses 99999999900000000300 for local development when the variable is unset. Deployments must configure the FSC Peer ID of their actual consumer. Custom issuance payloads and user-saved scenarios keep the value entered by the user.

See the DvTP consent architecture and sequence diagrams for the component boundaries and the grant, use and revocation flows.

A service provider can also query through an integrator: a processor with an FSC peer of its own that connects on the provider's behalf over a delegated connection. The consent then binds the provider, the FSC delegator, and the integrator needs a mandate in the DvTP onboarding register. The demo uses the direct path by default; make demo-integrator adds the integrator to a running demo. See docs/integrator.md.

A qualified trust service provider (QTSP) can verify attributes of a citizen against the BRP through the ASIP mock of the ETSI TS 119 478 route (services/asi-provider, services/asi-authorization-server). The ASIP is an FSC consumer peer of its own (...1200). make demo-asi adds it to a running demo-eudi or demo-full; a stub of the Discover Interface (services/asi-discovery-stub) finds its data services per attribute, and a reference QTSP (services/qtsp-reference, http://localhost:9427) is where a user starts. The developer portal's ASI tab lays a request down at that QTSP, with scenarios for each verification result, and shows the outcome next to the chain. After a successful result, the QTSP offers a clearly marked, non-qualified test statement to the same citizen's NL Wallet. The existing central issuance server uses a separate QTSP usecase and certificate set and fetches the pending statement over internal HTTPS. See services/asi-provider/README.md, and docs/asi-route.md for what in this route is real, stubbed or a GBO interpretation.

S01 is the architecture role identifier used in the demo diagrams for the consent-register; it is not a separate service. The demo consent token is a bearer JWT. The consent-register generates an ephemeral P-256 key when CONSENT_SIGNING_KEY_PATH is empty, matching its default in-memory consent store. A persistent deployment must provide a stable PKCS#8 or SEC1 P-256 private key and set an explicit CONSENT_SIGNING_KEY_ID. The demo JWKS exposes only the current key; production rotation must retain old public keys until their tokens expire. Production hardening should also shorten the token lifetime and bind proof of possession to the FSC/mTLS identity (for example with a confirmation claim); the current explicit dienstverlener_oin check prevents cross-consumer use but does not make a stolen bearer token non-replayable by that same consumer.

Because the token returns through the browser, the consent portal only accepts http or https return URLs whose exact origin occurs in VITE_ALLOWED_RETURN_ORIGINS (comma-separated). Compose derives the demo value from DIENSTVERLENER_PUBLIC_URL; production images must supply the same value as a build argument.

EUDI wallet issuance

  1. Start make demo-full or make demo-eudi and open the landing page.
  2. Select income 2024, income 2025, the BRP/RvIG death certificate or the explicitly labelled unsecured demo credential.
  3. Scan the newly generated QR code with the compatible test wallet.
  4. Share the PID for mock BSN 999991772, review the credential preview and accept issuance.
  5. Inspect the resulting policy decision and trace in the developer portal.

QR sessions are stateful. Generate and scan a new QR after restarting the issuance server or changing activated source metadata.

How the demo works

The DvTP and FSC-backed EUDI entrypoints share transport, policy evaluation, identifier resolution and source access. Belastingdienst and BRP/RvIG are separate logical sources with their own metadata, data services, policies and certificate sets; this demo publishes both through one FSC participant. The EUDI adapter contains no hard-coded source or offer catalog: active source metadata generates the issuance-server products and the frontend offer list.

The demo combines production-grade components with deliberate test doubles:

Area In this repository
Transport OpenFSC managers, controllers, Inways and Outways
Authorization OpenFTV v0.1.0 PDP with OPA/Rego policies
Source access Go GraphQL services behind source-side sidecars
Observability OpenTelemetry, Jaeger, Loki and Grafana
Processing logs ldv-logboek per Verantwoordelijke — Belastingdienst, RvIG and GBO (LDV v1.0.0), alongside the PDP's ADL and FSC-Logging
Identity and data Synthetic citizens, deterministic BSNk and mock DigiD
Consent In-memory demo register

This is a reference architecture, not a production-ready deployment. In the EUDI demo, the disclosed subject is not yet independently cryptographically bound to the selected source record before policy evaluation. See SECURITY.md for the security boundary and known limitations.

What a denial tells the citizen

A denied request carries the code the policy gives the consumer, following the FTV GraphQL profile: it says that the request was refused, never why or which field. The reason per field stays with the PDP and the developer portal.

The disclosure decision sits in one place, on the server: services/dienstverlener-backend/consumer/denial.go maps the upstream reason onto a denial_code. Only FIELD_NOT_PERMITTED passes through — a refused field is the one denial the citizen can act on, by giving consent again. Everything else, including ACCESS_DENIED, an unrecognised code and every transport failure, becomes UNAVAILABLE.

The consumer frontend renders that code and nothing else (dienstverlener-mock/src/lib/denialMessage.ts). It never reads reason, which stays technical text for logs and the developer portal. A code the frontend does not know falls through to the generic message rather than being shown, so a code added to the policy later cannot surface on a citizen's screen by default.

The citizen screen shows the trace-id, which is something to quote to a helpdesk, and not the reason code, which is operator detail. The developer portal is where the full decision, its reason and the policy path are shown.

One caveat while running this demo: the FSC Inway drops the PDP's reason before it reaches the consumer (OpenFSC #308), so until that fix lands upstream every denial degrades to the generic message. The backend handles both cases; the specific messages appear once the Inway forwards the reason.

Testing

CI runs Go linting and tests, Rego validation and tests, frontend type checks, Helm validation and Docker builds. For a focused local change, run the same check in the affected component, for example:

(cd services/eudi-adapter && go test -timeout 60s ./...)

make policy-test   # opa check, format diff and Rego unit tests, same image as CI

See CONTRIBUTING.md for the pull-request checklist.

Editing policies

The PDP in the default stack loads policies/ from disk and hot-reloads on save. A module that does not compile is not rejected loudly: OpenFTV still logs policy added/replaced, stays healthy and keeps answering with the previous rules (#151). A rule whose change has no effect is therefore often a rule that never compiled. Run

make policy-check

after every edit. It reports a syntax error with file and line in a second or two, without the stack running. Every make target that starts the PDP runs it first.

Troubleshooting

Start by checking container state and logs:

docker compose ps
docker compose logs --tail=200

Common fixes:

  • use make demo-full or the matching demo-* target instead of starting individual containers;
  • after changing source metadata, rerun make onboard-demo-sources; the adapter observes the promoted release live. Run make eudi-config to rematerialize and restart the issuance-server;
  • scan a fresh QR after a restart or metadata change;
  • if Cloudflare reports HIT or an increasing Age for issuer metadata, correct the cache-bypass rule and purge the issuance hostname;
  • use make demo-down to stop both stacks, or make fsc-clean only when you intentionally want to remove disposable local FSC state.

The detailed diagnosis for port conflicts, certificates, FSC contracts, source activation, wallet trust and cached QR sessions is in TROUBLESHOOTING.md.

Further reading

GBO is maintained by the Dutch Ministry of the Interior and Kingdom Relations (BZK), Digital Government Directorate, with ICTU as technical steward.

Repository owner

The repository owner is Jeroen de Kok (ICTU), reachable at jeroen.dekok@ictu.nl. See publiccode.yml for additional repository metadata.

About

Demo omgeving voor GBO

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

0