Web Access policy
Who reaches a web console through ISG, and what each of their requests may do. How a request travels is on how Web Access works; the identities and groups these rules use are on identities, groups and locks.
Who reaches an application
Web Access (apps.yaml, set up as in configuring applications) applies the same identities
and groups to a web console. The login is the web UI's: password and second factor, or SSO. Each application has an id
(APP-2026-000001); an approval, a session, a lock and an audit line belong to that id, not to the name, so two
applications of the same name never share them. The pages show the friendly name, then the id. Then, per
application:
Groups first: allow_groups
A user in none of the listed groups is refused (APP_ACCESS_DENIED); there is
no application open to every signed-in user.
What the application learns about the user
Nothing, a signed assertion, or a shared account's secret — see configuring applications.
Approval, as a time window
With require_approval the user asks for a duration (up to
approval_max_grant_seconds, 14400 by default) and someone else decides it in the web UI — nobody approves
their own request. A pending request expires after approval_timeout_seconds (300 by default). The window opens
at the decision; inside it the application can be opened again, and at its end the application session closes
too. approval_single_use spends the approval on the first opening.
Self-service access on a stated reason
With approval_mode: self (the default is four_eyes) nobody
decides: the user picks the duration, writes a justification, and the window opens at once. The justification
is then mandatory — approval_reason_min_len characters at least (10 by default, at least 1), and if
approval_reason_pattern is set, a part of it must match that RE2 pattern (a search, not a full match: write
^…$ for one). The same two keys make a four-eyes request's justification mandatory too. The grant is one
COMMAND_APPROVAL_GRANTED audit line with approval_mode: self; the justification's text stays out of the audit
and the ticket, which only says the access was self-service. The window, approval_single_use, an
administrator's close and the end of the window work as for an approved request; a broker older than this
feature does not know the key and asks for a four-eyes approval instead.
What each request may do
Request-level rules
http_rules in policy.yaml judge a request by application, method, path and role.
A rule's action is deny (the default), step_up or allow; of the matching rules deny wins over step_up,
and step_up over allow, whatever their order in the file, so an early, broad allow cannot override a later
deny. A denied request gets 403
(APP_REQUEST_DENIED); the application never sees it, and the session stays. A rule sees the request that
opens a WebSocket, not the frames, and not an operation encoded in a request body. ISG's own paths (/_isg/)
and the application's logout_paths are never judged.
A fresh second factor for a sensitive path
An action: step_up rule (with its required valid_for, from
1m to 60m) holds a matching request until the user confirms the second factor again: a page load goes to the
web UI, which asks for the code the web UI's mfa_mode uses — an authenticator code (or a recovery code), or an
e-mailed one — also for a user who signed in with SSO. After it the application session passes the rule's
requests for valid_for, never past the session's own end. A request that is not a page load (a form post, an
API call, a WebSocket opening) gets 403 and the confirmation address, also in the X-ISG-Step-Up header: confirm
in the browser, then repeat the operation. A WebSocket opened after the confirmation is not cut when it expires.
A user with no usable factor (no authenticator under mfa_mode: totp, no e-mail address under email) is
refused with an explanation, not sent to enrollment. The audit records APP_STEP_UP_REQUIRED, APP_STEP_UP_OK
and APP_STEP_UP_FAILED. Under allow mode a step_up rule also lists its requests.
Allow-list mode, with a trial
http_app_modes puts an application into mode allow: only a request an
allow rule matches (and no deny rule) gets through, everything else is 403 with rule http_mode:allow. Mode
allow_audit is the trial run: the same requests pass, but each writes APP_REQUEST_WOULD_DENY — the list of
the application's static files and API calls the allow rules still miss. Start an application in
allow_audit, switch to allow once the audit is quiet. isg system check warns about an allow application
no allow rule names: every request to it would be 403.
Conditions
A rule's when narrows it to days of the week, a time of day (the end is exclusive, a window
may span midnight), a date range (both ends included) and client networks (CIDR, IPv4 and IPv6); the given
conditions all have to hold. Days and times are read in the policy's timezone (Europe/Budapest by
default). The client address is the connection's own: the gateway sits behind a keepalived address, not a proxy.
A window that closes while a WebSocket is open does not cut it.
Upgrade every broker first
An older broker reads an allow or a step_up rule as a deny and ignores when
(all stricter), but it does not know http_app_modes, so the allow-list would not apply there. Add an allow-list to
policy.yaml only after every broker runs a version that knows it. One exception: v0.547.0 knows allow but
rejects step_up as a load error, so on a v0.547.0 broker a step_up rule makes the whole policy.yaml fail and
every Web Access request gets 503 — add step_up rules only after every broker runs v0.548.0 or later.
What is recorded
Every request
Every request is one line of the session's transcript: method, path, status, size, time —
no body by default. Query parameters by name only, unless the application sets record_query.
Request bodies, opt-in
An application in apps.yaml (not a dynamic one) with record_body: true also puts
the body of its POST, PUT, PATCH and DELETE requests on the line — only what the application actually read, at
most record_body_max_bytes (default 4096, 256 to 65536) of it, the full size always. A refused request's body
and a WebSocket handshake are never read. The body is masked before it is written, and the masking cannot be
turned off: in a form or JSON body the value of every field whose name contains password, passwd, pwd,
secret, token, api_key, apikey, auth, authorization, credential, private_key, session,
cookie, otp or totp (any case; record_body_mask_fields adds names, it cannot remove one) becomes ***,
and in every recorded value a Bearer or Basic credential, a JWT and a PEM private key become ***. A
multipart body shows only its parts' names, file names and sizes; any other type only its type and size. A body
that does not parse is marked, not dropped from the recording. The body lives in the transcript, so it is kept
and archived exactly as the transcript is (retention.yaml transcripts), and whoever can read the
transcript today can read it. A body can carry personal data, and a WORM archive cannot correct a value the
masking missed — enable it only for applications whose changes the audit needs.
How a session ends, and who manages the applications
How it ends
Signing out of the web UI, losing the group, or an administrator closing the session ends the application session on every broker, within about 5 s for the serving broker.
Who manages the applications
With dynamic_apps_domain set, an administrator past the
session_admin_group gate adds, changes and deletes applications on the web UI's Manage apps page
(/apps/manage); an auditor sees the list but cannot write, and a refused write is audited as
SESSION_ADMIN_DENIED. The applications in apps.yaml cannot be changed there. A save names what has no effect:
approval fields filled in while approval is off are not saved, and a group the identity source does not have,
or one without members, is named; the save stands either way. A change of the groups takes
effect on the next request; deleting an application closes its sessions (reason app_deleted). The audit log
records APP_CREATED, APP_UPDATED (with the old and new groups, approval and level) and APP_DELETED.