pmcf 6.19.0 → 6.20.1

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.
Files changed (2) hide show
  1. package/package.json +4 -4
  2. package/src/services/bind.mjs +201 -270
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmcf",
3
- "version": "6.19.0",
3
+ "version": "6.20.1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -50,10 +50,10 @@
50
50
  "lint:docs": "documentation lint ./src**/*.mjs"
51
51
  },
52
52
  "dependencies": {
53
- "aggregated-map": "^1.0.8",
53
+ "aggregated-map": "^1.0.9",
54
54
  "content-entry-transform": "^1.7.0",
55
55
  "ip-utilties": "^3.10.0",
56
- "npm-pkgbuild": "^20.8.9",
56
+ "npm-pkgbuild": "^20.8.11",
57
57
  "pacc": "^11.0.2",
58
58
  "package-directory": "^8.2.0",
59
59
  "yaml": "^2.9.0"
@@ -63,7 +63,7 @@
63
63
  "ava": "^8.0.1",
64
64
  "c8": "^12.0.0",
65
65
  "documentation": "^14.0.3",
66
- "semantic-release": "^25.0.8"
66
+ "semantic-release": "^25.0.9"
67
67
  },
68
68
  "engines": {
69
69
  "node": ">=26.5.1"
@@ -1,12 +1,7 @@
1
1
  import { join, dirname } from "node:path";
2
2
  import { createHmac } from "node:crypto";
3
3
  import { FileContentProvider } from "npm-pkgbuild";
4
- import {
5
- isLinkLocal,
6
- reverseArpa,
7
- FAMILY_IPV4,
8
- FAMILY_IPV6
9
- } from "ip-utilties";
4
+ import { reverseArpa, FAMILY_IPV4, FAMILY_IPV6 } from "ip-utilties";
10
5
  import {
11
6
  default_collection_attribute,
12
7
  default_collection_attribute_writable,
@@ -14,7 +9,6 @@ import {
14
9
  duration_attribute_writable,
15
10
  name_attribute,
16
11
  string_attribute,
17
- string_set_attribute_writable,
18
12
  string_set_attribute,
19
13
  boolean_attribute_writable_true,
20
14
  boolean_attribute_writable_false,
@@ -40,6 +34,7 @@ import {
40
34
  } from "../dns-utils.mjs";
41
35
  import { addHook } from "../hooks.mjs";
42
36
  import { owner_attribute } from "../common-attributes.mjs";
37
+ import { NetworkAddress } from "../network-address.mjs";
43
38
 
44
39
  const bindNetworkAddressTypes = networkAddressType + "|bind_group";
45
40
 
@@ -68,25 +63,48 @@ class bind_zone extends Base {
68
63
  return this.id;
69
64
  }
70
65
 
71
- get directory() {
72
- return this.source.name;
73
- }
74
-
75
66
  get file() {
76
67
  return `${this.directory}/${this.domain}.zone`;
77
68
  }
78
69
 
79
- constructor(owner, id, config, source) {
70
+ constructor(owner, id, config, location) {
80
71
  super();
81
72
  this.id = id;
82
73
  this.config = config;
83
- this.source = source;
74
+ this.directory = location;
84
75
  this.records = new Set(owner.defaultRecords);
85
76
 
77
+ if (owner.hasLocationRecord) {
78
+ this.records.add(DNSRecord("location", "TXT", location));
79
+ }
80
+
86
81
  config.zones.push(this);
87
82
  }
88
83
  }
89
84
 
85
+ class catalog_zone extends bind_zone {
86
+ static attributes = {
87
+ version: { ...string_attribute, name: "version" }
88
+ };
89
+
90
+ static {
91
+ addType(this);
92
+ }
93
+
94
+ constructor(owner, id, config, location) {
95
+ super(owner, id, config, location);
96
+ this.records.add(DNSRecord("version", "TXT", `"${this.version}"`));
97
+ }
98
+
99
+ get isCatalog() {
100
+ return true;
101
+ }
102
+
103
+ get version() {
104
+ return "2";
105
+ }
106
+ }
107
+
90
108
  class bind_zone_config extends Base {
91
109
  static priority = 1;
92
110
  static attributes = {
@@ -104,10 +122,57 @@ class bind_zone_config extends Base {
104
122
  }
105
123
 
106
124
  constructor(owner, name) {
107
- super();
108
- this.owner = owner;
125
+ super(owner);
109
126
  this.name = name;
110
127
  }
128
+
129
+ async write(outputControl, group) {
130
+ const dir = outputControl.dir;
131
+
132
+ console.log(`config: ${group.name}/${this.name}`);
133
+
134
+ const content = [];
135
+
136
+ for (const zone of this.zones) {
137
+ console.log(` file: ${zone.file}`);
138
+
139
+ content.push(`zone \"${zone.id}\" {`);
140
+
141
+ if (group.sharedWith) {
142
+ content.push(` in-view ${group.sharedWith.name};`);
143
+ } else {
144
+ content.push(` type ${this.type};`);
145
+ content.push(` file \"${zone.file}\";`);
146
+ content.push(
147
+ addressesStatement(
148
+ "allow-update",
149
+ group.allowedUpdates,
150
+ "none;",
151
+ " "
152
+ )
153
+ );
154
+ content.push(` notify ${yesno(group.notify)};`);
155
+ }
156
+ content.push(`};`, "");
157
+
158
+ let maxKeyLength = 0;
159
+ for (const r of zone.records) {
160
+ if (r.key.length > maxKeyLength) {
161
+ maxKeyLength = r.key.length;
162
+ }
163
+ }
164
+
165
+ await writeLines(
166
+ join(dir, "var/lib/named"),
167
+ zone.file,
168
+ [...zone.records]
169
+ .sort(sortZoneRecords)
170
+ .map(r => r.toString(maxKeyLength, group.recordTTL))
171
+ );
172
+ }
173
+
174
+ await writeLines(join(dir, `etc/named/${group.name}`), this.name, content);
175
+ }
111
176
  }
112
177
 
113
178
  class bind_group extends Base {
@@ -121,19 +186,11 @@ class bind_group extends Base {
121
186
  collection: true,
122
187
  writable: true
123
188
  },
124
- excludeInterfaceKinds: {
125
- ...string_set_attribute_writable,
126
- name: "excludeInterfaceKinds"
127
- },
128
- exclude: {
129
- ...default_collection_attribute_writable,
130
- name: "exclude",
131
- type: networkAddressType
132
- },
133
189
  entries: {
134
190
  ...default_collection_attribute_writable,
135
- type: networkAddressType + "|owner",
136
- name: "entries"
191
+ type: NetworkAddress,
192
+ name: "entries",
193
+ expression: true
137
194
  },
138
195
  domains: {
139
196
  ...string_set_attribute,
@@ -203,22 +260,26 @@ class bind_group extends Base {
203
260
 
204
261
  access = [];
205
262
  allowedUpdates = [];
206
- entries = [];
207
- zoneConfigs = [];
208
- exclude = new Set();
209
- excludeInterfaceKinds = new Set();
210
263
  notify = true;
211
264
  hasCatalog = true;
212
265
  hasSVRRecords = true;
213
266
  hasLinkLocalAdresses = bind_group.attributes.hasLinkLocalAdresses.default;
214
267
  recordTTL = "1W";
215
268
 
269
+ set entries(value) {
270
+ this._entries = value;
271
+ }
272
+
273
+ get entries() {
274
+ return this._entries ? this.expression(this._entries) : [];
275
+ }
276
+
216
277
  /**
217
278
  * Type of the group.
218
279
  * @return {string} view | unknown
219
280
  */
220
281
  get type() {
221
- if (this.entries.length > 0 || this.sharedWith) {
282
+ if (this.sharedWith || this._entries) {
222
283
  return "view";
223
284
  }
224
285
 
@@ -261,179 +322,82 @@ class bind_group extends Base {
261
322
 
262
323
  get domains() {
263
324
  return this.entries.reduce(
264
- (all, net) => all.union(net.localDomains),
325
+ (all, address) => all.union(address.domains),
265
326
  new Set()
266
327
  );
267
328
  }
268
329
 
269
- get zones() {
270
- const zs = new Map();
271
-
272
-
273
- for (const source of this.entries) {
274
- for (const domain of source.localDomains) {
275
- const config = new bind_zone_config(this, `${domain}.zone.conf`);
276
-
277
- this.zoneConfigs.push(config);
330
+ get zoneConfigs() {
331
+ this.zones;
332
+ return this._zoneConfigs || new Map();
333
+ }
278
334
 
279
- const z = new bind_zone(this, domain, config, source);
335
+ intoCatalog(zone, locationName) {
336
+ if (this.hasCatalog) {
337
+ const catalogConfig = this.zoneConfigs.getOrInsertComputed(
338
+ `catalog.${locationName}`,
339
+ id => new bind_zone_config(this, `${id}.zone.conf`)
340
+ );
280
341
 
281
- zs.set(z.id, z);
342
+ const catalogZone = this._zones.getOrInsertComputed(
343
+ `catalog.${locationName}`,
344
+ id => new catalog_zone(this, id, catalogConfig, locationName)
345
+ );
282
346
 
283
- for (const {
284
- address,
285
- subnet,
286
- networkInterface,
287
- domainNames,
288
- family
289
- } of source.networkAddresses()) {
290
- // console.log(address);
291
- }
292
- }
347
+ const hash = createHmac("sha1", zone.id).digest("hex");
348
+ catalogZone.records.add(
349
+ DNSRecord(`${hash}.zones`, "PTR", dnsFullName(zone.id))
350
+ );
293
351
  }
294
- return zs;
295
- }
296
352
 
297
- async packageContent(outputControl) {
298
- outputControl.packageData.sources.push(
299
- ...(await Array.fromAsync(
300
- this.templateContent(...outputControl.permissions)
301
- ))
302
- );
303
-
304
- return (
305
- await Promise.all([
306
- this.generateACLs(outputControl),
307
- this.generateZoneDefs(outputControl, this.entries)
308
- ])
309
- ).find(r => r)
310
- ? true
311
- : false;
353
+ return zone;
312
354
  }
313
355
 
314
- async generateACLs(outputControl) {
315
- const acls = addressesStatement(
316
- `acl ${this.name}`,
317
- addresses(this.access, { aggregate: true })
318
- );
356
+ get zones() {
357
+ const entries = [...this.entries];
319
358
 
320
- if (acls.length) {
321
- await writeLines(
322
- join(outputControl.dir, "etc/named"),
323
- `0-acl-${this.name}.conf`,
324
- acls
325
- );
359
+ if (!this._zones && entries.length > 0) {
360
+ this._zoneConfigs = new Map();
361
+ this._zones = new Map();
326
362
 
327
- return true;
328
- }
363
+ const hosts = new Set();
364
+ const addresses = new Set();
329
365
 
330
- return false;
331
- }
366
+ console.log("ZONES for", this.owner.owner.name, this.name);
332
367
 
333
- async generateZoneDefs(outputControl, sources) {
334
- for (const source of sources) {
335
- console.log(
336
- "ZONE",
337
- source.toString(),
338
- [...source.localDomains].join(" ")
339
- );
368
+ for (const na of entries) {
369
+ const address = na.address;
370
+ const host = na.host;
340
371
 
341
- for (const domain of source.localDomains) {
342
- const locationName = source.name;
343
- const reverseZones = new Map();
344
-
345
- const config = {
346
- name: `${domain}.zone.conf`,
347
- type: this.service.serverType,
348
- zones: []
349
- };
350
- outputControl.configs.push(config);
351
-
352
- const zone = {
353
- id: domain,
354
- config,
355
- file: `${locationName}/${domain}.zone`,
356
- records: new Set(this.defaultRecords)
357
- };
358
-
359
- if (this.hasLocationRecord) {
360
- zone.records.add(DNSRecord("location", "TXT", locationName));
361
- }
372
+ if (!addresses.has(address)) {
373
+ addresses.add(address);
362
374
 
363
- config.zones.push(zone);
364
-
365
- this.assignCatalog(outputControl, zone, domain);
366
-
367
- const hosts = new Set();
368
- const addresses = new Set();
369
-
370
- for (const {
371
- address,
372
- subnet,
373
- networkInterface,
374
- domainNames,
375
- family
376
- } of source.networkAddresses()) {
377
- if (
378
- !this.exclude.has(networkInterface.network) &&
379
- !this.excludeInterfaceKinds.has(networkInterface.kind)
380
- ) {
381
- if (
382
- !addresses.has(address) &&
383
- (this.hasLinkLocalAdresses || !isLinkLocal(address))
384
- ) {
385
- addresses.add(address);
386
-
387
- let reverseZone = reverseZones.get(subnet);
388
-
389
- // is there already a matching subnet ?
390
- if (!reverseZone) {
391
- for (const [presentSubnet, zone] of reverseZones) {
392
- if (presentSubnet.matchesAddress(subnet.address)) {
393
- reverseZone = zone;
394
- }
395
- }
396
- }
375
+ const locationName = host.owner.name;
397
376
 
398
- if (!reverseZone) {
399
- const id = reverseArpa(subnet.prefix);
400
- reverseZone = {
401
- id,
402
- config,
403
- file: `${locationName}/${id}.zone`,
404
- records: new Set(this.defaultRecords)
405
- };
406
- config.zones.push(reverseZone);
407
- reverseZones.set(subnet, reverseZone);
408
-
409
- this.assignCatalog(outputControl, reverseZone, domain);
410
- }
377
+ for (const domain of na.domains) {
378
+ const config = this.zoneConfigs.getOrInsertComputed(
379
+ domain,
380
+ domain => new bind_zone_config(this, `${domain}.zone.conf`)
381
+ );
411
382
 
412
- for (const domainName of domainNames) {
413
- if (domainName.endsWith(zone.id) && domainName[0] !== "*") {
414
- zone.records.add(
415
- DNSRecord(
416
- dnsFullName(domainName),
417
- dnsRecordTypeForAddressFamily(family),
418
- address
419
- )
420
- );
421
-
422
- reverseZone.records.add(
423
- DNSRecord(
424
- dnsFullName(reverseArpa(address)),
425
- "PTR",
426
- dnsFullName(domainName)
427
- )
428
- );
429
- }
430
- }
431
- }
383
+ const zone = this._zones.getOrInsertComputed(domain, domain =>
384
+ this.intoCatalog(
385
+ new bind_zone(this, domain, config, locationName),
386
+ locationName
387
+ )
388
+ );
389
+
390
+ const reverseZone = this._zones.getOrInsertComputed(
391
+ reverseArpa(na.subnet.prefix),
392
+ domain =>
393
+ this.intoCatalog(
394
+ new bind_zone(this, domain, config, locationName),
395
+ locationName
396
+ )
397
+ );
432
398
 
433
- const host = networkInterface.host;
434
399
  if (host && !hosts.has(host)) {
435
400
  hosts.add(host);
436
-
437
401
  for (const foreignDomainName of host.foreignDomainNames) {
438
402
  zone.records.add(
439
403
  DNSRecord("outfacing", "PTR", dnsFullName(foreignDomainName))
@@ -455,105 +419,75 @@ class bind_group extends Base {
455
419
  zone.records.add(r);
456
420
  }
457
421
  }
422
+
423
+ for (const domainName of na.domainNames) {
424
+ if (domainName.endsWith(domain) && domainName[0] !== "*") {
425
+ zone.records.add(
426
+ DNSRecord(
427
+ dnsFullName(domainName),
428
+ dnsRecordTypeForAddressFamily(na.family),
429
+ address
430
+ )
431
+ );
432
+
433
+ reverseZone.records.add(
434
+ DNSRecord(
435
+ dnsFullName(reverseArpa(address)),
436
+ "PTR",
437
+ dnsFullName(domainName)
438
+ )
439
+ );
440
+ }
441
+ }
458
442
  }
459
443
  }
460
444
  }
461
445
  }
462
-
463
- await this.writeZones(outputControl);
464
-
465
- return outputControl.packageData;
446
+ return this._zones;
466
447
  }
467
448
 
468
- assignCatalog(outputControl, zone, name) {
469
- if (!this.hasCatalog) {
470
- return;
471
- }
472
-
473
- const directory = dirname(zone.file);
474
-
475
- let catalogZone = outputControl.catalogs.get(directory);
476
-
477
- if (!catalogZone) {
478
- catalogZone = {
479
- catalog: true,
480
- id: `catalog.${name}`,
481
- file: `${directory}/catalog.${name}.zone`,
482
- records: new Set([
483
- ...this.defaultRecords,
484
- DNSRecord("version", "TXT", '"2"')
485
- ])
486
- };
487
- outputControl.catalogs.set(directory, catalogZone);
488
-
489
- const config = {
490
- name: `catalog.${name}.zone.conf`,
491
- type: this.service.serverType,
492
- zones: [catalogZone]
493
- };
494
- catalogZone.config = config;
495
- outputControl.configs.push(config);
496
- }
497
- zone.catalogZone = catalogZone;
498
-
499
- const hash = createHmac("sha1", zone.id).digest("hex");
500
- catalogZone.records.add(
501
- DNSRecord(`${hash}.zones`, "PTR", dnsFullName(zone.id))
449
+ async packageContent(outputControl) {
450
+ outputControl.packageData.sources.push(
451
+ ...(await Array.fromAsync(
452
+ this.templateContent(...outputControl.permissions)
453
+ ))
502
454
  );
503
455
 
504
- return catalogZone;
456
+ return (
457
+ await Promise.all([
458
+ this.generateACLs(outputControl),
459
+ this.generateZoneDefs(outputControl)
460
+ ])
461
+ ).find(r => r)
462
+ ? true
463
+ : false;
505
464
  }
506
465
 
507
- async writeZones(outputControl) {
508
- for (const config of outputControl.configs) {
509
- console.log(`config: ${this.name}/${config.name}`);
510
-
511
- const content = [];
512
-
513
- for (const zone of config.zones) {
514
- console.log(` file: ${zone.file}`);
515
-
516
- content.push(`zone \"${zone.id}\" {`);
466
+ async generateACLs(outputControl) {
467
+ const acls = addressesStatement(
468
+ `acl ${this.name}`,
469
+ addresses(this.access, { aggregate: true })
470
+ );
517
471
 
518
- if (this.sharedWith) {
519
- content.push(` in-view ${this.sharedWith.name};`);
520
- } else {
521
- content.push(` type ${config.type};`);
522
- content.push(` file \"${zone.file}\";`);
523
- content.push(
524
- addressesStatement(
525
- "allow-update",
526
- this.allowedUpdates,
527
- "none;",
528
- " "
529
- )
530
- );
531
- content.push(` notify ${yesno(this.notify)};`);
532
- }
533
- content.push(`};`, "");
472
+ if (acls.length) {
473
+ await writeLines(
474
+ join(outputControl.dir, "etc/named"),
475
+ `0-acl-${this.name}.conf`,
476
+ acls
477
+ );
534
478
 
535
- let maxKeyLength = 0;
536
- for (const r of zone.records) {
537
- if (r.key.length > maxKeyLength) {
538
- maxKeyLength = r.key.length;
539
- }
540
- }
479
+ return true;
480
+ }
541
481
 
542
- await writeLines(
543
- join(outputControl.dir, "var/lib/named"),
544
- zone.file,
545
- [...zone.records]
546
- .sort(sortZoneRecords)
547
- .map(r => r.toString(maxKeyLength, this.recordTTL))
548
- );
549
- }
482
+ return false;
483
+ }
550
484
 
551
- await writeLines(
552
- join(outputControl.dir, `etc/named/${this.name}`),
553
- config.name,
554
- content
555
- );
485
+ async generateZoneDefs(outputControl) {
486
+ for (const config of this.zoneConfigs.values()) {
487
+ await config.write(outputControl, this);
556
488
  }
489
+
490
+ return outputControl.packageData;
557
491
  }
558
492
  }
559
493
 
@@ -682,7 +616,6 @@ export class bind extends ExtraSourceService {
682
616
  const present = await this.writeForwarders(outputControl);
683
617
 
684
618
  if (hasContent || present) {
685
- //console.log(packageData);
686
619
  yield packageData;
687
620
  }
688
621
  }
@@ -752,12 +685,10 @@ export class bind extends ExtraSourceService {
752
685
  );
753
686
  }
754
687
  }
755
-
756
- this.assignCatalog(outputControl, zone, `outfacting.${host.owner.name}`);
757
688
  });
758
689
  }
759
690
  }
760
691
 
761
692
  function newOutputControl(packageData, dir, permissions) {
762
- return { configs: [], catalogs: new Map(), packageData, dir, permissions };
693
+ return { configs: [], packageData, dir, permissions };
763
694
  }