Ctrl K
ComunidadGuías…

docs/adr/0073-dnssec-per-query-validation.md

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# ADR-0073: Per-Query DNSSEC Validation with @relaycorp/dnssec

**Status**: Superseded by [ADR-0082](0082-removal-of-unimplemented-features.md)  
**Date**: 2026-09-23  
**Supersedes**: None  
**Related**: ADR-0002, ADR-0061, ADR-0072

## Context

The UnboundResolver (ADR-0072) uses a resolver-level negative-control probe (`sigfail.verteiltesysteme.net`) at boot and every 10 minutes to prove DNSSEC validation is active. However, `node:dns` exposes no per-query AD flag, so the resolver stamps every Available verdict with the same DNSSEC status (`valid` or `unchecked`) based on the last successful probe.

**Critical gap**: Between revalidations (default 10 min), a resolver could be reconfigured via `rndc` to `val-permissive-mode: yes`, causing it to accept bogus signatures. During this window, Available verdicts would be stamped `valid` despite no cryptographic validation occurring.

## Decision

1. **Optional per-query DNSSEC validation**: Add `DNS_PER_QUERY_DNSSEC` flag (default: `false`) that triggers a full cryptographic DNSSEC chain validation (DS → DNSKEY → RRSIG) for each Available verdict using `@relaycorp/dnssec`.

2. **Resolver adapter**: The validation uses the same Unbound host resolver instance, passed to `@relaycorp/dnssec` for the cryptographic verification.

3. **Fail-open on timeout/error**: If per-query validation times out (default 2s) or errors, fall back to the resolver-level stamp instead of downgrading the verdict. This preserves availability while adding defense-in-depth.

4. **Mode-aware mapping**:
   - `strict`: Only `valid` passes; `bogus` → `unchecked`, `insecure` → `unchecked`
   - `permissive`: `valid` or `insecure` → `valid`; `bogus` → `unchecked`
   - `disabled`: Always `unchecked`

5. **Configuration**:
   - `DNS_PER_QUERY_DNSSEC`: Enable feature (default: `false`)
   - `DNS_PER_QUERY_DNSSEC_TIMEOUT_MS`: Per-query timeout (default: 2000ms)

## Consequences

### Positive
- **Cryptographic proof per domain**: Closes the revalidation window where `val-permissive-mode` could bypass validation.
- **Defense in depth**: Resolver-level probe + per-query validation = two independent validation layers.
- **Conservative (ADR-0002)**: Fail-open preserves availability; false positives are impossible, only false negatives (missed Available) which is acceptable.
- **Observability**: Per-query validation duration and outcome can be added to metrics.

### Negative
- **Latency**: Adds ~50-200ms per Available domain (only when enabled and DNSSEC validation is active).
- **CPU cost**: Cryptographic verification per domain.
- **Complexity**: Requires resolver adapter; production use needs proper `@relaycorp/dnssec` Resolver integration.

### Neutral
- Default `false` maintains current behavior for existing deployments.
- Only affects Available verdicts; Registered/Unknown unchanged.

## Implementation Files

- `src/providers/dns/dnssec-validation.ts`: Core validation logic with injectable test fn
- `src/providers/dns/unbound-resolver.ts`: Integration in `#lookup()` for Available verdicts
- `src/app/provider-factory.ts`: Config propagation
- `src/config.ts`: New config options

## Verification

- All existing tests pass (2375)
- New test file: `src/providers/dns/__tests__/dnssec-per-query-validation.test.ts` (6 tests)
- TypeScript strict: clean
- ESLint: clean

## Rollback

Set `DNS_PER_QUERY_DNSSEC=false` to disable entirely. No schema changes.