What it does here
Caddy is the front door. It is the only container that publishes a port to the internet, and it terminates HTTPS for every console, routing each hostname to the right internal service.
It is supporting infrastructure rather than an IAM tool, but it earns its place in the guide: it is the single point of exposure, and understanding it is understanding why nothing else in the lab is reachable from outside.
How it is deployed
Container caddy, publishing ports 80 and 443. Its configuration is one readable file, the Caddyfile — see Installation §4.1.
What the configuration says
| Concern | How Caddy handles it here |
|---|---|
| TLS certificates | obtained and renewed automatically from Let's Encrypt — nothing to remember or rotate |
| Routing | one block per hostname, each proxying to an internal container |
| HTTP | redirected to HTTPS automatically |
| Self-signed back ends | Teleport and the Wazuh dashboard speak HTTPS internally with their own certificates; Caddy is told to accept them |
| Proxy headers | X-Forwarded-Proto is passed so applications know the original request was HTTPS |
That last row is not academic. midPoint originally generated http:// redirect links because it did not know TLS had been terminated upstream, which broke its login page — a classic reverse-proxy fault worth recognising.
The security posture
- Only 22, 80 and 443 are open at the firewall.
- No other container publishes a host port — the directory, the databases, OPA and the Wazuh internals are reachable only on the private Docker network.
- Every console therefore shares one audited entry point.
Three ways to reach OPA
The same engine, three doors. Which one you use says a lot about what you are doing — demonstrating, operating, or debugging.
| Route | Who uses it | Good for |
|---|---|---|
| PDP console in the Portal | instructor, IT Ops | showing the policy and its decisions to a room |
| OPA's REST API from a container | engineers | asking the engine directly, with no app involved |
| Files and scripts on the host | whoever runs the lab | editing policy, watching decisions, running tests |
1 · From the app — the PDP console
IT Admin › Policy decision point
Sign in to the Portal as a user holding it-admins and open IT Admin › Open PDP console. Two tabs:
- Live decisions — recent authorization decisions with user, action, amount, ALLOW / DENY and the reason OPA gave. Click around the Portal in another tab, then refresh this one.
- Policy — the Rego OPA is actually running, read live from the engine rather than from disk, so the page cannot show you something that is not in force. Read-only by default.
An Edit policy button turns that tab into an editor. Saving needs the policy.edit entitlement, applies to OPA immediately, and is written to disk so it survives a restart.
It is fail-safe. OPA compiles the policy before accepting it. A Rego error is rejected with a message and the running policy is untouched — a bad edit cannot take the lab down.
Worth saying out loud to candidates: editing authorization from a web form is a teaching device, not a production pattern. Real policy lives in version control, is reviewed, tested in CI and shipped as a bundle. This console exists so you can feel a policy change take effect — which is precisely why change control matters.
2 · From a container — OPA's own API
OPA listens on opa:8181 on the internal network. It is not published to the host and not exposed through Caddy: the API has no authentication, so it must never leave the private network. Ask it from any container on ciam-net.
Ask for a decision — no app, no login, no session:
docker exec -i portal python -c "import requests;print(requests.post('http://opa:8181/v1/data/golonex/portal/decision',json={'input':{'user':'x','groups':['app-users','finance-ap-approver'],'action':'payment.approve','amount':50001}}).json())"See what policies are loaded:
docker exec portal python -c "import requests;print([p['id'] for p in requests.get('http://opa:8181/v1/policies').json()['result']])"The endpoints that matter: GET /v1/policies (what is loaded), POST /v1/data/golonex/portal/decision (ask), PUT /v1/policies/<id> (replace — this is what the console uses), and GET /health.
3 · From the host — files, scripts and logs
The policy is a plain file on the server, bind-mounted into the container at /policies:
| Path | What it is |
|---|---|
/opt/ciam-lab/opa/policies/portal.rego | the live policy |
/opt/ciam-lab/opa/policies/portal.rego.orig | pristine copy — the restore point for the live-edit demo |
/opt/ciam-lab/opa/policies/portal_test.rego | the policy's own unit tests |
/opt/ciam-lab/opa-ask.sh | ask OPA a question from the command line |
/opt/ciam-lab/opa-watch.sh | live feed of every decision — put it on a projector |
Ask a question:
/opt/ciam-lab/opa-ask.sh payment.approve app-users,finance-ap-approver 50001Watch decisions live (Ctrl-C to stop):
/opt/ciam-lab/opa-watch.shRun the policy's unit tests:
docker exec opa /opa test /policies -vRaw decision log — every decision, with full input and result:
docker logs --tail 20 opa--watch, so a change to portal.rego on disk takes effect only after
docker restart opa — about four seconds, during which the Portal fails closed and denies
everything. The PDP console sidesteps this: it pushes policy through the API, which applies instantly with no
restart.Every decision is audited
The Portal writes one JSON line per decision, and Wazuh turns the interesting ones into alerts:
| Rule | Level | Fires on |
|---|---|---|
100201 | 4 | any denied action, naming the user and the reason |
100202 | 5 | a payment approved, with the amount |
100203 | 4 | an HR record changed — workforce data feeding IGA |
100204 | 4 | an invoice created |
100205 | 10 | the authorization policy itself was changed |
100210 | 10 | a segregation-of-duties violation blocked at the PDP |
Rule 100205 is deliberately the loudest thing in the lab. Ask a candidate why that is right: changing the policy changes every future decision, so it outranks any single denied action.
Try this
- Request any console over plain
http://and watch it redirect to HTTPS. - Ask yourself which of these tools you would be comfortable exposing directly — the answer, for a directory or a database, is none of them.