Dia 10: SSH no Linux — Chaves, Tunnels e Hardening

No Dia 8 configurámos redes Linux com ip, ss e nmcli; no Dia 9 protegemos essas redes com firewalls. Hoje, Dia 10, entramos no protocolo que torna a administração remota segura: SSH — o túnel encriptado que todo o sysadmin usa diariamente para gerir servidores, copiar ficheiros e saltar entre máquinas sem expor credenciais.

OpenSSH é o standard de facto para acesso remoto em Linux, macOS e BSD. Neste artigo configuras ssh_config e sshd_config, geras chaves ed25519 e RSA, dominas o ssh-agent, crias túneis de port forwarding (local, remoto e dinâmico), endureces o daemon com hardening e usas Match blocks para aplicar regras por utilizador, grupo ou endereço.

Neste artigo

Configuração Cliente e Servidor: ssh_config e sshd_config

OpenSSH separa configuração em dois ficheiros: ssh_config (cliente — lido quando tu te ligas a outro servidor) e sshd_config (daemon — lido quando outros se ligam a ti). Compreender esta distinção é a base de toda a configuração SSH.

ssh_config — o lado do cliente

O ficheiro global fica em /etc/ssh/ssh_config e o por-utilizador em ~/.ssh/config. Este último é onde defines aliases (Host blocks) para te ligares a servidores sem memorizar IPs, portas ou identidades.

# ~/.ssh/config — configuração personalizada do cliente

# Alias para servidor de produção
Host prod
    HostName 192.168.1.100
    User deploy
    Port 2222
    IdentityFile ~/.ssh/id_ed25519_prod
    ForwardAgent yes

# Alias para bastion (jump host)
Host bastion
    HostName bastion.exemplo.pt
    User admin
    IdentityFile ~/.ssh/id_ed25519_bastion

# Ligar a servidores internos via bastion
Host internal-*
    HostName %h.internal.exemplo.pt
    ProxyJump bastion
    User admin
    IdentityFile ~/.ssh/id_ed25519_internal

# Definições globais (aplicam-se a todos os Hosts)
Host *
    ServerAliveInterval 60
    ServerAliveCountMax 3
    AddKeysToAgent yes
    IdentitiesOnly yes

Com esta configuração, em vez de escrever ssh -p 2222 -i ~/.ssh/id_ed25519_prod [email protected], escreves apenas ssh prod. O ProxyJump (OpenSSH 7.3+) substitui o antigo ProxyCommand nc ... e é a forma moderna de saltar por bastion hosts.

sshd_config — o lado do servidor

O ficheiro /etc/ssh/sshd_config controla o comportamento do daemon sshd. Depois de editar, valida a sintaxe antes de reiniciar:

# Validar sintaxe do sshd_config (não reinicia o serviço)
sudo sshd -t

# Se não houver erros, reinicia o serviço
sudo systemctl restart sshd
# Em sistemas Red Hat / Fedora:
sudo systemctl restart sshd

# Verificar configuração efectiva (lista todas as directivas)
sudo sshd -T | head -30
ⓘ Dica: O comando sshd -T mostra a configuração efectiva (após processar ficheiros incluídos e defaults), não apenas o que está no ficheiro. É a forma mais fiável de confirmar o que o daemon está realmente a usar.

Em Debian/Ubuntu o serviço chama-se ssh ou ssh.service; em RHEL/Fedora/CentOS chama-se sshd ou sshd.service. O binário é sempre sshd, independentemente da distribuição.

Directiva Ficheiro Descrição
Host ssh_config Define alias e padrão de correspondência
HostName ssh_config IP ou FQDN real do servidor
IdentityFile ssh_config Caminho da chave privada a usar
ProxyJump ssh_config Bastion/jump host para chegar ao destino
Port Ambos Porta TCP (default 22)
PermitRootLogin sshd_config Controla login directo como root

Chaves SSH: ed25519 vs RSA

