# SPEC / BACKLOG — Validação Mail TLS no Wizard (IMAP :993 + Cert Multi-SAN)

**Versão:** 1.0  
**Data:** 2026-06-12  
**Autor:** Roger / Cursor DevOps  
**Estado:** 📋 **BACKLOG** (correcção manual VM112 ✅ concluída)  
**Depende de:** `010-admin-domain-validation/spec.md`, `SPEC-CORRECAO-ADMIN-DOMINIOS-VIRTUAIS.md`

---

## 1. Resposta directa

**Sim** — os passos executados manualmente em Jun/2026 (cert multi-SAN 10 domínios, deploy `nginx.crt`, validação IMAP `:993`, renovação via CT114 HTTP-01) **devem fazer parte da sequência de validação e provisionamento do wizard**.

Hoje o wizard valida **presença do hostname no certbot** (`cert_san`), mas **não valida o certificado efectivamente servido** em IMAP/SMTP — exactamente o cenário que causou **"Wrong Site" no Thunderbird**.

---

## 2. Lacuna actual

| O que o wizard faz hoje | O que falta |
|-------------------------|-------------|
| `cert_san` → `certbot certificates` lista SANs | Runtime TLS em `:993` / `:465` |
| `webmail_https` → GET `:443` | Cert `:6071` admin (spec 010, não implementada) |
| `haproxy_sni` → ficheiro SNI CT114 | Deploy pós-expand (`carbonio-cert-deploy.sh`) |
| — | Renovação automatizada (`certbot-renew-mail-vm112-multi.sh`) |
| — | Alerta quando SAN falta após onboarding novo domínio |

---

## 3. Sequência alvo — 14 checks infra (P1 bloqueiam `ready`)

### 3.1 Já existem (6) — `infrastructure.py`

```
carbonio_domain → dns_mail → haproxy_sni → traefik_router → cert_san → webmail_https
```

### 3.2 Admin — spec 010 (4) — **pendente implementação**

```
→ admin_block_nginx → admin_redirect_443 → admin_cert_6071 → admin_login_6071
```

Ver: `specs/010-admin-domain-validation/spec.md`

### 3.3 Mail TLS — **NOVO backlog (4)**

| ID | Label | O que valida | Método | P |
|----|-------|--------------|--------|---|
| `imap_cert_san_993` | IMAP TLS :993 | Cert apresentado inclui `mail.{dominio}` no SAN | `openssl s_client -connect 127.0.0.1:993 -servername {host}` | **P1** |
| `smtp_cert_san_465` | SMTP TLS :465 | Idem (cert global nginx/smtpd) | `openssl s_client -connect 127.0.0.1:465 -servername {host}` | **P2** |
| `cert_deployed_global` | Cert deploy Carbonio | `nginx.crt` == `mail-vm112-multi/fullchain.pem` (hash ou mtime) | comparação ficheiros | **P1** |
| `cert_renew_ready` | Renovação LE | Script + cron/systemd timer activo | `test -x /usr/local/bin/certbot-renew-mail-vm112-multi.sh` | **P2** |

**Regra `ready`:** todos P1 OK. P2 = warning visível no Desk, não bloqueia onboarding.

### 3.4 Diagrama completo

```mermaid
flowchart LR
  A[carbonio_domain] --> B[dns_mail]
  B --> C[haproxy_sni]
  C --> D[traefik_router]
  D --> E[cert_san LE lista]
  E --> F[webmail_https]
  F --> G[admin_block_nginx]
  G --> H[admin_redirect_443]
  H --> I[admin_cert_6071]
  I --> J[admin_login_6071]
  J --> K[imap_cert_san_993]
  K --> L[cert_deployed_global]
  L --> M[cert_renew_ready]
```

---

## 4. Provisionamento — steps novos / alterados

### 4.1 Melhorar step `cert_san` existente

**Problema:** expand LE com DNS multi-token falha para domínios em contas CF diferentes (bet*).

**Solução implementada manualmente (Jun/2026):**

