Preparing a web console
What a web console needs before ISG can broker it — the Web Access counterpart of target host preparation. The console itself is not changed in most cases: what has to be decided is how it learns who the user is, and where its shared account's secret lives.
The network
- The console's port has to be reachable from the brokers (each broker's own address, not the shared one).
- The browser reaches the console only through ISG: once Web Access is in place, the direct route from the
admins' network can be closed on the console's host. With the shipped Ansible, the monitoring host and the mail
catcher do this through their app-source and admin-source lists, such as
isg_monitoring_firewall_app_sourcesandisg_monitoring_firewall_admin_sources(see configuring applications). - The application's public name (
host) resolves to the brokers' shared address, not to the console.
Choosing how the console learns who the user is
| The console… | identity |
What to prepare |
|---|---|---|
| has no users of its own (Prometheus, a mail catcher) | none |
nothing |
accepts a signed token in a header (Grafana's [auth.jwt]) |
jwt |
the console trusts the listener's public key; role_map for its roles |
| takes HTTP Basic, or an API key in a header (a BMC's Redfish API) | credential, kind: basic or header |
a shared account, its secret in OpenBao |
| signs in with a form and a session cookie (most web GUIs) | credential, kind: form |
a shared account, its secret in OpenBao, and the sign-in recipe |
A console that accepts a signed token
Grafana as the example, in its configuration file (or the matching environment variables):
[auth.jwt]
enabled = true
header_name = X-ISG-Assertion
jwk_set_url = https://grafana.apps.example.internal:8444/.well-known/isg/jwks.json
expect_claims = {"aud": "grafana", "iss": "isg"}
username_claim = sub
email_claim = email
role_attribute_path = role
The shipped Ansible sets this for the bundled Grafana, with jwk_set_file instead of the URL (the JWKS file from the
config source), so Grafana depends on neither the brokers nor name resolution. Keep reserved_usernames as it is:
the token's sub lands on the console's local account of the same name.
A shared account
The account is created on the console, by the console's own administrator, with the rights the users of that
credentials entry should have — a read-only account for a read-only group, an administrator account for the
administrators. Its secret goes to OpenBao, written by the operator; ISG only reads it:
bao kv put secret/isg/apps/<app name>/<account name> username=<account> password=<password>
bao kv put secret/isg/apps/<app name>/<account name> value=<api key>
The path is <kv_mount>/data/<prefix>/<app>/<name>; the brokers' OpenBao token needs read on that prefix (the
isg_bao_init role's broker policy has it). isg system check on a broker says whether every account is readable:
the Web Access credentials row. For a form sign-in with a second factor, add totp_secret (the base32 seed) and
use {{totp}} in the recipe.
A form sign-in recipe
The recipe repeats what the console's own login page does. To write one:
- Open the console's login page directly, with the browser's developer tools on the Network tab, and sign in.
- Find the request that carries the user name and password: its method, path, and whether the body is a form
(
encoding: form) or JSON (encoding: json). If the page first loads a token (a CSRF field in the HTML, a header, a cookie), add aGETstep before it with anextractrule (from: body,header:<Name>orcookie:<name>, and an RE2regexwith one capturing group) and use it as{{token.<name>}}. - Success: what the answer to the sign-in looks like — its status, the session cookie it sets, or where it redirects. Name at least one.
- Signed out: what an answer looks like once the console's session has ended — usually a 401 from its API, or
a redirect to the login page. If the console also answers 401 for something that is not a sign-out (a token
rotation its own script handles), name a string of that answer's body in
unless_body_contains. - Cookies the page's script has to read itself (a CSRF cookie mirrored into a header) go to
browser_cookies; every other cookie stays with the broker.
Behaviours seen on real consoles
- The origin of a request (Grafana). Grafana refuses a cookie-authenticated API call whose
Originis not its own address ("origin not allowed", 403). ISG rewrites theOriginandRefererof akind: formapplication to the upstream's address when they are the application's public address, so Grafana needs nocsrf_trusted_origins(measured on Grafana 13). A foreign origin still goes up unchanged. - Token rotation (Grafana). About every 10 minutes Grafana answers its own front end with 401 and a body that
says "session.token.rotate"; the front end then rotates the token itself. With that string in
logged_outunless_body_contains(as in the example on configuring applications), ISG passes those answers to the browser and does not sign in again — without it, ISG signs in again and a modifying request in flight gets the 409 page. - A token kept in the page (some BMC web interfaces). A console whose script receives its session token in a
response body and keeps it in the page's storage cannot be brokered by a form recipe: the broker never sees the
token being used. Its API with Basic authentication (
kind: basic) usually can. - The console's own sign-out button. Put its path into
logout_paths, so that pressing it ends the ISG application session instead of showing the console's own login page.