As chaves SSH substituem palavras-passe por criptografia assimétrica: tens uma chave privada (que nunca sai da tua máquina) e uma chave pública (que colocas no servidor em ~/.ssh/authorized_keys). O servidor verifica que tens a chave privada correspondente sem que ela alguma vez viaje pela rede.

Gerar chaves com ssh-keygen

# ed25519 — recomendado (curva elliptic, mais rápido e seguro)
ssh-keygen -t ed25519 -C "admin@servidor-prod-2026"

# RSA 4096 — para sistemas legados sem suporte ed25519
ssh-keygen -t rsa -b 4096 -C "admin@servidor-legado"

# ed25519 com ficheiro de chave personalizado
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_prod -C "deploy@prod"

# Verificar fingerprint de uma chave existente
ssh-keygen -lf ~/.ssh/id_ed25519
# Output: 256 SHA256:xxxxx admin@servidor-prod-2026 (ED25519)

# Verificar fingerprint em formato Base64 (para comparison)
ssh-keygen -E base64 -lf ~/.ssh/id_ed25519

Comparação: ed25519 vs RSA

Característica ed25519 RSA 4096
Tamanho da chave 256 bits (muito pequena) 4096 bits (ficheiro grande)
Velocidade de assinatura Rápida (curva elliptic) Lenta (modular exponentiation)
Segurança equivalente ~128 bits de segurança ~128 bits (RSA-3072) a 256 bits (RSA-4096)
Compatibilidade OpenSSH 6.5+ (2014) Universal (todos os servidores)
Recomendação Sim — default moderno Apenas se ed25519 não for suportado
ⓘ Recomendação: Usa ed25519 em todos os servidores modernos (OpenSSH 6.5+, lançado em 2014). Reserva RSA 4096 apenas para sistemas legados que não suportem ed25519. Nunca uses RSA com menos de 2048 bits nem DSA (depreciado desde OpenSSH 7.0).

Copiar chaves públicas para o servidor

# Método 1: ssh-copy-id (recomendado)
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@servidor

# Método 2: manual (quando ssh-copy-id não está disponível)
cat ~/.ssh/id_ed25519.pub | ssh user@servidor "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"

# Copiar para um servidor com porta non-standard
ssh-copy-id -i ~/.ssh/id_ed25519.pub -p 2222 user@servidor

ssh-agent e ssh-keygen: Gestão de Chaves

O ssh-agent é um daemon que mantém as chaves privadas decifradas em memória, evitando que tenhas de introduzir a passphrase a cada ligação. Em ambientes de trabalho com múltiplas chaves (produção, staging, bastion), é indispensável.

# Iniciar ssh-agent (se não estiver a correr)
eval "$(ssh-agent -s)"
# Output: Agent pid 12345

# Adicionar chave ao agent (pede passphrase se a chave tiver)
ssh-add ~/.ssh/id_ed25519

# Listar chaves carregadas no agent
ssh-add -l
# Output: 256 SHA256:xxxxx admin@servidor-prod-2026 (ED25519)

# Remover todas as chaves do agent
ssh-add -D

# Remover chave específica
ssh-add -d ~/.ssh/id_ed25519

# Em systemd, usar keychain para persistência entre sessões:
# Debian/Ubuntu:
sudo apt install keychain
# Fedora/RHEL:
sudo dnf install keychain

# Adicionar ao ~/.bashrc ou ~/.zshrc:
eval "$(keychain --eval --quiet ~/.ssh/id_ed25519)"
⚠️ Atenção com ForwardAgent: A directiva ForwardAgent yes permite que o servidor remoto use as tuas chaves locais para saltar para um terceiro servidor. É conveniente para ProxyJump, mas se o servidor remoto estiver comprometido, o atacante pode usar o teu agent para se ligar a outros sistemas. Usa apenas em servidores de confiança.

Port Forwarding: Local, Remote e Dynamic

