# Gateway configuration

Source: https://wndsoftware.com/docs/gateway-configuration

### Gateway configuration

Configure sign-in, host access, flow APIs, screen tables, privileged access and usage intelligence on your gateway.


### Configuration reference

Download and install the gateway from the [browser deployment guide](https://wndsoftware.com/browser). Its release archive includes `gateway.example.toml` and the full deployment README.

The configuration is a TOML file: `--config FILE`, else the `WND_GATEWAY_CONFIG` environment variable, else `/etc/wnd-gateway/gateway.toml` (Windows: `C:\ProgramData\WND Software\WND Gateway\gateway.toml`). A complete, commented example is [`gateway.example.toml`](https://wndsoftware.com/downloads/wnd-product-examples.zip). A setting the gateway doesn't know is a mistake (so a typing error isn't silently ignored); `wnd-gateway check-config` says which.


#### `[server]`

On a small screen, swipe the table to read all columns.


| Setting | Default | What it does |
| --- | --- | --- |
| `listen` | `"127.0.0.1:8080"` | The address and port to listen on, such as `"0.0.0.0:443"`. Any address other than this computer's own needs `tls_certificate` and `tls_key`: plain HTTP is only for testing on the server itself. |
| `public_url` | (required with HTTPS) | The address people open, such as `"https://terminal.bank.example"`. Sign-in redirects use it, and the gateway only accepts browser connections that come from this page. |
| `tls_certificate` | none | The certificate for that name (PEM, with its chain). |
| `tls_key` | none | Its private key (PEM). Readable by the gateway's account only. |
| `data_dir` | `/var/lib/wnd-gateway` (Windows `C:\ProgramData\WND Software\WND Gateway`) | Where the audit log, usage logs, guides and recordings are kept, and files on their way to browsers. |
| `log_format` | `"json"` | The gateway's own log on standard output: `"json"` (one object per line) or `"text"`. |
| `log_file` | none (Windows service: `gateway.log` in `data_dir`) | Also write the log to this file. |
| `client_address_header` | none | The header a reverse proxy puts the browser's address in, such as `"X-Forwarded-For"`. Only set it with such a proxy: anyone could send the header otherwise. |
| `reconnect_grace_seconds` | `60` | How long a browser window that lost its connection has to come back to its sessions. |
| `computer_name` | `WEB-` + this server's name | The computer name of the gateway's sessions in the audit and usage logs. |


#### `[sign_in]`

On a small screen, swipe the table to read all columns.


| Setting | Default | What it does |
| --- | --- | --- |
| `idle_timeout_minutes` | `30` | Signed out after this many minutes without using WND Terminal. |
| `max_session_hours` | `12` | Signed in again after this many hours. |


#### `[sign_in.oidc]`

On a small screen, swipe the table to read all columns.


| Setting | Default | What it does |
| --- | --- | --- |
| `issuer` | (required) | The provider's issuer URL (https). |
| `client_id` | (required) | The app registration's client ID. |
| `client_secret` / `client_secret_file` | (one is required) | The client secret, or a file holding it (better: the configuration then holds no secret). |
| `token_auth` | `"client_secret_basic"` | How the secret is sent to the token endpoint: `"client_secret_basic"` or `"client_secret_post"`. |
| `scopes` | `["openid", "profile", "email"]` | Must include `openid`. |
| `username_claim` | `"preferred_username"` | The ID token claim with the person's login. When it isn't there, `email`, then `sub`. |
| `display_name_claim` | `"name"` | The claim with the name shown in the title bar. |
| `groups_claim` | `"groups"` | The claim listing the person's groups. |
| `allowed_domains` | none (anyone the provider signs in) | Only verified e-mail addresses (or Google `hd`) in these domains. |
| `required_groups` | none | Only people in at least one of these groups (compared without regard to case). |
| `button_label` | `"Sign in with your organization's account"` | The sign-in button's words. |


#### `[sign_in.local]`

On a small screen, swipe the table to read all columns.


| Setting | Default | What it does |
| --- | --- | --- |
| `users_file` | (required) | The users file (TOML, `[[user]]` entries with `name`, `password` (an Argon2id hash), `display_name`, `groups`, `disabled`, `helpdesk`). |


#### `[policy]`

On a small screen, swipe the table to read all columns.


| Setting | Default | What it does |
| --- | --- | --- |
| `file` | none (the defaults) | The organization's `policy.json`, the same format as the desktop app's ([policy settings](https://wndsoftware.com/docs/policy-settings)). The gateway doesn't start with a policy file it can't read. |


#### `[license]`

On a small screen, swipe the table to read all columns.


| Setting | Default | What it does |
| --- | --- | --- |
| `file` | see below | The license file from WND Software's customer portal. |
| `key` | none | Or the license key itself (`wnd1....`). |

Without either, the gateway looks where the desktop app does: the policy's `License` (key) and `LicenseFile` settings, `WND_TERMINAL_LICENSE`, next to the system policy file, then `wnd-terminal.license` in `data_dir`.


#### `[hosts]`

On a small screen, swipe the table to read all columns.


| Setting | Default | What it does |
| --- | --- | --- |
| `allowed_networks` | none | Address ranges sessions may connect to, such as `["10.20.0.0/16", "192.0.2.10", "fd00:1::/64"]`. See [Which hosts people can reach](https://wndsoftware.com/docs/gateway-configuration#hosts). |
| `allowed_ports` | `[23, 992]` | The ports for hosts people type themselves. Add `22` to let them type UNIX and Linux hosts for SSH. |
| `proxy` | none | The proxy every session goes through, when the gateway's network only lets connections out through one: `"socks5://proxy.bank.example:1080"`, `"socks4a://..."`, `"http://proxy.bank.example:8080"`, `"system"` (the gateway computer's own settings: on Linux `ALL_PROXY`, `HTTPS_PROXY`, `HTTP_PROXY` and `NO_PROXY`) or `"none"`. It takes the place of the policy's `Proxy`. A connection in the policy with its own `Proxy` keeps it. People in the browser can't choose a proxy. |
| `proxy_user`, `proxy_password` / `proxy_password_file` | none | The proxy's user name and password, if it asks for one (SOCKS5 user name and password, or HTTP Basic). The user name can also be in `proxy` (`socks5://svc-wnd@proxy.bank.example:1080`). The password is never logged. |


#### `[limits]`

On a small screen, swipe the table to read all columns.


| Setting | Default | What it does |
| --- | --- | --- |
| `max_sessions` | `500` | Sessions (terminals and printers) open on the gateway at once, for everyone. |
| `max_sessions_per_user` | `10` | Sessions one person may have open at once. |
| `max_browsers_per_user` | `5` | Browser windows one person may have connected at once. |
| `sign_in_attempts_per_minute` | `10` | Sign-in attempts from one address per minute (more are refused for a minute). |
| `failed_passwords_before_lockout` | `5` | Wrong passwords (local users) before that login waits 15 minutes. |

People who reach a limit are told in plain words ("You have 10 sessions open, the most your organization allows for one person. Close one to open another.").


#### `[roaming]`

Sessions that belong to the person, not the browser window. See [Roaming sessions](https://wndsoftware.com/docs/roaming) and [docs/roaming.md](https://wndsoftware.com/docs/roaming).

On a small screen, swipe the table to read all columns.


| Setting | Default | What it does |
| --- | --- | --- |
| `enabled` | `true` | Sessions belong to the person, and continue on any of their devices. `false`: each browser window has its own sessions, which close with it. |
| `keep_minutes` | `480` | How long a session nobody has open keeps running (1 to 10080). |
| `max_sessions_per_person` | `8` | The most sessions one person can leave running with no window open. The one left longest closes first. |


#### `[helpdesk]`

The help desk joining a person's session from a ticket, with their consent. Off without this section. See [The help desk joining a session](https://wndsoftware.com/docs/help-desk) and [docs/help-desk.md](https://wndsoftware.com/docs/help-desk).

On a small screen, swipe the table to read all columns.


| Setting | Default | What it does |
| --- | --- | --- |
| `group` | none | The help desk's group, from the identity provider's groups claim or a local user's `groups`. Local users can also have `helpdesk = true`. |
| `join_minutes` | `15` | How long a ticket's Join live link works (1 to 1440). |
| `max_minutes` | `60` | The longest the help desk can stay with a session (1 to 480). |


#### `[ai]`

On a small screen, swipe the table to read all columns.


| Setting | Default | What it does |
| --- | --- | --- |
| `api_key` / `api_key_file` | none | The key for the AI model server in the policy's `AIEndpoint`, if it needs one. In a browser, people can't set their own AI server or key. |


#### `[import]`

Switch day: each person's set-up from the switch pack, added the first time they open WND Terminal in the browser. See [docs/switch-day.md](https://wndsoftware.com/docs/switch-day).

On a small screen, swipe the table to read all columns.


| Setting | Default | What it does |
| --- | --- | --- |
| `first_run_url` | none | A folder on the gateway, or an `https://` address, with the pack's `users` files. The gateway reads `<sign-in name>.wndconfig` for the person who signed in (the name lower case, without `DOMAIN\` or `@domain`). The policy's `FirstRunImport` is for desktops and isn't used here. |
| `first_run_once` | `true` | `false` imports a person's file again whenever it changes. |

The browser remembers what it imported, so a person who uses two browsers gets their set-up in each. The audit log has `import.firstrun` for each import.


#### `[embed]`

Terminals inside your own web applications. See [Terminals in your own web applications](https://wndsoftware.com/docs/embed-kit) and [docs/embed-sdk.md](https://wndsoftware.com/docs/embed-kit).

On a small screen, swipe the table to read all columns.


| Setting | Default | What it does |
| --- | --- | --- |
| `allowed_origins` | (required) | The web applications that may show embedded terminals, as origins, such as `["https://crm.bank.example"]`. |
| `secret` / `secret_file` |  | HS256: the secret your application's server signs embed tokens with (at least 32 characters), or a file holding it. |
| `public_key_file` |  | RS256: the public key (PEM) of the key pair your application's server signs embed tokens with. Give this or a secret. |
| `issuer` | none | If set, every embed token's `iss` must be this. |
| `allow` | `["keyboard", "read", "write"]` | What embedded terminals may do: the person types in them (`keyboard`), the page reads the screen (`read`), the page types and sends keys (`write`). |
| `connections` | none (any) | The policy's connections an embedded terminal may open, by name. |
| `max_token_seconds` | `300` | The longest an embed token may be good for (1 to 3600). |


#### `[sign_on]`

Signing people on to z/OS and IBM i with RACF PassTickets. See [Signing on to hosts with PassTickets](https://wndsoftware.com/#signing-on-to-hosts-with-passtickets).

On a small screen, swipe the table to read all columns.


| Setting | Default | What it does |
| --- | --- | --- |
| `user_id_claim` | none | The ID token claim that holds each person's host user ID, such as `"mainframe_user_id"`. |
| `user_id_file` | none | A file of sign-in names and host user IDs, for people without the claim and for local users. |

Each connection that signs on with a PassTicket is a `[[sign_on.passticket]]`:

On a small screen, swipe the table to read all columns.


| Setting | Default | What it does |
| --- | --- | --- |
| `connection` | none | The connection's name in the policy, such as `"Production (TSO)"`. |
| `host` / `port` | none / any port | Or the host name (and port) the session connects to. |
| `application` | (required) | The PassTicket application name: the RACF PTKTDATA profile, such as `"TSOPROD"` or `"CICSPROD"`, or the one IT set up on IBM i. |
| `algorithm` | `"legacy"` | `"legacy"` (DES) or `"enhanced"` (HMAC-SHA-512), as the profile is set up. |
| `characters` | `"upper"` | Enhanced PassTickets only: `"upper"` or `"mixed"`, as the profile's `TYPE`. |
| `key_file` / `key_env` | (one is required) | The secured sign-on key in hexadecimal: a file only the gateway's account can read, or an environment variable. |
| `steps` | (required) | What to do on the logon screens, in WND Terminal's macro language (`wait`, `type`, `press`, `move`), typing `{user}` (the host user ID) and `{passticket}`. |


#### `[mcp]`

AI agents working sessions through the gateway's MCP endpoint, `/mcp`. Off unless `enabled`. See [AI agents](https://wndsoftware.com/docs/mcp) and [docs/mcp.md](https://wndsoftware.com/docs/mcp).

On a small screen, swipe the table to read all columns.


| Setting | Default | What it does |
| --- | --- | --- |
| `enabled` | `false` | Agents may connect at `/mcp`. |
| `mode` | `"readonly"` | What agents may do: `"readonly"`, `"ask"` (each change waits at `/approvals`) or `"allow"`. With the policy's `AgentActionsMode` set too, the stricter one wins. |
| `approvers_group` | none | The group (from the identity provider, or a local user's `groups`) whose members approve agents' changes at `/approvals`. None: each person approves their own agent's changes. |
| `approval_timeout_seconds` | `300` | How long a change waits for an answer (1 to 3600). No answer is no. |
| `sandbox_only` | `false` | Every session agents open plays a recording from `sandbox_folder` on a replay host, never a real host. |
| `sandbox_folder` | none | The recordings (`.wndrec`) the sandbox plays, and flows (`.wndflow`) to go with them. Required when anything is in the sandbox. |
| `audiences` | none | Audiences accepted in people's tokens besides `sign_in.oidc.client_id`, such as `"api://wnd-gateway"` for an access token. |

Each program that works on its own, not as a person, is a `[mcp.service_accounts.NAME]` (the name is letters, digits, `-`, `_` and `.`):

On a small screen, swipe the table to read all columns.


| Setting | Default | What it does |
| --- | --- | --- |
| `token_sha256` | (required) | The SHA-256 of its token, in hexadecimal. `wnd-gateway new-agent-token NAME` makes a token and prints this. |
| `connections` | none | The policy's connections it may open, by name (`*` and `?` allowed). None: those `AgentConnections` allows. With both, both must allow it. |
| `flows_folder` | none | A folder of flows that become its tools, besides the policy's `AgentFlowFolders`. |
| `mode` | `mcp.mode` | Stricter than `mcp.mode` for this account. It can't be less strict. |
| `sandbox_only` | `false` | Only the sandbox for this account. |


#### `[privileged]`

Privileged access: keys that wait for a second person's approval, windows of elevated access, and the review queue. The rules are the policy's `PrivilegedRules`, then the ones in `rules_file`. See [docs/privileged-access.md](https://wndsoftware.com/docs/privileged-access).

On a small screen, swipe the table to read all columns.


| Setting | Default | What it does |
| --- | --- | --- |
| `rules_file` | none | More rules (a JSON list), after the policy's. `wnd-gateway check-privileged FILE` checks it. |
| `approvers_group` | `mcp.approvers_group` | The group whose members approve held keys and elevated access at `/approvals`, when a rule names no group of its own. Nobody ever approves their own request. |
| `reviewers_group` | none | The group whose members review privileged recordings at `/review`. |
| `approval_timeout_seconds` | the policy's `PrivilegedApprovalTimeout`, else `300` | How long a held key waits for an approver (1 to 86400). No answer is no. |
| `max_elevation_minutes` | `120` | The longest window of elevated access a person may ask for (1 to 1440). |
| `recordings_folder` | the gateway's recordings | Where `/review` reads recordings. |
| `webhook_url` | none | Each new approval request is posted here as JSON (its number, kind, rule, who and a link to `/approvals`, but never the screen), for a chat channel or a ticket flow. |


#### `[api]`

Gateway configuration at `/api/v1`, with an OpenAPI description. Off unless `enabled`. See [Screen flows as APIs](https://wndsoftware.com/docs/flow-apis) and [docs/flow-apis.md](https://wndsoftware.com/docs/gateway-configuration). Programs use the tokens of `[mcp]`: a service account's (`[mcp.service_accounts]`) or a person's.

On a small screen, swipe the table to read all columns.


| Setting | Default | What it does |
| --- | --- | --- |
| `enabled` | `false` | Programs may call flows at `/api/v1`. |
| `mode` | `"readonly"` | What flows that change data (`writes: yes`) may do: `"readonly"` (refused, 409), `"ask"` (an approver decides at `/approvals` first, and the call answers 202) or `"allow"`. The policy's `AgentActionsMode` and a service account's `mode` hold too, and the stricter one wins. |
| `flows_folder` | none | The folder of flows served, besides the policy's `AgentFlowFolders`. With `mcp.sandbox_only`, the flows in `mcp.sandbox_folder` too. |
| `connection` | none | The policy's connection a flow runs in when its header has no `#! connection:` line. |
| `public_url` | the gateway's address with `/api/v1` | The API's address in the OpenAPI document (`servers`). |
| `title` | "company on the license mainframe API" | The OpenAPI document's title. |
| `max_sessions_per_connection` | `4` | Sessions open at once for a caller (a person or a service account) on a connection, for flows that start on the same screen (the same `reset` macro). From 1 to 100. |
| `pool_idle_minutes` | `15` | A pooled session nobody used for this long closes (1 to 1440). |
| `queue_timeout_seconds` | `30` | How long a call waits for a session when every one is busy (0 to 600). Then it's answered 503. |
| `max_queue` | `50` | The most calls waiting for a session, for one caller and one connection. More are answered 503 at once. |

`mcp.sandbox_only` (or a service account's `sandbox_only`) applies to the API too: its sessions play recordings from `mcp.sandbox_folder` on replay hosts.


#### `[tables]`

Screen tables that Excel, Power BI, Tableau and scripts read as an OData feed at `/odata`. Off unless `folder` is set. See [Screen tables](https://wndsoftware.com/docs/screen-tables) and [docs/screen-tables.md](https://wndsoftware.com/docs/screen-tables).

On a small screen, swipe the table to read all columns.


| Setting | Default | What it does |
| --- | --- | --- |
| `folder` | none | The folder of screen tables: flows (`.wndflow`) with a `table:` line. The feed lists every table in it, and it's read again within 10 seconds of a change. |
| `cache_seconds` | `60` | How long an answer is kept for the same caller, table and parameters, so a Power BI refresh that asks twice reaches the host once. `0` keeps nothing, and 86400 is the most. |

Callers sign in as for `/api/v1`: a service account's token or a person's token as a bearer token, or, at `/odata` only, a service account's name and token with Basic authentication, which is what Excel and Power BI ask for. The tables run in the same sessions as screen flows served as APIs, so the `[api]` session settings and the sandbox hold for them too.


#### `[insight]`

Usage intelligence: desktops send their response time records to `/collect`, and people in `viewers_group` see the usage report at `/insights`. Off without this section. Your license must include Usage intelligence. See [Usage intelligence](https://wndsoftware.com/docs/usage-intelligence) and [docs/usage-intelligence.md](https://wndsoftware.com/docs/usage-intelligence).

On a small screen, swipe the table to read all columns.


| Setting | Default | What it does |
| --- | --- | --- |
| `collector_token_sha256` | none | The SHA-256 of the token desktops send (the policy's `ResponseTimeCollectorToken`). `wnd-gateway new-collector-token` makes a token and prints this. None: `/collect` takes nothing, and the report has the gateway's own sessions only. |
| `keep_days` | `400` | Days of records kept (1 to 3660). Older day files are removed once a day. |
| `viewers_group` | none | The group (from the identity provider, or a local user's `groups`) whose members see the report at `/insights`. None: nobody does. |
| `service_accounts` | none | Service accounts from `[mcp.service_accounts]`, by name, that may read `/insights/data.json` with their token (Power BI, Excel). |
| `folders` | none | More folders of day files to include, such as the policy's `ResponseTimeFolder`. |
| `max_mb_per_day` | `1024` | The most `/collect` takes in a day, in megabytes, from every desktop together. Past it, desktops are asked to wait and keep their records to send later. |


### Signing on to hosts with PassTickets

People who signed in to the gateway can be signed on to z/OS (TSO, CICS, IMS and anything else RACF, ACF2 or Top Secret protects) and to IBM i without typing a host password. When someone opens a connection set up here, the gateway:

- finds their host user ID: the `user_id_claim` from their ID token, else the line for their sign-in name in `user_id_file`;
- waits for the logon screen and plays the connection's `steps`;
- right before `{passticket}` is typed, makes a PassTicket for that user ID and the connection's `application` from the secured sign-on key, and types it into the password field.

A PassTicket works once, for a short time (10 minutes for the legacy kind, as the profile's `TIMEOUT` says for the enhanced kind), so a ticket that's seen is of no use later. The person sees the session log on by itself. If they have no host user ID, or something on the logon screen isn't what the steps expect, the session stays on the logon screen and the window says why.


```text
[sign_on]
user_id_claim = "mainframe_user_id"
user_id_file = "/etc/wnd-gateway/user-ids.toml"

[[sign_on.passticket]]
connection = "Production (TSO)"
application = "TSOPROD"
key_file = "/etc/wnd-gateway/tsoprod.key"
steps = """
wait "Logon ===>"
type {user}
press Enter
wait "ENTER PASSWORD"
type {passticket}
press Enter
"""

[[sign_on.passticket]]
connection = "Payroll (IBM i)"
application = "PAYROLL"
algorithm = "enhanced"
key_env = "WND_PAYROLL_PASSTICKET_KEY"
steps = """
wait "Sign On"
type {user}
press FieldExit
type {passticket}
press Enter
"""
```

The user ID file is TOML, one line per person, the sign-in name in quotes:


```text
"alice@bank.example" = "ALICE01"
"bob" = "BOB02"
```

Setting it up on the host.  On z/OS, define the application's PassTicket profile with the key, as for any PassTicket program (`RDEFINE PTKTDATA TSOPROD SSIGNON(KEYMASKED(...))`, or `KEYENCRYPTED` / `EPTKEYLABEL` for an ICSF key and enhanced PassTickets, then `SETROPTS RACLIST(PTKTDATA) REFRESH`), and allow the gateway's sign-ons as your security team requires. The key in `key_file` or `key_env` is the same key, in hexadecimal: 8 bytes (16 digits) for legacy PassTickets, 32 to 256 bytes for enhanced ones. On IBM i, set up the application's PassTicket key as IBM documents for your release (the same legacy algorithm as QSYGENPT). User IDs and application names are 1 to 8 characters (A-Z, 0-9, #, $, @); IBM i user profiles longer than 8 characters can't be signed on this way.

The key stays on the gateway.  It is read when the gateway starts and kept in memory only (wiped when the gateway stops). It's never written to the gateway's log or the audit log, never part of a message, and never sent to a browser. The PassTicket is made on the gateway, typed into the host's hidden password field, and wiped. The browser gets the screen, which hides that field. Keep `key_file` readable by the gateway's account only (`chmod 600`), or use `key_env` with your service manager's secret store. `wnd-gateway check-config` reads the keys and the user ID file and says if something is wrong, without showing the key.

The audit log  records every automatic sign-on as `session.auto_sign_on`: who (the person signed in), the host user ID (`host_user`), the `application`, the `method` (PassTicket) and the `algorithm`, and whether it worked. Never the key or the ticket.

Checked against IBM's documentation, once more against a real host.  Both algorithms are written from IBM's description (z/OS Security Server RACF Macros and Interfaces, and APAR OA59196 for the enhanced one). IBM publishes no complete worked example, so WND Terminal's tests check its code against a second, separately written implementation and against IBM's one published value (the legacy translation step). Before relying on it, sign on once to a test RACF (or IBM i) with a key you define there, for each algorithm you use.

Technical reviewer: [Albert Joseph](https://wndsoftware.com/authors/albert-joseph), WND Software.
