mirror of
https://github.com/BagelHole/DevOps-Security-Agent-Skills.git
synced 2026-08-22 12:49:53 +02:00
V2
This commit is contained in:
@@ -9,68 +9,300 @@ metadata:
|
||||
|
||||
# SSH Configuration
|
||||
|
||||
Secure SSH server and client configuration.
|
||||
Secure SSH server and client configuration for production environments, including key management, hardened sshd settings, bastion host architecture, tunneling, and multiplexing.
|
||||
|
||||
## Key Management
|
||||
## When to Use
|
||||
|
||||
- Setting up secure remote access to Linux or Unix servers
|
||||
- Hardening SSH daemon configuration to meet compliance requirements
|
||||
- Configuring bastion / jump hosts for private network access
|
||||
- Creating SSH tunnels for secure port forwarding
|
||||
- Managing SSH keys for teams or automated deployments
|
||||
- Troubleshooting connection, authentication, or performance issues
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- OpenSSH client installed locally (`ssh -V` to verify)
|
||||
- OpenSSH server installed on target (`sshd`)
|
||||
- Root or sudo access on the server for sshd_config changes
|
||||
- Firewall rules allowing TCP port 22 (or custom SSH port)
|
||||
|
||||
## Key Generation and Management
|
||||
|
||||
```bash
|
||||
# Generate key
|
||||
ssh-keygen -t ed25519 -C "user@example.com"
|
||||
# Generate an Ed25519 key (recommended -- fast, secure, short)
|
||||
ssh-keygen -t ed25519 -C "jane@example.com" -f ~/.ssh/id_ed25519
|
||||
|
||||
# Copy to server
|
||||
ssh-copy-id user@server
|
||||
# Generate an RSA 4096-bit key (for legacy compatibility)
|
||||
ssh-keygen -t rsa -b 4096 -C "jane@example.com" -f ~/.ssh/id_rsa_legacy
|
||||
|
||||
# Add to agent
|
||||
# Generate a key with a custom comment and no passphrase (CI/CD use only)
|
||||
ssh-keygen -t ed25519 -C "ci-deploy-key" -f ~/.ssh/ci_deploy -N ""
|
||||
|
||||
# Copy public key to a remote server
|
||||
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@server
|
||||
|
||||
# Manually append a public key (when ssh-copy-id is unavailable)
|
||||
cat ~/.ssh/id_ed25519.pub | ssh user@server "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
|
||||
|
||||
# List fingerprints of keys on the agent
|
||||
ssh-add -l
|
||||
|
||||
# Start the SSH agent and add a key
|
||||
eval "$(ssh-agent -s)"
|
||||
ssh-add ~/.ssh/id_ed25519
|
||||
|
||||
# Add a key with a lifetime (auto-removed after 8 hours)
|
||||
ssh-add -t 28800 ~/.ssh/id_ed25519
|
||||
|
||||
# Remove all keys from the agent
|
||||
ssh-add -D
|
||||
|
||||
# Convert an OpenSSH key to PEM format (for tools that need it)
|
||||
ssh-keygen -p -m PEM -f ~/.ssh/id_rsa_legacy
|
||||
|
||||
# Show the public key fingerprint (SHA256)
|
||||
ssh-keygen -lf ~/.ssh/id_ed25519.pub
|
||||
|
||||
# Rotate a key: generate new, deploy, then revoke old
|
||||
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_new -C "jane@example.com rotated $(date +%Y-%m)"
|
||||
ssh-copy-id -i ~/.ssh/id_ed25519_new.pub user@server
|
||||
# After verifying the new key works, remove the old public key from authorized_keys on the server
|
||||
```
|
||||
|
||||
## SSH Config (~/.ssh/config)
|
||||
## SSH Client Configuration (~/.ssh/config)
|
||||
|
||||
```
|
||||
Host production
|
||||
HostName prod.example.com
|
||||
User deploy
|
||||
IdentityFile ~/.ssh/prod_key
|
||||
Port 22
|
||||
```text
|
||||
# Global defaults applied to all hosts
|
||||
Host *
|
||||
AddKeysToAgent yes
|
||||
IdentitiesOnly yes
|
||||
ServerAliveInterval 60
|
||||
ServerAliveCountMax 3
|
||||
TCPKeepAlive yes
|
||||
Compression yes
|
||||
|
||||
# Production servers via bastion
|
||||
Host bastion
|
||||
HostName bastion.example.com
|
||||
User admin
|
||||
|
||||
Host internal
|
||||
HostName 10.0.0.5
|
||||
User admin
|
||||
User ops
|
||||
IdentityFile ~/.ssh/id_ed25519
|
||||
Port 22
|
||||
|
||||
Host prod-web-*
|
||||
User deploy
|
||||
IdentityFile ~/.ssh/id_ed25519
|
||||
ProxyJump bastion
|
||||
Port 22
|
||||
|
||||
Host prod-web-1
|
||||
HostName 10.0.1.10
|
||||
|
||||
Host prod-web-2
|
||||
HostName 10.0.1.11
|
||||
|
||||
# Staging accessed directly
|
||||
Host staging
|
||||
HostName staging.example.com
|
||||
User deploy
|
||||
IdentityFile ~/.ssh/id_ed25519_staging
|
||||
|
||||
# Database tunnel through bastion
|
||||
Host db-tunnel
|
||||
HostName 10.0.2.50
|
||||
User dba
|
||||
ProxyJump bastion
|
||||
LocalForward 5432 localhost:5432
|
||||
|
||||
# GitHub deploy key
|
||||
Host github-deploy
|
||||
HostName github.com
|
||||
User git
|
||||
IdentityFile ~/.ssh/github_deploy_key
|
||||
IdentitiesOnly yes
|
||||
|
||||
# Connection multiplexing for faster repeated connections
|
||||
Host fast-*
|
||||
ControlMaster auto
|
||||
ControlPath ~/.ssh/sockets/%r@%h-%p
|
||||
ControlPersist 600
|
||||
```
|
||||
|
||||
## Secure Server Config
|
||||
```bash
|
||||
# Create the sockets directory for multiplexing
|
||||
mkdir -p ~/.ssh/sockets
|
||||
chmod 700 ~/.ssh/sockets
|
||||
```
|
||||
|
||||
## Hardened Server Configuration (/etc/ssh/sshd_config)
|
||||
|
||||
```bash
|
||||
# /etc/ssh/sshd_config
|
||||
# /etc/ssh/sshd_config -- hardened configuration
|
||||
# -----------------------------------------------
|
||||
|
||||
# Listen on a non-default port (obscurity, not security -- combine with firewall)
|
||||
Port 22
|
||||
|
||||
# Protocol and key exchange
|
||||
Protocol 2
|
||||
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512
|
||||
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com
|
||||
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
|
||||
|
||||
# Authentication
|
||||
PermitRootLogin no
|
||||
PasswordAuthentication no
|
||||
PubkeyAuthentication yes
|
||||
AuthenticationMethods publickey
|
||||
MaxAuthTries 3
|
||||
AllowUsers deploy admin
|
||||
```
|
||||
MaxSessions 5
|
||||
LoginGraceTime 30
|
||||
|
||||
## Tunneling
|
||||
# Restrict users and groups
|
||||
AllowGroups ssh-users ops-team
|
||||
# AllowUsers deploy admin
|
||||
|
||||
# Disable unused authentication methods
|
||||
ChallengeResponseAuthentication no
|
||||
KerberosAuthentication no
|
||||
GSSAPIAuthentication no
|
||||
|
||||
# Forwarding controls
|
||||
AllowTcpForwarding yes
|
||||
AllowAgentForwarding no
|
||||
X11Forwarding no
|
||||
PermitTunnel no
|
||||
|
||||
# Security hardening
|
||||
ClientAliveInterval 300
|
||||
ClientAliveCountMax 2
|
||||
UsePAM yes
|
||||
UseDNS no
|
||||
PermitEmptyPasswords no
|
||||
PermitUserEnvironment no
|
||||
|
||||
# Logging
|
||||
SyslogFacility AUTH
|
||||
LogLevel VERBOSE
|
||||
|
||||
# SFTP subsystem
|
||||
Subsystem sftp /usr/lib/openssh/sftp-server -f AUTH -l INFO
|
||||
|
||||
# Match block: restrict deploy user to SFTP only
|
||||
Match User sftponly
|
||||
ForceCommand internal-sftp
|
||||
ChrootDirectory /home/%u
|
||||
AllowTcpForwarding no
|
||||
AllowAgentForwarding no
|
||||
X11Forwarding no
|
||||
```
|
||||
|
||||
```bash
|
||||
# Local port forward
|
||||
ssh -L 8080:internal:80 bastion
|
||||
# Validate configuration before restarting
|
||||
sshd -t
|
||||
|
||||
# Remote port forward
|
||||
ssh -R 8080:localhost:80 server
|
||||
# Restart sshd to apply changes
|
||||
systemctl restart sshd
|
||||
|
||||
# SOCKS proxy
|
||||
ssh -D 1080 server
|
||||
# Always keep an existing session open while testing
|
||||
# Open a NEW terminal to verify you can still connect before closing the old one
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
## Bastion Host Setup
|
||||
|
||||
- Use ed25519 keys
|
||||
- Disable password auth
|
||||
- Use SSH agent forwarding carefully
|
||||
- Implement jump hosts/bastions
|
||||
```bash
|
||||
# On the bastion server, restrict forwarding to internal subnets only
|
||||
# /etc/ssh/sshd_config addition on bastion:
|
||||
AllowTcpForwarding yes
|
||||
PermitOpen 10.0.0.0/8:22 10.0.0.0/8:5432
|
||||
|
||||
# Disable shell access for jump-only users
|
||||
Match User jump-user
|
||||
PermitTTY no
|
||||
ForceCommand /usr/sbin/nologin
|
||||
AllowTcpForwarding yes
|
||||
|
||||
# Connect through the bastion from a client in one command
|
||||
ssh -J ops@bastion.example.com deploy@10.0.1.10
|
||||
|
||||
# Equivalent using ProxyCommand (older SSH versions)
|
||||
ssh -o ProxyCommand="ssh -W %h:%p ops@bastion.example.com" deploy@10.0.1.10
|
||||
|
||||
# Multi-hop: client -> bastion -> app-server -> db-server
|
||||
ssh -J ops@bastion,deploy@10.0.1.10 dba@10.0.2.50
|
||||
```
|
||||
|
||||
## SSH Tunneling
|
||||
|
||||
```bash
|
||||
# Local port forward: access remote service on localhost
|
||||
# Access remote PostgreSQL (10.0.2.50:5432) via bastion at localhost:5432
|
||||
ssh -L 5432:10.0.2.50:5432 ops@bastion.example.com -N
|
||||
|
||||
# Remote port forward: expose local service to the remote network
|
||||
# Make local dev server (localhost:3000) available on server port 8080
|
||||
ssh -R 8080:localhost:3000 user@server -N
|
||||
|
||||
# Dynamic SOCKS proxy: route all traffic through the server
|
||||
ssh -D 1080 user@server -N
|
||||
# Then configure browser or apps to use SOCKS5 proxy at localhost:1080
|
||||
|
||||
# Tunnel with a background process
|
||||
ssh -fN -L 5432:10.0.2.50:5432 ops@bastion.example.com
|
||||
# Find and kill the tunnel later
|
||||
ps aux | grep "ssh -fN" | grep -v grep
|
||||
kill <pid>
|
||||
|
||||
# Autossh for persistent tunnels (auto-reconnects)
|
||||
autossh -M 0 -f -N -L 5432:10.0.2.50:5432 ops@bastion.example.com \
|
||||
-o "ServerAliveInterval=30" -o "ServerAliveCountMax=3"
|
||||
```
|
||||
|
||||
## Agent Forwarding (Use with Caution)
|
||||
|
||||
```bash
|
||||
# Enable agent forwarding for a single connection
|
||||
ssh -A user@bastion
|
||||
|
||||
# From the bastion, your local keys are available to authenticate further
|
||||
ssh deploy@10.0.1.10 # Uses your local key via the agent
|
||||
|
||||
# SECURITY WARNING: Agent forwarding exposes your keys to anyone with root
|
||||
# on the intermediate host. Prefer ProxyJump instead.
|
||||
|
||||
# Safer alternative: ProxyJump does not expose the agent
|
||||
ssh -J ops@bastion deploy@10.0.1.10
|
||||
```
|
||||
|
||||
## SSH Key Restrictions in authorized_keys
|
||||
|
||||
```text
|
||||
# Restrict a key to a specific command only (backup key)
|
||||
command="/usr/local/bin/run-backup.sh",no-port-forwarding,no-X11-forwarding,no-agent-forwarding ssh-ed25519 AAAA... backup@example.com
|
||||
|
||||
# Restrict a key to specific source IPs
|
||||
from="10.0.0.0/24,192.168.1.0/24" ssh-ed25519 AAAA... admin@example.com
|
||||
|
||||
# Read-only SFTP key with chroot
|
||||
command="internal-sftp",no-port-forwarding,no-pty ssh-ed25519 AAAA... sftp-upload@example.com
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Symptom | Diagnostic Command | Common Fix |
|
||||
|---|---|---|
|
||||
| Connection refused | `ss -tlnp \| grep 22` on server | Ensure sshd is running; check firewall rules |
|
||||
| Permission denied (publickey) | `ssh -vvv user@server` | Verify key is in authorized_keys, permissions 600/700 |
|
||||
| Host key verification failed | `ssh-keygen -R server` | Remove stale host key; verify server identity |
|
||||
| Connection timeout | `ssh -o ConnectTimeout=5 user@server` | Check network path, security groups, NACLs |
|
||||
| Slow SSH login | Check `UseDNS` in sshd_config | Set `UseDNS no`; check reverse DNS |
|
||||
| Broken pipe / dropped sessions | Add `ServerAliveInterval 60` to config | Configure keepalive on both client and server |
|
||||
| Agent forwarding not working | `ssh-add -l` on bastion | Ensure `-A` flag used and agent has keys loaded |
|
||||
| Tunnel port already in use | `ss -tlnp \| grep <port>` | Kill existing tunnel or use a different local port |
|
||||
|
||||
## Related Skills
|
||||
|
||||
- `linux-administration` -- General Linux system administration
|
||||
- `user-management` -- Managing the users who connect via SSH
|
||||
- `systemd-services` -- Managing sshd as a systemd service
|
||||
- `performance-tuning` -- Network tuning for SSH performance
|
||||
|
||||
Reference in New Issue
Block a user