ssh-easy-config 0.2.27

dotnet tool install --global ssh-easy-config --version 0.2.27
                    
This package contains a .NET tool you can call from the shell/command line.
dotnet new tool-manifest
                    
if you are setting up this repo
dotnet tool install --local ssh-easy-config --version 0.2.27
                    
This package contains a .NET tool you can call from the shell/command line.
#tool dotnet:?package=ssh-easy-config&version=0.2.27
                    
nuke :add-package ssh-easy-config --version 0.2.27
                    

ssh-easy-config

Cross-platform SSH key management, sharing, and diagnostics.

A .NET 10 CLI tool that handles the entire SSH setup lifecycle: generating keys, sharing them between machines, configuring client and server settings, and diagnosing connectivity problems.

Windows is fully tested and production-ready. Linux, macOS, and WSL support is implemented but not yet validated end-to-end -- confirmations and PRs from users on those platforms are very welcome.

Quick Start

Zero-install (requires .NET 10)

dnx ssh-easy-config

This downloads and runs the tool in a single command with no permanent installation.

Global tool install

dotnet tool install -g ssh-easy-config
ssh-easy-config

Features

  • Ed25519 key generation -- generates keys using modern cryptography, stored in platform-standard locations
  • Three key exchange modes -- share keys over the local network (mDNS + pairing code), via clipboard, or through a file
  • Interactive wizard -- step-by-step guided setup when run with no arguments
  • 5-layer connectivity diagnostics -- pinpoints SSH problems from network reachability down to WSL-specific issues
  • SSH config management -- create and manage host aliases, audit and harden sshd_config
  • Cross-platform -- native support for Windows, Linux, macOS, and WSL (treated as a distinct platform, not generic Linux)
  • Permission enforcement -- validates and fixes file permissions on keys and config files
  • No centralized relay -- all key exchange happens directly between machines
  • Windows-hardened -- handles ghost OpenSSH Server installs, MS-linked account key migration, service registration, and automatic sshd restarts

Commands

Interactive Wizard

ssh-easy-config

Running with no arguments launches a guided wizard using Spectre.Console. It walks through key generation, sharing, configuration, and diagnostics interactively.

setup

Full SSH provisioning: generate keys, install/start/enable sshd, open firewall, fix permissions.

ssh-easy-config setup
ssh-easy-config setup -y              # auto-confirm all prompts
ssh-easy-config setup --approve-admin # also approve UAC elevation

On Windows, setup handles:

  • Installing OpenSSH Server (via Add-WindowsCapability, with winget fallback for ghost installs)
  • Registering the sshd service if Windows fails to create it
  • MS-linked account detection and administrators_authorized_keys configuration
  • UAC elevation prompts when admin rights are needed

Options:

Option Description
--yes / -y Auto-confirm all prompts
--verbose Show detailed output
--approve-admin Also approve UAC elevation (Windows)

share

Share your public key with another machine.

# Network mode (default) -- uses mDNS discovery and pairing code
ssh-easy-config share

# Clipboard mode -- outputs a base64-encoded block to paste on the other machine
ssh-easy-config share --mode clipboard

# File mode -- writes a .sshec bundle to disk
ssh-easy-config share --mode file --output ./my-key-bundle.sshec

Options:

Option Description
--mode Transfer mode: network (default), clipboard, or file
--output Output file path (used with --mode file)
--host Hostname/IP to advertise (skips the selection prompt)

receive

Receive a key shared from another machine.

# Network mode (default) -- discovers the sender via mDNS
ssh-easy-config receive

# Clipboard mode -- prompts you to paste the base64 block
ssh-easy-config receive --mode clipboard

# File mode -- reads from a .sshec bundle
ssh-easy-config receive --mode file --input ./my-key-bundle.sshec

Options:

Option Description
--mode Transfer mode: network (default), clipboard, or file
--input Input file path (used with --mode file)
--host Host to connect to (skips mDNS discovery)
--port Port to connect to
--code Pairing code (skips the prompt)

After key exchange, the tool:

  • Adds the remote key to authorized_keys (and administrators_authorized_keys on Windows admin accounts)
  • Checks PubkeyAuthentication is enabled in sshd_config and offers to enable it
  • Prompts to restart sshd so changes take effect
  • Offers to create an SSH config alias for the remote machine

diagnose

Diagnose SSH connectivity to a host.

# Diagnose a specific host
ssh-easy-config diagnose myserver

# JSON output for scripting or CI
ssh-easy-config diagnose myserver --json

# Show all checks including skipped ones
ssh-easy-config diagnose myserver --verbose

