docs: registrar liberação de SIP/RTP no firewall e confusão de senhas
Atualiza TODO.md/SECURITY.md para refletir a regra de nftables real (SIP/ RTP liberados para RFC1918, AMI/ARI seguem bloqueados incondicionalmente) e documenta em TROUBLESHOOTING.md o caso completo: timeout de registro de softphone causado pelo firewall + confusão entre senha de login web e senha SIP do ramal (duas credenciais diferentes).
This commit is contained in:
11
TODO.md
11
TODO.md
@@ -349,8 +349,15 @@ banco. Todos os fixtures de teste foram removidos/desativados ao final.
|
|||||||
`docs/SECURITY.md`, revisitar se o frontend algum dia sair da mesma
|
`docs/SECURITY.md`, revisitar se o frontend algum dia sair da mesma
|
||||||
origem da API).
|
origem da API).
|
||||||
- [x] nftables final revisado — regras conferidas contra o estado atual
|
- [x] nftables final revisado — regras conferidas contra o estado atual
|
||||||
(Nginx na porta 80, AMI/ARI/SIP ainda bloqueados da interface LAN
|
(Nginx na porta 80, SSH nunca bloqueado). **Atualizado após a Fase
|
||||||
`ens18`, SSH nunca bloqueado). Nenhuma alteração necessária.
|
10**: o bloqueio incondicional de SIP/RTP na interface LAN
|
||||||
|
inviabilizava o uso real de softphones (um agente de verdade nunca
|
||||||
|
conseguiria registrar, já que o OpenSIPS não foi implantado —
|
||||||
|
`docs/OPENSIPS.md`). Corrigido liberando SIP (5060/udp) e RTP
|
||||||
|
(10000-20000/udp) para toda a faixa RFC1918, mantendo AMI/ARI
|
||||||
|
bloqueados incondicionalmente mesmo de origem privada. Achado em
|
||||||
|
produção real: usuário tentou registrar um softphone e o contador de
|
||||||
|
drop da regra confirmou 109 pacotes descartados.
|
||||||
- [x] Logs estruturados JSON (sem segredos) — confirmado: `redact.paths`
|
- [x] Logs estruturados JSON (sem segredos) — confirmado: `redact.paths`
|
||||||
no `nestjs-pino` cobre `authorization`, `cookie`, `password`,
|
no `nestjs-pino` cobre `authorization`, `cookie`, `password`,
|
||||||
`currentPassword`, `newPassword`, `set-cookie`; varredura nos logs
|
`currentPassword`, `newPassword`, `set-cookie`; varredura nos logs
|
||||||
|
|||||||
@@ -53,12 +53,20 @@
|
|||||||
|
|
||||||
## Rede
|
## Rede
|
||||||
|
|
||||||
- Único ponto exposto à LAN: Nginx, porta 80 (443 quando HTTPS configurado
|
- Único ponto exposto à LAN sem restrição de origem: Nginx, porta 80 (443
|
||||||
— `docs/OPERATIONS.md`).
|
quando HTTPS configurado — `docs/OPERATIONS.md`).
|
||||||
- Postgres/Redis nunca publicados fora de `127.0.0.1`/rede Docker interna.
|
- Postgres/Redis nunca publicados fora de `127.0.0.1`/rede Docker interna.
|
||||||
- AMI (5038), ARI (8088/8089), SIP (5060/5061), RTP (10000-20000) bloqueados
|
- AMI (5038) e ARI (8088/8089) bloqueados incondicionalmente da interface
|
||||||
da interface LAN por nftables (`infrastructure/nftables/b2bcall.nft`),
|
LAN por nftables (`infrastructure/nftables/b2bcall.nft`), mesmo o
|
||||||
mesmo o Asterisk rodando em `network_mode: host`. SSH nunca bloqueado.
|
Asterisk rodando em `network_mode: host` — nenhum softphone ou host
|
||||||
|
externo deve alcançá-los, só a própria aplicação via rede interna.
|
||||||
|
- SIP (5060/udp) e RTP (10000-20000/udp) são liberados na interface LAN,
|
||||||
|
mas **só para origem dentro do RFC1918** (10.0.0.0/8, 172.16.0.0/12,
|
||||||
|
192.168.0.0/16) — necessário para softphones/ramais reais de agentes
|
||||||
|
registrarem, já que o OpenSIPS que ficaria na frente (`docs/OPENSIPS.md`)
|
||||||
|
não foi implantado nesta fase. Origem fora do RFC1918 (internet pública)
|
||||||
|
continua bloqueada — o Asterisk nunca deve receber SIP de fora da rede
|
||||||
|
privada (agente.md seção 5). SSH nunca bloqueado.
|
||||||
- CORS: `origin` restrito a uma allowlist configurável (`ALLOWED_ORIGINS`),
|
- CORS: `origin` restrito a uma allowlist configurável (`ALLOWED_ORIGINS`),
|
||||||
`credentials: true` só para essas origens.
|
`credentials: true` só para essas origens.
|
||||||
- Helmet (`@fastify/helmet`) ativo: `X-Content-Type-Options`,
|
- Helmet (`@fastify/helmet`) ativo: `X-Content-Type-Options`,
|
||||||
@@ -88,7 +96,8 @@
|
|||||||
| Agente não eleva a própria permissão | ✅ |
|
| Agente não eleva a própria permissão | ✅ |
|
||||||
| Segredos de tronco/ramal cifrados em repouso | ✅ |
|
| Segredos de tronco/ramal cifrados em repouso | ✅ |
|
||||||
| Postgres/Redis não expostos à LAN | ✅ |
|
| Postgres/Redis não expostos à LAN | ✅ |
|
||||||
| AMI/ARI/SIP não expostos à LAN | ✅ (nftables) |
|
| AMI/ARI não expostos à LAN | ✅ (nftables, incondicional) |
|
||||||
|
| SIP/RTP restritos a origem RFC1918 na LAN | ✅ (nftables — liberado deliberadamente para softphones reais) |
|
||||||
| Rate limiting de login | ✅ |
|
| Rate limiting de login | ✅ |
|
||||||
| Auditoria de ações sensíveis, sem vazar segredos | ✅ |
|
| Auditoria de ações sensíveis, sem vazar segredos | ✅ |
|
||||||
| Logs estruturados sem credenciais | ✅ |
|
| Logs estruturados sem credenciais | ✅ |
|
||||||
|
|||||||
@@ -3,6 +3,38 @@
|
|||||||
Problemas reais encontrados durante o desenvolvimento deste projeto e como
|
Problemas reais encontrados durante o desenvolvimento deste projeto e como
|
||||||
foram diagnosticados/resolvidos — mantido como referência para o futuro.
|
foram diagnosticados/resolvidos — mantido como referência para o futuro.
|
||||||
|
|
||||||
|
## Softphone não registra ("timeout")
|
||||||
|
|
||||||
|
**Sintoma**: usuário criou um ramal, colocou usuário/senha num softphone
|
||||||
|
real e o registro dava timeout (sem resposta nenhuma do servidor).
|
||||||
|
|
||||||
|
**Causa raiz nº 1 — firewall**: nftables bloqueava incondicionalmente SIP
|
||||||
|
(5060/udp) e RTP (10000-20000) vindos da interface LAN (`ens18`) — regra
|
||||||
|
criada na Fase 2 como proteção, mas que também bloqueia qualquer uso real,
|
||||||
|
já que o OpenSIPS que ficaria na frente (`docs/OPENSIPS.md`) nunca foi
|
||||||
|
implantado. Diagnosticado direto pelo contador da regra:
|
||||||
|
`nft list table inet b2bcall_fw` mostrando `udp dport 5060 ... drop` com
|
||||||
|
contador subindo a cada tentativa. **Fix**: liberar SIP/RTP para origem
|
||||||
|
RFC1918 (rede privada), mantendo AMI/ARI sempre bloqueados — ver
|
||||||
|
`docs/SECURITY.md`.
|
||||||
|
|
||||||
|
**Causa raiz nº 2 — depois de liberar o firewall, "Failed to authenticate"**:
|
||||||
|
o usuário estava digitando a **senha de login web** (a do `admin@b2bcall.
|
||||||
|
local`) no campo de senha do softphone, em vez da **senha SIP** do próprio
|
||||||
|
ramal (que é uma credencial completamente diferente, gerada por ramal).
|
||||||
|
Confirmado ativando `pjsip set logger on` e vendo o REGISTER chegando e
|
||||||
|
falhando na autenticação. **Lição**: deixar claro na interface (ou pelo
|
||||||
|
menos na documentação) que são dois sistemas de credenciais distintos —
|
||||||
|
fácil de confundir porque os dois são "a senha que o sistema gerou".
|
||||||
|
|
||||||
|
**Melhoria feita por causa disso**: antes, a única forma de recuperar uma
|
||||||
|
senha SIP esquecida era resetá-la (invalidando a anterior). Agora dá para
|
||||||
|
ver e editar a senha SIP diretamente no formulário de edição do ramal
|
||||||
|
(`GET /extensions/:id/password`, decifra sob demanda) — diferente da
|
||||||
|
senha de login (hash argon2id, irreversível por design), a senha SIP é
|
||||||
|
cifrada (reversível) porque o próprio Asterisk precisa dela em texto puro
|
||||||
|
para autenticar.
|
||||||
|
|
||||||
## "Body cannot be empty when content-type is set to 'application/json'" em quase todo botão de ação
|
## "Body cannot be empty when content-type is set to 'application/json'" em quase todo botão de ação
|
||||||
|
|
||||||
**Sintoma**: usuário real testando no navegador (não `curl`) relatou erro
|
**Sintoma**: usuário real testando no navegador (não `curl`) relatou erro
|
||||||
|
|||||||
Reference in New Issue
Block a user