| Componente | Path VM112 / CT114 |
|------------|-------------------|
| Hook HTTP-01 | `/usr/local/bin/cf-http-auth-all.sh` |
| Hook cleanup | `/usr/local/bin/cf-http-cleanup-all.sh` |
| Hook DNS multi (fallback) | `/usr/local/bin/cf-dns-auth-multi-zone.sh` |
| Renew wrapper | `/usr/local/bin/certbot-renew-mail-vm112-multi.sh` |
| Deploy | `/usr/local/bin/carbonio-cert-deploy.sh` |
| CT114 nginx temp :80 | `docker run acme-http-80` (durante renew) |

**Wizard `do_cert()` deve:**

1. Calcular SANs: todos `mail.*` activos + `nfe.{dominio}` se aplicável
2. Tentar expand `mail-vm112-multi` (HTTP-01 via CT114 — parar Traefik ~60s)
3. Executar `carbonio-cert-deploy.sh`
4. Re-correr checks `imap_cert_san_993` + `cert_deployed_global`
5. Emitir webhook `infra.cert.deployed` → VM122

### 4.2 Novo step `mail_tls_verify` (só validação, pós-provision)

Idempotente — não altera infra, apenas confirma runtime TLS.

---

## 5. Implementação wizard — ficheiros

| Ficheiro | Acção | Esforço |
|----------|-------|---------|
| `services/mail_tls_validation.py` | **Criar** — checks :993/:465, cert deploy | M |
| `services/admin_domain_validation.py` | **Criar** (spec 010) | M |
| `services/infrastructure.py` | Integrar 8 checks (4 admin + 4 mail TLS) | M |
| `services/infrastructure.py` `do_cert()` | HTTP-01 + CT114 orchestration | L |
| `tests/test_mail_tls_validation.py` | Unit + mock openssl | S |
| `tests/test_infrastructure_status.py` | Snapshot 14 steps | S |

### 5.1 Interface proposta — `mail_tls_validation.py`

```python
def check_imap_cert_san(mail_host: str, port: int = 993) -> StepResult
def check_smtp_cert_san(mail_host: str, port: int = 465) -> StepResult
def check_cert_deployed_global() -> StepResult
def check_cert_renew_ready() -> StepResult
def verify_mail_tls_runtime(domain: str, mail_aliases: list[str] | None) -> list[StepResult]
```

**`check_imap_cert_san` — lógica:**

```python
# openssl s_client -connect 127.0.0.1:993 -servername mail.example.com
# Parse SAN; ok se mail_host in SAN ou CN == mail_host
# ok=False → message: "Wrong Site — SAN não inclui {host} (CN={cn})"
```

---

## 6. Integração VM122 Desk

| Evento webhook | Quando | Payload mínimo |
|----------------|--------|----------------|
| `infra.mail_tls.failed` | `imap_cert_san_993` falha | domain, mail_host, cn, sans |
| `infra.cert.deployed` | pós `carbonio-cert-deploy.sh` | domains[], expiry, serial |
| `admin.validation.failed` | spec 010 | (já definido) |

**UI Desk — card domínio:**

| Coluna | Fonte |
|--------|-------|
| IMAP TLS | `imap_cert_san_993` |
| Admin :6071 | `admin_cert_6071` |
| Cert LE SAN | `cert_san` |
| Último deploy | `cert_deployed_global` |

---

## 7. Referência — estado VM112 pós-correcção manual (baseline)

| Check | 9 domínios Carbonio | Notas |
|-------|---------------------|-------|
| `cert_san` (lista LE) | ✅ 10 SANs | inclui bet* via HTTP-01 |
| `imap_cert_san_993` | ✅ 10/10 | cert global multi-SAN |
| `admin_*` (spec 010) | ✅ 9/9 | scripts offline OK |
| `cert_renew_ready` | ✅ script manual | falta cron no wizard |

**SANs actuais `mail-vm112-multi`:**

```
mail.ligbox.com.br, mail.diarissima.com, mail.dratcoin.com,
mail.ibytera.com, mail.myvexx.com, nfe.diarissima.com,
mail.betinplace.com, mail.betinsport.com, mail.eplacebets.com,
mail.iofficebooks.com
```

---

## 8. Backlog — fases de implementação

### Fase 1 — Validação runtime (sem alterar provision) — **P1**

