Bootstrapping idempotente no Ubuntu 24.04 via cURL | Bash: stdin /dev/tty, senhas efêmeras e SSH modular
Voltar para blog

Bootstrapping idempotente no Ubuntu 24.04 via cURL | Bash: stdin /dev/tty, senhas efêmeras e SSH modular

21/09/2026 · 3 min · DevOps

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:

  1. Atualizar o sistema sem intervenção (DEBIAN_FRONTEND=noninteractive).
  2. Gerar uma senha forte e aleatória em tempo de execução para o root, sem credenciais fixas no repositório.
  3. Instalar pacotes essenciais e o agente de monitoramento da infraestrutura.
  4. Liberar temporariamente o login por senha para o root no SSH para viabilizar a transferência das chaves públicas do desenvolvedor.
  5. Pausar e aguardar a validação interativa do operador, oferecendo opções claras de continuar, abortar ou reiniciar.
  6. Reverter o SSH para o estado seguro (bloqueando autenticação por senha para root e reativando as inclusões modulares).
  7. Inserir regras no topo do iptables com 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:

  1. Comentamos a diretiva Include e ativamos PermitRootLogin yes.
  2. Reiniciamos o daemon ssh.
  3. Após a transferência e validação da chave em /root/.ssh/authorized_keys, restauramos o backup original e reativamos o Include.

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:

Este artigo foi útil?

Deixe uma reação rápida para apoiar o conteúdo:

CC BY-NC

Este post está licenciado sob CC BY-NC.

Comentários

Participe da discussão abaixo.

0 comentários