What it does here
Apache Guacamole is a clientless remote-access gateway. It brokers a session to a server and renders it entirely in the browser — nothing installed, nothing configured on your machine — and it can record what happens.
It sits beside Teleport rather than duplicating it. Teleport leads with identity-aware, just-in-time access; Guacamole leads with reach — the same browser window can carry SSH, RDP or VNC, which is why gateways like this are common in front of mixed estates.
How it is deployed
Three containers: guacd (the daemon that actually speaks the remote protocols), a PostgreSQL database for users and connections, and the guacamole web application. A fourth container, target-db01, is the server you connect to — see Installation §4.7.
Settings in the web console
Sign in at guacamole.iamlab.genbay.co as guacadmin. Most configuration lives under Settings, reached from the user menu at the top right.
Settings › Connections
One connection is defined: golonex-db-01 (SSH, recorded). Open it and the parameters explain how a gateway works:
| Parameter | Value here | Why it matters |
|---|---|---|
| Protocol | SSH | the same screen could carry RDP or VNC |
| Hostname / port | target-db01 : 2222 | an internal address — the server is not reachable from outside |
| Username | golonex-ops | the account on the target |
| Password | stored in the connection | the user never learns it — credential injection |
| Recording path | /recordings | where sessions are written |
| Record keys | enabled | keystrokes are captured, not just the screen |
That fourth row is the governance idea: the gateway holds the secret, so access can be withdrawn by removing a connection rather than rotating a password everyone knows.
Settings › Users and Groups
Where gateway accounts and their permissions live — who may see which connection, and who may administer. In this lab you work as guacadmin; in production these would be separated.
Recordings and history
Settings › Session recording (and the connection history) lets you list past sessions and play them back, the same accountability Teleport provides by a different route.
Using a session
Launching a connection gives you a terminal in the browser. The Guacamole menu (Ctrl+Alt+Shift) exposes the clipboard, zoom and disconnect — useful, and also a reminder that a gateway sits in the middle of the session and can control what passes through it.
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
- Connect, run a few commands, disconnect, then replay the recording.
- Note that you were never told the server's password — and could not tell anyone else.