# General local SSH health check (no host)
ssh-easy-config diagnose

Each check offers an auto-fix prompt where safe (start sshd, open firewall port, fix file permissions, etc.).

Options:

Option Description
host The host to diagnose (optional)
--json Output results as JSON
--verbose Show all checks including skipped

config

Manage SSH client and server configuration.

# Audit current sshd_config settings
ssh-easy-config config audit

# Apply security hardening to sshd_config
ssh-easy-config config harden

# List and manage host aliases in ~/.ssh/config
ssh-easy-config config hosts

# Fix Windows key migration issues (admin authorized_keys, Match block)
ssh-easy-config config fix

Actions:

Action Description
audit Review sshd_config and recommend changes
harden Apply security settings (disable password auth, enable pubkey auth, etc.)
hosts Manage Host alias entries in ~/.ssh/config
fix Fix Windows admin key migration and permissions (run as Administrator)

Hardening applies these changes with user confirmation and always creates a backup first:

  • Disable PasswordAuthentication
  • Set PubkeyAuthentication yes
  • Configure AllowUsers / AllowGroups
  • Set PermitRootLogin no (or prohibit-password)

Key Exchange

ssh-easy-config supports three modes for transferring public keys between machines. All three exchange the same data (public keys, hostnames, preferred usernames, and optional host alias suggestions) -- only the transport differs.

Network Mode (default)

The primary mode. Two machines on the same network exchange keys directly:

  1. Machine A runs ssh-easy-config share -- starts a temporary listener, displays a 6-digit pairing code, and advertises via mDNS
  2. Machine B runs ssh-easy-config receive -- discovers Machine A via mDNS (_ssh-easy._tcp service) and prompts for the pairing code
  3. The pairing code derives a shared encryption key (via HKDF) to secure the transfer
  4. Both machines display a fingerprint confirmation of the exchanged keys for visual verification
  5. After exchange, both sides prompt to add keys, enable pubkey auth, restart sshd, and create config aliases
  6. The listener shuts down after one successful exchange

The share side detects available hostnames including the machine name, mDNS .local name, Tailscale FQDN (if available), and local IP addresses. This lets you choose the right address for the receiving machine's network context.

If mDNS discovery fails (different subnets, mDNS blocked), the user can fall back to entering an IP address and port manually. The share side displays a copyable receive command for quick one-liner usage.

Clipboard Mode

For machines that cannot reach each other over the network:

# On the sending machine
ssh-easy-config share --mode clipboard
# Copy the base64-encoded output

# On the receiving machine
ssh-easy-config receive --mode clipboard
# Paste the block when prompted

File Mode

For transferring via USB drive, shared folder, or any other file transport:

# On the sending machine
ssh-easy-config share --mode file --output ./keys.sshec

# Transfer the .sshec file to the other machine, then:
ssh-easy-config receive --mode file --input ./keys.sshec

Platform Support

ssh-easy-config detects the current platform at startup and adapts its behavior accordingly.

Concern Linux macOS Windows WSL
SSH paths ~/.ssh/ ~/.ssh/ %USERPROFILE%\.ssh\ ~/.ssh/
Service management systemctl launchctl sc.exe / Get-Service systemctl or service
Permissions chmod chmod icacls chmod
sshd_config location /etc/ssh/sshd_config /etc/ssh/sshd_config %ProgramData%\ssh\sshd_config /etc/ssh/sshd_config
Admin authorized keys authorized_keys authorized_keys administrators_authorized_keys authorized_keys
Firewall iptables/ufw/firewalld pfctl netsh Inherits Windows firewall
Tested Community Community Yes Community

Windows-Specific Handling

Windows SSH has several pitfalls that ssh-easy-config handles automatically:

  • Ghost OpenSSH installs -- Add-WindowsCapability sometimes reports success but doesn't install sshd.exe. The tool detects this and falls back to winget install.
  • Service registration -- even when sshd.exe exists, the Windows service may not be created. The tool registers it via sc create.
  • MS-linked accounts -- Windows admin users with Microsoft-linked accounts need keys in %ProgramData%\ssh\administrators_authorized_keys with a Match Group administrators block in sshd_config. The tool handles this automatically.
  • sshd restart -- Windows OpenSSH requires a service restart to pick up authorized_keys changes. The tool prompts for this after key exchange.
  • PubkeyAuthentication -- checked and enabled automatically during key exchange if not already set.

WSL as a First-Class Target

