@takodotid/circuit 1.0.0-rc.2 → 1.0.0-rc.3
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/package.json +1 -1
- package/src/checks/device.ts +48 -5
- package/src/cli/editor-types.generated.ts +5 -5
- package/src/cli/setup.ts +10 -6
- package/src/cli/version.ts +1 -1
- package/src/config/define.ts +6 -2
- package/src/config/device.ts +2 -2
- package/src/config/firewall.ts +14 -0
- package/src/config/interfaces.ts +50 -4
- package/src/config/keys.ts +8 -4
- package/src/model.ts +4 -2
- package/src/platforms/derive.ts +30 -1
- package/src/platforms/fortios/TRAPS.md +1 -0
- package/src/platforms/fortios/index.ts +6 -5
- package/src/platforms/fortios/plan.ts +22 -0
- package/src/platforms/fortios/render.ts +19 -0
- package/src/platforms/junos/index.ts +1 -0
- package/src/platforms/raisecom-ros/index.ts +1 -0
- package/src/platforms/routeros/TRAPS.md +4 -0
- package/src/platforms/routeros/export.ts +6 -0
- package/src/platforms/routeros/index.ts +1 -0
- package/src/platforms/routeros/plan.ts +6 -0
- package/src/platforms/routeros/render.ts +85 -10
- package/src/platforms/vrp/index.ts +1 -0
- package/src/project/project.ts +5 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@takodotid/circuit",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.3",
|
|
4
4
|
"description": "Declarative, vendor-neutral network configuration: describe each device in TypeScript, and Circuit makes the device match. One binary, nothing to install.",
|
|
5
5
|
"homepage": "https://circuit.tako.id",
|
|
6
6
|
"bugs": {
|
package/src/checks/device.ts
CHANGED
|
@@ -77,7 +77,7 @@ export class DeviceChecks {
|
|
|
77
77
|
DeviceChecks.interfaces(device, ports, report, ip);
|
|
78
78
|
DeviceChecks.routing(device, report, cidr, ip);
|
|
79
79
|
DeviceChecks.policies(device, report, cidr);
|
|
80
|
-
DeviceChecks.firewall(device, cidr);
|
|
80
|
+
DeviceChecks.firewall(device, report, cidr);
|
|
81
81
|
DeviceChecks.services(device, report, cidr);
|
|
82
82
|
DeviceChecks.certificates(device, report);
|
|
83
83
|
DeviceChecks.vdoms(device, report, cidr, ip);
|
|
@@ -217,17 +217,53 @@ export class DeviceChecks {
|
|
|
217
217
|
const where = `interfaces.${kind}.${name}`;
|
|
218
218
|
if (ports.includes(name)) report(`${where} has the name of a port`);
|
|
219
219
|
|
|
220
|
-
if (kind === "gre" || kind === "vxlan") {
|
|
220
|
+
if (kind === "gre" || kind === "vxlan" || kind === "eoip") {
|
|
221
221
|
const tunnel = settings as Extract<Interface, { remote_address: string }>;
|
|
222
222
|
ip(where, tunnel.local_address);
|
|
223
223
|
ip(where, tunnel.remote_address);
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
+
if (kind === "eoip") {
|
|
227
|
+
const id = (settings as Extract<Interface, { tunnel_id: number }>).tunnel_id;
|
|
228
|
+
if (!(Number.isInteger(id) && id >= 0 && id <= 65535))
|
|
229
|
+
report(`${where}.tunnel_id ${id} is not a whole number from 0 to 65535`);
|
|
230
|
+
|
|
231
|
+
const same = Object.entries(device.interfaces?.eoip ?? {}).find(
|
|
232
|
+
([other, tunnel]) => other !== name && tunnel?.tunnel_id === id
|
|
233
|
+
);
|
|
234
|
+
if (same && same[0] > name) report(`${where}.tunnel_id ${id} is also interfaces.eoip.${same[0]}'s`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (settings.shaping && !(settings.shaping.mbps > 0)) report(`${where}.shaping.mbps ${settings.shaping.mbps} is not above 0`);
|
|
238
|
+
|
|
239
|
+
if (kind === "vlan_interface") {
|
|
240
|
+
const subnets = (settings.addresses ?? []).map((entry) => DeviceChecks.addressOf(entry));
|
|
241
|
+
|
|
242
|
+
for (const address of (settings as Extract<Interface, { vlan: string }>).proxy_arp ?? []) {
|
|
243
|
+
ip(`${where}.proxy_arp`, address);
|
|
244
|
+
if (!subnets.some((subnet) => Address.isCidr(subnet) && Address.contains(subnet, address)))
|
|
245
|
+
report(`${where}.proxy_arp ${address} is in none of its subnets, so no host there asks for it`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
226
249
|
if (kind === "lag") {
|
|
250
|
+
const lag = settings as Extract<Interface, { mode?: string }> & {
|
|
251
|
+
link_check?: { arp_targets: readonly string[]; interval_ms: number };
|
|
252
|
+
};
|
|
227
253
|
DeviceChecks.frames(where, settings as Switched, report);
|
|
228
254
|
|
|
229
|
-
const
|
|
230
|
-
|
|
255
|
+
const ports = Derive.ports(device).filter(([, port]) => port.in_lag === name);
|
|
256
|
+
const tunnels = Object.entries(device.interfaces?.eoip ?? {}).filter(([, tunnel]) => tunnel?.in_lag === name);
|
|
257
|
+
if (!ports.length && !tunnels.length) report(`${where} has no members`);
|
|
258
|
+
if (tunnels.length && lag.mode !== "round_robin") report(`${where} has EoIP members, which only a round_robin LAG takes`);
|
|
259
|
+
|
|
260
|
+
const check = lag.link_check;
|
|
261
|
+
if (check) {
|
|
262
|
+
if (!check.arp_targets.length) report(`${where}.link_check.arp_targets is empty`);
|
|
263
|
+
for (const target of check.arp_targets) ip(`${where}.link_check.arp_targets`, target);
|
|
264
|
+
if (!(Number.isInteger(check.interval_ms) && check.interval_ms > 0))
|
|
265
|
+
report(`${where}.link_check.interval_ms ${check.interval_ms} is not a whole number above 0`);
|
|
266
|
+
}
|
|
231
267
|
}
|
|
232
268
|
|
|
233
269
|
if (kind === "bridge") {
|
|
@@ -355,7 +391,7 @@ export class DeviceChecks {
|
|
|
355
391
|
for (const policy of Object.keys(policies)) visit(policy, []);
|
|
356
392
|
}
|
|
357
393
|
|
|
358
|
-
private static firewall(device: Device, cidr: Checker): void {
|
|
394
|
+
private static firewall(device: Device, report: Report, cidr: Checker): void {
|
|
359
395
|
const firewall = device.firewall;
|
|
360
396
|
|
|
361
397
|
for (const [name, members] of Object.entries(firewall?.address_sets ?? {})) {
|
|
@@ -371,8 +407,15 @@ export class DeviceChecks {
|
|
|
371
407
|
})),
|
|
372
408
|
{ where: "firewall.nat.source", rules: firewall?.nat?.source ?? [] },
|
|
373
409
|
{ where: "firewall.nat.destination", rules: firewall?.nat?.destination ?? [] },
|
|
410
|
+
{ where: "firewall.tcp_mss", rules: firewall?.tcp_mss ?? [] },
|
|
374
411
|
];
|
|
375
412
|
|
|
413
|
+
(firewall?.tcp_mss ?? []).forEach((rule, index) => {
|
|
414
|
+
// 536 is the MSS every IPv4 host takes (RFC 879); 65495 is the largest IP packet less 40 bytes of headers.
|
|
415
|
+
if (rule.mss !== "path_mtu" && !(Number.isInteger(rule.mss) && rule.mss >= 536 && rule.mss <= 65495))
|
|
416
|
+
report(`firewall.tcp_mss[${index}].mss ${rule.mss} is not "path_mtu" or a whole number from 536 to 65495`);
|
|
417
|
+
});
|
|
418
|
+
|
|
376
419
|
for (const { where, rules } of chains) {
|
|
377
420
|
for (const rule of rules) {
|
|
378
421
|
// The model holds one prefix a side; a list is taken as the language writes it.
|