Add metrics, protected headers and pgpkeys actuator endpoint

- metrics (optional Micrometer): per-recipient outcome/policy/reason counter,
  refused counter, key lookup timer below the cache, cache size gauge
- SendReport.RecipientOutcome gains a structured PlaintextReason
- protected headers (RFC 9788, mail.pgp.protected-headers, off by default):
  real headers inside the encrypted part, outer Subject "[...]"
- encrypting keeps the caller's Message-ID
- pgpkeys endpoint (optional Actuator): inspect policy, lookup and key
  validity per address; evict one or all cached lookups
- KeyDiscoveryCustomizer and SecureMailSenderCustomizer extension points

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G71TQ5jDETVwdUJxGAwaiT
This commit is contained in:
Claude committed 2026-10-12 03:33:17 +00:00
1 parent 3bce057eab
commit f63636f8f4
22 files changed
+767 -22

No files matched your search

+18 -5
View File
@@ -155,10 +155,22 @@ mail.pgp:
relay: { ... } # see RELAY mode
```
## Observability
- `mail.pgp.sent{mode=encrypted|plaintext}`, `mail.pgp.downgrade{reason=no_key|unreachable|invalid_key}`
- `mail.pgp.wkd.lookup{result=found|not_found|unreachable, method=advanced|direct}`
- Caffeine cache metrics; actuator endpoint `pgpkeys` to inspect/evict a cached address.
## Observability (M8)
Micrometer is optional; with a `MeterRegistry` bean these are registered:
- `mail.pgp.recipients{outcome=encrypted|plaintext|relayed, policy, reason=none|not_found|unreachable|rejected|plaintext_policy}`
— a downgrade is `outcome=plaintext, policy=opportunistic`
- `mail.pgp.refused` — messages refused by the policy
- `mail.pgp.key.lookup{result=found|not_found|unreachable, source=wkd_advanced|wkd_direct|none}` — timer for lookups
not answered from the cache
- `mail.pgp.key.cache.size`
Actuator is optional; when the `pgpkeys` endpoint is exposed
(`management.endpoints.web.exposure.include=pgpkeys`):
- `GET /actuator/pgpkeys` — cache size
- `GET /actuator/pgpkeys/{email}` — policy, lookup result, and validation of each key
- `DELETE /actuator/pgpkeys/{email}`, `DELETE /actuator/pgpkeys` — evict cached lookups (key rotation)
Extension points: `KeyDiscoveryCustomizer` (wraps WKD discovery below the cache) and `SecureMailSenderCustomizer`.
## Milestones
1. **Skeleton** — parent POM, modules, CI. ✅
@@ -179,4 +191,5 @@ mail.pgp:
7. **Demo + E2E** with GreenMail; manual check against a real WKD domain, decrypt in Thunderbird.
Demo app ✅ — encrypts when a valid key is found, otherwise sends plaintext (logged). Verified live:
`test@thinhha.pro` → found via advanced method, fpr `ADA6EFA0CC1630D602889820112495670FF1B4F0`.
8. **Hardening** — Micrometer metrics, protected headers, actuator endpoint, docs.
8. **Hardening** — Micrometer metrics, protected headers (RFC 9788, opt-in), actuator endpoint, docs. ✅
Encrypting no longer regenerates the caller's Message-ID.