Repository navigation
Deployment
The Hub serves HTTP on :8081, agent mTLS gRPC on :9090, and the SSH gateway on
:2222 by default. Expose the gateway through TCP when enabling vey ssh;
HTTPS-only reverse proxy routes do not carry SSH. Managed nodes need the agent's
outbound Hub connection; gateway shells do not use the node's own SSH daemon.
See Proxy Configuration and SSH Gateway.
The standard agent systemd installer omits User=, so the agent runs as root.
Local Veyport admin shells inherit that identity in both the browser and CLI.
LDAP shells instead use the mapped host account, which must be resolvable on each
managed node. Changing the agent service identity also changes the OS permissions
available to its file operations and inherited admin shells; see Architecture.
TL;DR
- What: Docker-first deployment with a single
docker-compose.yml; bare-metal binary also available- Who: The person deploying Veyport Hub and agents
- Why: One file to download, one command to run - no cloning, no building
- Where: Hub on a central server (Docker or bare-metal); agents on each managed server
- When:
curlthe compose file,docker compose up -d, open the browser- How: Docker Compose (primary) or build from source (contributors)
- Docker and Docker Compose (v2+)
- A Linux server (amd64 or arm64)
- A domain name pointing to your server (for production use with TLS)
# Download the compose file
curl -O https://raw.githubusercontent.com/lorconksu/veyport/main/docker-compose.yml
# Start Veyport
docker compose up -dThe Hub starts on port 8081 (HTTP) and 9090 (gRPC). Open http://localhost:8081 in your browser to create the initial admin account and set up 2FA. Then run the one-liner shown in the UI on each server you want to manage.
That's it. No cloning, no building, no dependencies.
The default docker-compose.yml:
services:
veyport:
image: yiucloud/veyport:latest
container_name: veyport
ports:
- "8081:8081" # HTTP - web UI and REST API
- "9090:9090" # gRPC - agent connections
volumes:
- veyport-data:/data # SQLite DB and persistent state
restart: unless-stopped
volumes:
veyport-data:| Setting | Value | Notes |
|---|---|---|
| Image | yiucloud/veyport:latest |
Pin to a specific tag (e.g. yiucloud/veyport:vX.Y.Z) for reproducible deployments |
| HTTP port | 8081 |
Web UI and REST API |
| gRPC port | 9090 |
Agent connections - must be reachable by agents |
| Data volume |
veyport-data mounted at /data
|
Contains the SQLite database (/data/veyport.db) and all persistent state |
| Binary path | /app/veyport |
The Hub binary inside the container |
Replace latest with a specific version tag to avoid unexpected upgrades:
image: yiucloud/veyport:vX.Y.ZThe published command-passthrough prototype is Hub 2.0.50-prototype.9 / tag
prototype-ssh-passthrough.9, with CLI and agent prototype 9 artifacts. Older
CLI prototype 6 builds are also compatible with the Hub. These prototype
builds are not a stable or main-branch release. Check current
publication and qualification on the release page,
verify compatible builds and target agent support, and follow the
owner-approved staging and rollback procedure. This publication information
is not new runtime qualification evidence.
If Traefik is also running in Docker, add labels to the Veyport service. Create a docker-compose.traefik.yml:
services:
veyport:
image: yiucloud/veyport:latest
container_name: veyport
volumes:
- veyport-data:/data
restart: unless-stopped
labels:
# HTTP router - web UI and REST API
- "traefik.enable=true"
- "traefik.http.routers.veyport.rule=Host(`veyport.example.com`)"
- "traefik.http.routers.veyport.entrypoints=websecure"
- "traefik.http.routers.veyport.tls.certresolver=letsencrypt"
- "traefik.http.routers.veyport.service=veyport"
- "traefik.http.services.veyport.loadbalancer.server.port=8081"
# gRPC router - agent connections (path prefix matches the proto package)
- "traefik.http.routers.veyport-grpc.rule=Host(`veyport.example.com`) && PathPrefix(`/veyport.v1.`)"
- "traefik.http.routers.veyport-grpc.entrypoints=websecure"
- "traefik.http.routers.veyport-grpc.tls.certresolver=letsencrypt"
- "traefik.http.routers.veyport-grpc.service=veyport-grpc"
- "traefik.http.services.veyport-grpc.loadbalancer.server.port=9090"
- "traefik.http.services.veyport-grpc.loadbalancer.server.scheme=h2c"
networks:
- traefik
volumes:
veyport-data:
networks:
traefik:
external: trueNo port mappings are needed when Traefik handles routing - traffic flows through the Docker network.
If Traefik runs outside Docker, use a file provider config. Create /etc/traefik/dynamic/veyport.yml:
http:
routers:
# HTTP and REST API traffic
veyport:
rule: "Host(`veyport.example.com`)"
entryPoints:
- websecure
tls:
certResolver: letsencrypt
service: veyport
# gRPC traffic from agents (path prefix matches the proto package)
veyport-grpc:
rule: "Host(`veyport.example.com`) && PathPrefix(`/veyport.v1.`)"
entryPoints:
- websecure
tls:
certResolver: letsencrypt
service: veyport-grpc
services:
veyport:
loadBalancer:
servers:
- url: "http://127.0.0.1:8081"
# h2c (cleartext HTTP/2) is required for gRPC backends without TLS
veyport-grpc:
loadBalancer:
servers:
- url: "h2c://127.0.0.1:9090"Make sure your main traefik.yml has the file provider enabled:
providers:
file:
directory: /etc/traefik/dynamic
watch: trueTraefik handles TLS termination (Let's Encrypt). The Hub sees plain HTTP on port 8081 and cleartext gRPC (h2c) on port 9090. The PathPrefix('/veyport.v1.') matcher routes agent gRPC connections correctly because gRPC uses the proto package path as the HTTP/2 :path header.
Application access is optional. The Hub accepts --application-domain and --application-portal-origin at startup. Set both when enabling application access; use the exact HTTPS origin of the Veyport portal, for example:
/app/veyport --addr :8081 --grpc-addr :9090 --db /data/veyport.db \
--agent-bin-dir /app --application-domain apps.example.com \
--application-portal-origin https://veyport.example.com
For Docker Compose, pass the same flags as the service command (the image entrypoint is /app/veyport). Keep the application's domain namespace dedicated to Veyport application hosts. Configure DNS and the HTTPS edge so each configured application hostname in *.apps.example.com reaches the Hub's HTTP listener, with a certificate valid for the exact hostname (typically a wildcard certificate). The edge must preserve the request host and support WebSocket upgrades. Keep the portal hostname and application namespace aligned with the two Hub flags.
Example when the portal is published as veyport.example.com:443 and the Hub also has a private control address:
--application-domain apps.example.com
--application-portal-origin https://veyport.example.com
--application-control-deny-authorities veyport.internal.example:8443,veyport.example.com:443
--application-control-deny-authorities is a comma-separated list of additional host:port control-plane authorities that app backends must not target. Configure it for published or private Hub control mappings that differ from the Hub listener addresses or portal origin. For Docker, account for both sides of each published port mapping: for example, a host mapping 443:8081 is reached by clients as veyport.example.com:443, while veyport.internal.example:8443 is a separate private address if that is how operators reach the Hub. Supply the client-visible authority, not the container's internal port, and keep this flag in the service command alongside the two application flags. The application hostname namespace itself must route to the Hub HTTP listener; backend application ports should remain private to the Hub or selected node.
Each application is separately configured with a backend URL, an allowed destination CIDR set, and either a Hub route or an enrolled-node route. The Hub validates DNS results and pins an allowed destination address for direct routes; node routes also enforce destination policy at the node. Use a custom CA only when the backend uses a private certificate authority. Do not disable certificate verification. Restrict backend CIDRs to the intended application network and keep Hub control-plane addresses out of those ranges.
For this prototype, each Hub startup durably ends existing application sessions and clears unused launch tickets before application admission. Users must reopen their apps; the original completed-MFA maximum deadline remains in force, and portal/CLI session policy is unchanged. Failed cleanup writes keep application admission closed until a retry succeeds. Session-end audit records remain queued for idempotent retry after audit storage recovers. The in-memory pending-revocation queue is capped at 1024 IDs; overflow closes application admission and requires durable bulk cleanup before access resumes. Existing housekeeping retries at most 100 pending IDs per pass and stops at the first failed write.
The Hub bounds application transport: up to 128 active proxy connections globally, 32 node-routed connections per node, eight concurrent requests per application session, 64 KiB transport frames, and a 30-second socket write-stall deadline. Backend response headers are capped at 1 MiB. Request and response bodies stream through the proxy rather than being collected as whole-body buffers; a 64 MiB transfer is a test scenario, not a configured maximum body size. These are runtime guardrails, not a substitute for measuring memory and cleanup under the deployment's expected load.
Native applications should use the configured public application URL for links and callbacks. Veyport does not rewrite arbitrary HTML or JavaScript URLs and does not follow backend redirects by opening a new backend connection. An external identity provider or other cross-origin service must be independently reachable and trusted by the browser; verify the application's redirect/callback behavior before enabling access.
Before enabling the feature on an existing Hub, take and verify a consistent database backup, retain the current image or binary and exact startup configuration, and record the candidate artifact digest. Agree on an owner-approved rollout and rollback procedure. If rollback is needed, restore a mutually compatible software/configuration/database set from the recorded backup; do not assume that replacing only the binary reverses database changes. Disabling the application flags stops the feature from being configured at startup but does not restore records written after the backup. No staging or public deployment qualification is implied by this guide.
The feature does not require exposing backend application servers directly to users. Choose DNS visibility, firewall rules, and edge exposure according to the deployment's access policy; this guide does not imply that a public application namespace is provisioned. See Application Access for grants, session behavior, and API details.
Agents are lightweight Go binaries deployed on each managed server. They dial out to the Hub's gRPC port - no inbound firewall rules are needed on the agent host.
The Hub serves an install script and the agent binaries. On the managed server, run:
curl -sSL https://veyport.example.com/install.sh | sudo bash -s -- \
--token <REGISTRATION_TOKEN> \
--hub veyport.example.com:443For a new node, the script will:
- Detect the OS and CPU architecture
-
Auto-detect an existing installation - if
veyport-agentis already installed and has a validagent.conf, the script callsveyport-agent --self-unregisterto remove the old server entry from the Hub before proceeding - Download the correct agent binary from
/install/{os}/{arch} - Write the configuration to
/etc/veyport/agent.conf - Install and enable a systemd service for the agent
- Verify registration - after starting the agent service, the script checks that the agent successfully registered with the Hub before reporting success
This script is an enrollment or replacement workflow, not an upgrade command.
On an existing installation it calls --self-unregister, which removes the
current Hub server entry before registering again. That can change the node's
identity and lose the continuity of its server history and assignments. Use the
built-in update flow below to preserve an enrolled node's identity, local
configuration, and credentials.
| Mode | Behaviour |
|---|---|
Piped from curl (non-interactive) |
Automatically replaces any existing installation without prompting; this unregisters and re-enrolls it |
| Run as a script manually (interactive terminal) | Prompts [R]eplace / [K]eep; Replace unregisters and re-enrolls, while Keep exits with a non-zero status |
If you prefer not to pipe to bash:
# Download the binary (example: linux/amd64)
curl -Lo /usr/local/bin/veyport-agent \
https://veyport.example.com/install/linux/amd64
chmod +x /usr/local/bin/veyport-agent
# Run directly
/usr/local/bin/veyport-agent \
--hub veyport.example.com:443 \
--token <REGISTRATION_TOKEN>After first registration, the agent writes its assigned server_id and hub_url to:
/etc/veyport/agent.conf
Example contents (JSON):
{
"server_id": "550e8400-e29b-41d4-a716-446655440000",
"hub_url": "veyport.example.com:443"
}On restart, the agent loads this file and reconnects without needing the registration token again.
The install script creates /etc/systemd/system/veyport-agent.service:
[Unit]
Description=Veyport Agent
After=network.target
[Service]
Type=simple
EnvironmentFile=/etc/veyport/agent.env
ExecStart=/bin/sh -eu -c 'if [ -f /etc/veyport/agent.conf ]; then exec /usr/local/bin/veyport-agent; else exec /usr/local/bin/veyport-agent --hub "$VEYPORT_HUB" --token "$VEYPORT_TOKEN" --ca-pin "$VEYPORT_HUB_CA_PIN"; fi'
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target| Flag | Description |
|---|---|
--hub <addr> |
Hub gRPC address (e.g. veyport.example.com:443 or 192.168.1.10:9090) |
--token <token> |
One-time registration token obtained from the Hub when creating a server record |
--insecure |
Disables TLS for development only, including self-unregistration. Never use on an untrusted network. |
--self-unregister |
Calls DELETE /api/servers/{id}/self-unregister on the Hub to remove the current server entry, then exits. Used by the install script before re-installing to clean up the old registration. Requires a valid agent.conf with a known server_id. |
The agent uses TLS by default for both hostnames and IP addresses. Self-unregistration uses the same host over HTTPS on port 443 and does not follow redirects, so its unregister credential stays on the original request. An IP address must have a trusted TLS endpoint to support self-unregistration.
--insecure explicitly enables plaintext transport for development only. With --self-unregister --insecure, the REST request uses HTTP on port 8081. Production deployments should use a TLS proxy and omit this flag.
The agent reconnects with exponential backoff - starting at 1 second and capping at 60 seconds. On each reconnect it re-sends a Register message so the Hub updates sysinfo and resets connection state.
| Direction | Protocol | Port | Notes |
|---|---|---|---|
| Agent - Hub | gRPC (HTTP/2) | 9090 (or 443 via Traefik) | Must be reachable from agent host |
| Hub - Agent | None | - | Agents always dial out; Hub never initiates |
Veyport can authenticate users against LDAP and map LDAP groups to Veyport roles. Admins can configure LDAP from Settings > Directory in the web UI. The Hub stores the same values in _config, so operators can still inspect or recover the configuration directly from the database when needed.
The Directory tab supports:
- Enabling or disabling LDAP login
- LDAP URL, service bind DN, and write-only bind password
- User and group search base DNs
- Search filters and attribute mappings
- StartTLS, TLS server name override, CA certificate PEM, and lab-only insecure transport
- Configurable LDAP groups for admin, auditor, viewer, and terminal access
- A service bind connection test
| Key | Purpose |
|---|---|
ldap.enabled |
Enables LDAP login when set to true
|
ldap.url |
LDAP endpoint, such as ldaps://freeipa.example.com:636
|
ldap.bind_dn |
Optional service account DN used for searches |
ldap.bind_password |
Service account password; encrypted enc: values are preferred |
ldap.user_base_dn |
Base DN for user searches |
ldap.group_base_dn |
Base DN for group searches |
ldap.user_search_filter |
User search filter, default (uid={username})
|
ldap.group_search_filter |
Group search filter, default `( |
ldap.username_attribute |
Username attribute, default uid
|
ldap.email_attribute |
Email attribute, default mail
|
ldap.external_id_attribute |
Stable external ID attribute, default entryUUID
|
ldap.group_name_attribute |
Group display/name attribute, default cn
|
ldap.start_tls |
Uses StartTLS with ldap:// when set to true
|
ldap.tls_server_name |
Optional TLS server name override |
ldap.ca_cert_pem |
Optional PEM CA bundle for LDAP server verification |
ldap.allow_insecure_transport |
Allows plaintext LDAP only for lab use |
ldap.admin_groups |
JSON array or comma-separated LDAP groups mapped to the Admin role |
ldap.auditor_groups |
JSON array or comma-separated LDAP groups mapped to the Auditor role |
ldap.viewer_groups |
JSON array or comma-separated LDAP groups mapped to the Viewer role |
ldap.terminal_groups |
JSON array or comma-separated LDAP groups eligible for terminal access |
LDAP transport is fail-closed by default. Use ldaps:// or set ldap.start_tls=true; plaintext ldap:// is rejected unless ldap.allow_insecure_transport=true.
| LDAP group | Veyport effect |
|---|---|
veyport-admins |
Admin role |
veyport-auditors |
Auditor role |
veyport-viewers |
Viewer role |
veyport-terminal-users |
Marks the LDAP user as eligible for terminal access |
These are defaults only. Change them in Settings > Directory to match your LDAP or FreeIPA group names.
Terminal access has an additional server-level gate. LDAP terminal users must also have a root (/) path assignment on the specific server. Local non-admin users cannot open terminal sessions.
As of v1.1, the Hub supports mutual TLS (mTLS) for gRPC connections from agents. This provides cryptographic identity verification beyond the initial registration token.
- Automatic CA generation -- On first boot, the Hub generates an ECDSA P-256 Certificate Authority. No manual certificate management is required.
- Agent certificates issued during registration -- When an agent registers, the Hub signs a client certificate (12-hour validity) and sends it to the agent over the gRPC stream.
- In-stream renewal -- Agents automatically renew their certificates at the 6-hour mark (50% lifetime), with no downtime or reconnection.
-
Agent cert storage -- Client certificates and keys are stored at
/etc/veyport/tls/on each agent host.
By default, mTLS is optional for backward compatibility. Agents that support mTLS will use it; older agents continue to work without it.
To enforce mTLS for all agent connections, add the --require-mtls flag:
# Docker: add to the command in docker-compose.yml
command: ["--require-mtls"]
# Bare-metal: add to the ExecStart line in the systemd unit
ExecStart=/opt/veyport/bin/veyport \
--addr 127.0.0.1:8081 \
--grpc-addr 0.0.0.0:9090 \
--db /var/lib/veyport/veyport.db \
--agent-bin-dir /opt/veyport/agent-bins \
--require-mtlsThis is a one-time transport migration for agents deployed before v1.1 that do
not have mTLS certificates. It is separate from normal agent updates, which use
vey servers update <node> and preserve the enrolled identity. To migrate an
agent that cannot establish mTLS:
- Re-register the agent (unregister from the Hub UI, then re-run the install script with a new token)
- The new agent will automatically receive an mTLS certificate during registration
- Once all agents are re-registered, enable
--require-mtlson the Hub
| File | Path | Description |
|---|---|---|
| Client cert | /etc/veyport/tls/client.crt |
Agent's certificate signed by Hub CA |
| Client key | /etc/veyport/tls/client.key |
Agent's private key (never leaves the machine) |
| CA cert | /etc/veyport/tls/ca.crt |
Hub CA certificate for verifying the Hub |
As of v1.1, the web UI uses httpOnly cookies for JWT token storage instead of localStorage. This change is fully automatic -- no configuration is needed.
- Tokens are set as
HttpOnly,Secure,SameSite=Strictcookies - CSRF protection uses a double-submit cookie pattern (handled by the frontend automatically)
- Non-browser clients (scripts, CLI tools) can still use
Authorization: Bearer <token>headers - The refresh token cookie is scoped to
/api/auth/refreshto limit exposure
No action is required from operators. The cookie auth is active by default when upgrading to v1.1.
If you are automating Veyport from cron, CI, or a scanner, do not script a human username/password/TOTP flow. Use a dedicated service account plus a CLI-created API token instead.
# Docker deployment
docker exec veyport /app/veyport admin create-api-token \
--username scanner \
--name nightly-scan \
--expires-in 720h \
--db /data/veyport.db
# Bare-metal deployment
./bin/veyport admin create-api-token \
--username scanner \
--name nightly-scan \
--expires-in 720h \
--db /var/lib/veyport/veyport.dbStore the printed token in a root-only env file on the machine that runs the automation:
sudo install -m 600 /dev/null /etc/veyport/automation.env
sudo editor /etc/veyport/automation.envExample contents:
VEYPORT_SCAN_BASE_URL=https://veyport.example.com
VEYPORT_API_TOKEN=adt_replace_meReference the token from your scripts as an Authorization: Bearer $VEYPORT_API_TOKEN header, and rotate it before the --expires-in window elapses by issuing a new token and updating the env file.
Veyport can be deployed in a Proxmox LXC container as a lightweight alternative to a full VM. This is useful for home lab and small-team deployments.
- Create an unprivileged LXC container (Debian/Ubuntu) with at least 512 MB RAM and 4 GB disk.
-
Install Docker inside the container (requires the
keyctlandnestingfeatures enabled on the LXC). -
Deploy with Docker Compose using the same
docker-compose.ymlas any other deployment. - Configure Traefik on the host or a separate LXC to reverse-proxy HTTPS traffic to the container's IP.
If Traefik runs on a separate LXC and Veyport runs in another container at 192.0.2.10:
# /etc/traefik/dynamic/veyport.yml on the Traefik host
http:
routers:
veyport:
rule: "Host(`veyport.example.com`)"
entryPoints: [websecure]
tls:
certResolver: letsencrypt
service: veyport
veyport-grpc:
rule: "Host(`veyport.example.com`) && PathPrefix(`/veyport.v1.`)"
entryPoints: [websecure]
tls:
certResolver: letsencrypt
service: veyport-grpc
services:
veyport:
loadBalancer:
servers:
- url: "http://192.0.2.10:8081"
veyport-grpc:
loadBalancer:
servers:
- url: "h2c://192.0.2.10:9090"In the Veyport container, bind the HTTP and gRPC ports to 0.0.0.0 so Traefik can reach them across the LXC network.
Point your domain's A record (and AAAA for IPv6) to the public IP of the server running Traefik.
veyport.example.com. IN A 203.0.113.10
Traefik will automatically obtain a Let's Encrypt certificate on first request once the DNS record propagates.
Veyport uses SQLite with WAL mode. The database is a single file.
-
Docker: The database lives at
/data/veyport.dbinside the container, persisted via theveyport-datanamed volume. -
Bare-metal: The database path is set via the
--dbflag (default:veyport.db).
Auto-migrations: On every startup, the Hub checks for and applies any unapplied migration files. No manual schema management is needed after upgrades.
Migration 031 adds the per-account SSH credential generation used to invalidate existing SSH certificates after password recovery. Existing accounts start at generation zero, so certificates issued before this migration remain compatible until a password recovery advances the generation. No manual database migration step is required.
Backups: SQLite's WAL mode makes it safe to copy the database file while the Hub is running. To take a consistent snapshot:
# Docker - use docker exec to run sqlite3 inside the container
docker exec veyport sqlite3 /data/veyport.db ".backup /data/backup-$(date +%Y%m%d).db"
# Bare-metal
sqlite3 /var/lib/veyport/veyport.db ".backup /var/lib/veyport/backup-$(date +%Y%m%d).db"Store backups off-host. The database contains all user accounts, server registrations, and audit logs.
If an admin is locked out (lost their authenticator app), use the admin reset-totp command directly on the server hosting the Hub. This requires shell access to the machine - it cannot be triggered via the web UI.
# If running via Docker
docker exec veyport /app/veyport admin reset-totp --username <username> --db /data/veyport.db
# If running as a bare-metal binary
./bin/veyport admin reset-totp --username <username> --db /var/lib/veyport/veyport.dbFor local accounts, including protected security principals, this atomically clears TOTP, installs an expiring temporary password, preserves completed initial setup, and records a system audit event. It retains the outgoing permanent password using the configured password history count; repeated resets replace the temporary credential without consuming history. The temporary password uses the configured TTL and is printed only after the transaction commits.
The reset increments token and SSH credential generations and revokes sessions, API tokens, application login authority, application sessions, and launch tickets. Any storage failure rolls back the entire reset. LDAP accounts are refused; directory passwords must be recovered in the directory.
The user must enroll TOTP and choose a fresh permanent password on their next login, before the temporary password expires. Protected local accounts receive authority limited to completing this recovery; ordinary protected-account changes still require governance approval.
If the database is locked (bare-metal only):
systemctl stop veyport
./bin/veyport admin reset-totp --username admin --db /var/lib/veyport/veyport.db
systemctl start veyportIn practice the Hub does not hold exclusive locks on the database (WAL mode), so running the command while the service is live is usually safe.
Use admin rotate-jwt-secret when you need to invalidate all existing sessions and API tokens — either as part of a routine security cadence or in response to a suspected credential compromise.
When to rotate:
- Routine: Annually, or per your organisation's key-rotation policy.
- Compromise response: Immediately if the database file or its signing secret may have been exposed.
# If running via Docker
docker exec veyport /app/veyport admin rotate-jwt-secret --db /data/veyport.db
# If running as a bare-metal binary
./bin/veyport admin rotate-jwt-secret --db /var/lib/veyport/veyport.dbWithout --yes the command prints an impact summary and asks for confirmation. Pass --yes for scripted use.
What this does:
- Generates a new JWT signing secret and persists it to the database.
- Revokes all API tokens immediately (count reported on stdout).
- Invalidates all active user sessions — every user must sign in again.
- Encrypted settings (LDAP bind password, SMTP password, CA key) are unaffected; the storage key is separate from the signing secret.
After rotating, restart the Hub to apply the new secret:
# Docker
docker compose -f /opt/veyport/docker-compose.yml restart
# Bare-metal
systemctl restart veyportVerification checklist:
- An access token issued before rotation is rejected (HTTP 401) on any API call.
- Fresh sign-in (password + TOTP) succeeds.
- Settings → Directory "Test Connection" passes.
- Test notification email sends successfully.
- Agents reconnect or remain connected.
-
GET /api/settings/hubshows the updatedjwt_secret_rotated_attimestamp. - Audit log contains an
auth.jwt_secret_rotatedevent with the revoked-token count.
Backup/restore note: A database backup is self-consistent — it pairs the secret with its ciphertexts. Restoring a pre-rotation backup undoes the rotation. If the rotation was a compromise response, re-rotate immediately after restoring.
Update the operator's CLI from its configured Hub with vey update. Use
vey update --check first to inspect the Hub version and verified artifact
metadata without downloading or changing files. The local updater supports
Linux and macOS amd64/arm64 and installs only to /usr/local/bin/vey. If it
reports a permission problem, rerun the exact command printed by the error with
explicit sudo; the command does not elevate itself. See CLI#update-the-cli
for version checks and recovery details. A CLI build that predates the built-in
updater needs one final install from the configured Hub's Install CLI command
before it can use vey update.
For a node update, the node must already be enrolled with a valid mTLS identity,
and its Veyport installation must never have enrolled Enhanced or other
governance controls. Updates are available only for Standard installations.
They require a live interactive administrator session; API tokens are refused.
The command uses the effective Hub selected by --hub, VEYPORT_HUB, or the
configured default. That Hub selects the exact artifact for the enrolled node's
Linux amd64/arm64 platform. No arbitrary URL or version can be supplied, and
the command will not downgrade an agent. Linux systemd nodes using the root-owned
/usr/local/bin/veyport-agent service are supported.
vey servers update web01 --check
vey servers update web01--check reports the current agent capability, Hub artifact, version, checksum,
and whether a one-time bootstrap is required. Current agents report file-list,
file-read, file-delete, upload, and log-tail activity to the Hub. These records
stay active until the agent reports actual completion, even if the HTTP caller
cancels or times out. Maintenance is refused while any known or unconfirmed
activity, terminal, command, or other agent work remains; once reserved, new
work is blocked. A client timeout, cancellation, disconnect, or stop request is
not proof that remote work finished. Let the work complete and verify the node
is idle before retrying. The command waits up to 150 seconds for the agent to
reconnect, report the exact target version, revision, and checksum, and durably
confirm the update. If it cannot confirm that evidence, inspect the durable
operation before retrying:
vey servers update web01 --status
vey servers update web01 --status --operation <operation-id>For an older enrolled agent, or an agent that cannot report complete file/log
quiescence (including prototype.4 agents), the CLI's check response includes
bootstrap_required, bootstrap_idle_confirmation_required, and the current
connection_id. Close file and log clients, agent sessions, and other work;
allow it to finish and verify the node is idle over direct SSH. The ordinary
preserving bootstrap uses only the explicit idle assertion:
vey servers update web01 --bootstrap-idle-confirmedIf the original binary is unstamped (for example, the legacy 0.1.0 agent from the prototype.5 staging incident), also establish its version from trustworthy deployment records and measure the installed bytes over direct SSH. Do not infer a version from an arbitrary unstamped executable. For that one-time path, pass both assertions:
vey servers update web01 --bootstrap-idle-confirmed \
--bootstrap-original-version 0.1.0 \
--bootstrap-original-sha256 <sha256-from-direct-ssh>Obtain the digest on the target with sudo sha256sum /usr/local/bin/veyport-agent.
The version and hash flags are mandatory as a
pair. They bind the operator's assertion to the exact bytes being replaced.
The helper checks the local package/platform, confirms that both linker-stamped
identity fields are absent, and verifies the measured SHA-256. A stamped binary
cannot use this original-identity path. The target must be newer than the
asserted original version. The Hub binds the original-identity assertion to
the authenticated actor, session, node, fresh connection, and one-use ticket.
The idle flag remains an operator assertion, not an automatic activity
detector; the Hub still rejects known active work and a changed connection. If
you cannot confirm idle, leave the node untouched and check again later.
The CLI prints a one-use direct-SSH bootstrap command tied to the node, Hub
artifact, operation, and (for an unstamped binary) the same version and SHA-256
assertions. Run that exact printed command on the node within 120 seconds. It
verifies the Hub artifact and original bytes, checks the service is idle, then
updates the existing installation while preserving its mTLS identity,
agent.conf, service configuration, and node ID. It does not re-enroll or use
a registration token. The HTTPS Hub hostname in the printed command must match
the hostname configured for the agent's mTLS connection; HTTPS and gRPC ports
may differ. Do not substitute or reconstruct the command by hand.
For the unstamped-original case, the printed command has this form and includes
the same assertions supplied to vey servers update:
curl -fsS --proto '=https' 'https://<hub>/install/agent-update.sh' | \
sudo bash -s -- --hub 'https://<hub>' --ticket '<64-char-ticket>' \
--original-version '0.1.0' --original-sha256 '<sha256>'Creating the operation and printing a command does not mean installation
succeeded. After running the command, inspect the durable operation and wait for
succeeded:
vey servers update web01 --status
vey servers update web01 --status --operation <operation-id>
vey ssh web01 -- 'hostname; id; uname -s'The Hub records success only after the agent reconnects and durably confirms the
exact target artifact. Verify the node's identity and OS over vey ssh as a
separate operational check.
An update that fails can be failed, rolled_back, or unknown. A legacy
agent's rollback may remain unknown because the old protocol cannot report an
exact binary hash. In that case, inspect the operation and use direct SSH to
verify the original binary checksum and service before acknowledging recovery.
The CLI prints the expected original SHA-256 and earliest recovery time. Only
after verification, run:
vey servers update web01 --acknowledge-recovery <operation-id> \
--verified-sha256 <verified-original-sha256>This is an administrator attestation that direct SSH checks passed; it is not remote confirmation of the binary. Do not acknowledge recovery if the checksum or service state is uncertain. A recovery acknowledgement releases the update maintenance reservation and is audited.
docker compose pull
docker compose up -dNew migrations (if any) run automatically on startup.
Do not use this generic latest/tag upgrade procedure for an unreleased feature branch. For the command-passthrough prototype, preserve a consistent SQLite and configuration backup, retain the current image for rollback, verify the exact prototype image digest/revision, and confirm that every target agent supports the new execution protocol before testing command mode. Legacy agents must retain interactive compatibility; do not replace unrelated production agents solely to exercise the prototype. The project owner controls staging deployment and release.
Enhanced audit is optional. Standard Hub operation requires no receipt, archive,
key service, or ELK service. Enabling Enhanced requires the separately qualified
receipt/archive services and fixed trust configuration; do not point a candidate
destination at an arbitrary user-supplied endpoint. The repository's isolated
lab runbooks under infra/audit-lab/ describe its private test topology and are
not general production deployment instructions.
- Build the new binary from source (
make build) - Copy the binary to the server:
scp bin/veyport user@yourserver:/opt/veyport/bin/veyport.new
- Swap the binary and restart:
mv /opt/veyport/bin/veyport.new /opt/veyport/bin/veyport systemctl restart veyport
- New migrations (if any) run automatically on startup.
For contributors or those who prefer running a bare-metal binary.
- Go 1.26+
- Node.js 25+
- Make
git clone https://github.com/lorconksu/veyport.git
cd veyport
make buildThe make build target runs three steps in sequence:
-
build-web- Runsnpm run buildinsideweb/, producingweb/dist/ -
embed-web- Copiesweb/dist/tohub/web/dist/so it's picked up bygo:embed -
build-hub- Compileshub/cmd/veyport/intobin/veyport
The output is a single self-contained binary at bin/veyport. No Node.js, no separate web server, no external dependencies at runtime.
./bin/veyport [flags]| Flag | Default | Description |
|---|---|---|
--addr |
:8080 |
HTTP listen address and port |
--grpc-addr |
:9090 |
gRPC listen address for agent connections |
--db |
veyport.db |
Path to the SQLite database file |
--agent-bin-dir |
`` | Directory containing agent binaries served via /install/{os}/{arch}
|
--require-mtls |
false |
Require agents to present valid mTLS certificates (v1.1+) |
--grpc-external-addr |
`` | External gRPC address shown in install commands (e.g. veyport.example.com:9443). Overridable from the admin UI. |
--dev |
false |
Enable development mode (permissive CORS, no embedded frontend served) |
Example:
./bin/veyport \
--addr 127.0.0.1:8081 \
--grpc-addr 0.0.0.0:9090 \
--db /var/lib/veyport/veyport.db \
--agent-bin-dir /opt/veyport/agent-binsBind HTTP to 127.0.0.1 (loopback only) when running behind a reverse proxy. The gRPC port (9090) must be reachable by agents - bind to 0.0.0.0 or the server's public interface.
On first run, migrations execute automatically and the database file is created. Navigate to your domain in a browser to complete initial setup.
Create /etc/systemd/system/veyport.service:
[Unit]
Description=Veyport Hub
After=network.target
[Service]
Type=simple
User=veyport
Group=veyport
WorkingDirectory=/opt/veyport
ExecStart=/opt/veyport/bin/veyport \
--addr 127.0.0.1:8081 \
--grpc-addr 0.0.0.0:9090 \
--db /var/lib/veyport/veyport.db \
--agent-bin-dir /opt/veyport/agent-bins
Restart=on-failure
RestartSec=5s
# Harden the service
NoNewPrivileges=true
ProtectSystem=strict
ReadWritePaths=/var/lib/veyport /opt/veyport/agent-bins
PrivateTmp=true
[Install]
WantedBy=multi-user.targetEnable and start:
# Create the user and directories
useradd --system --no-create-home --shell /usr/sbin/nologin veyport
mkdir -p /opt/veyport/bin /var/lib/veyport
cp bin/veyport /opt/veyport/bin/veyport
chown -R veyport:veyport /opt/veyport /var/lib/veyport
# Enable and start
systemctl daemon-reload
systemctl enable veyport
systemctl start veyport
systemctl status veyportEstablished SSH gateway and browser terminal shells revalidate their captured
credential generation and current terminal authorization from SQLite every second,
including when the shell is silent or only receiving agent output. Each check
requires an eligible account, the current administrator role or LDAP terminal
grant and exact root (/) assignment for that server, an existing server, and
the execution username captured at shell creation. Browser shells also require
that their captured owning session still exists, belongs to that account, has
well-formed timestamps, and remains within its absolute and idle limits. Both
transports recheck current policy coverage and local policy continuity; recovery
state, covered Enhanced execution, and unavailable decisions close the shell.
This detects role downgrades,
permission removal, directory authorization changes, and veyport admin reset-totp
changes made by a separate process. A failed read closes the shell locally;
each check has a 250 ms budget, including waiting for a query slot or connection.
Credential reads use a separate pool capped at eight connections, with SQLite
busy waits disabled during connection initialization and reads. WAL or rollback
lock contention therefore fails closed immediately instead of inheriting the
writer pool's five-second busy wait. Revocation is detected by the next polling check: one interval plus the read
budget is 1.25 seconds, subject to process scheduling. The output gate is revoked
before cancelling an in-flight write; completing transport cancellation can add
latency. The Hub prioritizes an exit while the client transport remains writable,
otherwise closes it, and sends TerminalClose to the captured agent connection
(up to a five-second send deadline). Output may continue until the next revalidation; this is not a
synchronous cross-process notification. Forced closure discards queued data and gates already-dequeued data against the
exact registration. It aborts an in-flight transport write before closure returns;
bytes already accepted by a network transport cannot be recalled. Browser data
writes have a one-second deadline. An SSH data write in progress may require
closing its whole retained SSH connection, including other channels. Further
agent output and client input are refused. Account disable, MFA removal, and password reset
atomically advance both browser and SSH credential generations. Re-enabling an
account never restores its pre-disable SSH certificates. SSH admission and revalidation also
require enrolled MFA. Governed password-reset and mfa-reset apply/reconcile
immediately close registrations with revoked credential generations. Replaying an
already applied recovery preserves shells authenticated after recovery completed;
durable generation checks still clean up stale registrations after a crash.
The terminal registry permits at most 256 interactive shells across the Hub, with the existing eight-per-node limit. At most 256 credential watchers may be active, including watchers finishing agent cleanup, with at most eight concurrent revalidation queries. Waiting for a query slot shares the 250 ms deadline; each watcher stops its ticker and releases its slot on registration removal/transport teardown. An agent-reported exit retains revalidation until buffered output is drained and the transport removes the registration. Agent output creates no extra database queries. These limits and the revalidation interval are fixed, with no new configuration keys. Capacity refusals use the existing terminal registration conflict response.
Temporary-password rotation and TOTP enrollment now commit together with recovery-grant consumption, credential revocation, password history, setup state and success audits. A storage failure preserves the temporary password and recovery grant; retry with a fresh authenticator code before recovery expires. A governance change that revokes an MFA recovery grant requires a fresh approved MFA reset; a subsequent password reset cannot restore the revoked grant.
Password changes capture the signed token generation, expiry, owning session, and account/MFA snapshot before reading the request body. The immediate write transaction rechecks that authority, session ownership and ended/absolute/idle state, and account eligibility before changing the password. Administrator reset and reactivation, password recovery, and combined password/TOTP enrollment also evaluate expiry after acquiring SQLite's write lock. Their injected clock advances by monotonic elapsed waiting time. Authority revoked or expired during body reads, password hashing, history checks, or writer contention causes refusal and leaves credentials, history, recovery grants, and success audits unchanged.
Administrator TOTP disable commits factor removal, both credential generations, browser/API/application revocation and its success audit in one immediate transaction. Persistence failures return an opaque server error and roll back the entire operation, including the audit. Setup persistence failures likewise return HTTP 500; retry enrollment with a fresh TOTP code. The removal transaction revalidates the authenticated actor's enabled account, current administrator role, MFA revision, credential generation and live session against the snapshot used to verify the administrator's TOTP. Revocation while the write waits for SQLite causes refusal without changing the target or audit.
Shell recovery grants reference the exact successful system user.totp_reset
audit. Retention compares UTC instants and deletes only records strictly older
than its cutoff. An audit linked to an unexpired shell recovery grant is retained
even if audit retention is shorter than the temporary-password lifetime. Once
the grant expires or is consumed, normal retention applies. Direct deletion of
the audit still deletes its grant, and invalid provenance blocks protected-account
MFA/password completion. Migration 033 also binds databases
that ran the early migration 032, discarding orphaned or invalid grants.
SSH certificates issued by this version include the immutable account ID and
credential generation as signed extensions. Migration 035 reserves existing and
future account IDs permanently, including after deletion. Recreating a username
creates a different identity. Certificates lacking the identity extension are
refused, including on retained connections and exec channels; after upgrading,
users must sign in with MFA and run vey ssh-cert again. The CA and gateway host
keys remain stable. Migration 036 also invalidates outstanding step-up proofs
that predate credential-generation binding; obtain a fresh action-bound proof.
Veyport User Guide
Getting Started
Using Veyport
Administration
Setup & Deployment
- Architecture
- Deployment
- Deployment On-Prem
- Deployment AWS
- Deployment Azure
- Deployment GCP
- Proxy Configuration
- Development
- API Reference
Help