O port forwarding do SSH permite criar túneis encriptados que redirecionam tráfego TCP através da ligação SSH. Existem três tipos: local (porta local → servidor remoto), remoto (porta no servidor → de volta para ti) e dinâmico (proxy SOCKS).

Local Port Forwarding (-L)

Encaminha uma porta da tua máquina local para um servidor acessível a partir do servidor SSH remoto. Útil para aceder a serviços internos (bases de dados, painéis web) que só escutam em localhost.

# Encaminhar porta local 8080 para o servidor interno 3306 (MySQL)
# Sintaxe: -L porta_local:host_remoto:porta_remota
ssh -L 8080:localhost:3306 user@servidor-remoto

# Aceder a um painel web interno que só escuta em localhost:8080 no servidor
ssh -L 9090:localhost:8080 user@servidor-remoto
# Depois abre no browser: http://localhost:9090

# Múltiplos forwards numa só ligação
ssh -L 3306:db.interno.pt:3306 -L 6379:redis.interno.pt:6379 user@bastion

# Forward em background (-f) sem executar comando remoto (-N)
ssh -f -N -L 5432:db.interno.pt:5432 user@bastion

Remote Port Forwarding (-R)

O inverso do local: encaminha uma porta no servidor remoto de volta para a tua máquina. Usado para expor um serviço local a uma rede remota, ou para aceder a um servidor atrás de NAT.

# Expor o teu servidor web local (porta 80) na porta 8080 do servidor remoto
ssh -R 8080:localhost:80 user@servidor-remoto

# Aceder a uma máquina atrás de NAT: a máquina atrás de NAT liga-se ao servidor público
ssh -R 2222:localhost:22 user@servidor-publico
# Agora no servidor público: ssh -p 2222 user@localhost liga à máquina atrás de NAT

# Reverse tunnel em background
ssh -f -N -R 9090:localhost:9090 user@servidor-remoto

Dynamic Port Forwarding (-D) — SOCKS Proxy

Cria um proxy SOCKS5 dinâmico. Qualquer aplicação que suporte SOCKS5 (browser, curl) pode encaminhar tráfego através do túnel SSH, permitindo navegação como se estivesses na rede do servidor remoto.

# Criar proxy SOCKS5 na porta 1080
ssh -D 1080 user@servidor-remoto

# Proxy SOCKS5 em background
ssh -f -N -D 1080 user@servidor-remoto

# Usar com curl
curl --socks5 localhost:1080 http://interno.exemplo.pt/

# Configurar Firefox para usar o proxy SOCKS5:
# Preferences > Network Settings > Manual proxy configuration
# SOCKS Host: localhost, Port: 1080, SOCKS v5
Tipo Flag Direção Caso de uso
Local -L Local → Remoto Aceder BD interna, painel web
Remote -R Remoto → Local Atravessar NAT, expor serviço local
Dynamic -D Bidireccional (SOCKS) Proxy web, navegação segura

Túneis SSH e Bastion Host

Um bastion host (ou jump host) é um servidor exposto à internet que serve como único ponto de entrada para uma rede interna. Em vez de expor todos os servidores ao mundo, apenas o bastion tem porta SSH aberta; os servidores internos só aceitam ligações do bastion. O OpenSSH 7.3+ introduziu ProxyJump (-J), que simplifica muito este padrão.

# ProxyJump na linha de comando (-J)
ssh -J [email protected] [email protected]

# ProxyJump com porta e utilizador diferentes no bastion
ssh -J [email protected]:2222 [email protected]

# Múltiplos saltos (chain de bastions)
ssh -J [email protected],[email protected] [email protected]

# Com ProxyJump definido no ~/.ssh/config (recomendado):
# Ver secção ssh_config acima — Host internal-* usa ProxyJump bastion
ssh internal-db
# Liga automaticamente: tu → bastion → db.interno.pt

