implant alerts
Envia avisos de eventos graves (ban permanente, seção do threat-status em
crit, lockdown, fallback do firewall no boot, feed recusado…) para um
Telegram ou um webhook HTTPS. Quem configura é o operador do próprio
servidor: canais e eventos no config.toml, credenciais num arquivo separado,
0600 root, que nunca aparece em log, saída ou JSON (v2.61.0+).
Uso
implant alerts <SUBCOMANDO> [OPTIONS]# canal Telegram: o token entra pelo stdin (ou --token-env VAR), nunca em argumento
sudo implant alerts channel add telegram --name ops --chat-id -1001234567890 --token-stdin
# canal webhook: POST HTTPS com corpo JSON; a URL é credencial (stdin ou variável)
sudo implant alerts channel add webhook --name siem --url-env SIEM_URL [--auth-env SIEM_AUTH]
sudo implant alerts subscribe --channel ops --on ban_permanent,threat_crit # sem --on: todos
sudo implant alerts test --channel ops # mensagem de teste
implant alerts status # config, credenciais (sem valores), últimos envios
implant alerts channel list
sudo implant alerts unsubscribe --channel ops --on threat_crit # sem --on: todas do canal
sudo implant alerts channel remove ops # leva credenciais e subscrições juntoSubcomandos
| Subcomando | O que faz |
|---|---|
status |
Config efetiva, estado das credenciais de cada canal (sem valores) e os últimos envios/falhas |
channel add telegram |
--name, --chat-id e o token por --token-stdin ou --token-env <VAR> |
channel add webhook |
--name, URL por --url-stdin ou --url-env <VAR>, header Authorization opcional por --auth-env <VAR> |
channel remove <nome> |
Remove o canal, as credenciais dele e as subscrições que apontam para ele |
channel list |
Canais, tipo, chat_id, situação das credenciais e eventos assinados |
subscribe |
--channel e --on <evento,…> (sem --on: todos); --template opcional |
unsubscribe |
--channel e --on (sem --on: todas as subscrições do canal) |
test --channel <nome> |
Envia uma mensagem de teste; sai ≠ 0 se o envio falhou |
channel addcom um nome que já existe recusa;--replacetroca o canal e as credenciais (rotação de token).- Credencial nunca entra por argumento de linha de comando (ficaria no histórico do shell e na lista de processos).
--dry-runvale em todos: valida e mostra o que faria, sem gravar nem enviar.- A edição do
config.tomlpreserva comentários e o resto do arquivo.
Onde fica cada coisa
| O quê | Onde | Permissão |
|---|---|---|
| Canais e subscrições | [alerts] do config.toml (/etc/protocolo-implant/config.toml) |
a do arquivo |
Credenciais (bot_token, url, authorization) |
alerts.secrets, ao lado do config.toml |
0600, dono root |
| Dedupe e teto por hora | /var/lib/implant/alerts-state.json |
0600 |
| Registro de envios e falhas | /var/lib/implant/alerts-events.jsonl |
— |
O alerts.secrets só é lido se for arquivo regular (não symlink), do usuário
que roda o implant e sem nenhum acesso para grupo/outros. Fora disso nada é
lido nem enviado, e a mensagem diz o que corrigir (chmod 600 / chown root:root). Credencial posta no config.toml (bot_token, url…) é recusada
com a indicação do lugar certo. Um [alerts] inválido desliga só os alertas:
os outros comandos não leem a seção.
Configuração
[alerts]
enabled = true # false silencia sem apagar nada
timeout_seconds = 5 # prazo de cada envio (1..30)
dedupe_window_seconds = 3600 # mesmo evento + mesmo alvo não repete nesta janela
max_per_hour = 20 # teto de alertas por hora
[alerts.channels.ops]
type = "telegram"
chat_id = "-1001234567890"
[alerts.channels.siem]
type = "webhook"
[[alerts.notify]]
on = ["ban_permanent", "threat_crit"]
channel = "ops"
template = "[{{::severity}}] {{::hostname}}: {{::title}}\n{{::detail}}" # opcionalO channel add e o subscribe escrevem essas seções; editar à mão também
vale.
Eventos
| Evento | Quando | Alvo (target) |
|---|---|---|
ban_permanent |
O auto-ban aplica um ban permanente | o IP |
threat_crit |
Uma seção do threat-status entra em crit (só com gravação do snapshot, isto é, sem --no-record) |
a seção |
lockdown_created |
lockdown create ou ssh-service disable |
o serviço |
emergency_mode_enabled |
emergency-mode enable desligou alguma feature |
as features |
firewall_boot_fallback |
Sem confirmação após o boot, o firewall-restore esvaziou os sets de bloqueio | boot |
firewall_install_reverted |
Instalação do firewall-restore desfeita por falta de confirmação | install |
feed_kept_previous |
O feed-block recusou o conteúdo novo e manteve o anterior | feed-block |
traefik_redeploy_failed |
O redeploy do Traefik pedido pelo traefik-accesslog --enable ao orquestrador falhou (warn com volta ao anterior, crit sem volta) |
o Traefik |
feed_refused |
O feed-block recusou o feed sem conteúdo anterior (nada aplicado) | feed-block |
O threat_crit só sai quando alguém roda implant threat-status (o comando
não tem timer próprio): quem consulta o estado periodicamente dispara o alerta.
Template
Variáveis: {{::hostname}} ([server] hostname do config, ou o do sistema),
{{::event}}, {{::target}}, {{::severity}} (info, warn, crit),
{{::title}}, {{::detail}} e {{::ts}} (RFC 3339). Variável desconhecida é
recusada ao gravar. Padrão: [{{::severity}}] {{::hostname}}: {{::title}} e o
detail na linha de baixo. Texto cortado em 3900 caracteres.
Webhook
POST na URL do alerts.secrets (só https://, sem seguir redirect),
Content-Type: application/json, header Authorization se configurado.
Corpo:
{
"event": "ban_permanent",
"target": "203.0.113.7",
"severity": "crit",
"hostname": "web-01",
"title": "Ban permanente aplicado",
"detail": "…",
"ts": "2026-10-18T12:00:00Z",
"text": "[crit] web-01: Ban permanente aplicado\n…"
}text é o template renderizado. Sucesso = HTTP 2xx.
Entrega
- Best-effort: cada envio tem o prazo de
timeout_seconds, a chamada inteira no máximo 20 s, sem retry. - Nunca derruba quem gerou o evento: falha (rede, HTTP de erro, credencial
ausente ou com permissão aberta) vira registro
alert_failed,alert_secrets_erroroualert_config_errorno log local, nunca erro do comando ou do daemon. - Dedupe: o mesmo evento para o mesmo alvo dentro da janela, ou acima do
teto por hora, vira
alert_suppressed(reason: duplicate | rate_limit). Só conta o envio entregue a pelo menos um canal: um envio que falhou não segura a repetição do mesmo evento. - Mensagens de erro passam por um filtro que tira qualquer valor de credencial.
Saída JSON
Envelope 1.0 com -o json:
| Envelope | Campos |
|---|---|
alerts.status |
config efetiva, canais, últimos envios |
alerts.channel.list |
channels[]: name, type, chat_id, credentials (ok | faltando: … | não verificado), events |
alerts.channel.add / .remove |
action, channel, dry_run, changed, config_path, secrets_path |
alerts.subscribe / .unsubscribe |
idem |
alerts.test |
sent, status, error, text (exit ≠ 0 se o envio falhou) |
Nenhum deles traz valor de credencial.
Veja também
- auto-ban, threat-status, lockdown, emergency-mode, firewall-restore, feed-block — as origens dos eventos