No description
  • Go 98.1%
  • Dockerfile 1.9%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Will befd054b5a Fix .gitignore anchoring, add cmd/ entrypoints
The bare patterns 'console-gw'/'console' also matched the cmd/console-gw
and cmd/console directories, silently excluding both main packages from
the initial commit. Anchor binary ignores to the repo root.
2026-08-11 23:57:21 +01:00
cmd Fix .gitignore anchoring, add cmd/ entrypoints 2026-08-11 23:57:21 +01:00
internal Initial commit: SSH console gateway + CLI 2026-08-11 23:57:02 +01:00
.dockerignore Initial commit: SSH console gateway + CLI 2026-08-11 23:57:02 +01:00
.gitignore Fix .gitignore anchoring, add cmd/ entrypoints 2026-08-11 23:57:21 +01:00
console-gw.example.yaml Initial commit: SSH console gateway + CLI 2026-08-11 23:57:02 +01:00
Dockerfile Initial commit: SSH console gateway + CLI 2026-08-11 23:57:02 +01:00
go.mod Initial commit: SSH console gateway + CLI 2026-08-11 23:57:02 +01:00
go.sum Initial commit: SSH console gateway + CLI 2026-08-11 23:57:02 +01:00
README.md Initial commit: SSH console gateway + CLI 2026-08-11 23:57:02 +01:00

console-gw

SSH gateway for home-lab serial consoles. The Cisco 2611XM router lab-r1 (lab-r1.net.mcda.dev / 10.20.1.70) acts as a console server, exposing its async serial lines via unauthenticated reverse-TELNET on TCP ports 2033+. console-gw puts a modern, authenticated SSH front-end in front of those cleartext ports, and will later also serve direct local serial ports.

Current lab-r1 telnet port mapping (fronted by this gateway):

Port Device Console name
2033 C892FSP c892fsp
2034 SRX320 srx320

Components

  • console-gw — the daemon. SSH server (public-key auth only); the SSH username selects the console (ssh srx320@gw -p 2222). Sessions are bridged to the backend and logged raw to log_dir.
  • console — thin CLI client (console list, console attach srx320).

Build

go build ./cmd/...

Configuration

Copy console-gw.example.yaml to console-gw.yaml (default path; override with -config):

listen: ":2222"
host_key: "./host_key"            # ed25519; generated if missing
authorized_keys: "./authorized_keys"
log_dir: "./sessions"
consoles:
  - name: srx320
    description: "Juniper SRX320 (core-1) console"
    transport: telnet
    address: "lab-r1.net.mcda.dev:2034"
  - name: c892fsp
    description: "Cisco C892FSP console"
    transport: telnet
    address: "lab-r1.net.mcda.dev:2033"

authorized_keys is a standard OpenSSH authorized_keys file; each key's comment is recorded and used in session log filenames.

Docker

Multi-stage build: static binary on gcr.io/distroless/static-debian12:nonroot (runs as uid 65532). The image's entrypoint reads /etc/console-gw/console-gw.yaml; writable state (host key, session logs) lives under /data.

docker build -t console-gw .

docker run --rm -p 2222:2222 \
  -v "$PWD/console-gw.yaml:/etc/console-gw/console-gw.yaml:ro" \
  -v "$PWD/authorized_keys:/etc/console-gw/authorized_keys:ro" \
  -v console-gw-data:/data \
  console-gw

The mounted config should point at the container paths:

listen: ":2222"
host_key: "/data/host_key"
authorized_keys: "/etc/console-gw/authorized_keys"
log_dir: "/data/sessions"

The daemon creates missing state directories (/data/sessions, the host key's parent) itself as the nonroot user.

Usage

# plain ssh, username selects the console
ssh srx320@gw -p 2222
ssh list@gw -p 2222            # table of consoles

# CLI client
console list
console attach srx320
console -addr gw.example.com:2222 -i ~/.ssh/id_ed25519 attach c892fsp

The CLI defaults its gateway address from ~/.config/console/config.yaml:

gateway: gw.example.com:2222

Behavior notes:

  • PTY is required to attach; a bare session (or username list) prints the console table and closes.
  • One active session per console; a concurrent attach gets a "console busy" error. Read-only shadow (follow) sessions are a planned future feature.
  • Window resizes are accepted but ignored by the telnet backend.

Security

  • SSH-only access, public-key authentication only (no passwords); anything not in authorized_keys is rejected and logged.
  • The telnet hop between the gateway and lab-r1 remains cleartext — keep it on the isolated lab network.
  • Every session is logged raw to <log_dir>/<console>-<timestamp>-<key>.log (backend output only).
  • The console CLI verifies the gateway against ~/.ssh/known_hosts when that file exists (add the gateway's host key there); otherwise it warns and skips verification.
  • Deployment: runs behind Tailscale, reachable only on the tailnet. A later Ansible role will automate deployment.

Roadmap: replacing the 2611XM

  • Add direct local serial consoles (transport: serial, e.g. a Linux box with USB-serial hubs) — config schema and backend interface already support it.
  • Migrate consoles off lab-r1 reverse-telnet port by port; config change only, clients unchanged.
  • Retire the 2611XM as console server once all lines are direct-attached.