No servidor interno, restringe o acesso para apenas aceitar ligações do bastion. No sshd_config dos servidores internos:

# /etc/ssh/sshd_config no servidor INTERNO
# Aceitar ligações apenas do IP do bastion
AllowUsers admin
ListenAddress 10.0.0.5

# Combinar com firewall (Dia 9):
# iptables: aceitar SSH apenas do bastion (10.0.0.1)
sudo iptables -A INPUT -p tcp --dport 22 -s 10.0.0.1 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 22 -j DROP

# nftables equivalente:
sudo nft add rule inet filter input tcp dport 22 ip saddr 10.0.0.1 accept
sudo nft add rule inet filter input tcp dport 22 drop

# UFW equivalente:
sudo ufw allow from 10.0.0.1 to any port 22
sudo ufw deny 22
ⓘ ProxyJump vs ProxyCommand: ProxyJump é o método moderno (OpenSSH 7.3+, 2016). O ProxyCommand nc %h %p ainda funciona mas é mais verboso e requer netcat instalado no bastion.

Hardening de sshd

O hardening de SSH é a diferença entre um servidor exposto e um servidor que resist a ataques automatizados. Estas directivas aplicam-se em /etc/ssh/sshd_config e reduzem drasticamente a superfície de ataque.

# /etc/ssh/sshd_config — hardening base

# 1. Desactivar login directo como root
# (forçar uso de sudo após login como utilizador normal)
PermitRootLogin no

# 2. Desactivar autenticação por palavra-passe
# (forçar chaves SSH)
PasswordAuthentication no

# 3. Desactivar autenticação PAM (reduz superfície)
# Em servidores com apenas chaves SSH:
UsePAM no
# Nota: em sistemas com MFA ou policies complexas, manter UsePAM yes

# 4. Restringir utilizadores que podem ligar-se
AllowUsers admin deploy
# ou por grupo:
AllowGroups ssh-users

# 5. Limitar tentativas de autenticação
MaxAuthTries 3

# 6. Timeout de login (segundos)
LoginGraceTime 30

# 7. Desactivar X11 forwarding se não for necessário
X11Forwarding no

# 8. Desactivar tunneling de dispositivos
PermitTunnel no

# 9. Alterar porta default (security through obscurity — reduz bot scans)
Port 2222
# Nota: combinar com firewall que bloqueie porta 22

# 10. Desactivar autenticação vazia
PermitEmptyPasswords no

# 11. Limite de sessões concorrentes
MaxSessions 10
MaxStartups 10:30:60

# 12. Logging verboso para auditoria
LogLevel VERBOSE

# 13. Desactivar protocolos legados
Protocol 2
# (Protocol 1 está depreciado desde OpenSSH 7.6 — já é default)
⚠️ Ordem de aplicação crítica: Antes de definir PasswordAuthentication no, garante que as chaves SSH estão configuradas e testadas. Se desactivares passwords antes de testar chaves, podes perder acesso ao servidor. Aplica uma directiva de cada vez, testa a ligação numa sessão separada e só depois reinicia o sshd.

fail2ban — protecção contra brute force

Mesmo com chaves SSH, os bots continuam a tentar login por palavra-passe. O fail2ban monitoriza logs de SSH e bloqueia IPs após tentativas falhadas (ver artigo fail2ban: Proteger SSH contra Brute Force no kbase.pt).

# Debian/Ubuntu:
sudo apt install fail2ban
# Fedora/RHEL:
sudo dnf install fail2ban-fail2ban

# Configuração base: /etc/fail2ban/jail.local
[sshd]
enabled = true
port = 2222
maxretry = 3
bantime = 3600
findtime = 600

# Verificar jails activos
sudo fail2ban-client status
sudo fail2ban-client status sshd

# Desbanir um IP
sudo fail2ban-client set sshd unbanip 192.168.1.50

Match Blocks: Regras Condicionais

Os Match blocks permitem aplicar directivas diferentes consoante o utilizador, grupo, endereço de origem ou porta. São a forma de ter políticas granulares sem múltiplos ficheiros de configuração. Tudo o que aparece antes do primeiro Match é o global; tudo o que está dentro de um Match sobrepõe-se ao global apenas para essa condição.

# /etc/ssh/sshd_config — Match blocks

# === Configuração global (aplica-se a todos) ===
PermitRootLogin no
PasswordAuthentication no
MaxAuthTries 3

# === Match por utilizador ===
# Permitir root apenas da rede interna (VPN/office)
Match User root Address 10.0.0.0/8,192.168.0.0/16
    PermitRootLogin yes
    PasswordAuthentication yes

# === Match por grupo ===
# Grupo "sftp-only" só tem SFTP, sem shell
Match Group sftp-only
    ChrootDirectory /sftp/%u
    ForceCommand internal-sftp
    AllowTcpForwarding no
    X11Forwarding no

# === Match por endereço ===
# Redes externas: apenas chaves, sem port forwarding
Match Address *,!10.0.0.0/8,!192.168.0.0/16
    PasswordAuthentication no
    AllowTcpForwarding no
    X11Forwarding no
    PermitTunnel no

# === Match por porta ===
# Porta 2222: acesso administrativo completo
Match LocalPort 2222
    AllowUsers admin
    PermitRootLogin no

# === Reverter ao global ===
# "Match all" explícito no fim (opcional, para clareza)
# Match all
# (directivas após isto voltam ao escopo global)

Os critérios de Match suportados pelo OpenSSH:

Critério Descrição Exemplo
Match User Utilizador específico Match User root
Match Group Membros de um grupo Match Group sftp-only
Match Address IP/CIDR de origem Match Address 10.0.0.0/8
Match LocalPort Porta de escuta Match LocalPort 2222
Match all Todas as condições (reset) Match all

Transferência de Ficheiros: SFTP vs SCP vs rsync

SSH oferece três protocolos para transferir ficheiros: SFTP (FTP sobre SSH), SCP (copia segura) e rsync over SSH. Cada um tem um caso de uso ideal.

SCP — cópia simples e rápida

# Copiar ficheiro local para servidor remoto
scp relatorio.pdf user@servidor:/tmp/

# Copiar ficheiro remoto para máquina local
scp user@servidor:/var/log/syslog ./

# Copiar directoria inteira (-r recursivo)
scp -r projecto/ user@servidor:/home/user/

# Usar porta non-standard (-P maiúsculo, ao contrário de ssh -p)
scp -P 2222 ficheiro.txt user@servidor:/tmp/

# Copiar entre dois servidores remotos (via máquina local)
scp user1@srv1:/dados/backup.tar.gz user2@srv2:/backups/

# Modo SFTP (OpenSSH 9.0+ usa SFTP como backend por default)
scp -s ficheiro.txt user@servidor:/tmp/

SFTP — sessão interativa

# Ligar via SFTP
sftp user@servidor
sftp> ls
sftp> cd /var/www
sftp> put relatorio.pdf
sftp> get syslog /tmp/
sftp> pwd
sftp> quit

# Transferência não-interativa (batch)
sftp -b <<'EOF' user@servidor
cd /var/backups
put backup-diario.tar.gz
ls -la
bye
EOF

# Chroot SFTP (sftp-only sem shell):
# Ver Match Group sftp-only na secção Match Blocks acima

rsync over SSH — sincronização incremental

# Sincronizar directoria local para remoto (incremental)
rsync -avz projecto/ user@servidor:/home/user/projecto/

# Sincronizar remoto para local
rsync -avz user@servidor:/var/log/ ./logs-remotos/

# -a: archive mode (preserva permissões, symlinks, timestamps)
# -v: verbose
# -z: compressão durante transferência
# --progress: mostrar progresso por ficheiro
# --delete: apagar no destino o que já não existe na origem

