Skip to content

GrantSSH/agent

Repository files navigation

grantssh-agent

Go binary installed on servers that use grantssh. It authenticates with the grantssh app, reports version and last-seen, enforces SSH access, reports SSH login events, and monitors sudo command usage.

Supported platforms: Linux amd64/arm64 and FreeBSD amd64/arm64. Tested on Debian, Ubuntu, Fedora, CentOS/RHEL, Rocky Linux, AlmaLinux, Amazon Linux, openSUSE, Gentoo, Arch, and Alpine. See docs/INSTALL.md for installation as a systemd service.

Prerequisites: Go 1.21+ and golangci-lint (e.g. brew install golangci-lint). For make fmt you also need goimports: go install golang.org/x/tools/cmd/goimports@latest.

  • Test config: ./bin/grantssh-agent --test (validates config, tests connectivity, checks event source, exits)
  • Build: make build or go build -o bin/grantssh-agent ./cmd/grantssh-agent
  • Build all platforms: make build-all (Linux + FreeBSD, amd64 + arm64)
  • Build dist: make dist (builds all + checksums in dist/)
  • Run: make run or ./bin/grantssh-agent
  • Test: make test or go test ./...
  • Lint: make lint
  • Format: make fmt (apply), make fmt-check (check only), make check (fmt-check + lint + test; run before committing)

See AGENTS.md for agent-focused setup, commands, and code style. See docs/INSTALL.md for install script, systemd unit, and configuration.

CI/CD

GitHub Actions workflows in .github/workflows/:

  • CI (ci.yml): Runs on every push to main and on PRs. Runs make fmt-check, make lint, go test -race ./..., then builds all 4 platform/arch binaries and uploads them as artifacts.
  • Release (release.yml): Triggers on tag push (v*). Runs the full check suite, builds all binaries with the tag as the version, and creates a GitHub Release with the binaries and a checksums.txt.

Creating a release

git tag v1.0.0
git push origin v1.0.0

This triggers the release workflow which builds, tests, and publishes binaries to a GitHub Release automatically. The release includes:

Binary OS Arch
grantssh-agent-linux-amd64 Linux x86_64
grantssh-agent-linux-arm64 Linux ARM64/aarch64
grantssh-agent-freebsd-amd64 FreeBSD x86_64
grantssh-agent-freebsd-arm64 FreeBSD ARM64
grantssh-agent-amd64 Linux x86_64 (legacy name for install script)
grantssh-agent-arm64 Linux ARM64 (legacy name for install script)
checksums.txt SHA256 checksums

All binaries are statically linked and work across all supported distros for their OS — no distro-specific builds needed.

Event schema versioning

The POST /api/agent/events request body includes a schema_version field so the app can handle payloads from old and new agents gracefully. The current version is 2.

{
  "schema_version": 2,
  "events": [...]
}
Version Changes
1 Initial: ssh_login events with server_account, fingerprint_sha256, remote_ip, remote_port, auth_method
2 Added event_type field. New event types: ssh_disconnect, sudo, failed_login_summary. New fields: target_user, command, working_dir, tty, attempt_count, first_seen, last_seen

When adding new fields or event types, bump EventSchemaVersion in internal/client/client.go.

Health / status endpoint

The agent exposes a local HTTP endpoint for monitoring tools (Nagios, Prometheus, Datadog, etc.).

Endpoint Response
GET /healthz 200 ok if the agent is running
GET /status JSON with version, uptime, last heartbeat/sync times, event counters

Configuration:

Setting Default Description
health_listen (config) 127.0.0.1:19099 Listen address. Set to "" to disable.
GRANTSSH_HEALTH_LISTEN (env) 127.0.0.1:19099 Same. Set to off to disable.

The default binds to localhost only — monitoring tools on the same machine can reach it, but it's not accessible from the network. To expose it (e.g. for a remote Prometheus scrape), set health_listen to 0.0.0.0:19099 or a specific interface.

To disable entirely: set "health_listen": "" in config.json or GRANTSSH_HEALTH_LISTEN=off in the environment.

Periodic stats logging (optional, off by default): Set "stats_log_interval_sec": 300 in config or GRANTSSH_STATS_LOG_INTERVAL_SEC=300 to log a stats summary every 5 minutes. SIGHUP-reloadable — you can enable/disable without restarting. Example output:

level=INFO msg="agent stats" uptime=4h32m events_sent=142 events_queued=0 failed_logins_flushed=3 last_heartbeat_ago=58s last_sync_ago=12s

Example /status response:

{
  "status": "running",
  "version": "v1.2.0",
  "uptime": "4h32m10s",
  "uptime_seconds": 16330,
  "last_heartbeat": "2026-03-07T14:30:00Z",
  "last_sync": "2026-03-07T14:30:05Z",
  "events_sent": 142,
  "events_queued": 0,
  "failed_logins_flushed": 3
}

