Bootstrapping Idempotente no Ubuntu 24.04 via cURL | Bash: stdin /dev/tty, senhas efêmeras e SSH modular#
Automatizar a configuração inicial de servidores em nuvem exige mais do que apenas encadear comandos em um arquivo shell: exige entender como o interpretador gerencia descritores de arquivos quando executado via pipe (curl | bash), como evitar bloqueios no pool de entropia do kernel e como lidar com a nova arquitetura de configurações modulares do SSH no Ubuntu 24.04 LTS (Noble Numbat).
Neste artigo, documento o desenvolvimento de um script de inicialização remota robusto, detalhando as armadilhas comuns de entrada/saída no terminal e o fluxo para provisionar uma máquina do zero com total segurança.
1. O objetivo do bootstrapping remoto#
A proposta era disponibilizar um script remoto invocado diretamente na máquina recém-criada via:
curl -fsSL https://domain.com.br/bootstrap.sh | bash
O script deve cumprir os seguintes requisitos:
- Atualizar o sistema sem intervenção (
DEBIAN_FRONTEND=noninteractive). - Gerar uma senha forte e aleatória em tempo de execução para o
root, sem credenciais fixas no repositório. - Instalar pacotes essenciais e o agente de monitoramento da infraestrutura.
- Liberar temporariamente o login por senha para o
rootno SSH para viabilizar a transferência das chaves públicas do desenvolvedor. - Pausar e aguardar a validação interativa do operador, oferecendo opções claras de continuar, abortar ou reiniciar.
- Reverter o SSH para o estado seguro (bloqueando autenticação por senha para root e reativando as inclusões modulares).
- Inserir regras no topo do
iptablescom persistência automática.
2. Desafios técnicos e decisões de implementação#
A. O conflito de stdin no curl | bash#
Quando você executa um script via pipe (curl ... | bash), o descritor de arquivo padrão stdin (file descriptor 0) do shell fica associado ao fluxo de saída do comando curl. Se o script contiver um comando read convencional, ele tentará ler a próxima linha do próprio script (que já foi consumida pelo pipe), pulando a pausa e falhando silenciosamente.
A solução: Forçar o read a ler diretamente do terminal de controle do usuário (/dev/tty):
read -p "Opção: " opt < /dev/tty
Isso garante que o script pause e aguarde a interação física no teclado, independentemente de ter sido alimentado via pipe.
B. Geração de entropia sem bloqueio (/dev/urandom)#
Em máquinas virtuais recém-provisionadas, o pool de entropia do kernel ainda não acumulou ruído suficiente de hardware. Utilizar /dev/random pode bloquear a execução indefinidamente até que o sistema colete eventos de I/O.
A solução segura e não bloqueante é ler do /dev/urandom e filtrar com base64 e tr:
NEW_ROOT_PASSWORD=$(head -c 12 /dev/urandom | base64 | tr -dc 'A-Za-z0-9' | head -c 16)
C. Estrutura modular do SSH no Ubuntu 24.04#
O Ubuntu 24.04 adota arquivos modulares em /etc/ssh/sshd_config.d/*.conf (como 50-cloud-init.conf), que podem sobrescrever as diretivas globais de /etc/ssh/sshd_config.
Para garantir que a liberação temporária de senha funcione durante o bootstrapping:
- Comentamos a diretiva
Includee ativamosPermitRootLogin yes. - Reiniciamos o daemon
ssh. - Após a transferência e validação da chave em
/root/.ssh/authorized_keys, restauramos o backup original e reativamos oInclude.
3. O script completo de bootstrapping#
Abaixo está o script pronto para produção:
#!/bin/bash
set -eo pipefail
# --- 1. Geração de Credenciais Efêmeras e Constantes ---
NEW_ROOT_PASSWORD=$(head -c 12 /dev/urandom | base64 | tr -dc 'A-Za-z0-9' | head -c 16)
AGENT_URL="https://domain.com.br/downloads/agent-service_1.0-1_all.deb"
AGENT_DEB="agent-service_1.0-1_all.deb"
# Validação de privilégios
if [[ $EUID -ne 0 ]]; then
echo "ERRO: Este script deve ser executado como root."
exit 1
fi
echo "[1/8] Definindo senha efêmera de root e atualizando sistema..."
echo "root:$NEW_ROOT_PASSWORD" | chpasswd
export DEBIAN_FRONTEND=noninteractive
apt-get update && apt-get upgrade -y
apt-get install -y vim wget iptables-persistent
echo "[2/8] Instalando agente de monitoramento..."
if wget -q -O "$AGENT_DEB" "$AGENT_URL"; then
apt-get install -y ./"$AGENT_DEB" || dpkg -i "$AGENT_DEB"
rm -f "$AGENT_DEB"
fi
echo "[3/8] Coletando endereço IP público..."
PUBLIC_IP=$(curl -s --connect-timeout 5 ifconfig.me || hostname -I | awk '{print $1}')
echo "[4/8] Habilitando temporariamente SSH para transferência de chaves..."
cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
sed -i 's/^Include \/etc\/ssh\/sshd_config.d\/\*.conf/#&/' /etc/ssh/sshd_config
sed -i 's/^#*PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config
sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config
systemctl restart ssh
echo "[5/8] Aguardando transferência de chaves autorizadas..."
while true; do
echo "========================================================"
echo "IP PÚBLICO: $PUBLIC_IP"
echo "SENHA TEMPORÁRIA: $NEW_ROOT_PASSWORD"
echo "Transfira sua chave: ssh-copy-id root@$PUBLIC_IP"
echo "Opções: [c]ontinuar | [a]bortar | [r]einiciar"
echo "========================================================"
# Leitura forçada do terminal de controle
read -p "Ação desejada: " opt < /dev/tty
case $opt in
[Cc]* )
if [ -s /root/.ssh/authorized_keys ]; then
echo "✓ Chave SSH detectada com sucesso em authorized_keys."
break
else
echo "AVISO: /root/.ssh/authorized_keys está vazio ou inexistente."
echo "Envie sua chave pública antes de prosseguir."
fi
;;
[Aa]* )
echo "Abortando. Revertendo SSH para o estado de segurança..."
mv /etc/ssh/sshd_config.bak /etc/ssh/sshd_config
systemctl restart ssh
exit 1
;;
[Rr]* )
echo "Limpando pasta .ssh para nova tentativa..."
rm -rf /root/.ssh && mkdir -p /root/.ssh && chmod 700 /root/.ssh
;;
* )
echo "Opção inválida. Digite 'c', 'a' ou 'r'."
;;
esac
done
echo "[6/8] Hardening do SSH: desativando senhas e restaurando configuração..."
mv /etc/ssh/sshd_config.bak /etc/ssh/sshd_config
systemctl restart ssh
echo "[7/8] Configurando regras de firewall..."
# Inserir no topo da chain para precedência sobre regras de DROP
iptables -I INPUT 1 -p tcp --dport 8080 -j ACCEPT
netfilter-persistent save
echo "[8/8] Provisionamento concluído com sucesso."
Considerações práticas de automação#
Ao desenhar automações em pipeline via shell script no Linux:
- Controle o descritor de terminal: Em pipelines
curl | bash, ostdinpadrão aponta para o stream de rede. Use sempre< /dev/ttyquando for necessário pausar para confirmação manual. - Nunca use senhas fixas: O
/dev/urandomgarante entropia imediata sem o risco de deadlocks no boot de VMs recém-nascidas. - Respeite a ordem de avaliação do Netfilter: Sempre insira regras no topo com
-I INPUT 1e garanta a persistência comnetfilter-persistent. - Gerencie processos órfãos: Caso serviços ou agentes de background fiquem pendurados após a instalação, consulte nosso guia sobre como matar árvores de processos complexas com pkill.
Este artigo foi útil?
Deixe uma reação rápida para apoiar o conteúdo:
Este post está licenciado sob CC BY-NC.



Comentários
Participe da discussão abaixo.
0 comentários