A web-based application for managing and accessing remote SSH and RDP connections from your browser. Organize connections in folders, share them with team members, and keep credentials encrypted at rest with a personal vault.
- SSH Terminal — Interactive terminal sessions powered by XTerm.js and Go WebSocket brokers, with sandbox-only managed file transfer
- RDP Viewer — Remote desktop connections via Apache Guacamole with clipboard sync and drive redirection
- VNC Viewer — VNC sessions via the Guacamole protocol
- Encrypted Vault — All credentials encrypted at rest with AES-256-GCM; master key derived from your password via Argon2id
- Secrets Keychain — Store login credentials, SSH keys, certificates, API keys, and secure notes with full versioning and expiry notifications
- Connection Sharing — Share connections with other users (read-only or full access) with per-recipient re-encryption
- Folder Organization — Hierarchical folder tree with drag-and-drop reordering for personal and team connections
- Tabbed Interface — Open multiple sessions side by side; pop out connections into standalone windows
- Multi-Tenant Organizations — Tenant-scoped RBAC with Owner/Admin/Operator/Member/Consultant/Auditor/Guest roles; time-limited memberships
- Team Collaboration — Teams with shared connection pools, folders, and vault sections
- Multi-Factor Authentication — TOTP, SMS OTP (Twilio, AWS SNS, Vonage), and WebAuthn/FIDO2 passkeys
- OAuth & SAML SSO — Google, Microsoft, GitHub, any OIDC provider, SAML 2.0, and LDAP identity providers
- Audit Logging — 100+ action types with IP and GeoIP tracking; geographic visualization for admins
- Session Recording — Record SSH (asciicast) and RDP/VNC (Guacamole format) sessions with in-browser playback and video export
- DLP Policies — Tenant and per-connection controls for clipboard copy/paste and file upload/download
- Connection Policy Enforcement — Admin-enforced SSH/RDP/VNC settings that override user configuration
- IP Allowlist — Per-tenant IP/CIDR allowlists with flag (audit) or block enforcement modes
- Session Limits — Max concurrent sessions per user and absolute session timeouts (OWASP A07)
- External Vault Integration — Reference credentials from HashiCorp Vault (KV v2) instead of storing them in Arsenale
- SSH Gateway Management — Deploy, scale, and monitor SSH gateway containers via Podman or Kubernetes
- JWT Authentication — Short-lived access tokens with httpOnly refresh cookies, CSRF protection, and token binding
| Layer | Technologies |
|---|---|
| Backend | Go split services under backend/ (control plane, brokers, orchestration, AI, runtime) |
| Client | React 19, Vite, Tailwind CSS 4, shadcn/ui, Material-UI v7, Zustand, XTerm.js, guacamole-common-js |
| Database | PostgreSQL 16 |
| Infrastructure | Podman / Kubernetes, Nginx, guacd, ssh-gateway |
- Node.js 22+
- Go 1.25+ or a container runtime for the bundled Go fallback
- Podman for compose-backed installs or minikube for Kubernetes validation
- Ansible for the installer and deployment flow
- npm 9+
git clone <repository-url>
cd arsenalenpm installmake setupThis installs the required Ansible collections and creates the encrypted Ansible vault used for long-lived deployment secrets.
make dev
npm run devmake dev runs the installer-aware development flow. With the default development capabilities it deploys the local fixture targets, demo databases, and bootstrap data; add DEV_ZERO_TRUST=true when you also want the tunnel fixtures and zero-trust acceptance path. npm run dev is optional when you want the Vite client on https://localhost:3005 against that stack.
For faster local iteration after that first full deploy, you can refresh only the changed images with the same saved dev profile:
make dev client
make dev gateways
make dev control-plane
make dev control-plane-api query-runnerThose scoped refreshes rebuild and restart only the requested services, reusing the last rendered dev compose/env artifacts. Use full make dev again when you change installer inputs, capabilities, certs, secrets, or compose wiring.
For headless local reruns, place the technician password in install/password.txt.
The repo Makefile auto-detects that file and passes it to the installer as
install_password_file, so make dev, make install, make deploy, and
make status can run without an interactive password prompt.
make installThe installer is CLI-only, interactive, password-gated, idempotent, and backend-aware. It can deploy:
- Development via the full local compose stack
- Production via Podman compose
- Production via Helm on Kubernetes
Docker is not a supported installer backend.
See docs/installer.md for the full flow.
The installer persists encrypted profile, state, status, log, and rendered-artifact payloads. On a target host the canonical location is:
/opt/arsenale/install/
Artifacts written there:
install-profile.encinstall-state.encinstall-status.encinstall-log.encrendered-artifacts.enc
Every rerun asks for the technician password before those artifacts are read. External tooling can read the encrypted installer status directly with the supported helper path instead of querying a running Arsenale instance:
make statusKey variables — see docs/environment.md for the full reference (123 variables).
| Variable | Default | Description |
|---|---|---|
DATABASE_URL |
postgresql://arsenale:arsenale_password@127.0.0.1:5432/arsenale |
PostgreSQL connection string |
JWT_SECRET |
— | Secret key for signing JWT tokens. Provide via JWT_SECRET or JWT_SECRET_FILE. |
JWT_EXPIRES_IN |
15m |
Access token TTL |
JWT_REFRESH_EXPIRES_IN |
7d |
Refresh token TTL |
GUACD_HOST |
localhost |
Guacamole daemon hostname |
GUACD_PORT |
4822 |
Guacamole daemon port |
GUACAMOLE_SECRET |
— | Guacamole token encryption key. Provide via GUACAMOLE_SECRET or GUACAMOLE_SECRET_FILE. |
SERVER_ENCRYPTION_KEY |
Auto-generated | 32-byte hex key for server-level encryption (required in production) |
NODE_ENV |
development |
Environment mode |
VAULT_TTL_MINUTES |
30 |
Vault session auto-lock timeout (minutes) |
CLIENT_URL |
http://localhost:3000 |
Client URL (CORS, OAuth redirects, emails) |
VITE_API_TARGET |
http://localhost:18080 |
Local Vite proxy target for /api |
VITE_GUAC_TARGET |
http://localhost:18091 |
Local Vite proxy target for /guacamole |
VITE_TERMINAL_TARGET |
http://localhost:18090 |
Local Vite proxy target for /ws/terminal |
RECORDING_ENABLED |
false |
Enable session recording |
arsenale/
├── backend/ # New Go backend monorepo (control, agent, runtime services)
│ ├── cmd/ # Service entrypoints (API, controller, brokers, agent services)
│ ├── internal/ # Internal Go packages
│ └── pkg/ # Shared Go contracts and workload spec
│
├── client/ # React frontend
│ ├── src/
│ │ ├── pages/ # Login, Register, Dashboard, RecordingPlayer, PublicShare
│ │ ├── components/ # UI components (RDP, VNC, Terminal, Sidebar, Tabs, Dialogs, Settings, Keychain)
│ │ ├── api/ # Axios API clients with JWT interceptor (29 modules)
│ │ ├── store/ # Zustand state stores (14 stores)
│ │ └── hooks/ # Custom React hooks
│ └── nginx.conf # Production reverse proxy config
│
├── gateways/ # Gateway containers and tunnel agent
│ ├── ssh-gateway/ # Optional SSH gateway container
│ ├── tunnel-agent/ # Zero-trust tunnel agent (Go module)
│ ├── guacd/ # Custom guacd with embedded tunnel agent
│ └── guacenc/ # Recording-to-video conversion sidecar
├── Makefile # Ansible deployment UX (make dev/deploy/etc.)
├── deployment/ansible/ # Installer playbooks, roles, schemas, and templates
├── deployment/helm/ # Helm chart for the Kubernetes backend
└── .env.example # Environment template (121 variables)
# Development
npm run dev # Start the Go dev stack and local Vite on :3005
npm run dev:client # Client only (Vite on :3005)
# Build
npm run build # Build the active Go/backend + client workspaces
npm run backend:build # Build the Go backend module
npm run backend:generate # Regenerate sqlc code for the converted Go domains
npm run build -w client # Client only
# Database
npm run db:migrate # Apply pending backend SQL migrations
npm run db:status # Show applied and pending backend migrations
npm run db:bootstrap # Compatibility alias of db:migrate
npm run db:push # Alias of db:migrate
# Infrastructure (via Makefile + Ansible)
make setup # First-time setup (Ansible collections, vault)
make install # Interactive installer entrypoint
make dev # Full installer-aware development environment
make dev-down # Stop dev infrastructure
make deploy # Production installer/deploy flow
make configure # Reconfigure an existing install
make recover # Rerun installer recovery flow
make status # Read encrypted installer status
# Go backend checks
npm run backend:test # Run Go backend testsOn a fresh Linux host, install the full production stack with a single command — no clone, no manual prerequisites:
curl -fsSL https://raw.githubusercontent.com/dnviti/arsenale/main/tools/installer/install-platform.sh \
| ARSENALE_DOMAIN=example.com bashThe bootstrap downloads the checksummed installer bundle published with each release,
installs prerequisites (Ansible, Podman, OpenSSL), generates and encrypts secrets, and
runs the same Ansible installer described below — pulling prebuilt images from
ghcr.io/dnviti/arsenale. Re-running upgrades the stack without rotating secrets.
After install, operate it from the bundle dir with cd /opt/arsenale/installer && make status.
Common overrides (all optional): ARSENALE_INSTALL_PASSWORD, ARSENALE_VAULT_PASSWORD,
ARSENALE_CAPABILITIES, ARSENALE_VERSION, ARSENALE_SECRETS_FILE (a filled
SECRETS.env for OAuth/SMTP), ARSENALE_HOST
/ ARSENALE_DEPLOY_USER (target a remote host over SSH), ARSENALE_NONINTERACTIVE=1.
The production installer runs over SSH to the target host (localhost by default) and uses
sudoto install Podman and create thearsenaleuser. The bootstrap seeds passwordless SSH-to-localhost and verifiessudo; on a remote target ensure key-based SSH and passwordless sudo for the deploy user.
Deploy the full stack with the installer (via Makefile):
# 1. First-time setup (install Ansible collections, generate vault)
make setup
# 2. Deploy or reconfigure production stack
make deploy
# 3. Read encrypted installer status
make statusProduction mode uses the same installer profile model across Podman and Kubernetes. Selected capabilities are installer-owned only; disabled capabilities remove their services, APIs, and UI affordances from the rendered runtime.
The installer can render and apply:
- Compose for Podman
- Helm for Kubernetes
Development mode always deploys the full stack and fixtures. Production mode deploys only the selected capabilities.
See deployment/ansible/README.md and docs/installer.md for the operator flow and artifact model.
The public edge is now Go-first: control-plane, desktop, terminal, tunnel, query, AI, and orchestration services run from backend/.
- The public
/apisurface is handled bycontrol-plane-api-go - Browser SSH and desktop runtime terminate in direct Go brokers
- Database sessions, orchestration, gateway management, and AI flows are verified against the Go services
- Legacy
server/code is no longer part of the default runtime or top-level build path
- 14 Zustand stores manage auth, connections, tabs, vault, teams, tenants, gateways, and UI preferences
- Axios interceptor handles automatic JWT refresh on 401 responses
- XTerm.js renders SSH terminals; guacamole-common-js renders RDP/VNC sessions
- All UI layout preferences persisted via
uiPreferencesStore(localStorage)
- User password → Argon2id → master key → AES-256-GCM encryption of all credentials
- Master key held in server memory with auto-expiring sessions (configurable TTL)
- Vault must be unlocked to view or use stored credentials
- Recovery key generated at registration enables vault access after password reset
Contributions are welcome! Please read CONTRIBUTING.md for setup instructions, code conventions, commit guidelines, and the PR process.
Before submitting a pull request, make sure the quality gate passes:
npm run verifyThis project is licensed under the Business Source License 1.1.