implant wireguard

Instala e opera a VPN WireGuard do servidor (v2.48.0+): sobe a interface, cadastra e remove devices (peers), libera a porta UDP no firewall do host e mostra o estado. O servidor é o hub da VPN; cada device (celular, notebook) é um peer com um IP /32 dentro da rede da VPN.

A fonte da verdade é o próprio /etc/wireguard/<iface>.conf do servidor. Todo comando que muda o conf faz backup antes, grava de forma atômica (modo 600) e restaura o backup se a aplicação falhar.

Uso

implant wireguard <subcomando> [OPTIONS]

Todos os subcomandos aceitam --iface <IFACE> (padrão wg0), o --dry-run global e -o json|yaml.

Subcomandos

`status`

Somente leitura. Mostra se o wireguard-tools e o módulo do kernel estão presentes, se a interface está no ar, se a unit wg-quick@<iface> está habilitada/ativa, se o conf existe, a rede, a porta, o endpoint, o estado do firewall e a lista de devices com o último handshake.

sudo implant wireguard status
sudo implant wireguard status -o json

`init`

Instala o wireguard-tools (e o iproute2) se faltar, cria o conf, habilita e sobe wg-quick@<iface> e libera a porta UDP no firewall do host.

Flag Descrição
--subnet <CIDR> Rede da VPN, ex.: 10.8.0.0/24. O servidor fica com o .1. Obrigatória sem --adopt
--port <PORT> Porta UDP (padrão 51820). Não combina com --adopt (vale a porta do conf)
--endpoint <HOST[:PORTA]> Endereço público que vai no .conf dos devices. Padrão: IP público detectado
--adopt Passa a gerenciar um conf existente sem reescrevê-lo
--force Recria o conf do zero (ver aviso abaixo)
--iface <IFACE> Interface (padrão wg0)

Comportamento:

  • Idempotente: rodar de novo com os mesmos parâmetros não muda nada (outcome: unchanged).
  • Trocar só o endpoint (--endpoint diferente, resto igual) atualiza o endpoint guardado sem reiniciar a VPN e sem --force (outcome: updated). Devices já cadastrados continuam com o .conf antigo; o novo endpoint vale para os próximos peer add.
  • Parâmetros diferentes de um conf existente (outra rede, outra porta) são recusados — a menos de --force.

--force é destrutivo. Gera uma nova chave do servidor e remove todos os devices: todo .conf distribuído para de funcionar. O conf anterior fica em backup. Não é preciso --force para trocar o endpoint.

Adotar um conf existente (`--adopt`)

Para um servidor que já tem WireGuard configurado à mão:

sudo implant wireguard init --adopt
sudo implant wireguard init --adopt --endpoint vpn.example.com
  • Rede e porta vêm do conf; nada é reescrito. Comentários, chaves desconhecidas (PostUp, Table, …) e a ordem das linhas são preservados.
  • Com --endpoint, só o endpoint é gravado no conf.
  • Conf com SaveConfig = true é recusado: o wg-quick reescreveria o arquivo ao parar a interface e apagaria os comentários. Desligue antes de adotar.
  • O nome de um device é o comentário # <nome> logo abaixo do [Peer].

A porta sobrevive ao reboot (v2.54.0+)

init (e --adopt) grava um drop-in do systemd para wg-quick@<iface> que chama implant wireguard firewall open --iface <iface> toda vez que a interface sobe. Em host com iptables em DROP e sem persistência de regras, a liberação feita em runtime sumia no boot e a VPN ficava no ar sem receber conexão.

`peer add `

Cadastra um device e imprime o .conf dele e um QR code para importar no app do WireGuard.

Flag Descrição
<NAME> Nome do device: a-z, 0-9 e -, até 32 caracteres. Nome repetido é recusado
--ip <IP> IP do device na VPN: auto (padrão, próximo livre) ou um IPv4 da rede
--public-key <KEY> Chave pública gerada fora. Sem ela, o implant gera o par de chaves
--dns <IP> DNS que vai no .conf do device
--endpoint <HOST[:PORTA]> Sobrepõe o endpoint guardado pelo init só neste device
--qr <svg|ansi|none> Formato do QR. Padrão: ansi no terminal, svg com -o json
--iface <IFACE> Interface (padrão wg0)

O device é aplicado na interface em execução sem derrubar as sessões dos outros devices.

A chave privada aparece uma única vez. Quando o implant gera o par, a chave privada do device existe só na saída deste comando: não é gravada em disco, em log nem em relatório. Não há como baixar o .conf de novo nem "regerar" a chave. Perdeu o .conf? Remova o device e cadastre outro:

