webgate 2.5.0__tar.gz → 2.7.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {webgate-2.5.0 → webgate-2.7.0}/.github/workflows/demo.yml +4 -2
- {webgate-2.5.0 → webgate-2.7.0}/.github/workflows/release.yml +5 -0
- {webgate-2.5.0 → webgate-2.7.0}/CHANGELOG.md +88 -0
- {webgate-2.5.0 → webgate-2.7.0}/PKG-INFO +15 -13
- {webgate-2.5.0 → webgate-2.7.0}/README.md +14 -12
- {webgate-2.5.0 → webgate-2.7.0}/ROADMAP.md +11 -2
- webgate-2.7.0/VERSION +1 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/LOCAL_TESTING.md +1 -1
- {webgate-2.5.0 → webgate-2.7.0}/docs/changelog.md +88 -0
- webgate-2.7.0/docs/guide/sso.md +83 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/guide/users.md +2 -2
- webgate-2.7.0/docs/screenshots/v2/audit.png +0 -0
- webgate-2.7.0/docs/screenshots/v2/palette.png +0 -0
- webgate-2.7.0/docs/screenshots/v2/settings.png +0 -0
- webgate-2.7.0/docs/screenshots/v2/sftp.png +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/screenshots/v2/site-manager.png +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/mkdocs.yml +1 -0
- {webgate-2.5.0 → webgate-2.7.0}/pyproject.toml +1 -1
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/app.py +8 -1
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/auth/models.py +4 -0
- webgate-2.7.0/src/webgate/auth/oidc.py +278 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/auth/routes.py +112 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/config.py +14 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/db/engine.py +1 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/files/routes.py +5 -1
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/runtime_config/registry.py +144 -0
- webgate-2.7.0/src/webgate/servers/crypto.py +47 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/servers/monitor.py +17 -6
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/servers/service.py +21 -7
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/static/index.html +99 -7
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/terminal/routes.py +9 -1
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/terminal/shared.py +10 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/terminal/ws_handler.py +21 -0
- {webgate-2.5.0 → webgate-2.7.0}/tests/e2e/test_ui.py +4 -2
- webgate-2.7.0/tests/test_credential_rotation.py +124 -0
- webgate-2.7.0/tests/test_session_limit.py +168 -0
- webgate-2.7.0/tests/test_sso.py +365 -0
- {webgate-2.5.0 → webgate-2.7.0}/uv.lock +1 -1
- webgate-2.5.0/VERSION +0 -1
- webgate-2.5.0/docs/screenshots/v2/audit.png +0 -0
- webgate-2.5.0/docs/screenshots/v2/palette.png +0 -0
- webgate-2.5.0/docs/screenshots/v2/settings.png +0 -0
- webgate-2.5.0/docs/screenshots/v2/sftp.png +0 -0
- webgate-2.5.0/src/webgate/servers/crypto.py +0 -27
- {webgate-2.5.0 → webgate-2.7.0}/.dockerignore +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/.github/workflows/ci.yml +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/.github/workflows/docs.yml +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/.gitignore +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/DESIGN.md +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/Dockerfile +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/Dockerfile.demo +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/Dockerfile.ssh-demo +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/LICENSE +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/PRODUCT.md +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/RELEASING.md +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/compose.dev.yml +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/compose.ha.yml +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/compose.playground.yml +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/compose.yml +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/deploy/nginx-ha.conf +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/api/auth.md +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/api/files.md +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/api/servers.md +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/api/terminal.md +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/architecture.md +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/getting-started/installation.md +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/getting-started/quickstart.md +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/getting-started/upgrade.md +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/guide/access-control.md +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/guide/advanced.md +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/guide/files.md +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/guide/ha.md +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/guide/integrations.md +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/guide/servers.md +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/guide/settings.md +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/guide/split.md +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/guide/terminal.md +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/index.md +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/screenshots/access-control.png +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/screenshots/audit.png +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/screenshots/edit-access-control.png +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/screenshots/editor.png +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/screenshots/light-theme.png +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/screenshots/login.png +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/screenshots/new-server-form.png +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/screenshots/playground/01-login.png +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/screenshots/playground/02-alice-ldap-empty.png +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/screenshots/playground/03-add-server-jump-via.png +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/screenshots/playground/04-dashboard-3-servers.png +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/screenshots/playground/05-terminal-via-jump-host.png +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/screenshots/playground/06-snippet-whoami.png +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/screenshots/playground/07-shared-joiner.png +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/screenshots/playground/07-shared-owner.png +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/screenshots/playground/08-webhook-fired.png +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/screenshots/playground/09-recordings-list.png +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/screenshots/playground/10-recording-replay.png +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/screenshots/playground/11-bob-limited.png +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/screenshots/sftp-restricted.png +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/screenshots/sftp.png +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/screenshots/site-manager.png +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/screenshots/split-view.png +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/screenshots/ssh-disabled.png +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/screenshots/terminal.png +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/screenshots/users.png +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/screenshots/v0.3/01-login-demo-banner.png +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/screenshots/v0.3/02-dashboard-jump-host.png +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/screenshots/v0.3/03-terminal-snippets-jump.png +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/screenshots/v0.3/04-snippet-executed.png +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/screenshots/v0.3/05-sftp-via-jump.png +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/screenshots/v0.3/06-webhooks-modal.png +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/screenshots/v0.3/07-webhook-test-fired.png +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/screenshots/v0.3/08-add-server-jump-via.png +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/screenshots/v0.4/01-shared-terminal-owner.png +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/screenshots/v0.4/02-shared-terminal-joiner.png +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/screenshots/v0.4/03-recording-replay.png +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/screenshots/v0.4/04-recordings-modal.png +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/screenshots/v2/agent.png +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/screenshots/v2/branding.png +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/screenshots/v2/light.png +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/screenshots/v2/login.png +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/screenshots/v2/terminal.png +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/docs/screenshots/v2/users.png +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/fly.toml +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/scripts/release_notes.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/__init__.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/__main__.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/agent/__init__.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/agent/cache.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/agent/conversation.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/agent/memory.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/agent/models.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/agent/provider.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/agent/routes.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/agent/service.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/agent/sftp_tools.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/agent/store.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/agent/tools.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/audit/__init__.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/audit/models.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/audit/service.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/auth/__init__.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/auth/ldap.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/auth/service.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/backup/__init__.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/backup/models.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/backup/routes.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/backup/service.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/branding/__init__.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/branding/routes.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/branding/store.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/db/__init__.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/demo.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/files/__init__.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/files/limits.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/files/models.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/files/pool.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/files/sftp_service.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/recordings/__init__.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/recordings/models.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/recordings/recorder.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/recordings/routes.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/runtime_config/__init__.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/runtime_config/routes.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/runtime_config/store.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/servers/__init__.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/servers/hostkeys.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/servers/models.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/servers/routes.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/snippets/__init__.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/snippets/models.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/snippets/routes.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/terminal/__init__.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/terminal/ssh_session.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/webhooks/__init__.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/webhooks/dispatcher.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/webhooks/models.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/src/webgate/webhooks/routes.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/tests/__init__.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/tests/conftest.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/tests/e2e/conftest.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/tests/e2e/sshlab.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/tests/test_agent_conversation.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/tests/test_agent_memory.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/tests/test_agent_settings.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/tests/test_agent_sftp_tools.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/tests/test_agent_tools.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/tests/test_audit_coverage.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/tests/test_auth.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/tests/test_backup.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/tests/test_branding.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/tests/test_files.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/tests/test_files_listing.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/tests/test_host_keys.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/tests/test_idle_sessions.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/tests/test_limits.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/tests/test_migrations.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/tests/test_monitor.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/tests/test_recordings.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/tests/test_release_notes.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/tests/test_secret_key_gate.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/tests/test_servers.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/tests/test_settings.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/tests/test_snippets.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/tests/test_terminal.py +0 -0
- {webgate-2.5.0 → webgate-2.7.0}/tests/test_webhook_dispatch.py +0 -0
|
@@ -4,8 +4,10 @@ name: Deploy demo
|
|
|
4
4
|
# it spent months serving a version several releases behind the repository.
|
|
5
5
|
|
|
6
6
|
on:
|
|
7
|
-
release:
|
|
8
|
-
|
|
7
|
+
# Called by release.yml rather than triggered by the release it creates: GitHub
|
|
8
|
+
# does not start workflows from events raised with GITHUB_TOKEN, so a
|
|
9
|
+
# `release: published` trigger here never fires and the demo silently stays behind.
|
|
10
|
+
workflow_call:
|
|
9
11
|
workflow_dispatch:
|
|
10
12
|
|
|
11
13
|
permissions:
|
|
@@ -1,5 +1,93 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v2.7.0 (2026-09-17) — single sign-on
|
|
4
|
+
|
|
5
|
+
LDAP already existed, but no company running Entra ID, Okta or Google Workspace is
|
|
6
|
+
going to keep a second directory for one tool. It was the first thing anyone asked
|
|
7
|
+
about, and the thing most likely to decide whether webgate gets deployed at all.
|
|
8
|
+
|
|
9
|
+
### Single sign-on (OpenID Connect)
|
|
10
|
+
|
|
11
|
+
**Admin → Settings → Single sign-on**, alongside local accounts and LDAP rather than
|
|
12
|
+
instead of them. Register one redirect URI with your provider, fill in the issuer,
|
|
13
|
+
client ID, secret and scopes, and everything else — the endpoints, the signing keys —
|
|
14
|
+
comes from the provider's discovery document.
|
|
15
|
+
|
|
16
|
+
- **The ID token is actually verified**: signature against the provider's published
|
|
17
|
+
keys, plus issuer, audience, expiry, and a nonce tied to that one sign-in, so a token
|
|
18
|
+
captured from another flow cannot be replayed into this one. A token that merely
|
|
19
|
+
decodes proves nothing.
|
|
20
|
+
- **Authorization code with PKCE**, so it is safe with or without a client secret.
|
|
21
|
+
- **The in-progress sign-in lives in the database**, not in worker memory. The browser
|
|
22
|
+
can come back to a different worker than the one it left, which is the whole point of
|
|
23
|
+
the stateless design.
|
|
24
|
+
- **The session token never travels in a URL.** The callback hands the page a one-time
|
|
25
|
+
code, good for about a minute, which it trades for a session — so nothing sensitive
|
|
26
|
+
reaches browser history or a proxy log.
|
|
27
|
+
- **A provider group grants nothing until an admin maps it**, exactly as LDAP works. A
|
|
28
|
+
directory group created next month cannot quietly open a webgate group that happens
|
|
29
|
+
to share its name. A separate admin-groups list grants admin.
|
|
30
|
+
- Sign-ins are audited as `sso_login`, with the groups granted.
|
|
31
|
+
- Accounts created this way have **no local password** — nothing to reset, nothing to
|
|
32
|
+
leak.
|
|
33
|
+
|
|
34
|
+
Twenty-one tests, run against a real identity provider started for the test: it
|
|
35
|
+
publishes a discovery document and a JWKS and signs its own tokens, so the
|
|
36
|
+
verification is exercised rather than mocked away. A token signed with the wrong key,
|
|
37
|
+
issued for another audience, expired, or carrying another sign-in's nonce is refused,
|
|
38
|
+
and each of those is a test.
|
|
39
|
+
|
|
40
|
+
[Setup, and what to do when nobody gets any groups](https://kalexnolasco.github.io/webgate/guide/sso/).
|
|
41
|
+
|
|
42
|
+
354 tests: 345 unit, 9 browser.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## v2.6.0 (2026-09-16) — rotating the secret key no longer breaks everything
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
|
|
50
|
+
- **A changed `WEBGATE_SECRET_KEY` made webgate fall over instead of explaining
|
|
51
|
+
itself.** v2.2.0 refuses the shipped default, which asks every existing install to
|
|
52
|
+
set a real one; doing that leaves rows encrypted under the old key. `decrypt_value`
|
|
53
|
+
raised a bare Fernet error from twelve call sites and none of them caught it. The
|
|
54
|
+
status monitor died on its first sweep and froze every server's dot; opening a
|
|
55
|
+
terminal failed the WebSocket **upgrade**, so the browser got a 500 with nothing in
|
|
56
|
+
it. Found on the public demo, by walking into it.
|
|
57
|
+
|
|
58
|
+
An unreadable credential is now its own error, naming the server, why it happened
|
|
59
|
+
and what to do. The monitor marks that one server offline and carries on, the
|
|
60
|
+
terminal answers with an error frame, SFTP returns 502, and a connection test
|
|
61
|
+
reports it.
|
|
62
|
+
|
|
63
|
+
- **The demo banner covered the top of the interface.** It was `position: fixed` with
|
|
64
|
+
nothing making room for it, so the app bar sat underneath. It is in flow now, which
|
|
65
|
+
also holds when it wraps to two lines on a narrow screen. Only visible with
|
|
66
|
+
`WEBGATE_DEMO_MODE=true`, which is why no test caught it.
|
|
67
|
+
|
|
68
|
+
- **Diagrams that did not show.** Nine style directives set a pale fill and left the
|
|
69
|
+
label to the theme, so on GitHub's dark background they were light text on a light
|
|
70
|
+
box. All 21 diagrams in the repository now render, and every label is readable on
|
|
71
|
+
both grounds — checked with the Mermaid version GitHub actually uses, in a browser,
|
|
72
|
+
one diagram per page.
|
|
73
|
+
|
|
74
|
+
### Added
|
|
75
|
+
|
|
76
|
+
- **The pinned host key is visible.** The fingerprint has been in the API since
|
|
77
|
+
v2.0.0 and nothing showed it. Servers carry a shield when a key is pinned, the edit
|
|
78
|
+
form shows the fingerprint, and an admin can clear it there — with the consequence
|
|
79
|
+
spelled out, since accepting a new key is the one moment the protection is stood
|
|
80
|
+
down.
|
|
81
|
+
- **A cap on concurrent SSH sessions per account** (**Admin → Settings → Security**,
|
|
82
|
+
0 for none). There was nothing stopping a looping tab from opening sessions until
|
|
83
|
+
the worker ran out. Checked before the SSH connection is made, and counted per
|
|
84
|
+
worker — which the setting says, because a gateway behind a load balancer allows
|
|
85
|
+
that many on each.
|
|
86
|
+
|
|
87
|
+
333 tests.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
3
91
|
## v2.5.0 (2026-09-16) — browser tests
|
|
4
92
|
|
|
5
93
|
The audit gap in v2.4.0 was reported by a person using the interface, and no test in
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: webgate
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.7.0
|
|
4
4
|
Summary: Self-hosted web application for remote server management via SSH terminal and SFTP file browser
|
|
5
5
|
Project-URL: Homepage, https://github.com/kalexnolasco/webgate
|
|
6
6
|
Project-URL: Documentation, https://kalexnolasco.github.io/webgate/
|
|
@@ -126,9 +126,9 @@ flowchart TB
|
|
|
126
126
|
GW -. "SSH :22" .-> APP2
|
|
127
127
|
GW -. "SSH :22" .-> WORKER
|
|
128
128
|
GW -. "SSH :22" .-> REDIS
|
|
129
|
-
style internet fill
|
|
130
|
-
style firewall fill
|
|
131
|
-
style internal fill
|
|
129
|
+
style internet fill:none,stroke:#4a90d9,stroke-width:2px
|
|
130
|
+
style firewall fill:none,stroke:#ff9800,stroke-width:2px
|
|
131
|
+
style internal fill:none,stroke:#5cb85c,stroke-width:2px
|
|
132
132
|
style GW fill:#ffcc02,stroke:#e6a800,color:#333
|
|
133
133
|
style YOU fill:#4a90d9,stroke:#2a6cb5,color:#fff
|
|
134
134
|
```
|
|
@@ -160,9 +160,9 @@ flowchart TB
|
|
|
160
160
|
WG -- "SSH/SFTP" --> APP1
|
|
161
161
|
WG -- "SSH/SFTP" --> WORKER
|
|
162
162
|
WG -- "SSH/SFTP" --> REDIS
|
|
163
|
-
style internet fill
|
|
164
|
-
style firewall fill
|
|
165
|
-
style internal fill
|
|
163
|
+
style internet fill:none,stroke:#4a90d9,stroke-width:2px
|
|
164
|
+
style firewall fill:none,stroke:#ff9800,stroke-width:2px
|
|
165
|
+
style internal fill:none,stroke:#5cb85c,stroke-width:2px
|
|
166
166
|
style WG fill:#5cb85c,stroke:#449d44,color:#fff
|
|
167
167
|
style ENG1 fill:#4a90d9,stroke:#2a6cb5,color:#fff
|
|
168
168
|
style ENG2 fill:#4a90d9,stroke:#2a6cb5,color:#fff
|
|
@@ -189,7 +189,7 @@ flowchart TB
|
|
|
189
189
|
| **SFTP** | Full file ops + drag & drop upload, **sortable columns**, **multi-select** with batch ZIP download and delete, hidden-file toggle, in-browser editor (CodeMirror 6), PDF/image preview |
|
|
190
190
|
| **Server Registry** | Groups, tags, password/key auth, encrypted at rest (Fernet), **verified host keys** (TOFU), **favourites and recents**, import/export JSON, **jump host / bastion** chaining |
|
|
191
191
|
| **Access Control** | Admin/user roles, per-server SSH/SFTP toggles, SFTP path restrictions, read-only SFTP mode, group-based visibility |
|
|
192
|
-
| **Auth** | JWT + bcrypt locally, **2FA TOTP**, **API keys** for automation, **LDAP / Active Directory**
|
|
192
|
+
| **Auth** | **Single sign-on (OpenID Connect)** — Entra ID, Okta, Google Workspace, Keycloak — with group→role mapping, plus JWT + bcrypt locally, **2FA TOTP**, **API keys** for automation, **LDAP / Active Directory** |
|
|
193
193
|
| **Compliance** | **Session recording** to asciinema cast files with browser replay, off by default and opted into per server, **audit log** covering every SFTP operation, registry and account change, searchable by filename, **webhooks** (HMAC-signed) on key events |
|
|
194
194
|
| **Migration** | **Full-state backup and restore** — servers with credentials, users, groups, webhooks and API keys in one passphrase-encrypted file, portable between instances |
|
|
195
195
|
| **AI agent** | Per-server iterative chat over **Ollama** or **OpenRouter**, read-only inspection tools, **works on SFTP-only hosts**, cached results and searchable findings — configured in the admin panel, off until then |
|
|
@@ -355,7 +355,7 @@ flowchart LR
|
|
|
355
355
|
style B fill:#4a90d9,stroke:#2a6cb5,color:#fff
|
|
356
356
|
style WG fill:#5cb85c,stroke:#449d44,color:#fff
|
|
357
357
|
style BAST fill:#ffcc02,stroke:#e6a800,color:#333
|
|
358
|
-
style INT fill:#
|
|
358
|
+
style INT fill:#b25e00,stroke:#ff9800,color:#fff
|
|
359
359
|
```
|
|
360
360
|
|
|
361
361
|
### Shared terminal session
|
|
@@ -407,7 +407,7 @@ flowchart LR
|
|
|
407
407
|
L1["alice ∈ cn=devs"]
|
|
408
408
|
L2["alice ∈ cn=admins"]
|
|
409
409
|
end
|
|
410
|
-
subgraph "Group mapping<br/>
|
|
410
|
+
subgraph MAP["Group mapping<br/>Admin → Settings → LDAP"]
|
|
411
411
|
M["{<br/> "devs": "production",<br/> "sre": "all"<br/>}"]
|
|
412
412
|
end
|
|
413
413
|
subgraph User
|
|
@@ -650,9 +650,9 @@ flowchart LR
|
|
|
650
650
|
W3 -. watches .-> LEASE
|
|
651
651
|
style LB fill:#ffcc02,stroke:#e6a800,color:#333
|
|
652
652
|
style W1 fill:#5cb85c,stroke:#449d44,color:#fff
|
|
653
|
-
style W2 fill:#
|
|
654
|
-
style W3 fill:#
|
|
655
|
-
style PG fill:#
|
|
653
|
+
style W2 fill:#2a6cb5,stroke:#4a90d9,color:#fff
|
|
654
|
+
style W3 fill:#2a6cb5,stroke:#4a90d9,color:#fff
|
|
655
|
+
style PG fill:#b25e00,stroke:#ff9800,color:#fff
|
|
656
656
|
```
|
|
657
657
|
|
|
658
658
|
Reference stack:
|
|
@@ -727,6 +727,7 @@ The demo middleware blocks all writes on `/api/*` (login, terminal share and tot
|
|
|
727
727
|
| **Servers** | `GET/POST/PUT/DELETE /api/servers`, `POST /api/servers/{id}/test`, `GET /api/servers/groups`, `POST /api/servers/import`, `GET /api/servers/export`, `GET /api/servers/status` |
|
|
728
728
|
| **Terminal** | `WS /api/ws/terminal/{server_id}` (owner), `WS /api/ws/terminal/quick` (one-off), `WS /api/ws/terminal/join/{token}?mode=rw\|ro` (joiner), `POST/DELETE /api/terminal/share/{session_id}` |
|
|
729
729
|
| **Files (SFTP)** | `GET /ls`, `GET /read`, `GET /download`, `GET /download-zip` (one directory), `POST /download-zip` (a chosen selection), `POST /upload`, `PUT /write`, `POST /mkdir`, `POST /rename`, `DELETE /delete`, `POST /chmod`, `GET /stat` (all under `/api/files/{server_id}/`) |
|
|
730
|
+
| **Single sign-on** | `GET /api/auth/sso/start`, `GET /api/auth/sso/callback`, `POST /api/auth/sso/exchange` |
|
|
730
731
|
| **Settings** | `GET /api/settings`, `PUT /api/settings`, `POST /api/settings/reset` (admin only) |
|
|
731
732
|
| **Agent** | `GET/PUT /api/agent/settings`, `POST /api/agent/models`, `POST /api/agent/chat/{server_id}`, `GET/DELETE /api/agent/conversations/{server_id}`, `GET /api/agent/findings` |
|
|
732
733
|
| **Branding** | `GET /api/branding` (public), `PUT/DELETE /api/branding` (admin only) |
|
|
@@ -790,6 +791,7 @@ docker compose -f compose.dev.yml up --build
|
|
|
790
791
|
|
|
791
792
|
## Security
|
|
792
793
|
|
|
794
|
+
- **Single sign-on** verifies the ID token properly: signature against the provider's published keys, plus issuer, audience, expiry and a per-sign-in nonce. The flow uses PKCE, its state lives in the database so it works across workers, and the session token is handed over as a one-time code rather than in a URL
|
|
793
795
|
- **Host keys are verified** — trust on first use. The first connection to a server records the key it presents; every connection after that is checked against it *before authentication runs*, so a mismatch sends nothing. Accepting a changed key is a deliberate, audited admin action. This covers the terminal, SFTP, the connection pool, the status monitor, the agent and jump hosts
|
|
794
796
|
- All SSH passwords and private keys are encrypted at rest with **Fernet** (key derived from `WEBGATE_SECRET_KEY`)
|
|
795
797
|
- **The default secret key is refused.** That key signs every session token and derives the credential encryption key, and its default is published in this repository — a deployment that never changed it will hand a valid admin token to anyone who asks. webgate now stops at startup rather than serving under it, unless it is bound to loopback only
|
|
@@ -77,9 +77,9 @@ flowchart TB
|
|
|
77
77
|
GW -. "SSH :22" .-> APP2
|
|
78
78
|
GW -. "SSH :22" .-> WORKER
|
|
79
79
|
GW -. "SSH :22" .-> REDIS
|
|
80
|
-
style internet fill
|
|
81
|
-
style firewall fill
|
|
82
|
-
style internal fill
|
|
80
|
+
style internet fill:none,stroke:#4a90d9,stroke-width:2px
|
|
81
|
+
style firewall fill:none,stroke:#ff9800,stroke-width:2px
|
|
82
|
+
style internal fill:none,stroke:#5cb85c,stroke-width:2px
|
|
83
83
|
style GW fill:#ffcc02,stroke:#e6a800,color:#333
|
|
84
84
|
style YOU fill:#4a90d9,stroke:#2a6cb5,color:#fff
|
|
85
85
|
```
|
|
@@ -111,9 +111,9 @@ flowchart TB
|
|
|
111
111
|
WG -- "SSH/SFTP" --> APP1
|
|
112
112
|
WG -- "SSH/SFTP" --> WORKER
|
|
113
113
|
WG -- "SSH/SFTP" --> REDIS
|
|
114
|
-
style internet fill
|
|
115
|
-
style firewall fill
|
|
116
|
-
style internal fill
|
|
114
|
+
style internet fill:none,stroke:#4a90d9,stroke-width:2px
|
|
115
|
+
style firewall fill:none,stroke:#ff9800,stroke-width:2px
|
|
116
|
+
style internal fill:none,stroke:#5cb85c,stroke-width:2px
|
|
117
117
|
style WG fill:#5cb85c,stroke:#449d44,color:#fff
|
|
118
118
|
style ENG1 fill:#4a90d9,stroke:#2a6cb5,color:#fff
|
|
119
119
|
style ENG2 fill:#4a90d9,stroke:#2a6cb5,color:#fff
|
|
@@ -140,7 +140,7 @@ flowchart TB
|
|
|
140
140
|
| **SFTP** | Full file ops + drag & drop upload, **sortable columns**, **multi-select** with batch ZIP download and delete, hidden-file toggle, in-browser editor (CodeMirror 6), PDF/image preview |
|
|
141
141
|
| **Server Registry** | Groups, tags, password/key auth, encrypted at rest (Fernet), **verified host keys** (TOFU), **favourites and recents**, import/export JSON, **jump host / bastion** chaining |
|
|
142
142
|
| **Access Control** | Admin/user roles, per-server SSH/SFTP toggles, SFTP path restrictions, read-only SFTP mode, group-based visibility |
|
|
143
|
-
| **Auth** | JWT + bcrypt locally, **2FA TOTP**, **API keys** for automation, **LDAP / Active Directory**
|
|
143
|
+
| **Auth** | **Single sign-on (OpenID Connect)** — Entra ID, Okta, Google Workspace, Keycloak — with group→role mapping, plus JWT + bcrypt locally, **2FA TOTP**, **API keys** for automation, **LDAP / Active Directory** |
|
|
144
144
|
| **Compliance** | **Session recording** to asciinema cast files with browser replay, off by default and opted into per server, **audit log** covering every SFTP operation, registry and account change, searchable by filename, **webhooks** (HMAC-signed) on key events |
|
|
145
145
|
| **Migration** | **Full-state backup and restore** — servers with credentials, users, groups, webhooks and API keys in one passphrase-encrypted file, portable between instances |
|
|
146
146
|
| **AI agent** | Per-server iterative chat over **Ollama** or **OpenRouter**, read-only inspection tools, **works on SFTP-only hosts**, cached results and searchable findings — configured in the admin panel, off until then |
|
|
@@ -306,7 +306,7 @@ flowchart LR
|
|
|
306
306
|
style B fill:#4a90d9,stroke:#2a6cb5,color:#fff
|
|
307
307
|
style WG fill:#5cb85c,stroke:#449d44,color:#fff
|
|
308
308
|
style BAST fill:#ffcc02,stroke:#e6a800,color:#333
|
|
309
|
-
style INT fill:#
|
|
309
|
+
style INT fill:#b25e00,stroke:#ff9800,color:#fff
|
|
310
310
|
```
|
|
311
311
|
|
|
312
312
|
### Shared terminal session
|
|
@@ -358,7 +358,7 @@ flowchart LR
|
|
|
358
358
|
L1["alice ∈ cn=devs"]
|
|
359
359
|
L2["alice ∈ cn=admins"]
|
|
360
360
|
end
|
|
361
|
-
subgraph "Group mapping<br/>
|
|
361
|
+
subgraph MAP["Group mapping<br/>Admin → Settings → LDAP"]
|
|
362
362
|
M["{<br/> "devs": "production",<br/> "sre": "all"<br/>}"]
|
|
363
363
|
end
|
|
364
364
|
subgraph User
|
|
@@ -601,9 +601,9 @@ flowchart LR
|
|
|
601
601
|
W3 -. watches .-> LEASE
|
|
602
602
|
style LB fill:#ffcc02,stroke:#e6a800,color:#333
|
|
603
603
|
style W1 fill:#5cb85c,stroke:#449d44,color:#fff
|
|
604
|
-
style W2 fill:#
|
|
605
|
-
style W3 fill:#
|
|
606
|
-
style PG fill:#
|
|
604
|
+
style W2 fill:#2a6cb5,stroke:#4a90d9,color:#fff
|
|
605
|
+
style W3 fill:#2a6cb5,stroke:#4a90d9,color:#fff
|
|
606
|
+
style PG fill:#b25e00,stroke:#ff9800,color:#fff
|
|
607
607
|
```
|
|
608
608
|
|
|
609
609
|
Reference stack:
|
|
@@ -678,6 +678,7 @@ The demo middleware blocks all writes on `/api/*` (login, terminal share and tot
|
|
|
678
678
|
| **Servers** | `GET/POST/PUT/DELETE /api/servers`, `POST /api/servers/{id}/test`, `GET /api/servers/groups`, `POST /api/servers/import`, `GET /api/servers/export`, `GET /api/servers/status` |
|
|
679
679
|
| **Terminal** | `WS /api/ws/terminal/{server_id}` (owner), `WS /api/ws/terminal/quick` (one-off), `WS /api/ws/terminal/join/{token}?mode=rw\|ro` (joiner), `POST/DELETE /api/terminal/share/{session_id}` |
|
|
680
680
|
| **Files (SFTP)** | `GET /ls`, `GET /read`, `GET /download`, `GET /download-zip` (one directory), `POST /download-zip` (a chosen selection), `POST /upload`, `PUT /write`, `POST /mkdir`, `POST /rename`, `DELETE /delete`, `POST /chmod`, `GET /stat` (all under `/api/files/{server_id}/`) |
|
|
681
|
+
| **Single sign-on** | `GET /api/auth/sso/start`, `GET /api/auth/sso/callback`, `POST /api/auth/sso/exchange` |
|
|
681
682
|
| **Settings** | `GET /api/settings`, `PUT /api/settings`, `POST /api/settings/reset` (admin only) |
|
|
682
683
|
| **Agent** | `GET/PUT /api/agent/settings`, `POST /api/agent/models`, `POST /api/agent/chat/{server_id}`, `GET/DELETE /api/agent/conversations/{server_id}`, `GET /api/agent/findings` |
|
|
683
684
|
| **Branding** | `GET /api/branding` (public), `PUT/DELETE /api/branding` (admin only) |
|
|
@@ -741,6 +742,7 @@ docker compose -f compose.dev.yml up --build
|
|
|
741
742
|
|
|
742
743
|
## Security
|
|
743
744
|
|
|
745
|
+
- **Single sign-on** verifies the ID token properly: signature against the provider's published keys, plus issuer, audience, expiry and a per-sign-in nonce. The flow uses PKCE, its state lives in the database so it works across workers, and the session token is handed over as a one-time code rather than in a URL
|
|
744
746
|
- **Host keys are verified** — trust on first use. The first connection to a server records the key it presents; every connection after that is checked against it *before authentication runs*, so a mismatch sends nothing. Accepting a changed key is a deliberate, audited admin action. This covers the terminal, SFTP, the connection pool, the status monitor, the agent and jump hosts
|
|
745
747
|
- All SSH passwords and private keys are encrypted at rest with **Fernet** (key derived from `WEBGATE_SECRET_KEY`)
|
|
746
748
|
- **The default secret key is refused.** That key signs every session token and derives the credential encryption key, and its default is published in this repository — a deployment that never changed it will hand a valid admin token to anyone who asks. webgate now stops at startup rather than serving under it, unless it is bound to loopback only
|
|
@@ -90,6 +90,17 @@ Tracks the development plan for **webgate**. Items are organized by release.
|
|
|
90
90
|
- [x] **The documentation screenshots are taken by those tests**, so they cannot drift from the shipped interface
|
|
91
91
|
- [x] **CI runs them on every push**, and uploads what they captured
|
|
92
92
|
|
|
93
|
+
### v2.6.0 — Key rotation, host key UI, session cap (2026-09-16)
|
|
94
|
+
- [x] **A changed secret key is handled** — it used to kill the monitor and fail the terminal's WebSocket upgrade with a bare 500
|
|
95
|
+
- [x] **The pinned host key fingerprint is visible and clearable** from the Site Manager
|
|
96
|
+
- [x] **A per-account cap on concurrent SSH sessions**
|
|
97
|
+
- [x] **The demo banner no longer covers the interface**, and every diagram in the repository renders legibly on both GitHub grounds
|
|
98
|
+
|
|
99
|
+
### v2.7.0 — Single sign-on (2026-09-17)
|
|
100
|
+
- [x] **OpenID Connect sign-on** for Entra ID, Okta, Google Workspace, Keycloak and Authentik, with the ID token verified against the provider's keys and a per-sign-in nonce
|
|
101
|
+
- [x] **Provider groups map to webgate groups**, and an unmapped group grants nothing
|
|
102
|
+
- [x] **The session token is handed over as a one-time code**, never in a URL
|
|
103
|
+
|
|
93
104
|
---
|
|
94
105
|
|
|
95
106
|
## Planned
|
|
@@ -97,8 +108,6 @@ Tracks the development plan for **webgate**. Items are organized by release.
|
|
|
97
108
|
### v2.1.x — Next
|
|
98
109
|
| Feature | Priority | Description |
|
|
99
110
|
|---------|----------|-------------|
|
|
100
|
-
| Per-user concurrent-session limit | Medium | Nothing caps how many SSH sessions one account may hold open |
|
|
101
|
-
| Host key fingerprint in the UI | Medium | The API exposes it and can clear it; the Site Manager does not show it yet |
|
|
102
111
|
| Internationalization (i18n) | Low | UI translations starting with English / Spanish |
|
|
103
112
|
|
|
104
113
|
### Later
|
webgate-2.7.0/VERSION
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2.7.0
|
|
@@ -55,7 +55,7 @@ flowchart LR
|
|
|
55
55
|
B -- SSH tunnel --> I
|
|
56
56
|
style WG fill:#5cb85c,stroke:#449d44,color:#fff
|
|
57
57
|
style B fill:#ffcc02,stroke:#e6a800,color:#333
|
|
58
|
-
style I fill:#
|
|
58
|
+
style I fill:#b25e00,stroke:#ff9800,color:#fff
|
|
59
59
|
```
|
|
60
60
|
|
|
61
61
|
`webgate` and `ssh-internal` share **no network**, so reaching `ssh-internal` is only possible via the bastion.
|
|
@@ -1,5 +1,93 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v2.7.0 (2026-09-17) — single sign-on
|
|
4
|
+
|
|
5
|
+
LDAP already existed, but no company running Entra ID, Okta or Google Workspace is
|
|
6
|
+
going to keep a second directory for one tool. It was the first thing anyone asked
|
|
7
|
+
about, and the thing most likely to decide whether webgate gets deployed at all.
|
|
8
|
+
|
|
9
|
+
### Single sign-on (OpenID Connect)
|
|
10
|
+
|
|
11
|
+
**Admin → Settings → Single sign-on**, alongside local accounts and LDAP rather than
|
|
12
|
+
instead of them. Register one redirect URI with your provider, fill in the issuer,
|
|
13
|
+
client ID, secret and scopes, and everything else — the endpoints, the signing keys —
|
|
14
|
+
comes from the provider's discovery document.
|
|
15
|
+
|
|
16
|
+
- **The ID token is actually verified**: signature against the provider's published
|
|
17
|
+
keys, plus issuer, audience, expiry, and a nonce tied to that one sign-in, so a token
|
|
18
|
+
captured from another flow cannot be replayed into this one. A token that merely
|
|
19
|
+
decodes proves nothing.
|
|
20
|
+
- **Authorization code with PKCE**, so it is safe with or without a client secret.
|
|
21
|
+
- **The in-progress sign-in lives in the database**, not in worker memory. The browser
|
|
22
|
+
can come back to a different worker than the one it left, which is the whole point of
|
|
23
|
+
the stateless design.
|
|
24
|
+
- **The session token never travels in a URL.** The callback hands the page a one-time
|
|
25
|
+
code, good for about a minute, which it trades for a session — so nothing sensitive
|
|
26
|
+
reaches browser history or a proxy log.
|
|
27
|
+
- **A provider group grants nothing until an admin maps it**, exactly as LDAP works. A
|
|
28
|
+
directory group created next month cannot quietly open a webgate group that happens
|
|
29
|
+
to share its name. A separate admin-groups list grants admin.
|
|
30
|
+
- Sign-ins are audited as `sso_login`, with the groups granted.
|
|
31
|
+
- Accounts created this way have **no local password** — nothing to reset, nothing to
|
|
32
|
+
leak.
|
|
33
|
+
|
|
34
|
+
Twenty-one tests, run against a real identity provider started for the test: it
|
|
35
|
+
publishes a discovery document and a JWKS and signs its own tokens, so the
|
|
36
|
+
verification is exercised rather than mocked away. A token signed with the wrong key,
|
|
37
|
+
issued for another audience, expired, or carrying another sign-in's nonce is refused,
|
|
38
|
+
and each of those is a test.
|
|
39
|
+
|
|
40
|
+
[Setup, and what to do when nobody gets any groups](https://kalexnolasco.github.io/webgate/guide/sso/).
|
|
41
|
+
|
|
42
|
+
354 tests: 345 unit, 9 browser.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## v2.6.0 (2026-09-16) — rotating the secret key no longer breaks everything
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
|
|
50
|
+
- **A changed `WEBGATE_SECRET_KEY` made webgate fall over instead of explaining
|
|
51
|
+
itself.** v2.2.0 refuses the shipped default, which asks every existing install to
|
|
52
|
+
set a real one; doing that leaves rows encrypted under the old key. `decrypt_value`
|
|
53
|
+
raised a bare Fernet error from twelve call sites and none of them caught it. The
|
|
54
|
+
status monitor died on its first sweep and froze every server's dot; opening a
|
|
55
|
+
terminal failed the WebSocket **upgrade**, so the browser got a 500 with nothing in
|
|
56
|
+
it. Found on the public demo, by walking into it.
|
|
57
|
+
|
|
58
|
+
An unreadable credential is now its own error, naming the server, why it happened
|
|
59
|
+
and what to do. The monitor marks that one server offline and carries on, the
|
|
60
|
+
terminal answers with an error frame, SFTP returns 502, and a connection test
|
|
61
|
+
reports it.
|
|
62
|
+
|
|
63
|
+
- **The demo banner covered the top of the interface.** It was `position: fixed` with
|
|
64
|
+
nothing making room for it, so the app bar sat underneath. It is in flow now, which
|
|
65
|
+
also holds when it wraps to two lines on a narrow screen. Only visible with
|
|
66
|
+
`WEBGATE_DEMO_MODE=true`, which is why no test caught it.
|
|
67
|
+
|
|
68
|
+
- **Diagrams that did not show.** Nine style directives set a pale fill and left the
|
|
69
|
+
label to the theme, so on GitHub's dark background they were light text on a light
|
|
70
|
+
box. All 21 diagrams in the repository now render, and every label is readable on
|
|
71
|
+
both grounds — checked with the Mermaid version GitHub actually uses, in a browser,
|
|
72
|
+
one diagram per page.
|
|
73
|
+
|
|
74
|
+
### Added
|
|
75
|
+
|
|
76
|
+
- **The pinned host key is visible.** The fingerprint has been in the API since
|
|
77
|
+
v2.0.0 and nothing showed it. Servers carry a shield when a key is pinned, the edit
|
|
78
|
+
form shows the fingerprint, and an admin can clear it there — with the consequence
|
|
79
|
+
spelled out, since accepting a new key is the one moment the protection is stood
|
|
80
|
+
down.
|
|
81
|
+
- **A cap on concurrent SSH sessions per account** (**Admin → Settings → Security**,
|
|
82
|
+
0 for none). There was nothing stopping a looping tab from opening sessions until
|
|
83
|
+
the worker ran out. Checked before the SSH connection is made, and counted per
|
|
84
|
+
worker — which the setting says, because a gateway behind a load balancer allows
|
|
85
|
+
that many on each.
|
|
86
|
+
|
|
87
|
+
333 tests.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
3
91
|
## v2.5.0 (2026-09-16) — browser tests
|
|
4
92
|
|
|
5
93
|
The audit gap in v2.4.0 was reported by a person using the interface, and no test in
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Single sign-on
|
|
2
|
+
|
|
3
|
+
**Admin → Settings → Single sign-on.** OpenID Connect, which is what Entra ID, Okta,
|
|
4
|
+
Google Workspace, Keycloak and Authentik all speak.
|
|
5
|
+
|
|
6
|
+
LDAP still works, and so do local accounts. Turning this on adds a button to the
|
|
7
|
+
sign-in screen; it does not take anything away.
|
|
8
|
+
|
|
9
|
+
## What to register with your provider
|
|
10
|
+
|
|
11
|
+
One redirect URI:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
https://your-webgate/api/auth/sso/callback
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Then fill in four things here: the **issuer URL**, the **client ID**, the **client
|
|
18
|
+
secret** if your provider issued one, and the **scopes**. Everything else — the
|
|
19
|
+
authorize and token endpoints, the signing keys — is read from
|
|
20
|
+
`{issuer}/.well-known/openid-configuration`, so there is nothing else to copy across.
|
|
21
|
+
|
|
22
|
+
| Provider | Issuer looks like |
|
|
23
|
+
|---|---|
|
|
24
|
+
| Entra ID | `https://login.microsoftonline.com/<tenant-id>/v2.0` |
|
|
25
|
+
| Okta | `https://<org>.okta.com/oauth2/default` |
|
|
26
|
+
| Google Workspace | `https://accounts.google.com` |
|
|
27
|
+
| Keycloak | `https://<host>/realms/<realm>` |
|
|
28
|
+
| Authentik | `https://<host>/application/o/<slug>/` |
|
|
29
|
+
|
|
30
|
+
!!! tip "Behind a proxy that rewrites the host"
|
|
31
|
+
webgate works out its own address from the request. If a proxy changes it, set
|
|
32
|
+
**Public URL** and the redirect will be that plus `/api/auth/sso/callback`.
|
|
33
|
+
|
|
34
|
+
## Groups decide what people can reach
|
|
35
|
+
|
|
36
|
+
A group from your directory means nothing here until an admin maps it:
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{"infra-oncall": "prod", "platform": "core"}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
An unmapped group grants nothing. That is deliberate: a directory group created next
|
|
43
|
+
month must not quietly open a webgate group that happens to share its name.
|
|
44
|
+
|
|
45
|
+
**Admin groups** is a separate list — membership in any of them makes the person a
|
|
46
|
+
webgate admin, and admins see every server.
|
|
47
|
+
|
|
48
|
+
Both are re-read on every sign-in, so removing somebody from a group in your directory
|
|
49
|
+
takes effect the next time they sign in. It does not end a session already open; the
|
|
50
|
+
session-token lifetime under **Security** decides that.
|
|
51
|
+
|
|
52
|
+
### If nobody gets any groups
|
|
53
|
+
|
|
54
|
+
Your provider is probably not sending the claim. Two things to check: the **scopes**
|
|
55
|
+
usually need an extra one (`groups` on Okta and Keycloak, `GroupMember.Read.All`
|
|
56
|
+
consented on Entra), and the **groups claim** has to match the name your provider
|
|
57
|
+
uses. Entra sends group *object ids* by default rather than names, unless the app
|
|
58
|
+
registration is set to emit names.
|
|
59
|
+
|
|
60
|
+
## What happens on sign-in
|
|
61
|
+
|
|
62
|
+
1. The button sends the browser to your provider, with PKCE and a one-time `state` and
|
|
63
|
+
`nonce`.
|
|
64
|
+
2. Your provider sends it back to the callback with a code.
|
|
65
|
+
3. webgate exchanges the code, then **verifies the ID token** — signature against your
|
|
66
|
+
provider's published keys, issuer, audience, expiry, and that the nonce matches the
|
|
67
|
+
one it issued for this sign-in.
|
|
68
|
+
4. The account is created or refreshed, with the groups from the mapping.
|
|
69
|
+
5. The browser is handed a **one-time code**, and trades it for a session. The session
|
|
70
|
+
token is never put in a URL, so it cannot end up in browser history or a proxy log.
|
|
71
|
+
|
|
72
|
+
The in-progress sign-in lives in the database, not in memory, so the browser can come
|
|
73
|
+
back to a different worker than the one it left. Abandoned attempts are cleared after
|
|
74
|
+
ten minutes.
|
|
75
|
+
|
|
76
|
+
## Notes
|
|
77
|
+
|
|
78
|
+
- A person who signs in this way has **no local password**. There is nothing to reset
|
|
79
|
+
and nothing to leak; your provider is the credential.
|
|
80
|
+
- 2FA is your provider's business here. webgate's own TOTP applies to local accounts.
|
|
81
|
+
- Every sign-in is recorded as `sso_login` in the audit log, with the groups granted.
|
|
82
|
+
- Turning SSO off leaves the accounts it created. They simply have no working password
|
|
83
|
+
until an admin sets one.
|
|
@@ -42,8 +42,8 @@ flowchart LR
|
|
|
42
42
|
BOB -->|sees| S3
|
|
43
43
|
|
|
44
44
|
style ADMIN fill:#5cb85c,stroke:#449d44,color:#fff
|
|
45
|
-
style ALICE fill:#
|
|
46
|
-
style BOB fill:#
|
|
45
|
+
style ALICE fill:#2a6cb5,stroke:#4a90d9,color:#fff
|
|
46
|
+
style BOB fill:#b25e00,stroke:#ff9800,color:#fff
|
|
47
47
|
```
|
|
48
48
|
|
|
49
49
|
## Editing Groups
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -46,6 +46,7 @@ nav:
|
|
|
46
46
|
- Split View: guide/split.md
|
|
47
47
|
- User Management: guide/users.md
|
|
48
48
|
- Access model (groups, LDAP, tags): guide/access-control.md
|
|
49
|
+
- Single sign-on (OIDC): guide/sso.md
|
|
49
50
|
- Advanced (jump host, snippets, sharing, recording): guide/advanced.md
|
|
50
51
|
- Integrations (webhooks, LDAP, API keys, reverse proxy): guide/integrations.md
|
|
51
52
|
- Settings (admin panel): guide/settings.md
|
|
@@ -13,6 +13,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
13
13
|
from webgate import __version__
|
|
14
14
|
from webgate.agent.routes import router as agent_router
|
|
15
15
|
from webgate.agent.store import load_config
|
|
16
|
+
from webgate.auth import oidc
|
|
16
17
|
from webgate.auth.routes import limiter
|
|
17
18
|
from webgate.auth.routes import router as auth_router
|
|
18
19
|
from webgate.auth.service import seed_admin
|
|
@@ -95,7 +96,13 @@ def create_app() -> FastAPI:
|
|
|
95
96
|
agent_available = False
|
|
96
97
|
if not settings.demo_mode:
|
|
97
98
|
agent_available = (await load_config(session)).enabled
|
|
98
|
-
return {
|
|
99
|
+
return {
|
|
100
|
+
"demo_mode": settings.demo_mode,
|
|
101
|
+
"agent_available": agent_available,
|
|
102
|
+
# The sign-in screen needs this before anyone has authenticated.
|
|
103
|
+
"sso_enabled": oidc.enabled(),
|
|
104
|
+
"sso_name": oidc.provider_name() if oidc.enabled() else "",
|
|
105
|
+
}
|
|
99
106
|
|
|
100
107
|
if settings.demo_mode:
|
|
101
108
|
# In demo mode block any state-changing request on /api/* except an
|