# Backup com exclusões
rsync -avz --delete \
    --exclude='*.tmp' --exclude='.git/' --exclude='node_modules/' \
    /home/user/projecto/ user@backup:/backups/projecto/

# Dry-run (simular sem executar — sempre antes de --delete)
rsync -avz --dry-run --delete projecto/ user@servidor:/backup/

# Usar porta e chave SSH específicas
rsync -avz -e "ssh -p 2222 -i ~/.ssh/id_ed25519" \
    dados/ user@servidor:/backups/dados/

# Limitar largura de banda (útil em ligações lentas)
rsync -avz --bwlimit=1000 dados/ user@servidor:/backup/
# 1000 = 1000 KB/s = ~1 MB/s
Ferramenta Melhor para Incremental Resume
SCP Cópia rápida de 1-2 ficheiros Não Não
SFTP Sessão interativa, chroot Não Parcial
rsync Sincronização, backups, directorias grandes Sim Sim

Erros Comuns

Problema Causa Solução
Permission denied (publickey) Chave privada não carregada no agent ou permissões erradas em ~/.ssh chmod 700 ~/.ssh && chmod 600 ~/.ssh/id_* e ssh-add
Connection refused sshd não está a correr ou firewall bloqueia sudo systemctl status sshd e verificar firewall (Dia 9)
Host key verification failed O servidor mudou a host key (reinstall, novo IP) Remover entrada antiga: ssh-keygen -R servidor
Bad permissions em authorized_keys Permissões demasiado abertas (group/world read) chmod 600 ~/.ssh/authorized_keys && chmod 700 ~/.ssh
sshd -t retorna erro mas ficheiro parece correcto Directiva Match mal posicionada — directivas globais após Match são interpretadas como parte do bloco Todas as directivas globais devem estar antes do primeiro Match
Ligação SSH lenta (demora 30s+ para pedir palavra-passe) DNS reverse lookup activo no sshd Adicionar UseDNS no ao sshd_config

Checklist de Configuração SSH

Aplica estes passos pela ordem indicada para endurecer qualquer servidor SSH novo. Cada passo tem um comando de verificação.

  1. Gerar chaves ed25519 no cliente: ssh-keygen -t ed25519 -C "user@host"
  2. Copiar chave pública para o servidor: ssh-copy-id -i ~/.ssh/id_ed25519.pub user@servidor
  3. Testar login com chave (sem palavra-passe): ssh -i ~/.ssh/id_ed25519 user@servidor
  4. Configurar aliases no cliente: criar ~/.ssh/config com Host blocks (ver secção ssh_config)
  5. Aplicar hardening no sshd_config: PermitRootLogin no, PasswordAuthentication no, MaxAuthTries 3
  6. Validar sintaxe antes de reiniciar: sudo sshd -t — se não output = sem erros
  7. Reiniciar sshd: sudo systemctl restart sshd (ou ssh em Debian/Ubuntu)
  8. Verificar permissões: ls -la ~/.ssh/ — chaves a 600, directoria a 700
  9. Instalar fail2ban: sudo apt install fail2ban (Debian) ou sudo dnf install fail2ban (Fedora)
  10. Verificação final: sudo sshd -T | grep -E "permitroot|passwordauth|maxauth" + ssh user@servidor "echo OK"
⚠️ Comandos críticos: PermitRootLogin no sem utilizador sudo configurado, PasswordAuthentication no sem chaves testadas, e AllowUsers mal escrito podem bloquear acesso total ao servidor. Testar sempre numa sessão SSH separada antes de fechar a sessão de configuração — nunca fechar a sessão activa até confirmar que a nova configuração funciona.

Artigos Relacionados

No Dia 11 entraremos no armazenamento Linux — partições, LVM, filesystems (ext4, xfs, btrfs) e /etc/fstab — onde os conhecimentos de sistema de ficheiros do Dia 2 se expandem para gestão de discos físicos e lógicos.