sudo implant wireguard peer remove notebook
sudo implant wireguard peer add notebook

Com --public-key, o implant só registra a chave pública: a saída não tem chave privada e o .conf fica a cargo de quem gerou o par.

O .conf gerado é split tunnel: o AllowedIPs do device é a rede da VPN mais o IP público do servidor (/32), com PersistentKeepalive = 25. Os apps de Android, iOS, Windows e macOS funcionam assim. Em notebook Linux com wg-quick, ter o IP do endpoint dentro do AllowedIPs cria loop de roteamento: remova o IP público do servidor do AllowedIPs nesse caso.

`peer remove `

Remove um device, pelo nome ou pela chave pública. A conexão do device cai na hora.

`peer list`

Lista os devices com IP e último handshake. Peers que existem só na interface em execução (fora do conf) aparecem marcados como "só runtime" e não são tocados pelo peer add.

`firewall open` / `firewall status` (v2.54.0+)

  • firewall open — libera a porta UDP da VPN (o ListenPort do conf; padrão 51820) no firewall do host. Idempotente; é o que o drop-in do systemd chama no boot. Sai com código 0 mesmo quando não há firewall local gerenciado.
  • firewall status — mostra se a porta está liberada, sem mudar nada.

Firewalls reconhecidos: ufw, firewalld e iptables com policy DROP. Sem nenhum deles bloqueando, o resultado é "sem firewall local" — libere a porta UDP no firewall do provedor de nuvem, se houver. No ufw, a regra criada é marcada com o comentário wireguard-<iface>; regras antigas sem a marca (versões anteriores à 2.53.0) são trocadas pela marcada.

`disable`

Para e desabilita wg-quick@<iface> e fecha a porta que o implant liberou. O conf é mantido (chave do servidor e devices continuam lá). Para religar com o mesmo conf:

sudo implant wireguard init --adopt

Várias redes por servidor (v2.57.0+)

O servidor pode ter mais de uma rede WireGuard (wg0, wg1…, cada uma com seu conf em /etc/wireguard/<iface>.conf). Todo subcomando aceita --iface (padrão wg0).

sudo implant wireguard init --iface wg1 --subnet 10.100.8.0/24 --port 51821
implant -o json wireguard networks                 # todas as redes do host (somente leitura)
implant -o json wireguard status --iface wg1
sudo implant wireguard disable --iface wg1         # para a rede; conf e isenção ficam
sudo implant wireguard remove --iface wg1          # remove a rede inteira
sudo implant --dry-run wireguard remove --iface wg1   # só o plano

`networks`

Lista as redes de /etc/wireguard/*.conf mais as interfaces WireGuard no ar sem conf. Cada item tem o formato do status --iface X e mais:

  • conf_exists e interface_up no topo (iguais aos de detection);
  • managed_by, lido do cabeçalho do conf: implant (cabeçalho # Gerenciado pelo implant …, gravado pelo init), o nome da ferramenta quando o cabeçalho é # Gerado por `<ferramenta> …` , ou desconhecido (inclusive conf adotado com --adopt, que não é reescrito);
  • role: hub (tem ListenPort e Address com prefixo menor que /32) ou client (Address /32 e um [Peer] com Endpoint). No cliente, subnet é o Address como está e listen_port pode ser null;
  • peers no formato do peer list (name vem do comentário logo abaixo do [Peer]);
  • firewall (só com a interface no ar) e rate_limit_whitelist.

Conf ilegível não derruba a lista: o item sai com error. Servidor sem WireGuard: networks: [], exit 0. Nenhuma saída traz chave privada.

`remove --iface X [--force]`

Remove a rede inteira, nesta ordem:

  1. systemctl disable --now wg-quick@X;
  2. fecha a porta no firewall (só as regras com o marcador wireguard-X);
  3. tira a porta da isenção do rate-limit;
  4. apaga o drop-in /etc/systemd/system/wg-quick@X.service.d/implant-firewall.conf e roda systemctl daemon-reload;
  5. guarda o conf em /etc/wireguard/X.conf.removed-<AAAAMMDDTHHMMSSZ> (modo
    1. e só então tira o original. O conf nunca é apagado sem backup.

Rede que não existe (sem conf e sem interface) dá outcome: "absent" e exit 0 — e ainda tira uma isenção órfã do rate-limit, se houver. Sem --force, o remove recusa (exit diferente de 0) a wg0, que é a rede padrão do servidor, e qualquer rede que não seja managed_by: implant. Interface só de runtime que continua no ar depois do disable não é do wg-quick: o comando falha com a mensagem e ela deve ser derrubada por quem a criou. Com --dry-run, nada muda e o json traz o plano em planned.

{"iface": "wg1", "outcome": "removed", "managed_by": "implant", "listen_port": 51821,
 "firewall": "closed", "firewall_detail": {"kind": "iptables", "opened": false},
 "rate_limit_whitelist": "removed", "backup": "/etc/wireguard/wg1.conf.removed-20261012T120000Z"}

rate_limit_whitelist no remove: removed, absent (rate-limit gerenciado, porta não estava isenta) ou not_managed (sem rate-limit gerenciado).

Porta da rede isenta do rate-limit

O init (inclusive --adopt) isenta a ListenPort da rede no rate-limit, presa à rede pelo marcador wireguard-<iface> — o mesmo do firewall: é um RETURN no topo da cadeia IMPLANT_RATELIMIT (v4 e v6), guardado no state do rate-limit (whitelist_ports) e refeito por apply, reconcile, add e remove. Só o rate-limit deixa de valer para a porta; blocklist e geo-block continuam. Se o ListenPort muda, a porta antiga da mesma rede sai. O disable mantém a isenção; o remove a tira. Opt-in respeitado: sem rate-limit aplicado nada é feito. Com o rate-limit suspenso (emergency-mode), só o state muda e o reconcile a recoloca. init, status e networks relatam rate_limit_whitelist: {"listed": true|false, "kind": "iptables"} ou null sem rate-limit gerenciado; um erro vem em error, sem falhar o init.

Detecção do firewall

A detecção é a mesma em init, status, disable e remove: ufw ativo, firewalld rodando, ou iptables quando o INPUT bloqueia (policy DROP/REJECT ou DROP final) ou quando já há regra wireguard-<iface> para fechar. INPUT aberto e sem regra nossa: kind: "none" em todos.

Flags globais relevantes

Flag Descrição
--dry-run Mostra o plano (lista "Faria:") sem alterar nada. Em JSON, vai no campo planned
-o, --output json|yaml Saída estruturada (envelope)
-v, --verbose Mais detalhes

Exemplos

# Criar a VPN na rede 10.8.0.0/24, porta padrão
sudo implant wireguard init --subnet 10.8.0.0/24 --endpoint vpn.example.com

# Simular antes
sudo implant wireguard init --subnet 10.8.0.0/24 --dry-run

# Cadastrar o celular (QR no terminal)
sudo implant wireguard peer add celular

# Cadastrar com IP fixo e DNS, sem QR
sudo implant wireguard peer add notebook --ip 10.8.0.10 --dns 10.8.0.1 --qr none

# Listar e remover
sudo implant wireguard peer list
sudo implant wireguard peer remove celular

# Conferir / reabrir a porta no firewall
sudo implant wireguard firewall status
sudo implant wireguard firewall open

# Parar a VPN mantendo o conf
sudo implant wireguard disable

Saída JSON

Comandos do envelope: wireguard.status, wireguard.init, wireguard.peer.add, wireguard.peer.remove, wireguard.peer.list, wireguard.firewall.open, wireguard.firewall.status, wireguard.disable.

peer add separa o que é sensível no bloco secret (ausente com --public-key):

{
  "peer":   { "name": "celular", "public_key": "…", "ip": "10.8.0.2/32" },
  "server": { "public_key": "…", "endpoint": "203.0.113.10:51820", "subnet": "10.8.0.0/24", "iface": "wg0" },
  "secret": { "private_key": "…", "config": "[Interface]\n…", "qr_svg": "<svg …>" }
}

init, disable e firewall * trazem firewall (opened, closed, not_managed, planned ou unknown) e firewall_detail (kind, opened e, quando houver, error/migrated). firewall: unknown significa que o comando do firewall falhou e o estado da regra não é conhecido — um aviso também sai no stderr.

Notas

  • A chave privada do servidor fica só no conf (modo 600) e nunca sai do servidor.
  • Backups do conf: /etc/wireguard/<iface>.conf.bak-<timestamp>, modo 600, os 5 mais recentes.
  • Com um console de gerenciamento remoto, a mesma operação é disparada pela CLI em -o json; o console recebe o secret uma vez, para mostrar o QR ao operador, e não deve guardá-lo.

Veja também

  • firewall — wrapper UFW
  • harden-network — o hardening detecta a interface WireGuard e ajusta ip_forward/rp_filter