Testing

The project has three layers of testing: unit tests, a mock API server for local development, and VM-based integration tests for real end-to-end verification.

Unit tests

make test        # or: go test ./...
make check       # fmt-check + lint + test (CI parity — run before committing)

Tests live alongside the code in *_test.go files. Packages with tests:

Package What it covers
internal/authlog SSH login line parsing (publickey, password, keyboard-interactive/pam), syslog prefix stripping, journal detection
internal/sudolog Sudo command parsing across all distro log formats, session tracker for fingerprint linking
internal/distro OS release detection (Ubuntu, Fedora, openSUSE, SLES, Gentoo, Calculate Linux)
internal/authkeys authorized_keys file parsing, managed block read/write, key validation
internal/client API client request/response handling
internal/config Config loading from file and environment
internal/creds Credential persistence
internal/events Event batching, queue persistence, retry
internal/schedule Time-based grant schedule evaluation
internal/sync Sync loop desired-state building
internal/updater Self-update version comparison
internal/version Version string handling

Mock API server

The mock API (test/mockapi) is a standalone Go program that mimics the grantssh app API. It accepts heartbeat, sync, and event requests, logs everything to stdout, and writes event payloads to a JSON-lines file for inspection.

# Build
go build -o bin/mockapi ./test/mockapi/

# Run (serves empty sync by default)
./bin/mockapi -port 8199 -events-log /tmp/events.jsonl

# Run with a custom sync response (e.g. to push SSH keys to the agent)
./bin/mockapi -port 8199 -events-log /tmp/events.jsonl -sync-response sync.json

Then point the agent at it:

GRANTSSH_APP_URL=http://localhost:8199 \
GRANTSSH_AGENT_ID=test \
GRANTSSH_AGENT_TOKEN=test \
./bin/grantssh-agent

The -events-log file is JSON-lines — one line per event with metadata (agent_id, received_at, event). You can inspect it to verify exactly what the agent sent:

# Show all events
cat /tmp/events.jsonl | python3 -m json.tool

# Show only sudo events
grep '"event_type":"sudo"' /tmp/events.jsonl

VM-based integration tests

For full end-to-end testing on real Linux distributions, the project includes a QEMU-based integration test runner. This catches issues that unit tests cannot — distro-specific journal behaviour, auth log paths, SSH authentication method differences, and sudo log format variations.

Prerequisites: qemu-system-x86_64, qemu-img, cloud-localds (or genisoimage), sshpass, curl. On Ubuntu/Debian: sudo apt install qemu-system-x86 qemu-utils cloud-image-utils sshpass.

# Build agent and mock API first
make build
go build -o bin/mockapi ./test/mockapi/

# Run against a specific distro
./test/integration/run.sh --distro ubuntu
./test/integration/run.sh --distro opensuse
./test/integration/run.sh --distro debian

Cloud images are downloaded on first run and cached in test/integration/images/. Without KVM, QEMU uses software emulation (~2 min boot); with KVM it is nearly instant.

The test runner:

  1. Boots a QEMU VM from a cloud image with cloud-init (sshd + test user)
  2. Starts the mock API on the host
  3. Copies the agent binary into the VM and starts it
  4. Triggers a real SSH login and sudo commands
  5. Verifies the mock API received correct event payloads (login + sudo with fingerprint)

See test/integration/README.md for full details, environment variables, and supported distros.

What the integration tests verify

Distro SSH auth method Sudo detection Journal/file Auth log path
Ubuntu 24.04 password journalctl /var/log/auth.log
Debian 12 password journalctl /var/log/auth.log
Fedora password journalctl /var/log/secure
Rocky Linux 9 publickey journalctl /var/log/secure
AlmaLinux 9 publickey journalctl /var/log/secure
Amazon Linux 2023 publickey journalctl /var/log/secure
openSUSE Leap 15.6 keyboard-interactive/pam journalctl /var/log/messages
Arch Linux publickey journalctl (journal only)
Alpine 3.20 publickey file tail /var/log/messages
Gentoo publickey file tail /var/log/messages
FreeBSD 14 publickey file tail /var/log/auth.log

Notes:

  • Alpine uses musl libc; the agent must be built with CGO_ENABLED=0 (the default make build does this).
  • Alpine and Gentoo with OpenRC have no journald; the agent automatically falls back to file-based log tailing.
  • Arch Linux uses journald exclusively by default (no auth log file); the agent auto-detects journal.
  • FreeBSD builds are cross-compiled with GOOS=freebsd. FreeBSD 13+ has /etc/os-release for distro detection.

About

The opensource GrantSSH agent

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors