Keyward is a zero-trust SSH access platform for replacing long-lived user SSH keys with device-bound authentication and short-lived OpenSSH user certificates.
Users connect with normal SSH tooling, but the credential they use is issued just in time after the local device client proves device possession, optional user presence, and current authorization. Servers validate certificates locally with standard OpenSSH TrustedUserCAKeys and reject revoked certificates through OpenSSH KRLs.
The target access model is:
User identity + trusted device + policy decision + short-lived SSH certificate
instead of:
User + static SSH private key
Traditional SSH key access is difficult to govern at enterprise scale:
- private keys are copied, exported, forgotten, and rarely rotated
- revocation is slow and inconsistent across servers
- access is often granted directly on hosts instead of through central policy
- auditing usually shows a Unix account, not the user, device, policy, and approval path
- emergency access removal is hard when certificates or keys remain valid on endpoints
This project keeps the server-side SSH path OpenSSH-native while moving access decisions into a central platform.
- Short-lived OpenSSH user certificates
- Device enrollment and trust lifecycle
- Local desktop client with SSH-agent-compatible behavior
- Windows Hello / local user presence policy support
- Central user, group, server, device, policy, and grant management
- Local admin login plus OIDC and SAML identity provider integration
- SAML group synchronization support, including Microsoft Entra ID group IDs
- Server enrollment and OpenSSH configuration sync
- Revocation through scoped OpenSSH KRL files
- Continuous server-agent watch mode for near-real-time revocation
- Audit events for issuance, denial, revocation, enrollment, and admin actions
- Cross-platform Go builds for Windows, macOS, and Linux
- GitHub Actions CI, GitHub code scanning, Dependabot, and release packaging
The system has three main components.
Location: platform/
The Flask platform is the control plane. It manages:
- authentication and local admin login
- users, groups, roles, and role bindings
- OIDC and SAML identity provider settings
- device enrollment and inventory
- server enrollment
- access grants
- conditional access policies
- SSH certificate issuance
- revocation state
- audit logging
- admin and user-facing web UI
The platform signs OpenSSH user certificates using a configured SSH user CA.
Location: agents/device-client/
The device client runs on the user's workstation. It provides:
- device enrollment through browser login approval
- local device identity key management
- local user presence checks where policy requires them
- automatic access sync
- automatic short-lived certificate issuance and refresh
- SSH-agent-compatible serving for OpenSSH
- Windows named pipe support
- desktop tray application
The current implementation stores development device material under ~/.keyward-ssh/. Production deployments should move device identity into hardware-backed or OS-protected storage such as TPM, Secure Enclave, Windows Hello, CNG KSP, FIDO2, or managed platform authenticators.
Location: agents/server-agent/
The server agent runs on SSH servers. It provides:
- server enrollment
- trusted SSH user CA synchronization
- OpenSSH include config rendering
- KRL generation for revoked active certificates
- continuous revocation watch mode
- diagnostics for effective SSH config and KRL checks
The server agent does not sit in the hot SSH authentication path. OpenSSH validates certificates locally, which keeps login latency low and avoids a hard dependency on platform availability during every SSH connection.
User runs ssh
|
v
Local Keyward SSH agent receives identity request
|
v
Client checks local state and optional user presence
|
v
Client signs a certificate request with its enrolled device key
|
v
Platform verifies device, user, nonce, server, access grant, and policy
|
v
Platform signs an ephemeral OpenSSH user certificate
|
v
Local agent offers the certificate to ssh
|
v
OpenSSH server validates TrustedUserCAKeys and RevokedKeys
The user still runs ordinary commands such as:
ssh alice@server.example.comThe difference is that the SSH credential is fresh, device-bound, policy-aware, auditable, and centrally revocable.
The platform records every certificate issuance with its serial, user, device, server, principal, policy decision, and validity window.
When access changes, the platform exposes only currently valid certificate serials that should now be rejected. The server agent fetches this state and regenerates an OpenSSH KRL:
/etc/ssh/keyward/revoked.krl
The generated OpenSSH include looks like:
TrustedUserCAKeys /etc/ssh/keyward/trusted_user_ca.pub
RevokedKeys /etc/ssh/keyward/revoked.krl
KRL state is intentionally bounded:
- expired certificates are not included
- KRL files are regenerated from current platform state, not appended forever
- server agents send their server ID so each host receives only relevant revoked serials
- short certificate TTLs keep the emergency revocation window small
Run continuous revocation sync on servers:
keyward-server-agent watch --interval 2s.
|-- .github/workflows GitHub Actions CI and release workflows
|-- agents
| |-- device-client Go desktop client, local SSH agent, tray app
| `-- server-agent Go SSH server enrollment and KRL sync agent
|-- deploy
| |-- docker-compose.yml Local development dependencies
| |-- openssh OpenSSH helper config and dev CA script
| `-- systemd Example server-agent unit
|-- platform Flask web platform and API
|-- proto Protocol definitions
`-- scripts Cross-platform build scripts
- Python 3.11+
- Go 1.25 for the device client module
- Go 1.22+ for the server agent module
- OpenSSH client/server tools
- Docker, if using the provided development services
docker compose -f deploy/docker-compose.yml up -dcd platform
python -m venv venv
. venv/bin/activate
pip install -r requirements-dev.txt
flask --app wsgi:app init-dbCreate a development SSH user CA:
cd ..
sh deploy/openssh/create-dev-ca.shRun the platform:
cd platform
flask --app wsgi:app runThe platform defaults to SQLite for development. Use DB_BACKEND=postgres or DATABASE_URL=... to use PostgreSQL.
cd platform
flask --app wsgi:app create-admin --email admin@example.comOpen:
http://127.0.0.1:5000/login
The user portal is available at:
http://127.0.0.1:5000/
Configure the server agent:
cd agents/server-agent
go run ./cmd/keyward-server-agent configure --platform-url http://127.0.0.1:5000Create an enrollment token in the admin UI:
Admin -> Servers -> New Enrollment Token
Use an exact hostname for one server, or a wildcard hostname pattern such as prod-web-* for a golden image or VM template rollout. Restrict the token with a short TTL, low max-use count, and allowed source CIDRs. Copy the raw token immediately; it is shown only once. On the server or template, store it in a root-readable file:
sudo install -m 0600 /dev/null /etc/keyward-server-agent/enrollment.token
sudo sh -c 'printf "%s\n" "<token>" > /etc/keyward-server-agent/enrollment.token'Redeem the token from that file. The agent sends the operating system hostname by default, or provisioning can pass an explicit name:
go run ./cmd/keyward-server-agent enroll --token-file /etc/keyward-server-agent/enrollment.token
go run ./cmd/keyward-server-agent enroll --token-file /etc/keyward-server-agent/enrollment.token --hostname "$(hostname -f)"Run one sync:
go run ./cmd/keyward-server-agent syncFor continuous revocation enforcement:
go run ./cmd/keyward-server-agent watch --interval 2sFor local development without writing to /etc, configure custom paths:
export KEYWARD_SERVER_AGENT_CONFIG=/tmp/keyward-server-agent/config.json
go run ./cmd/keyward-server-agent configure \
--platform-url http://127.0.0.1:5000 \
--state-dir /tmp/keyward-server-agent \
--trusted-user-ca /tmp/keyward-server-agent/trusted_user_ca.pub \
--sshd-include /tmp/keyward-server-agent/keyward.conf \
--revocation-state /tmp/keyward-server-agent/revocation-state.json \
--revoked-keys /tmp/keyward-server-agent/revoked.krlConfigure the client:
cd agents/device-client
go run ./cmd/keyward-agent configure --platform-url http://127.0.0.1:5000Enroll the device:
go run ./cmd/keyward-agent enroll \
--name alice-laptop \
--platform linuxThe client opens or prints a browser approval URL. The user logs into the platform, approves the device enrollment, and the client completes enrollment by signing the platform challenge.
Device approval is step-up protected. Local users must re-enter their password before trusting a new device. Federated users must have a recent SSO session, otherwise they need to sign out and sign in again before approval.
The desktop tray app can run the same client behavior as a background application:
go run ./cmd/keyward-trayIn the admin UI, create an access grant:
Admin -> Policies & Grants
An access grant maps:
- a user or group
- to a server, server group, or all servers
- with one or more Unix principals, such as
alice,deploy, orroot
Policies can further restrict access by:
- device trust status
- server environment
- server tags
- SSH principal
- source IP
- certificate TTL
- user presence requirements
- SSH certificate options and extensions
Start the local agent:
cd agents/device-client
go run ./cmd/keyward-agent serveThe agent syncs access and refreshes short-lived certificates automatically.
Manual certificate issuance is available for debugging:
go run ./cmd/keyward-agent issue-cert \
--server dev-server-01 \
--principal alice \
--identity ~/.keyward-ssh/dev-server-01Then connect:
ssh -i ~/.keyward-ssh/dev-server-01 alice@dev-server-01When using the agent flow, point SSH at the configured agent socket or pipe.
Windows fallback when another agent owns the default OpenSSH pipe:
Host *
IdentityAgent \\.\pipe\keyward-agent
Then run:
keyward-agent serve --socket \\.\pipe\keyward-agentPolicies can require local user presence before a certificate-backed SSH login is allowed.
Configure presence mode:
keyward-agent configure --presence-mode autoSupported modes:
auto Windows Hello on Windows, local confirmation on macOS
windows_hello Windows WebAuthn platform authenticator / Windows Hello
macos_touchid macOS local confirmation prompt
yubikey custom command hook for YubiKey/FIDO tooling
command custom command hook
none disable local presence checks on the client
On Windows, the client uses the Win32 WebAuthn API directly. The first SSH login that requires presence enrolls a local platform credential in Windows Hello and stores only the credential handle. Later SSH logins request a WebAuthn assertion with user verification required, allowing Windows Hello to satisfy policy with PIN, fingerprint, face unlock, or another configured platform factor.
Custom command mode receives the reason in KEYWARD_SSH_REASON and must exit with status 0 to approve:
keyward-agent configure \
--presence-mode command \
--presence-command 'ykman oath accounts code ssh-login >/dev/null'Presence checks are cached briefly to avoid multiple prompts during one SSH handshake:
keyward-agent configure --presence-cache-seconds 30OIDC and SAML settings are managed in:
Admin -> Settings -> Identity Provider Integrations
For Microsoft Entra ID OIDC, use a Web redirect URI:
https://<platform-host>/oidc/callback
Typical settings:
Issuer URL: https://login.microsoftonline.com/<tenant-id>/v2.0
Client ID: <application client id>
Client Secret: <client secret value>
Scopes: openid email profile
Email Claim: preferred_username
Name Claim: name
Groups Claim: groups
Allowed Tenant ID: <tenant-id>
For SAML, use the platform metadata and ACS URLs shown in the settings page:
SP Metadata: https://<platform-host>/saml/metadata
ACS URL: https://<platform-host>/saml/acs
Federated logins create or update users automatically, enforce ALLOWED_LOGIN_DOMAINS, and can sync incoming group claims into platform groups for policy assignment.
Build all Go agents for Linux, macOS, and Windows:
./scripts/build-go.shBuild selected targets:
./scripts/build-go.sh linux/amd64 darwin/arm64 windows/amd64From PowerShell:
pwsh ./scripts/build-go.ps1Artifacts are written to:
dist/go/<os>-<arch>/
The build scripts pass -buildvcs=false so Go does not fail when the source tree has no usable Git metadata.
Run platform checks:
cd platform
python -m compileall app tests wsgi.py
python -m ruff check app tests wsgi.py
python -m pytestRun Go tests:
cd agents/device-client
go test ./...
cd ../server-agent
go test ./...GitHub Actions workflows are included:
CI: Python lint/test/compile, Go tests, and cross-platform Go builds- GitHub CodeQL default setup: code scanning managed in repository security settings
Release: builds release artifacts onv*tagsDependabot: dependency update PRs for Actions, Python, and Go modules
Create a release:
git tag v0.1.0
git push origin v0.1.0The release workflow publishes archives and SHA256SUMS.
Install the server-agent as a system service:
sudo cp keyward-server-agent /usr/local/bin/keyward-server-agent
sudo cp deploy/systemd/keyward-server-agent.service /etc/systemd/system/keyward-server-agent.service
sudo systemctl daemon-reload
sudo systemctl enable --now keyward-server-agentReload sshd after RevokedKeys is added to config for the first time:
sudo systemctl reload sshdInspect KRL contents:
ssh-keygen -Q -l -f /etc/ssh/keyward/revoked.krlVerify a certificate against the configured KRL:
keyward-server-agent verify-revocation --cert /path/to/id_ed25519-cert.pubInspect effective sshd config:
sshd -TThis repository is an active development implementation, not a turnkey hardened production product yet.
Implemented security foundations include:
- short-lived OpenSSH user certificates
- signed device enrollment challenge
- signed certificate requests
- replay nonce tracking
- device trust state
- central policy checks
- KRL-based server-side revocation
- per-server scoped revocation state
- audit logging
- local user presence policy hooks
Production hardening still recommended:
- protect the SSH CA key with HSM/KMS or an isolated signer
- use hardware-backed non-exportable device keys
- add mTLS between agents and platform
- sign and notarize desktop binaries
- implement signed auto-update
- add database migrations for production lifecycle management
- add SIEM export and alerting
- add SCIM provisioning completion
- add high-availability deployment manifests
This project is using GNU GENERAL PUBLIC LICENSE v3 License
