@serve.zone/dcrouter 17.10.0 → 17.10.2

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.
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/dcrouter',
6
- version: '17.10.0',
6
+ version: '17.10.2',
7
7
  description: 'A multifaceted routing service handling mail and SMS delivery functions.'
8
8
  };
9
9
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHNfd2ViLzAwX2NvbW1pdGluZm9fZGF0YS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7R0FFRztBQUNILE1BQU0sQ0FBQyxNQUFNLFVBQVUsR0FBRztJQUN4QixJQUFJLEVBQUUsc0JBQXNCO0lBQzVCLE9BQU8sRUFBRSxTQUFTO0lBQ2xCLFdBQVcsRUFBRSwwRUFBMEU7Q0FDeEYsQ0FBQSJ9
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@serve.zone/dcrouter",
3
3
  "private": false,
4
- "version": "17.10.0",
4
+ "version": "17.10.2",
5
5
  "description": "A multifaceted routing service handling mail and SMS delivery functions.",
6
6
  "type": "module",
7
7
  "bin": {
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/dcrouter',
6
- version: '17.10.0',
6
+ version: '17.10.2',
7
7
  description: 'A multifaceted routing service handling mail and SMS delivery functions.'
8
8
  }
@@ -132,8 +132,12 @@ export class GatewayRouteDnsReconciler {
132
132
  const targetIp = optionsArg.delete
133
133
  ? undefined
134
134
  : this.getDesiredTargetIp(optionsArg.gatewayClientId, hostname);
135
- const proxied = optionsArg.proxied ?? false;
136
- const workKey = this.buildWorkKey(optionsArg, hostname, mode, targetIp, proxied);
135
+ // Routes synced before proxy intent was persisted carry no intent at all.
136
+ // Resolving that to a hardcoded default here would delete a live proxied
137
+ // record and recreate it unproxied, so the intent stays undefined until it
138
+ // can be resolved against the current record state under the hostname lock.
139
+ const proxiedIntent = optionsArg.proxied;
140
+ const workKey = this.buildWorkKey(optionsArg, hostname, mode, targetIp, proxiedIntent);
137
141
  const latestIntent = this.latestIntentByHostname.get(hostname);
138
142
  if (latestIntent?.workKey === workKey) return await latestIntent.promise;
139
143
 
@@ -156,7 +160,7 @@ export class GatewayRouteDnsReconciler {
156
160
  mode,
157
161
  checkedAt,
158
162
  targetIp,
159
- proxied,
163
+ proxiedIntent,
160
164
  executionArg.providerZoneRefreshes,
161
165
  );
162
166
  });
@@ -184,7 +188,7 @@ export class GatewayRouteDnsReconciler {
184
188
  mode: Exclude<plugins.servezoneInterfaces.data.TGatewayRouteDnsMode, 'skip'>,
185
189
  checkedAt: number,
186
190
  targetIp: string | undefined,
187
- proxied: boolean,
191
+ proxiedIntent: boolean | undefined,
188
192
  providerZoneRefreshes?: Map<string, Promise<IProviderZoneRefreshResult>>,
189
193
  ): Promise<TGatewayDnsResult> {
190
194
  const claimOwnerId = this.buildClaimOwnerId(optionsArg.gatewayClientId, hostname);
@@ -294,6 +298,11 @@ export class GatewayRouteDnsReconciler {
294
298
  );
295
299
  }
296
300
 
301
+ // Absent proxy intent must never be read as "not proxied": that would make
302
+ // the desired lookup miss a live proxied record and replace it with an
303
+ // unproxied one, exposing the origin. Fall back to the stored record state
304
+ // the same way dnsMode and ingress fall back to their persisted values.
305
+ const proxied = proxiedIntent ?? this.getStoredProxiedState(records, recordType, claimOwnerId);
297
306
  const desired = records.find((recordArg) => recordArg.type === recordType
298
307
  && recordArg.value === targetIp
299
308
  && recordArg.ttl === GATEWAY_ROUTE_DNS_TTL
@@ -620,7 +629,7 @@ export class GatewayRouteDnsReconciler {
620
629
  hostnameArg: string,
621
630
  modeArg: Exclude<plugins.servezoneInterfaces.data.TGatewayRouteDnsMode, 'skip'>,
622
631
  targetIpArg: string | undefined,
623
- proxiedArg: boolean,
632
+ proxiedArg: boolean | undefined,
624
633
  ): string {
625
634
  return JSON.stringify({
626
635
  hostname: hostnameArg,
@@ -629,11 +638,37 @@ export class GatewayRouteDnsReconciler {
629
638
  mode: modeArg,
630
639
  delete: optionsArg.delete === true,
631
640
  targetIp: targetIpArg || null,
632
- proxied: proxiedArg,
641
+ proxied: proxiedArg ?? null,
633
642
  claims: this.getActiveClaimsForHostname(hostnameArg),
634
643
  });
635
644
  }
636
645
 
646
+ /**
647
+ * Recover the effective proxy state for a claim whose route carries no
648
+ * explicit proxy intent. Preferring the reconciler's own record keeps managed
649
+ * proxying stable, and falling back to any address record under the hostname
650
+ * keeps an operator's proxy protection when automation claims the hostname.
651
+ */
652
+ private getStoredProxiedState(
653
+ recordsArg: DnsRecordDoc[],
654
+ recordTypeArg: 'A' | 'AAAA',
655
+ claimOwnerIdArg: string,
656
+ ): boolean {
657
+ const isOwned = (recordArg: DnsRecordDoc) => recordArg.managedBy === GATEWAY_ROUTE_DNS_MANAGED_BY
658
+ && recordArg.managedOwnerId === claimOwnerIdArg;
659
+ const preferenceOrder: Array<(recordArg: DnsRecordDoc) => boolean> = [
660
+ (recordArg) => recordArg.type === recordTypeArg && isOwned(recordArg),
661
+ (recordArg) => isOwned(recordArg),
662
+ (recordArg) => recordArg.type === recordTypeArg,
663
+ () => true,
664
+ ];
665
+ for (const matches of preferenceOrder) {
666
+ const candidate = recordsArg.find((recordArg) => matches(recordArg));
667
+ if (candidate) return candidate.proxied === true;
668
+ }
669
+ return false;
670
+ }
671
+
637
672
  private async refreshProviderZone(
638
673
  zoneIdArg: string,
639
674
  providerZoneRefreshesArg?: Map<string, Promise<IProviderZoneRefreshResult>>,
@@ -1383,6 +1383,12 @@ export class GatewayClientHandler {
1383
1383
  ? { gatewayDnsProxied: dnsProxied } : {}),
1384
1384
  };
1385
1385
  const normalizedRoute = this.normalizeGatewayClientRoute(validatedRoute, resolvedOwnership, externalKey);
1386
+ if (
1387
+ validatedRoute.remoteIngress === undefined
1388
+ && existingRoute?.route.remoteIngress
1389
+ ) {
1390
+ normalizedRoute.remoteIngress = structuredClone(existingRoute.route.remoteIngress);
1391
+ }
1386
1392
  if (!normalizedRoute.ingress) {
1387
1393
  const existingIngress = existingRoute?.route.ingress;
1388
1394
  normalizedRoute.ingress = existingIngress
@@ -1396,6 +1402,7 @@ export class GatewayClientHandler {
1396
1402
  + `${existingIngress ? 'preserved stored policy' : 'applied legacy directHub+smartVpn compatibility policy'}`,
1397
1403
  );
1398
1404
  }
1405
+ this.assertGatewayRouteHasIngressPath(normalizedRoute);
1399
1406
 
1400
1407
  if (existingRoute) {
1401
1408
  const snapshot = structuredClone(existingRoute);
@@ -1770,6 +1777,21 @@ export class GatewayClientHandler {
1770
1777
  return normalizedRoute;
1771
1778
  }
1772
1779
 
1780
+ private assertGatewayRouteHasIngressPath(
1781
+ routeArg: interfaces.data.IDcRouterRouteConfig,
1782
+ ): void {
1783
+ if (
1784
+ routeArg.ingress?.directHub
1785
+ || routeArg.ingress?.smartVpn
1786
+ || routeArg.remoteIngress?.enabled
1787
+ ) {
1788
+ return;
1789
+ }
1790
+ throw new plugins.typedrequest.TypedResponseError(
1791
+ 'gateway route must enable directHub, smartVpn, or RemoteIngress',
1792
+ );
1793
+ }
1794
+
1773
1795
  private assertGatewayClientRouteOwnership(
1774
1796
  ownership: TResolvedGatewayClientOwnership,
1775
1797
  route?: interfaces.data.IDcRouterRouteConfig,
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/dcrouter',
6
- version: '17.10.0',
6
+ version: '17.10.2',
7
7
  description: 'A multifaceted routing service handling mail and SMS delivery functions.'
8
8
  }