WSL is treated as a distinct platform, not generic Linux:

  • Detected via /proc/version containing "Microsoft" or the WSL_DISTRO_NAME environment variable
  • Supports cross-boundary setup between the Windows host and WSL instances
  • Detects WSL1 vs WSL2 for correct network stack handling and port forwarding
  • Translates paths between WSL (/mnt/c/Users/james/.ssh/) and Windows (C:\Users\james\.ssh\)
  • When running inside WSL, offers to configure both the WSL-side and Windows-side SSH

Diagnostics

The diagnose command runs a 5-layer check sequence. Each layer builds on the previous one, stopping to report at the first failure with an actionable message and an offer to auto-fix where safe.

Layer 1 -- Network Reachability

  • DNS resolution (forward and reverse)
  • TCP port connectivity (default 22, or custom port from ssh_config)
  • Firewall detection heuristics (connection refused vs. timeout)
  • Windows Firewall rule checks

Layer 2 -- SSH Service Status

  • Whether sshd is running on the target (inferred from connection handshake)
  • SSH protocol version banner check
  • Local sshd service status (running and enabled)

Layer 3 -- Authentication Audit

  • Key-based authentication attempt and result interpretation
  • Local key existence and match against remote authorized_keys
  • Detection of common key mismatches (wrong type, expired, revoked)

Layer 4 -- Configuration Audit

  • Client-side: ~/.ssh/config syntax validation, IdentityFile path checks, permission checks
  • Server-side (if accessible): sshd_config review -- pubkey auth enabled, password auth status, AllowUsers/AllowGroups
  • File permission checks on both ends
  • Windows: administrators_authorized_keys existence and Match block verification

Layer 5 -- WSL-Specific Checks

  • Whether SSH is configured inside WSL, on the Windows host, or both
  • Port forwarding between Windows and WSL
  • Interop path issues (/mnt/c/Users/... vs C:\Users\...)

Output Modes

  • Interactive (default): colored terminal output with fix-it prompts
  • JSON (--json): machine-readable output for scripting and CI pipelines
  • Verbose (--verbose): full trace of every check, including those that were skipped

Known Issues and Platform Gaps

Linux

  • RestartSshServiceAsync and IsSshServiceRunningAsync hard-code the service name sshd, but Debian/Ubuntu uses ssh. Affects service start/restart/status checks.
  • GetLinuxServiceName only checks /lib/systemd/system/ssh.service, not /usr/lib/systemd/system/ssh.service (used by newer distros).
  • systemctl start is called without sudo first (then retried with sudo service on failure).
  • firewalld open-port command passes a bash -c compound command that may not split correctly via ProcessStartInfo.
  • Clipboard (xsel) is called without --input flag.
  • Writing to /etc/ssh/sshd_config fails for non-root with no helpful error message.

macOS

  • launchctl stop/start com.openssh.sshd is deprecated on macOS 12+; should use launchctl kickstart.
  • systemsetup -setremotelogin on is a no-op under SIP on macOS 13+ (Ventura) without Full Disk Access.
  • IsSshServiceRunningAsync may return false positives (checks if service was ever loaded, not if currently running).
  • pf firewall check always returns "open" without inspecting actual rules.

WSL

  • CheckLinuxSshdEnabledAsync hard-codes PlatformKind.Linux instead of using the platform's actual kind.
  • WSL2 mirrored networking (networkingMode=mirrored in .wslconfig) still triggers the port-forwarding warning.

Contributions and bug reports for any platform are welcome at GitHub Issues.

Building from Source

Requires .NET 10 SDK.

# Build
dotnet build src/SshEasyConfig.csproj

# Run tests
dotnet test

# Create NuGet package
dotnet pack src/SshEasyConfig.csproj

Dependencies

No SSH library is bundled. Key generation uses .NET cryptography APIs directly. Connection testing in diagnostics and service management shell out to the system's ssh, systemctl, launchctl, sc.exe, etc.

License

MIT

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

Version Downloads Last Updated
0.2.27 31 3/30/2026
0.2.26 35 3/29/2026
0.2.25 38 3/29/2026
0.2.24 34 3/29/2026
0.2.23 28 3/29/2026
0.2.22 26 3/29/2026
0.2.21 30 3/29/2026
0.2.20 65 3/28/2026
0.2.19 100 3/28/2026
0.2.18 40 3/28/2026
0.2.17 44 3/27/2026
0.2.16 33 3/27/2026
0.2.15 36 3/27/2026
0.2.14 31 3/27/2026
0.2.13 35 3/27/2026
0.2.12 33 3/27/2026
0.2.11 36 3/27/2026
0.2.10 28 3/26/2026
0.2.9 30 3/26/2026
0.2.8 26 3/26/2026
Loading failed