#!/usr/bin/env bash # # Relógio do SERVIDOR sincronizado com NTP autenticado (NTS). # Rodar UMA VEZ no servidor, como root: sudo bash deploy/ntp-seguro.sh # # ─── POR QUE NO HOST E NÃO NO CONTAINER ────────────────────────────────────── # Container não tem relógio próprio: ele lê o clock do kernel do host. Rodar # chrony/ntpd dentro do container exigiria CAP_SYS_TIME e mudaria a hora do # SERVIDOR INTEIRO a partir de dentro de um processo da aplicação — por isso # não se faz. Dentro do container a gente fixa só o FUSO (TZ, no Dockerfile); # a HORA vem daqui. # # ─── POR QUE NTS E NÃO NTP COMUM ───────────────────────────────────────────── # NTP puro é UDP sem autenticação: qualquer um no caminho pode responder no # lugar do servidor e empurrar a hora da máquina. Isso não é teórico para este # sistema — hora errada muda o dia da consolidação, o recorte do período # financeiro e a validade de sessão/cookie. NTS (RFC 8915) faz uma troca de # chaves por TLS (porta TCP 4460) e assina os pacotes NTP, então uma resposta # forjada é descartada. O `chronyc authdata` no fim mostra se está valendo. # # ─── REDE ──────────────────────────────────────────────────────────────────── # Precisa de saída para: UDP 123 (NTP) e TCP 4460 (NTS-KE). # Se o firewall bloquear a 4460, o NTS não fecha e o chrony fica sem fonte. set -euo pipefail [ "$(id -u)" -eq 0 ] || { echo "ERRO: rode com sudo."; exit 1; } command -v apt-get >/dev/null || { echo "ERRO: script escrito para Debian/Ubuntu."; exit 1; } echo "==> Hora ANTES:"; date; echo # ── 1. chrony ──────────────────────────────────────────────────────────────── # NTS só existe no chrony >= 4.0 (Debian 11+ / Ubuntu 22.04+ já têm). if ! command -v chronyd >/dev/null; then echo "==> Instalando chrony" apt-get update -qq DEBIAN_FRONTEND=noninteractive apt-get install -y chrony fi versao="$(chronyd --version 2>&1 | grep -oE '[0-9]+\.[0-9]+' | head -1)" if [ "${versao%%.*}" -lt 4 ]; then echo "ERRO: chrony $versao não fala NTS (precisa >= 4.0). Atualize o sistema." exit 1 fi echo "==> chrony $versao (NTS disponível)" # ── 2. Fontes NTS ──────────────────────────────────────────────────────────── # Três fontes independentes de propósito: com uma só, ela cai e o relógio fica # à deriva sem ninguém notar; com três, o chrony ainda descarta a que divergir. CONF_NTS="$(cat <<'CONF' # Gerado por deploy/ntp-seguro.sh — fontes de tempo AUTENTICADAS (NTS). # `iburst`: acerta rápido no boot, em vez de levar minutos. server time.cloudflare.com iburst nts server nts.netnod.se iburst nts server ptbtime1.ptb.de iburst nts # Cache das chaves NTS: sem isto, todo restart refaz a troca TLS e o relógio # demora a fechar quando a internet ainda não subiu. ntsdumpdir /var/lib/chrony # Sincroniza também o relógio de hardware (RTC), que é de onde a máquina lê a # hora ao ligar — é o que evita voltar errado depois de um reboot/queda de luz. rtcsync # makestep: corrige de uma vez (em vez de "escorregar" devagar) quando o erro # é grande, mas só nas 3 primeiras medições. É a linha certa para servidor # físico. SE ESTE SERVIDOR FOR VM QUE HIBERNA/PAUSA e a hora só aparece errada # depois de uma pausa longa, troque pela linha comentada abaixo — ela permite # o salto a qualquer momento (inclusive para trás, então saiba o que ganha): makestep 1.0 3 # makestep 1.0 -1 CONF )" CHRONY_CONF=/etc/chrony/chrony.conf [ -f "$CHRONY_CONF" ] || CHRONY_CONF=/etc/chrony.conf cp -n "$CHRONY_CONF" "$CHRONY_CONF.bak-ntp-seguro" 2>/dev/null || true # Onde escrever: o Debian/Ubuntu moderno lê o diretório conf.d. Se essa linha # não existir na config, escrever lá seria IGNORADO EM SILÊNCIO — nesse caso a # gente anexa no próprio chrony.conf, entre marcadores, para continuar sendo # possível rodar o script de novo sem duplicar. if grep -qE '^\s*confdir\s+.*conf\.d' "$CHRONY_CONF" 2>/dev/null; then mkdir -p /etc/chrony/conf.d printf '%s\n' "$CONF_NTS" > /etc/chrony/conf.d/nts-reem.conf echo "==> Fontes NTS em /etc/chrony/conf.d/nts-reem.conf" else sed -i '/# >>> nts-reem >>>/,/# <<< nts-reem <<>> nts-reem >>>"; printf '%s\n' "$CONF_NTS"; echo "# <<< nts-reem <<<"; } >> "$CHRONY_CONF" echo "==> Fontes NTS anexadas em $CHRONY_CONF" fi # ── 3. Desligar as fontes NÃO autenticadas ─────────────────────────────────── # Deixar o `pool` padrão junto anularia o ganho: o chrony poderia se decidir # por uma fonte sem assinatura. Só comentamos (o .bak fica ao lado). sed -i -E 's/^([[:space:]]*(pool|server)[[:space:]]+.*)$/# [nts-reem] \1/' "$CHRONY_CONF" sed -i -E '/# >>> nts-reem >>>/,/# <<< nts-reem <</dev/null || true systemctl enable --now chrony 2>/dev/null || systemctl enable --now chronyd echo "==> Reiniciando chrony" systemctl restart chrony 2>/dev/null || systemctl restart chronyd sleep 5 chronyc makestep >/dev/null 2>&1 || true # ── 5. Conferência ─────────────────────────────────────────────────────────── echo echo "==> Fontes (o '^*' marca a que está valendo):" chronyc -N sources -v || true echo echo "==> NTS (queremos 'KeyID/Type/KLen' preenchido e Cook > 0 nas 3 linhas):" chronyc -N authdata || true echo echo "==> Estado:"; chronyc tracking || true timedatectl || true echo echo "==> Hora DEPOIS:"; date cat <<'FIM' Pronto. O relógio agora se mantém sozinho, inclusive depois de reboot. Se a hora tinha saltado, reinicie os containers para os processos longos pegarem o clock corrigido: docker compose restart Para conferir depois, a qualquer momento: chronyc tracking # 'System time' deve ficar em milissegundos chronyc -N authdata # NTS ativo em cada fonte FIM