-
-
Notifications
You must be signed in to change notification settings - Fork 1
Guest Portal
Since
IRIS-NG-v2.4.0. Upgrading to it applies four one-way migrations — back the database up first — and rebuilds the nginx image. The portal itself stays off until the tunnel agent's key is set (see How the portal is exposed below); an instance that never sets it is unchanged.
War rooms can admit guests: people outside your IRIS-NG instance — the affected organisation's incident lead, a partner agency's liaison — who take part in one room and nothing else. A guest is not a user account. They never see the login page, hold no permissions, have no case access of their own, and cannot reach any page or API outside the room they were invited to. Inside the room they take part at the same level as a responder, and they see the cases the room's leads have attached, read-only. They enter through the guest portal, a stripped-down view of the room, reached through a Cloudflare tunnel that exposes only that portal.
| Member (IRIS-NG user) | Guest | |
|---|---|---|
| Account |
user row, roles, permissions, case ACL |
none: a row in the room's guest list |
| Signs in at |
/login (password, MFA, SSO) |
the portal, with email + password |
| Sees | the whole product per their permissions | one war room, in the portal layout |
| Case data | per case ACL | every case attached to the room, read-only, through the room only |
| Room role | lead / responder / observer | responder level, never lead |
| Ends | account deactivation | expiry (14 days by default), revoke, or the room closing |
A guest does what a responder does with the room's own content: read and post in the stream (threads, pins, decisions and notes included), create polls and close their own, vote, create, update and delete room tasks, create, edit and delete room notes and folders, draft and edit SitReps (with preview and the revision trail), add room timelines and their events, create and edit teams, seed the ICS forms and export them as PDF, and read the operational summary and the member list.
A guest also sees every case attached to the room, read-only: the Cases tab lists each with its name, state, open date and attachment note (never the customer, the owner or task counts, and no link into the case pages); the Timelines tab shows the cases' events; the Tasks tab lists the case tasks; the Notes tab lists the case notes and opens them; the stream carries the case activity lane. Attaching a case to a room with guests is the sharing decision — there is no per-case switch, so a lead attaches a case only when its content may reach every guest in the room.
A guest cannot publish or delete a SitRep, delete a timeline, manage members or guests, attach, detach or annotate cases, open a case peek, change the room's status, name or settings, use correlation, STIX or MISP, or run any AI feature (SitRep draft, ICS draft, operational summary); the Correlation tab is not shown. A guest who seeds the ICS forms gets the plain forms, and the AI pass waits for an analyst. The server enforces all of this; the portal only hides what would be refused.
Guest-authored content — messages, polls, SitReps and their revisions, tasks, notes,
timelines and events, teams — is attributed as Name (Organisation), with a guest
badge in the stream, and stays in the room after the guest is removed.
On the room's Members tab, + Add member → Guest:
- Name, organisation and email, how many days the invitation is valid (1–90, default 14), and an optional password. Leave the password blank for a random 16-character one (letters and digits); a password you type must satisfy the instance's own password policy from Server settings.
- Invite creates the guest and shows the invitation link and the password once, each with a Copy button, plus the room's address. When SMTP is configured on Server settings the same information is emailed to the guest; when it is not, relay it yourself. The link and the password are never displayed again — New link and Reset password on the Members tab issue replacements.
The Members tab lists guests under Guests with their status (active, expired, revoked,
closed), expiry, last sign-in and a locked chip after too many failed sign-ins. Leads
have per-guest actions: +14d extends the expiry, New link issues a fresh
invitation link (the old one stops working, the password stays), Reset password issues
a new random password (emailed and shown once; the link stays), Revoke ends access on
the guest's next request, Remove deletes the guest and the invitation. Closing the room
ends every guest's access; reopening it restores them.
Email and password are the credential. The invitation link opens the sign-in form with the email filled in and the room fixed; a forwarded link admits nobody without the password. A guest who lost the email can open the room's address directly (see Room addresses below) and sign in with email and password there. Every failure shows one generic message; ten failures lock the guest for fifteen minutes, which a password reset clears. Expired, revoked and closed invitations show a plain explanation, never a login redirect. Leave in the portal banner ends the session; the session also ends when the browser closes.
Opening an invitation in a browser that is signed in to IRIS-NG as a user signs that user out first: a guest session never sits under a user session.
Every room has a slug, derived from its name when the room is created and editable by a
lead in the room's Edit dialog (lowercase letters, digits and hyphens; blank derives it
again from the name). On the portal hostname the room lives at
https://<portal hostname>/<slug>, and an invitation is https://<portal hostname>/join/<secret>.
On the instance's own address the same pages are /portal/r/<slug> and
/portal/join/<secret>. The Members tab shows the room's current address.
The feature is designed for a Cloudflare tunnel, so that nothing but the portal ever becomes reachable from outside:
- nginx serves a second, portal-only server block on
PORTAL_PORT(default8081) on the Docker network only. It proxies the portal pages, the war-rooms API the portal page uses, and static assets. Everything else, including/login, answers 404. Sign-in and invitation paths are rate-limited. - Requests through that block carry a marker the app recognises: a user session or an API key arriving through the portal is refused, so the tunnel cannot be used to reach IRIS-NG as an analyst even with valid credentials. The main server block strips the marker, so it cannot be forged from the normal origin.
- A tunnel agent container (
docker-compose.portal.yml, built fromdocker/tunnel/) runscloudflaredpointed at that port, asks the instance every fifteen seconds which tunnel mode to run, and reports its status back. It authenticates with a shared key,PORTAL_TUNNEL_AGENT_KEYin.env(generate one withopenssl rand -hex 32); without the key the agent endpoints are off and the Settings page says so.
# .env: PORTAL_PORT=8081 and PORTAL_TUNNEL_AGENT_KEY=<random hex>
docker compose -f docker-compose.dev.yml -f docker-compose.portal.yml up -d --build tunnelOr let the update script do it: bash scripts/update.sh --enable-portal generates the
key into .env, recreates the app so it reads it, and starts the agent — also on an
install that is already up to date. Once the key is in .env, every later run of the
script keeps the agent rebuilt alongside the rest (see
Getting Started).
The Helm chart does not carry this: it has no nginx pod, so the portal server block and the tunnel overlay are Docker Compose features.
A page on the Manage IRIS rail (server administrators):
-
Tunnel mode. Quick tunnel (testing only): an anonymous
trycloudflare.comtunnel with no account. Its hostname is random and changes whenever the agent restarts, so invitation links stop working after a restart, and nothing can be put in front of it. Named tunnel: a tunnel from your own Cloudflare account, with a fixed hostname that can sit behind Cloudflare Access. The agent switches within about fifteen seconds of a save. - Public URL. The base address guests use. Required for a named tunnel; leave it empty for a quick tunnel and the agent's reported hostname is used. Invitation links and room addresses are built from it, in this order: this field, then a connected agent's report, then the instance's own address.
-
Tunnel token. Write-only. Blank keeps the stored token; typing
clearremoves it. A named tunnel cannot be saved without one. -
Tunnel status. The agent's last report: reporting or silent, the mode it runs, whether
the tunnel is connected, the hostname, the
cloudflaredversion and the last error.
- In Cloudflare Zero Trust: Networks → Tunnels → Create a tunnel → Cloudflared, name
it, and copy the token from the install command (the long string after
--token). - On the tunnel's Public Hostname tab add your hostname, for example
rooms.example.com, with serviceHTTP→nginx:8081(the portal server block). - Paste the token and
https://rooms.example.cominto Settings → Guest Portal, select Named tunnel, save. The status card confirms the connection. - Recommended: Access → Applications, a self-hosted application on that hostname with a one-time-PIN policy for the domains you invite. Guests then confirm their email with Cloudflare before the portal even loads. IRIS-NG does not yet verify the Access token itself; that binding is planned.
Switching modes changes the hostname, so existing invitation links stop working; use New link on each room afterwards.
- Invitation secrets and passwords are stored hashed; both are shown exactly once.
- The tunnel exposes the portal to the internet. Use a named tunnel with Cloudflare Access
for anything beyond a test, keep the shared agent key out of version control (
.envis ignored), and treat the quick tunnel as a demo. - A guest's messages reach every room member through the normal notification events; a guest receives no notifications, only the invitation and password emails.
- Granular per-guest permissions and a per-case "share with guests" switch are not part of this version; AI features are never offered to guests.
- The tunnel does not watch the rooms: with no room, every room closed or every room deleted it stays up and the hostname keeps answering (room addresses 404 or 410, the sign-in paths stay reachable, rate limited). Stop the tunnel container, or switch to a named tunnel behind Cloudflare Access, when nothing is open. An explicit off mode is under consideration.
The invitation link uses the instance's own address. No public URL is set and no connected tunnel report exists. Either start the tunnel agent (its hostname is used automatically in quick mode) or set the public URL on Settings → Guest Portal.
The status card says the agent is silent. The tunnel container is not running, cannot
reach the app, or has a different PORTAL_TUNNEL_AGENT_KEY than the app. Check
docker logs iriswebapp_tunnel; the app reads the key from .env at container start, so
a changed key needs app recreated as well.
Named tunnel: not connected, "Provided Tunnel token is not valid." The token was copied incompletely or belongs to a deleted tunnel. Paste it again; the agent retries every fifteen seconds.
A guest gets 503 on the invitation link. The join and sign-in paths are rate-limited (ten per minute per client). Wait a minute.
A guest sees "Sign-in failed" with the right password. Ten failures lock the guest for
fifteen minutes; the Members tab shows locked. A password reset clears it immediately.
A guest can see a case they should not. The case is attached to the room: attaching shares a case with every guest in that room. Detach it, or move the guests to a room that only carries the cases they may see.