Add core modules (SSH args parser, cache, resolver, NetBox client) with tests
Release / release (push) Failing after 51s
Release / release (push) Failing after 51s
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
# netssh
|
||||
|
||||
A transparent SSH wrapper that resolves hostnames via [NetBox](https://netbox.dev/) before connecting.
|
||||
|
||||
Instead of looking up an IP manually, you just type the hostname as it appears in NetBox:
|
||||
|
||||
```sh
|
||||
netssh my-router-01
|
||||
netssh -p 2222 admin@app-server-03 uptime
|
||||
```
|
||||
|
||||
`netssh` looks up the host in NetBox, resolves the right IP using a configurable strategy chain, and replaces the process with the native `ssh` binary — so all your existing SSH configs, keys, and agent forwarding work without any changes.
|
||||
|
||||
## Features
|
||||
|
||||
- **Transparent proxy** — replaces itself with `ssh` via `syscall.Exec`, preserving all SSH flags and options
|
||||
- **Flexible IP resolution** — configurable chain of strategies: management subnet, primary IP, or named interface
|
||||
- **Interactive TUI** — fuzzy search with live NetBox queries and 300 ms debouncing (start with `netssh`, no arguments)
|
||||
- **Persistent cache** — successful lookups are cached to `~/.cache/netssh/hosts.json` for instant shell completion
|
||||
- **Shell completion** — tab-complete hostnames from the cache in zsh, bash, and fish
|
||||
- **Default SSH user** — set a fallback username once in config instead of typing it every time
|
||||
|
||||
## Installation
|
||||
|
||||
### One-liner (Linux & macOS)
|
||||
|
||||
```sh
|
||||
curl -fsSL https://git.zb-server.de/Sebi/ssh-netbox-wrapper/raw/branch/main/install.sh | bash
|
||||
```
|
||||
|
||||
The script detects your OS and architecture, downloads the matching binary from the [latest release](https://git.zb-server.de/Sebi/ssh-netbox-wrapper/releases/latest), verifies the SHA-256 checksum, and installs to `/usr/local/bin/netssh` (using `sudo` only if necessary).
|
||||
|
||||
To install to a custom directory:
|
||||
|
||||
```sh
|
||||
INSTALL_DIR=~/.local/bin curl -fsSL https://git.zb-server.de/Sebi/ssh-netbox-wrapper/raw/branch/main/install.sh | bash
|
||||
```
|
||||
|
||||
### Build from source
|
||||
|
||||
```sh
|
||||
git clone ssh://git@git.zb-server.de:30022/Sebi/ssh-netbox-wrapper.git
|
||||
cd ssh-netbox-wrapper
|
||||
go build -o netssh ./cmd/netssh
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Create `~/.config/netssh.yaml`:
|
||||
|
||||
```yaml
|
||||
netbox:
|
||||
url: https://netbox.example.com
|
||||
token: your-api-token-here
|
||||
|
||||
resolver:
|
||||
# Strategies are tried in order; the first to return an IP wins.
|
||||
strategies:
|
||||
- management_subnet
|
||||
- primary_ip
|
||||
# Used by the management_subnet strategy.
|
||||
management_subnets:
|
||||
- 10.0.0.0/8
|
||||
- 172.16.0.0/12
|
||||
# Used by the interface_name strategy.
|
||||
interface_name: mgmt0
|
||||
|
||||
cache:
|
||||
ttl: 3600 # seconds; 0 = always query NetBox on connect (cache still used for completion)
|
||||
# path: ~/.cache/netssh/hosts.json # default
|
||||
|
||||
ssh:
|
||||
default_user: admin # used when no user is specified on the command line
|
||||
```
|
||||
|
||||
Any value can be overridden with environment variables (`NETSSH_NETBOX_URL`, `NETSSH_NETBOX_TOKEN`, etc.) or will be read from the config file.
|
||||
|
||||
## Usage
|
||||
|
||||
### SSH wrapper mode
|
||||
|
||||
Pass any SSH flags and a NetBox hostname:
|
||||
|
||||
```sh
|
||||
netssh my-router-01
|
||||
netssh -p 2222 admin@app-server-03 uptime
|
||||
netssh -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no db-primary
|
||||
```
|
||||
|
||||
The process is replaced by `ssh` with the resolved IP — your `~/.ssh/config`, agent, and keys all work as normal.
|
||||
|
||||
### Default username
|
||||
|
||||
Set `ssh.default_user` in the config to avoid typing a username every time:
|
||||
|
||||
```sh
|
||||
netssh my-router # → ssh -l admin 10.0.0.1
|
||||
```
|
||||
|
||||
The default is only applied when no user is specified on the command line. An explicit user always takes precedence:
|
||||
|
||||
```sh
|
||||
netssh root@my-router # user@ prefix wins → ssh root@10.0.0.1
|
||||
netssh -l ops my-router # -l flag wins → ssh -l ops 10.0.0.1
|
||||
```
|
||||
|
||||
### Interactive TUI
|
||||
|
||||
Run without arguments to open the interactive search:
|
||||
|
||||
```sh
|
||||
netssh
|
||||
```
|
||||
|
||||
| Key | Action |
|
||||
|-----|--------|
|
||||
| type | filter hosts (300 ms debounce → NetBox query) |
|
||||
| `Tab` | autocomplete top result into the search field |
|
||||
| `↑` / `↓` | navigate results |
|
||||
| `Enter` | connect to selected host |
|
||||
| `Esc` / `Ctrl+C` | quit |
|
||||
|
||||
### Cache management
|
||||
|
||||
```sh
|
||||
netssh cache list # show all cached entries
|
||||
netssh cache refresh # re-fetch all hosts from NetBox
|
||||
netssh cache clear # wipe the cache
|
||||
```
|
||||
|
||||
### Search (for scripting)
|
||||
|
||||
```sh
|
||||
netssh search app- # prints matching hostnames, one per line
|
||||
```
|
||||
|
||||
## IP Resolution Strategies
|
||||
|
||||
Strategies are tried in the configured order; the first to succeed wins.
|
||||
|
||||
| Name | Description |
|
||||
|------|-------------|
|
||||
| `primary_ip` | Returns the `primary_ip4` (or `primary_ip6`) set in NetBox. No extra API call. |
|
||||
| `management_subnet` | Fetches all IPs for the host and returns the first one matching a configured CIDR. |
|
||||
| `interface_name` | Fetches IPs attached to a specific named interface (e.g. `mgmt0`). |
|
||||
|
||||
## Shell Completion
|
||||
|
||||
### zsh
|
||||
|
||||
```sh
|
||||
netssh completion zsh > "${fpath[1]}/_netssh"
|
||||
```
|
||||
|
||||
Or add to `.zshrc`:
|
||||
|
||||
```zsh
|
||||
source <(netssh completion zsh)
|
||||
```
|
||||
|
||||
### bash
|
||||
|
||||
```sh
|
||||
netssh completion bash > /etc/bash_completion.d/netssh
|
||||
```
|
||||
|
||||
### fish
|
||||
|
||||
```sh
|
||||
netssh completion fish > ~/.config/fish/completions/netssh.fish
|
||||
```
|
||||
|
||||
Completions are served from the local cache — no network request on every `<Tab>`.
|
||||
|
||||
## Development
|
||||
|
||||
```sh
|
||||
go test ./... # run all tests
|
||||
go build ./... # build all packages
|
||||
```
|
||||
|
||||
The test suite covers the cache, NetBox client (via `httptest`), IP resolver chain, and SSH argument parser.
|
||||
|
||||
## How it works
|
||||
|
||||
1. `netssh` checks whether the first argument is a known subcommand (`search`, `cache`, `completion`). If not, it enters SSH wrapper mode.
|
||||
2. It parses the SSH arguments to extract the destination hostname, handling all flags that consume an extra argument (`-p`, `-i`, `-J`, …).
|
||||
3. It checks the local cache. If the entry exists and is within the TTL, it connects immediately.
|
||||
4. Otherwise it queries NetBox (`/api/dcim/devices/` and `/api/virtualization/virtual-machines/` in parallel), runs the result through the resolver chain, and caches the IP.
|
||||
5. It calls `syscall.Exec` to replace itself with `ssh`, substituting the hostname with the resolved IP.
|
||||
Reference in New Issue
Block a user