goBastion is a tool for managing SSH access, user roles, and keys on a bastion host. The project is currently under active development, and contributions are welcome!
π GitHub Repository: https://github.com/phd59fr/goBastion
π³ Docker Hub Image: https://hub.docker.com/r/phd59fr/gobastion
In goBastion, the database is the single source of truth for SSH keys and access management. This means that the system always reflects the state of the database. Any key or access added manually to the system without passing through the bastion will be automatically removed to maintain consistency.
-
Key Addition: When a user adds an SSH key, it is first validated and stored in the database. The bastion then automatically synchronizes the database with the system, adding the key to the appropriate location.
-
Automatic Synchronization: The bastion enforces the database state every 5 minutes automatically. If it finds an SSH key, user, or host entry not in the database, it is immediately corrected to ensure security and consistency. The
--syncflag allows triggering this on demand.
- Centralized Control: All modifications go through the bastion, ensuring tight access management.
- Enhanced Security: Unauthorized keys cannot remain on the system.
- State Consistency: The system always mirrors the database state.
- Audit and Traceability: Every change is recorded in the database.
- Fully Automated Management: No need for manual checks; synchronization handles everything.
- Easy Exportability: The system can be deployed on a new container effortlessly. Since the database is the source of truth, replicating it with synchronization scripts provides a functional bastion on a new instance.
| Command | Description |
|---|---|
π selfListIngressKeys |
List your ingress SSH keys (keys for connecting to the bastion). |
β selfAddIngressKey |
Add a new ingress SSH key (optional expiry). |
β selfDelIngressKey |
Delete an ingress SSH key. |
π selfListEgressKeys |
List your egress SSH keys (keys for connecting from the bastion to servers). |
π selfGenerateEgressKey |
Generate a new egress SSH key. |
π selfListAccesses |
List your personal server accesses. |
β selfAddAccess |
Add access to a personal server (supports IP restriction, TTL, protocol). |
β selfDelAccess |
Remove access to a personal server. |
π selfListAliases |
List your personal SSH aliases. |
β selfAddAlias |
Add a personal SSH alias. |
β selfDelAlias |
Delete a personal SSH alias. |
β selfRemoveHostFromKnownHosts |
Remove a host from your known_hosts file. |
π selfReplaceKnownHost |
Trust a new host key after it changed (TOFU reset). |
π selfSetupTOTP |
Enable TOTP two-factor authentication (generates QR/OTP URI). |
π selfDisableTOTP |
Disable TOTP two-factor authentication. |
π selfSetPassword |
Set a password second factor (MFA). Required at every login if set. |
π selfChangePassword |
Change your password second factor. |
π selfDisablePassword |
Disable password second factor (MFA). |
π‘οΈ selfAddIngressKeyPIV |
Add a PIV/YubiKey hardware-attested ingress key. |
π selfGenerateBackupCodes |
Generate TOTP backup codes (single-use recovery codes). |
π selfShowBackupCodeCount |
Show remaining backup codes count. |
| Command | Description |
|---|---|
π accountList |
List all user accounts. |
βΉοΈ accountInfo |
Show detailed information about a user account. |
β accountCreate |
Create a new user account. |
β accountDelete |
Delete a user account. |
βοΈ accountModify |
Modify a user account (promote/demote to admin/user). Cannot demote the last remaining admin. |
π accountListIngressKeys |
List the ingress SSH keys of a user. |
π accountListEgressKeys |
List the egress SSH keys of a user. |
π accountListAccess |
List all server accesses of a user. |
β accountAddAccess |
Grant a user access to a server (supports IP restriction, TTL, protocol). |
β accountDelAccess |
Remove a user's access to a server. |
π whoHasAccessTo |
Show all users with access to a specific server (supports CIDR). |
π accountDisableTOTP |
Disable TOTP two-factor authentication for a user. |
π accountSetPassword |
(admin) Set or clear a user's password second factor. |
π‘οΈ pivAddTrustAnchor |
Register a Yubico PIV CA certificate as a trust anchor. |
π pivListTrustAnchors |
List all registered PIV trust anchor CAs. |
β pivRemoveTrustAnchor |
Remove a PIV trust anchor CA. |
| Command | Description |
|---|---|
βΉοΈ groupInfo |
Show detailed information about a group. |
π groupList |
List all groups. |
β groupCreate |
Create a new group. |
β groupDelete |
Delete a group. |
β groupAddMember |
Add a user to a group. |
β groupDelMember |
Remove a user from a group. |
π groupGenerateEgressKey |
Generate a new egress SSH key for the group. |
π groupListEgressKeys |
List all egress SSH keys associated with a group. |
π groupListAccesses |
List all accesses assigned to a group. |
β groupAddAccess |
Grant access to a group (supports protocol restriction). The optional TCP connectivity check is restricted to private/reserved IP ranges to prevent network scanning. Use --force to skip. |
β groupDelAccess |
Remove access from a group. |
π groupSetMFA |
Enable or disable JIT MFA requirement for a group (owner/admin only). |
β groupAddAlias |
Add a group SSH alias. |
β groupDelAlias |
Delete a group SSH alias. |
π groupListAliases |
List all group SSH aliases. |
goBastion supports multiple second-factor authentication methods that stack: password, TOTP, and JIT MFA per group.
| Command | Description |
|---|---|
selfSetupTOTP |
Generate a TOTP secret and display the QR/OTP URI to add to your authenticator app. |
selfDisableTOTP |
Disable TOTP for your own account. |
accountDisableTOTP |
(admin) Disable TOTP for any user account. |
Once TOTP is enabled, the bastion will prompt for a 6-digit code at every interactive or passthrough login.
Backup codes are single-use recovery codes that can be used instead of a TOTP code when you lose access to your authenticator app.
| Command | Description |
|---|---|
selfGenerateBackupCodes |
Generate 10 new backup codes. Previous codes are invalidated. |
selfShowBackupCodeCount |
Show how many backup codes remain unused. |
- Each code can only be used once and is removed after use.
- Backup codes are accepted in the same prompt as TOTP codes (
Enter TOTP code (or backup code):). - Generating new codes invalidates all previous codes.
| Command | Description |
|---|---|
selfSetPassword |
Set a bcrypt-hashed password as a second factor. Required at every login. |
selfChangePassword |
Change your password second factor (requires current password). |
accountSetPassword |
(admin) Set or clear a user's password second factor. |
Password MFA is independent of TOTP β both can be active simultaneously.
When a group has JIT MFA enabled via groupSetMFA, any user connecting via that group must pass a TOTP challenge at connection time, even if global TOTP is not enabled for their account. The user must have a TOTP secret configured (selfSetupTOTP) for this to work.
| Command | Description |
|---|---|
groupSetMFA |
(owner/admin) Enable or disable JIT MFA for a group. |
goBastion supports two passthrough modes depending on whether you need to use the bastion's egress key (recommended) or your own key on the target.
goBastion acts as a minimal SSH server and connects to the target with its own egress key. Your local key does not need to be on the target server.
Host my-server
HostName 192.168.1.10
User myuser
ProxyCommand ssh -p 2222 -- bastion_user@bastion "sftp-session myuser@%h:%p"
StrictHostKeyChecking no
UserKnownHostsFile /dev/null
StrictHostKeyChecking nois required because goBastion generates an ephemeral host key for the fake SSH server on each connection.
Then use sftp normally:
sftp my-serverPasses -W %h:%p as a quoted string after -- so glibc does not treat it as a native SSH flag:
Host my-server
HostName 192.168.1.10
User myuser
ProxyCommand ssh -p 2222 -- bastion_user@bastion "-W %h:%p"Why
--before the hostname?
On Linux (glibc),ssh -W host:portopens a rawdirect-tcpipchannel that bypasses goBastion's access controls β and is refused by the bastion's sshd.
The--tells SSH's option parser to stop processing flags, so-W %h:%pbecomes the remote exec command forwarded to goBastion, which handles it as a controlled TCP proxy viaparseTCPProxyRequest.
This enables:
scp file.txt user@my-server:/path/sftp user@my-serverrsync -avz ./dir/ user@my-server:/path/
All passthrough connections are subject to the same access control rules as interactive SSH sessions.
Access entries can be restricted to a specific transfer protocol using the --protocol flag on selfAddAccess, accountAddAccess, and groupAddAccess:
| Value | Meaning |
|---|---|
ssh |
All protocols (default, backwards-compatible) |
scpupload |
SCP upload only (scp -t) |
scpdownload |
SCP download only (scp -f) |
sftp |
SFTP only |
rsync |
rsync only |
Example: grant a user rsync-only access to a backup server:
groupAddAccess --group backups --server 10.0.0.5 --username backup --protocol rsync
Every access entry (selfAddAccess, accountAddAccess, groupAddAccess) supports two optional constraints:
| Flag | Description |
|---|---|
--ttl <days> |
Access expires automatically after N days. Omit for permanent access. |
--from <CIDRs> |
Restrict access to specific source IP ranges (comma-separated, e.g. 10.0.0.0/8,192.168.1.0/24). Omit to allow all IPs. |
Both constraints are enforced at connection time - expired or out-of-range connections are denied.
The Expires and From columns appear in all listAccesses outputs.
Security note (IP restrictions): If a
--fromCIDR restriction is set on an access entry and the bastion cannot determine the client IP (e.g. missingSSH_CLIENT), the connection is denied (fail-closed policy). This prevents accidental bypass of IP-based access controls.
PIV attestation lets users prove that their SSH private key was generated inside a hardware token (e.g. YubiKey) and cannot be exported. The full x509 attestation chain is verified against admin-registered CA certificates before the key is accepted.
Admin setup:
pivAddTrustAnchor --name yubico-root --cert /path/to/yubico-piv-ca.pem
pivListTrustAnchors
pivRemoveTrustAnchor --name yubico-root
User workflow (YubiKey):
# Export attestation data from YubiKey
yubico-piv-tool --action=attest --slot=9a > attest.pem
yubico-piv-tool --action=read-cert --slot=f9 > intermediate.pem
ssh-keygen -D /usr/lib/x86_64-linux-gnu/libykcs11.so -e > my_piv_key.pub
# Add the key to the bastion (chain is verified server-side)
selfAddIngressKeyPIV --attest attest.pem --intermediate intermediate.pem $(cat my_piv_key.pub)Keys added via PIV attestation are marked PIV in selfListIngressKeys.
goBastion transparently passes through mosh-server invocations, enabling Mosh
sessions through the bastion. No special configuration is needed on the client side.
# Standard mosh usage - works through the bastion
mosh --ssh="ssh -J user@bastion:2222" user@my-serverThe bastion detects the mosh-server command in SSH_ORIGINAL_COMMAND and exec's it directly.
UDP ports 60001-61000 must be open on the target server (not the bastion) for the Mosh UDP connection.
| Command | Description |
|---|---|
π ttyList |
List recorded SSH sessions. |
ttyPlay |
Replay a recorded SSH session. |
| Command | Description |
|---|---|
β help |
Display the help menu with available commands. |
βΉοΈ info |
Show application version and details. |
πͺ exit |
Exit the application. |
accountAddAccessaccountCreateaccountDelAccessaccountDeleteaccountInfoaccountListaccountListAccessaccountListIngressKeysaccountListEgressKeysaccountModifyaccountSetPasswordwhoHasAccessToaccountDisableTOTPpivAddTrustAnchorpivListTrustAnchorspivRemoveTrustAnchorgroupCreategroupDelete
Note:
ttyListandttyPlayare available to all users (for their own sessions) and to admins (for all sessions).
| Permission | Owner | ACLKeeper | GateKeeper | Member |
|---|---|---|---|---|
groupAddAccess |
β | β | β | |
groupDelAccess |
β | β | β | |
groupSetMFA |
β | |||
groupAddMember |
β | β | ||
groupDelMember |
β | β | ||
groupGenerateEgressKey |
β | |||
groupAddAlias |
β | β | β | |
groupDelAlias |
β | β | β | |
groupInfo |
β | β | β | β |
groupList |
β | β | β | β |
groupListAccesses |
β | β | β | β |
groupListAliases |
β | β | β | β |
groupListEgressKeys |
β | β | β | β |
selfAddAccessselfAddAliasselfAddIngressKeyselfAddIngressKeyPIVselfChangePasswordselfDelAccessselfDelAliasselfDelIngressKeyselfDisablePasswordselfDisableTOTPselfGenerateBackupCodesselfGenerateEgressKeyselfListAccessesselfListAliasesselfListEgressKeysselfListIngressKeysselfRemoveHostFromKnownHostsselfReplaceKnownHostselfSetPasswordselfSetupTOTPselfShowBackupCodeCountttyList(own sessions only)ttyPlay(own sessions only)
β Alias Priority Warning:
If an alias is defined by the user (selfAddAlias) and the group defines an alias with the same name (groupAddAlias), the user-defined alias always takes precedence
helpinfoexit
-
Clone the repository:
git clone https://github.com/phd59fr/goBastion.git cd goBastion -
Build the Docker container:
docker build -t gobastion . -
Run the Docker container:
docker run --name gobastion --hostname goBastion -it -p 2222:22 gobastion:latest
You can also use the official Docker Hub image:
docker run --name gobastion --hostname goBastion -it -p 2222:22 phd59fr/gobastion:latest
(optional) 3a. Launch the container with a volume to persist the database and ttyrec:
docker run --name gobastion --hostname goBastion -it -p 2222:22 \ -v /path/to/your/dbvolume:/var/lib/goBastion \ -v /path/to/your/ttyvolume:/app/ttyrec gobastion:latest
(optional) 3b. Use an external database (see Environment Variables):
docker run --name gobastion --hostname goBastion -it -p 2222:22 \ -e DB_DRIVER=postgres \ -e DB_DSN="host=db user=gobastion password=secret dbname=gobastion port=5432 sslmode=disable" \ gobastion:latest -
Simplified usage with an Alias (Optional):
alias gobastion='ssh -tp 2222 user@localhost --'
-
Connect to the bastion host (interactive mode):
ssh -tp 2222 user@localhost (or alias gobastion)(optional) 5a. Connect to the bastion host with a command (non-interactive mode):
ssh -tp 2222 user@localhost -- -osh selfListIngressKeys (or alias gobastion -osh selfListIngressKeys)(optional) 5b. Connect to the target host through the bastion:
ssh -tp 2222 user@localhost -- user@targethost (ssh options supported) (or alias gobastion user@targethost)(optional) 5c. Use SFTP / SCP / rsync through the bastion β see SCP / SFTP / rsync Passthrough for full configuration details.
| Variable | Default | Description |
|---|---|---|
DB_DRIVER |
sqlite |
Database backend: sqlite, mysql, or postgres |
DB_DSN |
(auto) | Database connection string. For SQLite, defaults to /var/lib/goBastion/bastion.db. Required for mysql and postgres. |
EGRESS_ENC_KEY |
(none) | AES key for encrypting egress private keys at rest. See Egress Key Encryption. |
LOG_FORMAT |
json |
Log output format: json (structured JSON, compatible with log aggregators) or plain (human-readable text for local debugging). |
MySQL:
DB_DRIVER=mysql
DB_DSN=gobastion:secret@tcp(db:3306)/gobastion?charset=utf8mb4&parseTime=True&loc=Local
PostgreSQL:
DB_DRIVER=postgres
DB_DSN=host=db user=gobastion password=secret dbname=gobastion port=5432 sslmode=disable
SQLite (custom path):
DB_DRIVER=sqlite
DB_DSN=file:/data/mybastion.db?cache=shared&mode=rwc
If the goBastion app user does not have CREATE TABLE / ALTER permissions on your database, the DBA can pre-create the schema manually.
The sql/ directory contains ready-to-run schema files:
| File | Database |
|---|---|
sql/postgres.sql |
PostgreSQL |
sql/mysql.sql |
MySQL |
PostgreSQL:
psql -h <host> -U <admin> -d <dbname> -f sql/postgres.sqlMySQL:
mysql -h <host> -u <admin> -p <dbname> < sql/mysql.sqlAfter creating the schema, grant the goBastion app user minimal privileges:
PostgreSQL:
GRANT USAGE ON SCHEMA public TO gobastion;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO gobastion;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO gobastion;MySQL:
GRANT SELECT, INSERT, UPDATE, DELETE ON gobastion.* TO 'gobastion'@'%';
FLUSH PRIVILEGES;The app user only needs
SELECT,INSERT,UPDATE,DELETEβ noCREATE,ALTER, orDROP.
By default, egress private keys are stored in the database in plaintext. To encrypt them at rest, set the EGRESS_ENC_KEY environment variable:
# Generate a 32-byte AES-256 key
openssl rand -base64 32 > egress_key.txt
export EGRESS_ENC_KEY=$(cat egress_key.txt)EGRESS_ENC_KEY accepts:
- Base64-encoded AES key (16/24/32 bytes)
- 32-byte raw passphrase
Migration behavior:
- If
EGRESS_ENC_KEYis set after keys were already stored in plaintext, existing keys are automatically re-encrypted on next use (transparent migration). - If
EGRESS_ENC_KEYis not set, keys remain in plaintext (backward-compatible).
docker run --name gobastion --hostname goBastion -it -p 2222:22 \
-e EGRESS_ENC_KEY="$(openssl rand -base64 32)" \
gobastion:latestThese flags are only available when running as root outside an SSH session:
| Flag | Command | Description |
|---|---|---|
--firstInstall |
docker exec -it goBastion /app/goBastion --firstInstall |
Manually bootstrap the first admin user (interactive) |
--regenerateSSHHostKeys |
docker exec -it goBastion /app/goBastion --regenerateSSHHostKeys |
Force-regenerate the bastion's SSH host keys |
--sync |
docker exec goBastion /app/goBastion --sync |
Enforce DB state onto the OS immediately (DB is source of truth); also runs automatically every 5 minutes |
--dbExport |
docker exec -i -e DB_EXPORT_KEY="$DB_EXPORT_KEY" goBastion /app/goBastion --dbExport > dump |
Dump the database as encrypted file to stdout |
--dbImport |
docker exec -i -e DB_EXPORT_KEY="$DB_EXPORT_KEY" goBastion /app/goBastion --dbImport < dump |
Restore the database from encrypted file on stdin |
--disableTOTP |
docker exec -it goBastion /app/goBastion --disableTOTP <user> |
Disable TOTP, password MFA, and backup codes for a user (recovery) |
The export is now a single encrypted file, independent of SQL dialects.
It is designed for portability across:
- SQLite
- MySQL
- PostgreSQL
Encryption is mandatory.
openssl rand -base64 32 > export_key.txt
export DB_EXPORT_KEY=$(cat export_key.txt)DB_EXPORT_KEY can be:
- base64 AES key (16/24/32 bytes)
- raw AES key
- passphrase (derived using Argon2id)
docker exec -i -e DB_EXPORT_KEY="$DB_EXPORT_KEY" goBastion /app/goBastion --dbExport > dumpdocker exec -i -e DB_EXPORT_KEY="$DB_EXPORT_KEY" goBastion /app/goBastion --dbImport < dump- Target DB must already have schema (AutoMigrate)
- Target DB must be empty
- Same key must be used for export/import
- Output goes to stdout, logs to stderr
Contributions are what make the open-source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated. Hereβs how you can help:
- Report bugs
- Suggest features
- Submit pull requests
To contribute:
- Fork the project
- Create a new branch (
git checkout -b feature/YourFeature) - Commit your changes (
git commit -m 'Add YourFeature') - Push to the branch (
git push origin feature/YourFeature) - Open a pull request
This project is licensed under the MIT License.
A simple star on this project repo is enough to keep me motivated for days. If youβre excited about this project, let me know with a tweet. If you have any questions, feel free to reach out to me on X.