| ID | Task | Est. |
|----|------|------|
| WZ-TLS-1 | Criar `mail_tls_validation.py` | 4h |
| WZ-TLS-2 | Adicionar `imap_cert_san_993` + `cert_deployed_global` em `get_status()` | 2h |
| WZ-TLS-3 | Testes unitários openssl mock | 2h |
| WZ-TLS-4 | CLI: `python -m app.services.mail_tls_validation --domain X` | 1h |

**Entrega:** API `GET /infrastructure/status/{domain}` devolve checks IMAP.

### Fase 2 — Admin checks (spec 010) — **P1**

| ID | Task | Est. |
|----|------|------|
| WZ-ADM-1 | `admin_domain_validation.py` (4 checks) | 6h |
| WZ-ADM-2 | Integrar em `get_status()` + `admin_ready` flag | 2h |
| WZ-ADM-3 | Reutilizar `check-admin-login-flow.mjs` via subprocess | 2h |

### Fase 3 — Provision cert + deploy automático — **P1**

| ID | Task | Est. |
|----|------|------|
| WZ-TLS-5 | Refactor `do_cert()` — HTTP-01 CT114 orchestration | 8h |
| WZ-TLS-6 | Idempotência: skip se SAN já presente | 2h |
| WZ-TLS-7 | Cron systemd `certbot-renew-mail-vm112-multi.timer` | 1h |
| WZ-TLS-8 | Webhook `infra.cert.deployed` → VM122 | 2h |

### Fase 4 — Desk + alertas — **P2**

| ID | Task | Est. |
|----|------|------|
| OPS-TLS-1 | Card domínio: colunas IMAP + Admin TLS | 4h |
| OPS-TLS-2 | Ticket auto `infra.mail_tls.failed` | 3h |
| OPS-TLS-3 | Scorecard AUD: +2 checks mail TLS | 2h |

---

## 9. Critérios de aceitação

- [ ] Novo domínio onboarded → wizard detecta SAN em falta **antes** do cliente configurar Thunderbird
- [ ] `GET infrastructure/status/{domain}` inclui `imap_cert_san_993` com mensagem acionável ("Wrong Site")
- [ ] `POST infrastructure/provision?step=cert_san` expande multi-SAN + deploy + re-valida IMAP
- [ ] Renovação LE não exige intervenção manual (timer + script CT114)
- [ ] Desk mostra estado TLS IMAP e Admin por domínio
- [ ] Falha IMAP TLS gera evento webhook VM122

---

## 10. Riscos e mitigação

| Risco | Mitigação |
|-------|-----------|
| Renew para Traefik ~60s (porta 80) | Timer 03:00 UTC; script idempotente; alerta se renew falha |
| bet* sem token CF DNS | Manter HTTP-01 via CT114 como path primário |
| nginx mail sem SNI real | Documentar: cert **global** — SAN multi obrigatório |
| Falso positivo `cert_san` (lista vs runtime) | Check `cert_deployed_global` + `imap_cert_san_993` |

---

## 11. Scripts / paths de referência (VM112)

```
/opt/ligbox-deploy/scripts/admin-login-check/
  apply-admin-nginx-overrides.py
  check-admin-login-flow.mjs
  sync-traefik-admin-certs.sh

/usr/local/bin/
  carbonio-cert-deploy.sh
  certbot-renew-mail-vm112-multi.sh
  cf-http-auth-all.sh
  cf-http-cleanup-all.sh
  cf-dns-auth-multi-zone.sh

/etc/letsencrypt/live/mail-vm112-multi/
/opt/zextras/conf/nginx.crt   ← cert global IMAP/SMTP
```

---

## 12. Índice relacionado

| Documento | Conteúdo |
|-----------|----------|
| `specs/010-admin-domain-validation/spec.md` | Admin :6071 — 4 checks |
| `specs/010-admin-domain-validation/correcao-vm112.md` | Correcção manual admin |
| `SPEC-CORRECAO-ADMIN-DOMINIOS-VIRTUAIS.md` | Histórico VM112 |
| `BACKLOG.md` (VM122) | Track WZ-TLS-* |

---

*Próximo passo quando possível: **Fase 1** (WZ-TLS-1..4) — validação runtime sem risco de alterar produção.*
