docs: add ssh howto
Covers keys, agent, config file, tunnels (local/remote/dynamic), file transfer (scp/rsync/sftp), escape sequences, and hardening.
This commit is contained in:
2
TODO.md
2
TODO.md
@@ -8,7 +8,7 @@
|
||||
- [x] jq — filters, select, map, slurp, recipes, @format
|
||||
- [x] curl — methods, headers, auth, upload, download, cookies, -w format
|
||||
- [ ] systemd — units, journalctl, timers
|
||||
- [ ] ssh — config, tunnels, keys, agent
|
||||
- [x] ssh — config, tunnels, keys, agent, file transfer, hardening
|
||||
- [ ] bash — parameter expansion, arrays, traps
|
||||
- [ ] make — targets, variables, patterns
|
||||
- [ ] vim — motions, registers, macros
|
||||
|
||||
315
topics/ssh.md
Normal file
315
topics/ssh.md
Normal file
@@ -0,0 +1,315 @@
|
||||
# SSH
|
||||
|
||||
> Secure shell — encrypted remote access, tunnels, and file transfer.
|
||||
|
||||
## Basics
|
||||
|
||||
```bash
|
||||
# Connect
|
||||
ssh user@host
|
||||
ssh user@host -p 2222 # non-standard port
|
||||
ssh host # uses current username
|
||||
|
||||
# Run command remotely (no interactive shell)
|
||||
ssh user@host 'uptime'
|
||||
ssh user@host 'cat /etc/os-release'
|
||||
ssh user@host 'sudo systemctl restart nginx'
|
||||
|
||||
# Run local script on remote host
|
||||
ssh user@host 'bash -s' < local-script.sh
|
||||
|
||||
# Force pseudo-terminal (needed for interactive commands via ssh)
|
||||
ssh -t user@host 'sudo vim /etc/hosts'
|
||||
```
|
||||
|
||||
## Keys
|
||||
|
||||
```bash
|
||||
# Generate key pair
|
||||
ssh-keygen -t ed25519 -C "user@host"
|
||||
ssh-keygen -t ed25519 -f ~/.ssh/id_project -C "project key"
|
||||
|
||||
# Copy public key to remote host
|
||||
ssh-copy-id user@host
|
||||
ssh-copy-id -i ~/.ssh/id_project.pub -p 2222 user@host
|
||||
|
||||
# Manual copy (when ssh-copy-id unavailable)
|
||||
cat ~/.ssh/id_ed25519.pub | ssh user@host 'mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys'
|
||||
|
||||
# Permissions (must be strict or SSH refuses)
|
||||
chmod 700 ~/.ssh
|
||||
chmod 600 ~/.ssh/id_ed25519 # private key
|
||||
chmod 644 ~/.ssh/id_ed25519.pub # public key
|
||||
chmod 600 ~/.ssh/authorized_keys
|
||||
chmod 600 ~/.ssh/config
|
||||
|
||||
# Show key fingerprint
|
||||
ssh-keygen -lf ~/.ssh/id_ed25519.pub
|
||||
ssh-keygen -lvf ~/.ssh/id_ed25519.pub # visual ASCII art
|
||||
```
|
||||
|
||||
## SSH Agent
|
||||
|
||||
```bash
|
||||
# Start agent
|
||||
eval "$(ssh-agent -s)"
|
||||
|
||||
# Add key
|
||||
ssh-add ~/.ssh/id_ed25519
|
||||
ssh-add -t 3600 ~/.ssh/id_ed25519 # expire after 1 hour
|
||||
|
||||
# List loaded keys
|
||||
ssh-add -l
|
||||
|
||||
# Remove all keys
|
||||
ssh-add -D
|
||||
|
||||
# Forward agent to remote host (-A flag)
|
||||
ssh -A user@bastion # remote can use your local keys
|
||||
```
|
||||
|
||||
## Config File (~/.ssh/config)
|
||||
|
||||
```ssh-config
|
||||
# Global defaults
|
||||
Host *
|
||||
ServerAliveInterval 60
|
||||
ServerAliveCountMax 3
|
||||
AddKeysToAgent yes
|
||||
IdentitiesOnly yes
|
||||
|
||||
# Simple host alias
|
||||
Host web1
|
||||
HostName 192.168.1.10
|
||||
User deploy
|
||||
Port 22
|
||||
IdentityFile ~/.ssh/id_ed25519
|
||||
|
||||
# Jump host (bastion)
|
||||
Host internal-db
|
||||
HostName 10.0.0.50
|
||||
User admin
|
||||
ProxyJump bastion
|
||||
|
||||
Host bastion
|
||||
HostName bastion.example.com
|
||||
User jump
|
||||
IdentityFile ~/.ssh/id_bastion
|
||||
ForwardAgent yes
|
||||
|
||||
# Wildcard match
|
||||
Host *.staging.example.com
|
||||
User deploy
|
||||
IdentityFile ~/.ssh/id_staging
|
||||
StrictHostKeyChecking no
|
||||
|
||||
# Multiple jump hosts
|
||||
Host deep-internal
|
||||
HostName 10.10.0.5
|
||||
User admin
|
||||
ProxyJump bastion,middle-host
|
||||
|
||||
# Keep connection alive for reuse
|
||||
Host *
|
||||
ControlMaster auto
|
||||
ControlPath ~/.ssh/sockets/%r@%h-%p
|
||||
ControlPersist 600
|
||||
```
|
||||
|
||||
After config, connect with just:
|
||||
|
||||
```bash
|
||||
ssh web1 # expands to full connection
|
||||
ssh internal-db # routes through bastion
|
||||
```
|
||||
|
||||
### Config Directives Reference
|
||||
|
||||
| Directive | Purpose |
|
||||
|------------------------|---------------------------------------------|
|
||||
| `HostName` | Real hostname/IP |
|
||||
| `User` | Login username |
|
||||
| `Port` | SSH port (default: 22) |
|
||||
| `IdentityFile` | Path to private key |
|
||||
| `IdentitiesOnly` | Only use specified keys, not agent |
|
||||
| `ProxyJump` | Jump through bastion host(s) |
|
||||
| `ProxyCommand` | Custom proxy command |
|
||||
| `ForwardAgent` | Forward agent to remote (`yes`/`no`) |
|
||||
| `ServerAliveInterval` | Keepalive interval in seconds |
|
||||
| `ServerAliveCountMax` | Keepalive failures before disconnect |
|
||||
| `ControlMaster` | Enable connection multiplexing |
|
||||
| `ControlPath` | Socket path for multiplexed connections |
|
||||
| `ControlPersist` | Keep master connection alive (seconds) |
|
||||
| `StrictHostKeyChecking`| `ask`, `yes`, `no`, `accept-new` |
|
||||
| `UserKnownHostsFile` | Path to known_hosts file |
|
||||
| `LocalForward` | Persistent local tunnel (same as `-L`) |
|
||||
| `RemoteForward` | Persistent remote tunnel (same as `-R`) |
|
||||
| `DynamicForward` | Persistent SOCKS proxy (same as `-D`) |
|
||||
|
||||
## Tunnels / Port Forwarding
|
||||
|
||||
### Local Forward (-L)
|
||||
|
||||
Access a remote service through a local port.
|
||||
|
||||
```bash
|
||||
# Local:8080 -> remote's localhost:80
|
||||
ssh -L 8080:localhost:80 user@host
|
||||
|
||||
# Local:5432 -> db server via jump host
|
||||
ssh -L 5432:db.internal:5432 user@bastion
|
||||
|
||||
# Bind to all interfaces (not just localhost)
|
||||
ssh -L 0.0.0.0:8080:localhost:80 user@host
|
||||
|
||||
# Background tunnel (no shell)
|
||||
ssh -fNL 8080:localhost:80 user@host
|
||||
```
|
||||
|
||||
### Remote Forward (-R)
|
||||
|
||||
Expose a local service to the remote host.
|
||||
|
||||
```bash
|
||||
# Remote:9090 -> local:3000
|
||||
ssh -R 9090:localhost:3000 user@host
|
||||
|
||||
# Background tunnel
|
||||
ssh -fNR 9090:localhost:3000 user@host
|
||||
```
|
||||
|
||||
### Dynamic Forward (-D) — SOCKS Proxy
|
||||
|
||||
```bash
|
||||
# SOCKS5 proxy on local:1080
|
||||
ssh -D 1080 user@host
|
||||
|
||||
# Use with curl
|
||||
curl --socks5-hostname localhost:1080 https://example.com
|
||||
|
||||
# Background SOCKS proxy
|
||||
ssh -fND 1080 user@host
|
||||
```
|
||||
|
||||
### Tunnel Flags
|
||||
|
||||
| Flag | Purpose |
|
||||
|------|-------------------------------------------|
|
||||
| `-f` | Fork to background after authentication |
|
||||
| `-N` | No remote command (tunnel only) |
|
||||
| `-T` | Disable pseudo-terminal allocation |
|
||||
| `-g` | Allow remote hosts to use local forwards |
|
||||
|
||||
## File Transfer
|
||||
|
||||
### SCP
|
||||
|
||||
```bash
|
||||
# Local -> remote
|
||||
scp file.txt user@host:/tmp/
|
||||
scp -P 2222 file.txt user@host:/tmp/ # non-standard port
|
||||
scp -r ./dir user@host:/opt/ # recursive
|
||||
|
||||
# Remote -> local
|
||||
scp user@host:/var/log/app.log ./
|
||||
scp -r user@host:/opt/data ./local-data/
|
||||
|
||||
# Between two remotes
|
||||
scp user@host1:/tmp/file.txt user@host2:/tmp/
|
||||
```
|
||||
|
||||
### Rsync over SSH
|
||||
|
||||
```bash
|
||||
# Sync directory to remote
|
||||
rsync -avz ./src/ user@host:/opt/app/src/
|
||||
|
||||
# Sync from remote
|
||||
rsync -avz user@host:/var/log/ ./logs/
|
||||
|
||||
# Custom SSH port
|
||||
rsync -avz -e 'ssh -p 2222' ./src/ user@host:/opt/app/
|
||||
|
||||
# Dry run
|
||||
rsync -avzn ./src/ user@host:/opt/app/src/
|
||||
|
||||
# Delete remote files not in local
|
||||
rsync -avz --delete ./src/ user@host:/opt/app/src/
|
||||
|
||||
# Exclude patterns
|
||||
rsync -avz --exclude='.git' --exclude='*.pyc' ./src/ user@host:/opt/app/
|
||||
```
|
||||
|
||||
### SFTP
|
||||
|
||||
```bash
|
||||
sftp user@host
|
||||
# Interactive commands: ls, cd, get, put, mkdir, rm
|
||||
|
||||
# Non-interactive
|
||||
sftp user@host <<< 'get /var/log/app.log'
|
||||
```
|
||||
|
||||
## Known Hosts
|
||||
|
||||
```bash
|
||||
# Remove stale entry (after server rebuild)
|
||||
ssh-keygen -R hostname
|
||||
ssh-keygen -R 192.168.1.10
|
||||
ssh-keygen -R "[hostname]:2222" # non-standard port
|
||||
|
||||
# Show stored fingerprint
|
||||
ssh-keygen -F hostname
|
||||
|
||||
# Accept new keys automatically, reject changed keys
|
||||
# In ~/.ssh/config:
|
||||
# StrictHostKeyChecking accept-new
|
||||
```
|
||||
|
||||
## Escape Sequences
|
||||
|
||||
During an SSH session, press `Enter` then:
|
||||
|
||||
| Sequence | Action |
|
||||
|----------|--------------------------------------|
|
||||
| `~.` | Disconnect (kill hung session) |
|
||||
| `~^Z` | Suspend SSH (background) |
|
||||
| `~#` | List forwarded connections |
|
||||
| `~&` | Background SSH (when waiting to close)|
|
||||
| `~?` | Show escape help |
|
||||
| `~~` | Send literal `~` |
|
||||
|
||||
## Hardening (Server-Side)
|
||||
|
||||
```bash
|
||||
# /etc/ssh/sshd_config — key settings
|
||||
PermitRootLogin no
|
||||
PasswordAuthentication no
|
||||
PubkeyAuthentication yes
|
||||
AuthorizedKeysFile .ssh/authorized_keys
|
||||
MaxAuthTries 3
|
||||
X11Forwarding no
|
||||
AllowUsers deploy admin
|
||||
```
|
||||
|
||||
```bash
|
||||
# Restart after changes
|
||||
sudo systemctl restart sshd
|
||||
```
|
||||
|
||||
## Gotchas
|
||||
|
||||
- Permissions too open on `~/.ssh/` or keys = SSH silently refuses them
|
||||
- `ForwardAgent yes` on untrusted hosts = your keys are exposed to that host's root
|
||||
- `~.` escape only works at start of line — press `Enter` first
|
||||
- `-L`/`-R` tunnels bind to `localhost` by default — use `0.0.0.0:` to expose wider
|
||||
- `ControlPersist` keeps connections open after exit — `ssh -O exit host` to close
|
||||
- `ProxyJump` requires OpenSSH 7.3+ — older versions need `ProxyCommand ssh -W %h:%p bastion`
|
||||
- `scp` is deprecated in favor of `sftp` or `rsync` in newer OpenSSH — still works but may warn
|
||||
- Agent forwarding through multiple hops: each hop must have `ForwardAgent yes`
|
||||
|
||||
## See Also
|
||||
|
||||
- `ansible` — uses SSH as transport
|
||||
- [SSH.com Academy](https://www.ssh.com/academy/ssh)
|
||||
- `man ssh_config` — full config reference
|
||||
Reference in New Issue
Block a user