@serve.zone/dcrouter 17.10.1 → 18.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/deno.json +1 -1
- package/dist_serve/bundle.js +360 -360
- package/dist_ts/00_commitinfo_data.js +2 -2
- package/dist_ts/acme/acme-failure-classification.d.ts +64 -0
- package/dist_ts/acme/acme-failure-classification.js +114 -0
- package/dist_ts/acme/classes.smartacme-lifecycle.d.ts +42 -0
- package/dist_ts/acme/classes.smartacme-lifecycle.js +75 -4
- package/dist_ts/acme/index.d.ts +1 -0
- package/dist_ts/acme/index.js +2 -1
- package/dist_ts/classes.dcrouter.d.ts +33 -9
- package/dist_ts/classes.dcrouter.js +145 -11
- package/dist_ts/config/classes.route-config-manager.d.ts +56 -0
- package/dist_ts/config/classes.route-config-manager.js +161 -1
- package/dist_ts/db/documents/classes.dns-authority.doc.d.ts +30 -0
- package/dist_ts/db/documents/classes.dns-authority.doc.js +108 -0
- package/dist_ts/db/documents/index.d.ts +1 -0
- package/dist_ts/db/documents/index.js +2 -1
- package/dist_ts/dns/classes.dns-server-runtime.d.ts +109 -1
- package/dist_ts/dns/classes.dns-server-runtime.js +212 -34
- package/dist_ts/dns/classes.gateway-route-dns-reconciler.d.ts +7 -0
- package/dist_ts/dns/classes.gateway-route-dns-reconciler.js +37 -6
- package/dist_ts/dns/domain-ownership.d.ts +111 -0
- package/dist_ts/dns/domain-ownership.js +152 -0
- package/dist_ts/dns/index.d.ts +2 -0
- package/dist_ts/dns/index.js +3 -1
- package/dist_ts/dns/manager.dns-authority.d.ts +143 -0
- package/dist_ts/dns/manager.dns-authority.js +481 -0
- package/dist_ts/dns/manager.dns.d.ts +121 -12
- package/dist_ts/dns/manager.dns.js +298 -28
- package/dist_ts/errors/error.codes.d.ts +4 -0
- package/dist_ts/errors/error.codes.js +5 -1
- package/dist_ts/opsserver/classes.opsserver.d.ts +1 -0
- package/dist_ts/opsserver/classes.opsserver.js +3 -1
- package/dist_ts/opsserver/handlers/acme-config.handler.js +6 -1
- package/dist_ts/opsserver/handlers/certificate.handler.d.ts +16 -0
- package/dist_ts/opsserver/handlers/certificate.handler.js +96 -10
- package/dist_ts/opsserver/handlers/config.handler.js +4 -2
- package/dist_ts/opsserver/handlers/dns-authority.handler.d.ts +20 -0
- package/dist_ts/opsserver/handlers/dns-authority.handler.js +108 -0
- package/dist_ts/opsserver/handlers/dns-provider.handler.js +5 -1
- package/dist_ts/opsserver/handlers/domain.handler.js +9 -1
- package/dist_ts/opsserver/handlers/gatewayclient.handler.js +2 -2
- package/dist_ts/opsserver/handlers/index.d.ts +1 -0
- package/dist_ts/opsserver/handlers/index.js +2 -1
- package/dist_ts_interfaces/data/dns-authority.d.ts +98 -0
- package/dist_ts_interfaces/data/dns-authority.js +26 -0
- package/dist_ts_interfaces/data/index.d.ts +1 -0
- package/dist_ts_interfaces/data/index.js +2 -1
- package/dist_ts_interfaces/data/route-management.d.ts +9 -2
- package/dist_ts_interfaces/data/route-management.js +3 -1
- package/dist_ts_interfaces/requests/certificate.d.ts +23 -0
- package/dist_ts_interfaces/requests/certificate.js +1 -1
- package/dist_ts_interfaces/requests/dns-authority.d.ts +80 -0
- package/dist_ts_interfaces/requests/dns-authority.js +3 -0
- package/dist_ts_interfaces/requests/index.d.ts +1 -0
- package/dist_ts_interfaces/requests/index.js +2 -1
- package/dist_ts_migrations/index.js +122 -9
- package/dist_ts_oci_container/index.js +9 -4
- package/dist_ts_web/00_commitinfo_data.js +2 -2
- package/package.json +1 -1
- package/readme.hints.md +412 -0
- package/readme.md +48 -6
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/acme/acme-failure-classification.ts +201 -0
- package/ts/acme/classes.smartacme-lifecycle.ts +104 -3
- package/ts/acme/index.ts +1 -0
- package/ts/classes.dcrouter.ts +193 -23
- package/ts/config/classes.route-config-manager.ts +197 -0
- package/ts/db/documents/classes.dns-authority.doc.ts +49 -0
- package/ts/db/documents/index.ts +1 -0
- package/ts/dns/classes.dns-server-runtime.ts +257 -38
- package/ts/dns/classes.gateway-route-dns-reconciler.ts +41 -6
- package/ts/dns/domain-ownership.ts +272 -0
- package/ts/dns/index.ts +2 -0
- package/ts/dns/manager.dns-authority.ts +558 -0
- package/ts/dns/manager.dns.ts +373 -27
- package/ts/errors/error.codes.ts +4 -0
- package/ts/opsserver/classes.opsserver.ts +2 -0
- package/ts/opsserver/handlers/acme-config.handler.ts +7 -0
- package/ts/opsserver/handlers/certificate.handler.ts +103 -8
- package/ts/opsserver/handlers/config.handler.ts +3 -1
- package/ts/opsserver/handlers/dns-authority.handler.ts +142 -0
- package/ts/opsserver/handlers/dns-provider.handler.ts +6 -0
- package/ts/opsserver/handlers/domain.handler.ts +12 -0
- package/ts/opsserver/handlers/gatewayclient.handler.ts +1 -1
- package/ts/opsserver/handlers/index.ts +1 -0
- package/ts/readme.md +1 -1
- package/ts_web/00_commitinfo_data.ts +1 -1
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import type { IDnsAuthorityDrift, IDnsAuthoritySettings, IDnsDelegationProbe, TDnsAuthorityState } from '../../dist_ts_interfaces/data/dns-authority.js';
|
|
2
|
+
/**
|
|
3
|
+
* Result of applying an authority change to the running process.
|
|
4
|
+
* Reconciliation is all-or-nothing: a partial application is rolled back.
|
|
5
|
+
*/
|
|
6
|
+
export interface IDnsAuthorityMutationResult {
|
|
7
|
+
success: boolean;
|
|
8
|
+
message?: string;
|
|
9
|
+
probe?: IDnsDelegationProbe;
|
|
10
|
+
settings?: IDnsAuthoritySettings;
|
|
11
|
+
}
|
|
12
|
+
/** Re-derives runtime state after the effective authority set changed. */
|
|
13
|
+
export type TDnsAuthorityReconciler = (reasonArg: string) => Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* DnsAuthorityManager — owns which zones dcrouter may answer for
|
|
16
|
+
* authoritatively, and proves that claim rather than accepting it.
|
|
17
|
+
*
|
|
18
|
+
* Why this exists: `dnsScopes` was the last un-migrated bootstrap option in an
|
|
19
|
+
* otherwise DB-driven router. It was read once at startup and had no mutation
|
|
20
|
+
* path, so claiming a newly delegated zone required a restart — measured at
|
|
21
|
+
* 30–60 s of total public outage. But simply making the declared list editable
|
|
22
|
+
* through the ops API would have been worse than the disease: the domain
|
|
23
|
+
* ownership predicate accepts zone coverage as *proof* precisely because a
|
|
24
|
+
* caller cannot write it. A `setDnsScopes` mutation would let an operator append
|
|
25
|
+
* any domain and manufacture that proof — the identical self-assertion vector
|
|
26
|
+
* that `createDcrouterDomain` used to have.
|
|
27
|
+
*
|
|
28
|
+
* So a zone earns authority by delegation instead: its public NS records must
|
|
29
|
+
* name our `dnsNsDomains`. An ops-API caller cannot fake that without actually
|
|
30
|
+
* controlling the domain, so the proof stays unforgeable while becoming
|
|
31
|
+
* mutable at runtime. It is also the comparison that was missing — claimed
|
|
32
|
+
* authority versus real delegation, checked by the mechanism itself rather than
|
|
33
|
+
* by an audit bolted on afterwards.
|
|
34
|
+
*
|
|
35
|
+
* **There is exactly one source of authority: this database document.** An
|
|
36
|
+
* earlier revision kept bootstrap `dnsScopes` as an always-in-effect floor and
|
|
37
|
+
* unioned it with the verified set. That is gone. A floor is a second
|
|
38
|
+
* representation of the same fact, it can only be changed by a redeploy, and it
|
|
39
|
+
* cannot be revoked through the API — all three of which are the properties
|
|
40
|
+
* that produced the outage this path exists to prevent.
|
|
41
|
+
*
|
|
42
|
+
* Removing the floor makes the *unreadable database* case load-bearing, so it is
|
|
43
|
+
* handled explicitly rather than by accident. Three situations, three answers:
|
|
44
|
+
*
|
|
45
|
+
* - **document missing** — a readable database that has never been seeded. The
|
|
46
|
+
* authority set is known, and it is empty: claim nothing. Logged at `error`
|
|
47
|
+
* with the remediation, and the startup drift audit enumerates every zone
|
|
48
|
+
* delegated to us that we are refusing to serve, which is the worklist.
|
|
49
|
+
* - **document present, no zones** — identical handling, different message:
|
|
50
|
+
* somebody revoked everything, which is a legitimate state we must not
|
|
51
|
+
* silently repopulate.
|
|
52
|
+
* - **document unreadable** — the authority set is *unknown*. `start()` throws
|
|
53
|
+
* rather than reporting an empty set, because rendering an unknown as "claim
|
|
54
|
+
* nothing" is exactly how a transient database fault would take every zone
|
|
55
|
+
* off the air. `DnsServerRuntime.setup()` then refuses to start on
|
|
56
|
+
* `getState() === 'unavailable'`. That second check is not redundant:
|
|
57
|
+
* `DnsServer.dependsOn('DnsManager')` only *orders* startup, and taskbuffer
|
|
58
|
+
* starts later levels even when an earlier optional service failed, so the
|
|
59
|
+
* consumer has to read the state itself. Both services stay failed and
|
|
60
|
+
* dcrouter runs degraded until it is restarted with a readable database —
|
|
61
|
+
* DNS down and visibly failed, rather than up and asserting nothing.
|
|
62
|
+
*
|
|
63
|
+
* Fail closed on authority, never silently on service: with an empty set the
|
|
64
|
+
* DNS server still starts, still serves DoH, and still picks zones up the moment
|
|
65
|
+
* one is verified — without a restart.
|
|
66
|
+
*/
|
|
67
|
+
export declare class DnsAuthorityManager {
|
|
68
|
+
private getExpectedNameservers;
|
|
69
|
+
private verifiedZones;
|
|
70
|
+
private updatedAt;
|
|
71
|
+
private updatedBy;
|
|
72
|
+
private state;
|
|
73
|
+
private reconciler?;
|
|
74
|
+
constructor(getExpectedNameservers: () => string[]);
|
|
75
|
+
start(): Promise<void>;
|
|
76
|
+
stop(): Promise<void>;
|
|
77
|
+
/**
|
|
78
|
+
* Wire the callback that re-derives runtime state (zone handlers, route
|
|
79
|
+
* certificate warnings, private-route overlay) after the effective set changes.
|
|
80
|
+
*/
|
|
81
|
+
setReconciler(reconciler?: TDnsAuthorityReconciler): void;
|
|
82
|
+
/**
|
|
83
|
+
* The authority set: delegation-verified zones, sorted so every consumer that
|
|
84
|
+
* needs a stable "first zone" (the DNSSEC signing zone, for one) gets the same
|
|
85
|
+
* answer across restarts regardless of database insertion order.
|
|
86
|
+
*
|
|
87
|
+
* This is what the ownership predicate consumes, and it is the only input to
|
|
88
|
+
* it. There is no bootstrap contribution.
|
|
89
|
+
*/
|
|
90
|
+
getEffectiveZoneNames(): string[];
|
|
91
|
+
/** Whether the stored authority set was readable when it was last loaded. */
|
|
92
|
+
getState(): TDnsAuthorityState;
|
|
93
|
+
getSettings(): IDnsAuthoritySettings;
|
|
94
|
+
/**
|
|
95
|
+
* Ask the public DNS whether `zone` is delegated to our nameservers.
|
|
96
|
+
*
|
|
97
|
+
* Deliberately uses `strategy: 'doh'` — a single DNS-over-HTTPS attempt
|
|
98
|
+
* against a public resolver, with no system-resolver fallback. smartdns'
|
|
99
|
+
* `getNameServers()` uses `dns.resolveNs`, i.e. the *system* resolver, which on
|
|
100
|
+
* a dcrouter host may be dcrouter itself: it would happily answer with the very
|
|
101
|
+
* NS records we generated, making the probe self-confirming. An independent
|
|
102
|
+
* vantage point is the whole point of the proof.
|
|
103
|
+
*
|
|
104
|
+
* The three verdicts are kept distinct on purpose. A timeout is not evidence
|
|
105
|
+
* that a zone is not ours; collapsing 'undeterminable' into 'not-delegated'
|
|
106
|
+
* would let a transient resolver fault revoke authority.
|
|
107
|
+
*/
|
|
108
|
+
probeDelegation(zoneArg: string): Promise<IDnsDelegationProbe>;
|
|
109
|
+
/**
|
|
110
|
+
* Claim authority over a zone, but only against a positive delegation proof.
|
|
111
|
+
* An undeterminable probe refuses the mutation — never assume either way.
|
|
112
|
+
*/
|
|
113
|
+
verifyZone(zoneArg: string, verifiedBy: string): Promise<IDnsAuthorityMutationResult>;
|
|
114
|
+
/**
|
|
115
|
+
* Drop a verified zone. Every zone is revocable — there is no undroppable
|
|
116
|
+
* deployment-declared floor any more, so withdrawing authority never needs a
|
|
117
|
+
* redeploy and never leaves the router asserting a claim it cannot retract.
|
|
118
|
+
*/
|
|
119
|
+
revokeZone(zoneArg: string, updatedBy: string): Promise<IDnsAuthorityMutationResult>;
|
|
120
|
+
/**
|
|
121
|
+
* Persist a new zone set and reconcile the running process against it.
|
|
122
|
+
*
|
|
123
|
+
* Fail closed: if reconciliation throws, the previous set is restored in the
|
|
124
|
+
* database and re-reconciled, so the router is never left half-converted.
|
|
125
|
+
* Reconciliation is idempotent, which is what makes the rollback safe.
|
|
126
|
+
*/
|
|
127
|
+
private applyZones;
|
|
128
|
+
private persist;
|
|
129
|
+
/**
|
|
130
|
+
* Compare claimed authority against what is actually true, in every direction.
|
|
131
|
+
*
|
|
132
|
+
* This is the check whose absence let one zone sit declared in `dnsScopes`
|
|
133
|
+
* while four live zones were delegated to our nameservers and unclaimed.
|
|
134
|
+
*
|
|
135
|
+
* Advisory by design. It never mutates the authority set: a resolver blip at
|
|
136
|
+
* startup must not revoke authority for every zone and convert a transient
|
|
137
|
+
* fault into the outage this whole path exists to avoid. Undeterminable probes
|
|
138
|
+
* are skipped rather than reported as drift.
|
|
139
|
+
*/
|
|
140
|
+
auditDelegationDrift(): Promise<IDnsAuthorityDrift[]>;
|
|
141
|
+
/** Run the audit and log every finding at `error`. Never throws. */
|
|
142
|
+
logDelegationDrift(): Promise<void>;
|
|
143
|
+
}
|
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
import * as plugins from '../plugins.js';
|
|
2
|
+
import { logger } from '../logger.js';
|
|
3
|
+
import { DnsAuthorityDoc, DomainDoc } from '../db/documents/index.js';
|
|
4
|
+
const normalizeZone = (zoneArg) => zoneArg.trim().toLowerCase().replace(/\.$/, '');
|
|
5
|
+
/** Strip the trailing dot smartdns/DoH returns on NS values. */
|
|
6
|
+
const normalizeNameserver = (nameserverArg) => nameserverArg.trim().toLowerCase().replace(/\.$/, '');
|
|
7
|
+
const isUsableZoneName = (zoneArg) => {
|
|
8
|
+
if (!zoneArg || zoneArg.length > 253)
|
|
9
|
+
return false;
|
|
10
|
+
if (zoneArg.includes('*') || zoneArg.includes(' ') || zoneArg.includes(','))
|
|
11
|
+
return false;
|
|
12
|
+
const labels = zoneArg.split('.');
|
|
13
|
+
if (labels.length < 2)
|
|
14
|
+
return false;
|
|
15
|
+
return labels.every((labelArg) => Boolean(labelArg)
|
|
16
|
+
&& labelArg.length <= 63
|
|
17
|
+
&& /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(labelArg));
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* DnsAuthorityManager — owns which zones dcrouter may answer for
|
|
21
|
+
* authoritatively, and proves that claim rather than accepting it.
|
|
22
|
+
*
|
|
23
|
+
* Why this exists: `dnsScopes` was the last un-migrated bootstrap option in an
|
|
24
|
+
* otherwise DB-driven router. It was read once at startup and had no mutation
|
|
25
|
+
* path, so claiming a newly delegated zone required a restart — measured at
|
|
26
|
+
* 30–60 s of total public outage. But simply making the declared list editable
|
|
27
|
+
* through the ops API would have been worse than the disease: the domain
|
|
28
|
+
* ownership predicate accepts zone coverage as *proof* precisely because a
|
|
29
|
+
* caller cannot write it. A `setDnsScopes` mutation would let an operator append
|
|
30
|
+
* any domain and manufacture that proof — the identical self-assertion vector
|
|
31
|
+
* that `createDcrouterDomain` used to have.
|
|
32
|
+
*
|
|
33
|
+
* So a zone earns authority by delegation instead: its public NS records must
|
|
34
|
+
* name our `dnsNsDomains`. An ops-API caller cannot fake that without actually
|
|
35
|
+
* controlling the domain, so the proof stays unforgeable while becoming
|
|
36
|
+
* mutable at runtime. It is also the comparison that was missing — claimed
|
|
37
|
+
* authority versus real delegation, checked by the mechanism itself rather than
|
|
38
|
+
* by an audit bolted on afterwards.
|
|
39
|
+
*
|
|
40
|
+
* **There is exactly one source of authority: this database document.** An
|
|
41
|
+
* earlier revision kept bootstrap `dnsScopes` as an always-in-effect floor and
|
|
42
|
+
* unioned it with the verified set. That is gone. A floor is a second
|
|
43
|
+
* representation of the same fact, it can only be changed by a redeploy, and it
|
|
44
|
+
* cannot be revoked through the API — all three of which are the properties
|
|
45
|
+
* that produced the outage this path exists to prevent.
|
|
46
|
+
*
|
|
47
|
+
* Removing the floor makes the *unreadable database* case load-bearing, so it is
|
|
48
|
+
* handled explicitly rather than by accident. Three situations, three answers:
|
|
49
|
+
*
|
|
50
|
+
* - **document missing** — a readable database that has never been seeded. The
|
|
51
|
+
* authority set is known, and it is empty: claim nothing. Logged at `error`
|
|
52
|
+
* with the remediation, and the startup drift audit enumerates every zone
|
|
53
|
+
* delegated to us that we are refusing to serve, which is the worklist.
|
|
54
|
+
* - **document present, no zones** — identical handling, different message:
|
|
55
|
+
* somebody revoked everything, which is a legitimate state we must not
|
|
56
|
+
* silently repopulate.
|
|
57
|
+
* - **document unreadable** — the authority set is *unknown*. `start()` throws
|
|
58
|
+
* rather than reporting an empty set, because rendering an unknown as "claim
|
|
59
|
+
* nothing" is exactly how a transient database fault would take every zone
|
|
60
|
+
* off the air. `DnsServerRuntime.setup()` then refuses to start on
|
|
61
|
+
* `getState() === 'unavailable'`. That second check is not redundant:
|
|
62
|
+
* `DnsServer.dependsOn('DnsManager')` only *orders* startup, and taskbuffer
|
|
63
|
+
* starts later levels even when an earlier optional service failed, so the
|
|
64
|
+
* consumer has to read the state itself. Both services stay failed and
|
|
65
|
+
* dcrouter runs degraded until it is restarted with a readable database —
|
|
66
|
+
* DNS down and visibly failed, rather than up and asserting nothing.
|
|
67
|
+
*
|
|
68
|
+
* Fail closed on authority, never silently on service: with an empty set the
|
|
69
|
+
* DNS server still starts, still serves DoH, and still picks zones up the moment
|
|
70
|
+
* one is verified — without a restart.
|
|
71
|
+
*/
|
|
72
|
+
export class DnsAuthorityManager {
|
|
73
|
+
getExpectedNameservers;
|
|
74
|
+
verifiedZones = [];
|
|
75
|
+
updatedAt = 0;
|
|
76
|
+
updatedBy = '';
|
|
77
|
+
state = 'unavailable';
|
|
78
|
+
reconciler;
|
|
79
|
+
constructor(getExpectedNameservers) {
|
|
80
|
+
this.getExpectedNameservers = getExpectedNameservers;
|
|
81
|
+
}
|
|
82
|
+
// ==========================================================================
|
|
83
|
+
// Lifecycle
|
|
84
|
+
// ==========================================================================
|
|
85
|
+
async start() {
|
|
86
|
+
let doc;
|
|
87
|
+
try {
|
|
88
|
+
doc = await DnsAuthorityDoc.load();
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
// An unknown authority set is not an empty one. Refuse to start rather
|
|
92
|
+
// than let every consumer read "no zones" as "revoke everything".
|
|
93
|
+
this.state = 'unavailable';
|
|
94
|
+
this.verifiedZones = [];
|
|
95
|
+
throw new Error(`DnsAuthorityManager: the stored DNS authority set is unreadable (${error.message}). `
|
|
96
|
+
+ 'dcrouter serves DNS authority from the database alone, so an unreadable document leaves authority '
|
|
97
|
+
+ 'unknown — refusing to start rather than claiming nothing for every zone.');
|
|
98
|
+
}
|
|
99
|
+
this.state = 'loaded';
|
|
100
|
+
this.verifiedZones = doc?.verifiedZones ? [...doc.verifiedZones] : [];
|
|
101
|
+
this.updatedAt = doc?.updatedAt ?? 0;
|
|
102
|
+
this.updatedBy = doc?.updatedBy ?? '';
|
|
103
|
+
if (this.verifiedZones.length > 0) {
|
|
104
|
+
logger.log('info', `DnsAuthorityManager: ${this.verifiedZones.length} delegation-verified zone(s) loaded `
|
|
105
|
+
+ `(${this.verifiedZones.map((entry) => normalizeZone(entry.zone)).join(', ')})`, { zone: 'dns' });
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
// Known-empty. Loud, because every zone is unserved until something is
|
|
109
|
+
// verified, and because this is the state a first deploy lands in.
|
|
110
|
+
logger.log('error', doc
|
|
111
|
+
? 'DnsAuthorityManager: the DNS authority document exists but lists no verified zones. '
|
|
112
|
+
+ 'dcrouter is authoritative for nothing and will REFUSE every query until a zone is verified.'
|
|
113
|
+
: 'DnsAuthorityManager: no DNS authority document exists yet. dcrouter is authoritative for nothing '
|
|
114
|
+
+ 'and will REFUSE every query until a zone is verified. Seed the document, or claim each delegated '
|
|
115
|
+
+ 'zone with dns-authority:write (probe first, then verify).', { zone: 'dns', authorityState: 'empty' });
|
|
116
|
+
}
|
|
117
|
+
async stop() {
|
|
118
|
+
this.verifiedZones = [];
|
|
119
|
+
this.state = 'unavailable';
|
|
120
|
+
this.reconciler = undefined;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Wire the callback that re-derives runtime state (zone handlers, route
|
|
124
|
+
* certificate warnings, private-route overlay) after the effective set changes.
|
|
125
|
+
*/
|
|
126
|
+
setReconciler(reconciler) {
|
|
127
|
+
this.reconciler = reconciler;
|
|
128
|
+
}
|
|
129
|
+
// ==========================================================================
|
|
130
|
+
// Reads
|
|
131
|
+
// ==========================================================================
|
|
132
|
+
/**
|
|
133
|
+
* The authority set: delegation-verified zones, sorted so every consumer that
|
|
134
|
+
* needs a stable "first zone" (the DNSSEC signing zone, for one) gets the same
|
|
135
|
+
* answer across restarts regardless of database insertion order.
|
|
136
|
+
*
|
|
137
|
+
* This is what the ownership predicate consumes, and it is the only input to
|
|
138
|
+
* it. There is no bootstrap contribution.
|
|
139
|
+
*/
|
|
140
|
+
getEffectiveZoneNames() {
|
|
141
|
+
const zones = new Set();
|
|
142
|
+
for (const verified of this.verifiedZones) {
|
|
143
|
+
const normalized = normalizeZone(verified.zone);
|
|
144
|
+
if (normalized)
|
|
145
|
+
zones.add(normalized);
|
|
146
|
+
}
|
|
147
|
+
return [...zones].sort();
|
|
148
|
+
}
|
|
149
|
+
/** Whether the stored authority set was readable when it was last loaded. */
|
|
150
|
+
getState() {
|
|
151
|
+
return this.state;
|
|
152
|
+
}
|
|
153
|
+
getSettings() {
|
|
154
|
+
return {
|
|
155
|
+
zones: this.verifiedZones
|
|
156
|
+
.map((entry) => ({ ...entry, zone: normalizeZone(entry.zone), origin: 'verified' }))
|
|
157
|
+
.sort((a, b) => a.zone.localeCompare(b.zone)),
|
|
158
|
+
state: this.state,
|
|
159
|
+
expectedNameservers: this.getExpectedNameservers().map(normalizeNameserver).filter(Boolean),
|
|
160
|
+
updatedAt: this.updatedAt,
|
|
161
|
+
updatedBy: this.updatedBy,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
// ==========================================================================
|
|
165
|
+
// Delegation probe
|
|
166
|
+
// ==========================================================================
|
|
167
|
+
/**
|
|
168
|
+
* Ask the public DNS whether `zone` is delegated to our nameservers.
|
|
169
|
+
*
|
|
170
|
+
* Deliberately uses `strategy: 'doh'` — a single DNS-over-HTTPS attempt
|
|
171
|
+
* against a public resolver, with no system-resolver fallback. smartdns'
|
|
172
|
+
* `getNameServers()` uses `dns.resolveNs`, i.e. the *system* resolver, which on
|
|
173
|
+
* a dcrouter host may be dcrouter itself: it would happily answer with the very
|
|
174
|
+
* NS records we generated, making the probe self-confirming. An independent
|
|
175
|
+
* vantage point is the whole point of the proof.
|
|
176
|
+
*
|
|
177
|
+
* The three verdicts are kept distinct on purpose. A timeout is not evidence
|
|
178
|
+
* that a zone is not ours; collapsing 'undeterminable' into 'not-delegated'
|
|
179
|
+
* would let a transient resolver fault revoke authority.
|
|
180
|
+
*/
|
|
181
|
+
async probeDelegation(zoneArg) {
|
|
182
|
+
const zone = normalizeZone(zoneArg);
|
|
183
|
+
const expectedNameservers = this.getExpectedNameservers()
|
|
184
|
+
.map(normalizeNameserver)
|
|
185
|
+
.filter(Boolean);
|
|
186
|
+
if (!isUsableZoneName(zone)) {
|
|
187
|
+
return {
|
|
188
|
+
zone,
|
|
189
|
+
verdict: 'not-delegated',
|
|
190
|
+
observedNameservers: [],
|
|
191
|
+
expectedNameservers,
|
|
192
|
+
detail: `'${zoneArg}' is not a usable zone name`,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
if (expectedNameservers.length === 0) {
|
|
196
|
+
return {
|
|
197
|
+
zone,
|
|
198
|
+
verdict: 'undeterminable',
|
|
199
|
+
observedNameservers: [],
|
|
200
|
+
expectedNameservers,
|
|
201
|
+
detail: 'dnsNsDomains is not configured, so there is no nameserver identity to prove delegation against',
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
const smartdnsClient = new plugins.smartdns.dnsClientMod.Smartdns({
|
|
205
|
+
strategy: 'doh',
|
|
206
|
+
allowDohFallback: false,
|
|
207
|
+
timeoutMs: 10_000,
|
|
208
|
+
});
|
|
209
|
+
let result;
|
|
210
|
+
try {
|
|
211
|
+
result = await smartdnsClient.queryRecords(zone, 'NS');
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
return {
|
|
215
|
+
zone,
|
|
216
|
+
verdict: 'undeterminable',
|
|
217
|
+
observedNameservers: [],
|
|
218
|
+
expectedNameservers,
|
|
219
|
+
detail: `delegation lookup threw: ${error.message}`,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
if (result.status === 'error') {
|
|
223
|
+
return {
|
|
224
|
+
zone,
|
|
225
|
+
verdict: 'undeterminable',
|
|
226
|
+
observedNameservers: [],
|
|
227
|
+
expectedNameservers,
|
|
228
|
+
detail: `delegation lookup failed (${result.error.kind}/${result.error.code}): ${result.error.message}`,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
if (result.status === 'missing') {
|
|
232
|
+
return {
|
|
233
|
+
zone,
|
|
234
|
+
verdict: 'not-delegated',
|
|
235
|
+
observedNameservers: [],
|
|
236
|
+
expectedNameservers,
|
|
237
|
+
detail: result.missingReason === 'nxdomain'
|
|
238
|
+
? `${zone} does not exist in public DNS`
|
|
239
|
+
: `${zone} has no NS records in public DNS`,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
const observedNameservers = [...new Set(result.records
|
|
243
|
+
.map((record) => normalizeNameserver(String(record.value ?? '')))
|
|
244
|
+
.filter((nameserver) => Boolean(nameserver)))];
|
|
245
|
+
const matches = observedNameservers.filter((observed) => expectedNameservers.includes(observed));
|
|
246
|
+
if (matches.length === 0) {
|
|
247
|
+
return {
|
|
248
|
+
zone,
|
|
249
|
+
verdict: 'not-delegated',
|
|
250
|
+
observedNameservers,
|
|
251
|
+
expectedNameservers,
|
|
252
|
+
detail: `${zone} is delegated to ${observedNameservers.join(', ') || 'nothing'}, which does not include any of our nameservers (${expectedNameservers.join(', ')})`,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
return {
|
|
256
|
+
zone,
|
|
257
|
+
verdict: 'delegated',
|
|
258
|
+
observedNameservers,
|
|
259
|
+
expectedNameservers,
|
|
260
|
+
detail: `${zone} is delegated to ${matches.join(', ')}`,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
// ==========================================================================
|
|
264
|
+
// Mutations
|
|
265
|
+
// ==========================================================================
|
|
266
|
+
/**
|
|
267
|
+
* Claim authority over a zone, but only against a positive delegation proof.
|
|
268
|
+
* An undeterminable probe refuses the mutation — never assume either way.
|
|
269
|
+
*/
|
|
270
|
+
async verifyZone(zoneArg, verifiedBy) {
|
|
271
|
+
const zone = normalizeZone(zoneArg);
|
|
272
|
+
if (!isUsableZoneName(zone)) {
|
|
273
|
+
return { success: false, message: `'${zoneArg}' is not a usable zone name` };
|
|
274
|
+
}
|
|
275
|
+
const probe = await this.probeDelegation(zone);
|
|
276
|
+
if (probe.verdict !== 'delegated') {
|
|
277
|
+
return {
|
|
278
|
+
success: false,
|
|
279
|
+
probe,
|
|
280
|
+
message: probe.verdict === 'undeterminable'
|
|
281
|
+
? `Cannot verify ${zone}: ${probe.detail}. Authority is not claimed on an undeterminable probe.`
|
|
282
|
+
: `Cannot verify ${zone}: ${probe.detail}`,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
const entry = {
|
|
286
|
+
zone,
|
|
287
|
+
origin: 'verified',
|
|
288
|
+
verifiedAt: Date.now(),
|
|
289
|
+
observedNameservers: probe.observedNameservers,
|
|
290
|
+
verifiedBy,
|
|
291
|
+
};
|
|
292
|
+
const nextZones = [
|
|
293
|
+
...this.verifiedZones.filter((candidate) => normalizeZone(candidate.zone) !== zone),
|
|
294
|
+
entry,
|
|
295
|
+
];
|
|
296
|
+
const applied = await this.applyZones(nextZones, verifiedBy, `authority claimed for ${zone}`);
|
|
297
|
+
if (!applied.success) {
|
|
298
|
+
return { ...applied, probe };
|
|
299
|
+
}
|
|
300
|
+
logger.log('info', `DnsAuthorityManager: ${zone} verified as ours (delegated to ${probe.observedNameservers.join(', ')}) and applied without a restart`, { zone: 'dns', verifiedBy });
|
|
301
|
+
return { success: true, probe, settings: this.getSettings() };
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Drop a verified zone. Every zone is revocable — there is no undroppable
|
|
305
|
+
* deployment-declared floor any more, so withdrawing authority never needs a
|
|
306
|
+
* redeploy and never leaves the router asserting a claim it cannot retract.
|
|
307
|
+
*/
|
|
308
|
+
async revokeZone(zoneArg, updatedBy) {
|
|
309
|
+
const zone = normalizeZone(zoneArg);
|
|
310
|
+
if (!this.verifiedZones.some((candidate) => normalizeZone(candidate.zone) === zone)) {
|
|
311
|
+
return { success: false, message: `${zone} is not a verified zone` };
|
|
312
|
+
}
|
|
313
|
+
const nextZones = this.verifiedZones.filter((candidate) => normalizeZone(candidate.zone) !== zone);
|
|
314
|
+
const applied = await this.applyZones(nextZones, updatedBy, `authority revoked for ${zone}`);
|
|
315
|
+
if (!applied.success) {
|
|
316
|
+
return applied;
|
|
317
|
+
}
|
|
318
|
+
logger.log('info', `DnsAuthorityManager: authority over ${zone} revoked and torn down in-process`, {
|
|
319
|
+
zone: 'dns',
|
|
320
|
+
updatedBy,
|
|
321
|
+
});
|
|
322
|
+
return { success: true, settings: this.getSettings() };
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Persist a new zone set and reconcile the running process against it.
|
|
326
|
+
*
|
|
327
|
+
* Fail closed: if reconciliation throws, the previous set is restored in the
|
|
328
|
+
* database and re-reconciled, so the router is never left half-converted.
|
|
329
|
+
* Reconciliation is idempotent, which is what makes the rollback safe.
|
|
330
|
+
*/
|
|
331
|
+
async applyZones(nextZones, updatedBy, reason) {
|
|
332
|
+
const previousZones = [...this.verifiedZones];
|
|
333
|
+
const previousUpdatedAt = this.updatedAt;
|
|
334
|
+
const previousUpdatedBy = this.updatedBy;
|
|
335
|
+
this.verifiedZones = nextZones;
|
|
336
|
+
this.updatedAt = Date.now();
|
|
337
|
+
this.updatedBy = updatedBy;
|
|
338
|
+
try {
|
|
339
|
+
await this.persist();
|
|
340
|
+
}
|
|
341
|
+
catch (error) {
|
|
342
|
+
this.verifiedZones = previousZones;
|
|
343
|
+
this.updatedAt = previousUpdatedAt;
|
|
344
|
+
this.updatedBy = previousUpdatedBy;
|
|
345
|
+
return { success: false, message: `Failed to persist DNS authority: ${error.message}` };
|
|
346
|
+
}
|
|
347
|
+
try {
|
|
348
|
+
await this.reconciler?.(reason);
|
|
349
|
+
}
|
|
350
|
+
catch (error) {
|
|
351
|
+
const applyError = error.message;
|
|
352
|
+
this.verifiedZones = previousZones;
|
|
353
|
+
this.updatedAt = previousUpdatedAt;
|
|
354
|
+
this.updatedBy = previousUpdatedBy;
|
|
355
|
+
let rollbackNote = '';
|
|
356
|
+
try {
|
|
357
|
+
await this.persist();
|
|
358
|
+
await this.reconciler?.(`rollback of ${reason}`);
|
|
359
|
+
}
|
|
360
|
+
catch (rollbackError) {
|
|
361
|
+
rollbackNote = ` Rollback also failed: ${rollbackError.message}. `
|
|
362
|
+
+ 'Runtime DNS state may be inconsistent — restart dcrouter to rebuild it deterministically.';
|
|
363
|
+
logger.log('error', `DnsAuthorityManager: rollback failed after ${reason}.${rollbackNote}`, {
|
|
364
|
+
zone: 'dns',
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
return {
|
|
368
|
+
success: false,
|
|
369
|
+
message: `Failed to apply DNS authority change (${reason}): ${applyError}. `
|
|
370
|
+
+ `The change was rolled back and nothing was left half-applied.${rollbackNote}`,
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
return { success: true, settings: this.getSettings() };
|
|
374
|
+
}
|
|
375
|
+
async persist() {
|
|
376
|
+
let doc = await DnsAuthorityDoc.load();
|
|
377
|
+
if (!doc) {
|
|
378
|
+
doc = new DnsAuthorityDoc();
|
|
379
|
+
doc.settingsId = 'dns-authority-settings';
|
|
380
|
+
}
|
|
381
|
+
doc.verifiedZones = this.verifiedZones;
|
|
382
|
+
doc.updatedAt = this.updatedAt;
|
|
383
|
+
doc.updatedBy = this.updatedBy;
|
|
384
|
+
await doc.save();
|
|
385
|
+
}
|
|
386
|
+
// ==========================================================================
|
|
387
|
+
// Drift audit
|
|
388
|
+
// ==========================================================================
|
|
389
|
+
/**
|
|
390
|
+
* Compare claimed authority against what is actually true, in every direction.
|
|
391
|
+
*
|
|
392
|
+
* This is the check whose absence let one zone sit declared in `dnsScopes`
|
|
393
|
+
* while four live zones were delegated to our nameservers and unclaimed.
|
|
394
|
+
*
|
|
395
|
+
* Advisory by design. It never mutates the authority set: a resolver blip at
|
|
396
|
+
* startup must not revoke authority for every zone and convert a transient
|
|
397
|
+
* fault into the outage this whole path exists to avoid. Undeterminable probes
|
|
398
|
+
* are skipped rather than reported as drift.
|
|
399
|
+
*/
|
|
400
|
+
async auditDelegationDrift() {
|
|
401
|
+
const drift = [];
|
|
402
|
+
const effectiveZones = new Set(this.getEffectiveZoneNames());
|
|
403
|
+
const domains = await DomainDoc.findAll();
|
|
404
|
+
const hostedZones = new Set(domains
|
|
405
|
+
.filter((domainArg) => domainArg.source === 'dcrouter')
|
|
406
|
+
.map((domainArg) => normalizeZone(domainArg.name))
|
|
407
|
+
.filter(Boolean));
|
|
408
|
+
// Direction 1: a dcrouter-hosted zone delegated to us but not claimed.
|
|
409
|
+
for (const zone of [...hostedZones].filter((zone) => !effectiveZones.has(zone))) {
|
|
410
|
+
const probe = await this.probeDelegation(zone);
|
|
411
|
+
if (probe.verdict !== 'delegated')
|
|
412
|
+
continue;
|
|
413
|
+
drift.push({
|
|
414
|
+
zone,
|
|
415
|
+
kind: 'delegated-but-unclaimed',
|
|
416
|
+
observedNameservers: probe.observedNameservers,
|
|
417
|
+
expectedNameservers: probe.expectedNameservers,
|
|
418
|
+
detail: `${zone} is delegated to ${probe.observedNameservers.join(', ')} but is not in the authority set, `
|
|
419
|
+
+ 'so dcrouter refuses to serve it authoritatively and will not request certificates for it',
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
// Direction 2: a verified zone whose delegation moved away from us.
|
|
423
|
+
for (const verified of this.verifiedZones) {
|
|
424
|
+
const zone = normalizeZone(verified.zone);
|
|
425
|
+
const probe = await this.probeDelegation(zone);
|
|
426
|
+
if (probe.verdict !== 'not-delegated')
|
|
427
|
+
continue;
|
|
428
|
+
drift.push({
|
|
429
|
+
zone,
|
|
430
|
+
kind: 'claimed-but-not-delegated',
|
|
431
|
+
observedNameservers: probe.observedNameservers,
|
|
432
|
+
expectedNameservers: probe.expectedNameservers,
|
|
433
|
+
detail: `${zone} was verified at ${verified.verifiedAt ? new Date(verified.verifiedAt).toISOString() : 'an unknown time'} `
|
|
434
|
+
+ `but its delegation now names ${probe.observedNameservers.join(', ') || 'nothing'}; `
|
|
435
|
+
+ 'dcrouter is still claiming authority over a zone that is no longer delegated to it',
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
// Direction 3: a claimed zone with nothing behind it. Records and generated
|
|
439
|
+
// apex NS both hang off a dcrouter-hosted DomainDoc, so authority without
|
|
440
|
+
// one is a zone we answer for and have nothing to say about — a lame
|
|
441
|
+
// delegation of our own making. Cheap to detect and impossible to notice
|
|
442
|
+
// otherwise, because the zone REFUSES nothing and answers nothing.
|
|
443
|
+
for (const zone of effectiveZones) {
|
|
444
|
+
if (hostedZones.has(zone))
|
|
445
|
+
continue;
|
|
446
|
+
drift.push({
|
|
447
|
+
zone,
|
|
448
|
+
kind: 'verified-but-unhosted',
|
|
449
|
+
observedNameservers: [],
|
|
450
|
+
expectedNameservers: this.getExpectedNameservers().map(normalizeNameserver).filter(Boolean),
|
|
451
|
+
detail: `${zone} is in the authority set but has no dcrouter-hosted domain, so no records and no `
|
|
452
|
+
+ 'generated apex NS are served for it; create the domain in the DNS manager or revoke the authority claim',
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
return drift;
|
|
456
|
+
}
|
|
457
|
+
/** Run the audit and log every finding at `error`. Never throws. */
|
|
458
|
+
async logDelegationDrift() {
|
|
459
|
+
let drift;
|
|
460
|
+
try {
|
|
461
|
+
drift = await this.auditDelegationDrift();
|
|
462
|
+
}
|
|
463
|
+
catch (error) {
|
|
464
|
+
logger.log('warn', `DnsAuthorityManager: delegation drift audit could not complete: ${error.message}`, { zone: 'dns' });
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
for (const entry of drift) {
|
|
468
|
+
logger.log('error', `DNS authority drift (${entry.kind}): ${entry.detail}`, {
|
|
469
|
+
zone: 'dns',
|
|
470
|
+
driftKind: entry.kind,
|
|
471
|
+
driftZone: entry.zone,
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
if (drift.length === 0) {
|
|
475
|
+
logger.log('info', 'DnsAuthorityManager: declared authority matches observed delegation', {
|
|
476
|
+
zone: 'dns',
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWFuYWdlci5kbnMtYXV0aG9yaXR5LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vdHMvZG5zL21hbmFnZXIuZG5zLWF1dGhvcml0eS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGVBQWUsQ0FBQztBQUN6QyxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sY0FBYyxDQUFDO0FBQ3RDLE9BQU8sRUFBRSxlQUFlLEVBQUUsU0FBUyxFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUF1QnRFLE1BQU0sYUFBYSxHQUFHLENBQUMsT0FBZSxFQUFVLEVBQUUsQ0FDaEQsT0FBTyxDQUFDLElBQUksRUFBRSxDQUFDLFdBQVcsRUFBRSxDQUFDLE9BQU8sQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLENBQUM7QUFFbEQsZ0VBQWdFO0FBQ2hFLE1BQU0sbUJBQW1CLEdBQUcsQ0FBQyxhQUFxQixFQUFVLEVBQUUsQ0FDNUQsYUFBYSxDQUFDLElBQUksRUFBRSxDQUFDLFdBQVcsRUFBRSxDQUFDLE9BQU8sQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLENBQUM7QUFFeEQsTUFBTSxnQkFBZ0IsR0FBRyxDQUFDLE9BQWUsRUFBVyxFQUFFO0lBQ3BELElBQUksQ0FBQyxPQUFPLElBQUksT0FBTyxDQUFDLE1BQU0sR0FBRyxHQUFHO1FBQUUsT0FBTyxLQUFLLENBQUM7SUFDbkQsSUFBSSxPQUFPLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxJQUFJLE9BQU8sQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLElBQUksT0FBTyxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUM7UUFBRSxPQUFPLEtBQUssQ0FBQztJQUMxRixNQUFNLE1BQU0sR0FBRyxPQUFPLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDO0lBQ2xDLElBQUksTUFBTSxDQUFDLE1BQU0sR0FBRyxDQUFDO1FBQUUsT0FBTyxLQUFLLENBQUM7SUFDcEMsT0FBTyxN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
|