@takodotid/circuit 0.43.1 → 1.0.0-rc.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.
- package/README.md +115 -37
- package/package.json +21 -58
- package/src/checks/across.ts +402 -0
- package/src/checks/built-in.ts +256 -0
- package/src/checks/device.ts +436 -0
- package/src/checks/index.ts +53 -0
- package/src/cli/advisories.ts +108 -0
- package/src/cli/app.ts +71 -0
- package/src/cli/editor-types.generated.ts +30 -0
- package/src/cli/editor-types.ts +69 -0
- package/src/cli/format.ts +124 -0
- package/src/cli/git-hooks.ts +79 -0
- package/src/cli/index.ts +350 -1170
- package/src/cli/migrate.ts +554 -0
- package/src/cli/project-commands.ts +656 -0
- package/src/cli/releases.ts +374 -0
- package/src/cli/remember.ts +339 -0
- package/src/cli/rename.ts +27 -33
- package/src/cli/secrets-password.ts +60 -0
- package/src/cli/secrets.ts +258 -0
- package/src/cli/setup.ts +300 -0
- package/src/cli/signing.ts +23 -0
- package/src/cli/text-editor.ts +268 -0
- package/src/cli/ui.ts +298 -0
- package/src/cli/version.ts +2 -0
- package/src/cli/versions.ts +158 -0
- package/src/config/address.ts +102 -0
- package/src/config/circuit.d.ts +30 -0
- package/src/config/common.ts +65 -0
- package/src/config/communities.ts +479 -0
- package/src/config/define.ts +684 -0
- package/src/config/device.ts +164 -0
- package/src/config/firewall.ts +150 -0
- package/src/config/index.ts +22 -0
- package/src/{schema → config}/interfaces.ts +121 -85
- package/src/config/keys.ts +434 -0
- package/src/config/management.ts +61 -0
- package/src/config/merge.ts +335 -0
- package/src/config/models.ts +70 -0
- package/src/config/monitoring.ts +64 -0
- package/src/config/network.ts +180 -0
- package/src/config/presets.ts +149 -0
- package/src/config/routing.ts +401 -0
- package/src/config/services.ts +51 -0
- package/src/config/switching.ts +84 -0
- package/src/config/system.ts +103 -0
- package/src/{schema → config}/time-zones.ts +1 -1
- package/src/core/address.ts +1 -1
- package/src/core/cabling.ts +8 -43
- package/src/core/graph.ts +40 -0
- package/src/{integrations → core}/registries.ts +5 -3
- package/src/core/secrets-file.ts +539 -0
- package/src/core/secrets.ts +83 -64
- package/src/core/topology.ts +103 -146
- package/src/core/utils.ts +12 -1
- package/src/model.ts +83 -0
- package/src/platforms/derive.ts +64 -37
- package/src/platforms/finding.ts +9 -0
- package/src/platforms/fortios/firewall.ts +76 -64
- package/src/platforms/fortios/index.ts +189 -125
- package/src/platforms/fortios/names.ts +51 -18
- package/src/platforms/fortios/render.ts +69 -62
- package/src/platforms/fortios/routing.ts +89 -58
- package/src/platforms/fortios/vdoms.ts +47 -16
- package/src/platforms/junos/TRAPS.md +2 -2
- package/src/platforms/junos/bgp.ts +63 -42
- package/src/platforms/junos/index.ts +122 -89
- package/src/platforms/junos/render.ts +139 -104
- package/src/platforms/platform.ts +10 -8
- package/src/platforms/raisecom-ros/TRAPS.md +1 -1
- package/src/platforms/raisecom-ros/index.ts +89 -70
- package/src/platforms/raisecom-ros/render.ts +49 -45
- package/src/platforms/routeros/TRAPS.md +3 -3
- package/src/platforms/routeros/index.ts +121 -60
- package/src/platforms/routeros/render.ts +214 -195
- package/src/platforms/vrp/TRAPS.md +1 -1
- package/src/platforms/vrp/index.ts +74 -50
- package/src/platforms/vrp/render.ts +67 -56
- package/src/project/project.ts +725 -0
- package/src/studio/app/env.d.ts +9 -0
- package/src/studio/app/index.html +18 -0
- package/src/studio/app/src/App.vue +95 -0
- package/src/studio/app/src/components/AddressesView.vue +38 -0
- package/src/studio/app/src/components/CablesView.vue +29 -0
- package/src/studio/app/src/components/CompareView.vue +160 -0
- package/src/studio/app/src/components/DataTable.vue +49 -0
- package/src/studio/app/src/components/DeviceView.vue +237 -0
- package/src/studio/app/src/components/DevicesView.vue +32 -0
- package/src/studio/app/src/components/Faceplate.vue +75 -0
- package/src/studio/app/src/components/GraphView.vue +282 -0
- package/src/studio/app/src/components/LineDiff.vue +24 -0
- package/src/studio/app/src/components/ProblemsView.vue +42 -0
- package/src/studio/app/src/components/SearchView.vue +72 -0
- package/src/studio/app/src/components/SecretsView.vue +112 -0
- package/src/studio/app/src/components/SessionsView.vue +35 -0
- package/src/studio/app/src/components/Settings.vue +95 -0
- package/src/studio/app/src/components/ToolsView.vue +118 -0
- package/src/studio/app/src/components/VlansView.vue +50 -0
- package/src/studio/app/src/lib/csv.ts +19 -0
- package/src/studio/{public/diff.js → app/src/lib/diff.ts} +40 -35
- package/src/studio/app/src/lib/format.ts +38 -0
- package/src/studio/app/src/lib/net.ts +68 -0
- package/src/studio/app/src/lib/secrets-crypto.ts +35 -0
- package/src/studio/app/src/lib/store.ts +71 -0
- package/src/studio/app/src/lib/tools.ts +217 -0
- package/src/studio/app/src/main.ts +9 -0
- package/src/studio/{public → app/src}/style.css +14 -4
- package/src/studio/app/tsconfig.json +17 -0
- package/src/studio/app/vite.config.ts +16 -0
- package/src/studio/assets.generated.ts +3 -0
- package/src/studio/data.ts +96 -188
- package/src/studio/git.ts +27 -43
- package/src/studio/server.ts +196 -107
- package/src/studio/types.ts +141 -0
- package/src/transport/console.ts +3 -3
- package/src/transport/ssh.ts +1 -1
- package/dist/chunks/index-3b343xva.js +0 -177
- package/dist/chunks/index-6wb41q67.js +0 -62
- package/dist/chunks/index-n7w3tg5y.js +0 -149
- package/dist/chunks/index-yzw86rc8.js +0 -921
- package/dist/chunks/new-w3zys5y4.js +0 -172
- package/dist/chunks/server-cxr9ynxc.js +0 -259
- package/dist/cli/index.js +0 -12922
- package/dist/helpers/index.js +0 -881
- package/dist/index.js +0 -28
- package/dist/types/core/address-space.d.ts +0 -62
- package/dist/types/core/address.d.ts +0 -44
- package/dist/types/core/cabling.d.ts +0 -41
- package/dist/types/core/cross-checks.d.ts +0 -28
- package/dist/types/core/define.d.ts +0 -79
- package/dist/types/core/project-files.d.ts +0 -12
- package/dist/types/core/schema-docs.d.ts +0 -25
- package/dist/types/core/secrets.d.ts +0 -62
- package/dist/types/core/topology.d.ts +0 -96
- package/dist/types/core/utils.d.ts +0 -10
- package/dist/types/core/validator.d.ts +0 -23
- package/dist/types/helpers/bgp.d.ts +0 -195
- package/dist/types/helpers/cables.d.ts +0 -69
- package/dist/types/helpers/checks.d.ts +0 -36
- package/dist/types/helpers/exchange.d.ts +0 -55
- package/dist/types/helpers/filters.d.ts +0 -51
- package/dist/types/helpers/index.d.ts +0 -14
- package/dist/types/helpers/route-server.d.ts +0 -52
- package/dist/types/helpers/upstreams.d.ts +0 -192
- package/dist/types/index.d.ts +0 -10
- package/dist/types/integrations/librenms.d.ts +0 -60
- package/dist/types/integrations/netbox.d.ts +0 -89
- package/dist/types/integrations/peeringdb.d.ts +0 -24
- package/dist/types/integrations/prometheus.d.ts +0 -24
- package/dist/types/integrations/registries.d.ts +0 -8
- package/dist/types/platforms/bgp-wait.d.ts +0 -24
- package/dist/types/platforms/catalog.d.ts +0 -109
- package/dist/types/platforms/derive.d.ts +0 -17
- package/dist/types/platforms/fortios/firewall.d.ts +0 -47
- package/dist/types/platforms/fortios/index.d.ts +0 -140
- package/dist/types/platforms/fortios/models.d.ts +0 -14
- package/dist/types/platforms/fortios/names.d.ts +0 -52
- package/dist/types/platforms/fortios/plan.d.ts +0 -114
- package/dist/types/platforms/fortios/render.d.ts +0 -77
- package/dist/types/platforms/fortios/routing.d.ts +0 -56
- package/dist/types/platforms/fortios/tree.d.ts +0 -37
- package/dist/types/platforms/fortios/vdoms.d.ts +0 -50
- package/dist/types/platforms/index.d.ts +0 -13
- package/dist/types/platforms/junos/bgp.d.ts +0 -44
- package/dist/types/platforms/junos/index.d.ts +0 -114
- package/dist/types/platforms/junos/models.d.ts +0 -14
- package/dist/types/platforms/junos/render.d.ts +0 -154
- package/dist/types/platforms/line-diff.d.ts +0 -67
- package/dist/types/platforms/platform.d.ts +0 -135
- package/dist/types/platforms/raisecom-ros/index.d.ts +0 -71
- package/dist/types/platforms/raisecom-ros/models.d.ts +0 -14
- package/dist/types/platforms/raisecom-ros/render.d.ts +0 -41
- package/dist/types/platforms/routeros/export.d.ts +0 -59
- package/dist/types/platforms/routeros/index.d.ts +0 -116
- package/dist/types/platforms/routeros/models.d.ts +0 -18
- package/dist/types/platforms/routeros/plan.d.ts +0 -58
- package/dist/types/platforms/routeros/render.d.ts +0 -250
- package/dist/types/platforms/vrp/index.d.ts +0 -83
- package/dist/types/platforms/vrp/models.d.ts +0 -12
- package/dist/types/platforms/vrp/render.d.ts +0 -96
- package/dist/types/schema/common.d.ts +0 -30
- package/dist/types/schema/device.d.ts +0 -126
- package/dist/types/schema/firewall.d.ts +0 -131
- package/dist/types/schema/index.d.ts +0 -8
- package/dist/types/schema/interfaces.d.ts +0 -205
- package/dist/types/schema/policy.d.ts +0 -107
- package/dist/types/schema/routing.d.ts +0 -161
- package/dist/types/schema/system.d.ts +0 -198
- package/dist/types/schema/time-zones.d.ts +0 -2
- package/dist/types/transport/console.d.ts +0 -115
- package/dist/types/transport/path-check.d.ts +0 -32
- package/dist/types/transport/ssh.d.ts +0 -27
- package/src/cli/graph-format.ts +0 -91
- package/src/cli/new.ts +0 -234
- package/src/core/address-space.ts +0 -320
- package/src/core/cross-checks.ts +0 -369
- package/src/core/define.ts +0 -124
- package/src/core/project-files.ts +0 -35
- package/src/core/schema-docs.ts +0 -174
- package/src/core/validator.ts +0 -508
- package/src/helpers/bgp.ts +0 -473
- package/src/helpers/cables.ts +0 -74
- package/src/helpers/checks.ts +0 -201
- package/src/helpers/exchange.ts +0 -123
- package/src/helpers/filters.ts +0 -107
- package/src/helpers/index.ts +0 -25
- package/src/helpers/route-server.ts +0 -212
- package/src/helpers/upstreams.ts +0 -369
- package/src/index.ts +0 -13
- package/src/integrations/librenms.ts +0 -291
- package/src/integrations/netbox.ts +0 -620
- package/src/integrations/peeringdb.ts +0 -173
- package/src/integrations/prometheus.ts +0 -90
- package/src/schema/common.ts +0 -41
- package/src/schema/device.ts +0 -145
- package/src/schema/firewall.ts +0 -140
- package/src/schema/index.ts +0 -8
- package/src/schema/policy.ts +0 -115
- package/src/schema/routing.ts +0 -173
- package/src/schema/system.ts +0 -203
- package/src/studio/public/app.js +0 -186
- package/src/studio/public/compare.js +0 -250
- package/src/studio/public/dom.js +0 -138
- package/src/studio/public/evaluate.js +0 -687
- package/src/studio/public/export.js +0 -130
- package/src/studio/public/graph.js +0 -1541
- package/src/studio/public/index.html +0 -83
- package/src/studio/public/net.js +0 -134
- package/src/studio/public/tables.js +0 -1289
- package/src/studio/public/tools.js +0 -610
- package/templates/colocation/checks.ts +0 -53
- package/templates/colocation/circuit.config.ts +0 -9
- package/templates/colocation/edge-01.ts +0 -133
- package/templates/colocation/routing.ts +0 -13
- package/templates/colocation/tenancy.ts +0 -76
- package/templates/colocation/tenants/acme.ts +0 -19
- package/templates/colocation/tenants/index.ts +0 -6
- package/templates/colocation/tor-01.ts +0 -33
- package/templates/edge-router/checks.ts +0 -20
- package/templates/edge-router/circuit.config.ts +0 -8
- package/templates/edge-router/edge-01.ts +0 -110
- package/templates/edge-router/routing.ts +0 -13
- package/templates/project/AGENTS.md +0 -51
- package/templates/project/CLAUDE.md +0 -1
- package/templates/project/README.md +0 -16
- package/templates/project/gitignore +0 -5
- package/templates/project/tsconfig.json +0 -13
- package/templates/single-site/checks.ts +0 -26
- package/templates/single-site/circuit.config.ts +0 -8
- package/templates/single-site/router.ts +0 -86
- package/templates/single-site/site.ts +0 -18
- package/templates/single-site/switch.ts +0 -34
- /package/src/studio/{public → app/public}/favicon.svg +0 -0
- /package/src/studio/{public → app/public}/special-gothic-latin-ext.woff2 +0 -0
- /package/src/studio/{public → app/public}/special-gothic-latin.woff2 +0 -0
- /package/src/studio/{public → app/public}/special-gothic-license.txt +0 -0
|
@@ -1,20 +1,27 @@
|
|
|
1
1
|
// How the platform names what the config names: ports and interfaces, prefixes, and the routing table an interface sits in.
|
|
2
2
|
|
|
3
3
|
import { Address } from "../../core/address";
|
|
4
|
-
import type { Device, Family } from "../../
|
|
4
|
+
import type { Device, Family } from "../../model";
|
|
5
5
|
import { Catalog } from "../catalog";
|
|
6
|
+
import { Derive, type NamedInterface } from "../derive";
|
|
6
7
|
|
|
7
|
-
type Addressed = { addresses?: readonly (string | { address: string;
|
|
8
|
+
type Addressed = { addresses?: readonly (string | { address: string; peer_address?: string })[]; vrf?: string; in_lag?: string };
|
|
8
9
|
|
|
9
10
|
export class FortiOsNames {
|
|
10
11
|
/** The bridges a device has, in the config's order. A device with any runs in transparent mode, each bridge a forward domain numbered by its place. */
|
|
11
12
|
static bridgesOf(device: Device): { name: string; members: readonly string[] }[] {
|
|
12
|
-
return Object.entries(device.interfaces ?? {}).flatMap(([name,
|
|
13
|
-
|
|
13
|
+
return Object.entries(device.interfaces?.bridge ?? {}).flatMap(([name, bridge]) =>
|
|
14
|
+
bridge ? [{ name, members: bridge.member_interfaces }] : []
|
|
14
15
|
);
|
|
15
16
|
}
|
|
16
17
|
|
|
18
|
+
/** Whether an interface is a GRE or VXLAN tunnel. */
|
|
19
|
+
static isTunnel(device: Device, name: string): boolean {
|
|
20
|
+
return Boolean(device.interfaces?.gre?.[name] ?? device.interfaces?.vxlan?.[name]);
|
|
21
|
+
}
|
|
22
|
+
|
|
17
23
|
/** Whether the device runs in transparent mode: it has a bridge. */
|
|
24
|
+
|
|
18
25
|
static isTransparent(device: Device): boolean {
|
|
19
26
|
return FortiOsNames.bridgesOf(device).length > 0;
|
|
20
27
|
}
|
|
@@ -26,12 +33,7 @@ export class FortiOsNames {
|
|
|
26
33
|
if (!FortiOsNames.isTransparent(device)) return undefined;
|
|
27
34
|
|
|
28
35
|
const members = new Set(FortiOsNames.bridgesOf(device).flatMap((bridge) => bridge.members));
|
|
29
|
-
const
|
|
30
|
-
string,
|
|
31
|
-
Addressed | undefined,
|
|
32
|
-
][];
|
|
33
|
-
|
|
34
|
-
for (const [name, settings] of holders) {
|
|
36
|
+
for (const [name, settings] of FortiOsNames.holders(device)) {
|
|
35
37
|
if (members.has(name)) continue;
|
|
36
38
|
const address = FortiOsNames.addressesOf(settings).find((each) => Address.family(Address.host(each)) === "ipv4");
|
|
37
39
|
if (address) return { name, address };
|
|
@@ -48,13 +50,48 @@ export class FortiOsNames {
|
|
|
48
50
|
|
|
49
51
|
/** The name the platform gives a port or an interface: a port by its spelling, any other interface by its key. */
|
|
50
52
|
static nameOf(device: Device, name: string): string {
|
|
51
|
-
if (device.
|
|
53
|
+
if (FortiOsNames.interfacesOf(device).some((entry) => entry.name === name)) return name;
|
|
54
|
+
|
|
52
55
|
return Catalog.spell(device.platform, device.model, name);
|
|
53
56
|
}
|
|
54
57
|
|
|
55
58
|
/** A port's or an interface's settings, by its key. */
|
|
56
59
|
static settingsOf(device: Device, name: string): Addressed | undefined {
|
|
57
|
-
return (device
|
|
60
|
+
return Derive.settings(device, name);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Every interface that is not a port, in the order the config writes them: its groups in the order they are written, and each group's interfaces in theirs. Where an order shows on the device, such as the interfaces NTP answers on, it is this one.
|
|
65
|
+
*/
|
|
66
|
+
static interfacesOf(device: Device): NamedInterface[] {
|
|
67
|
+
return Object.entries(device.interfaces ?? {}).flatMap(([kind, group]) =>
|
|
68
|
+
kind === "ethernet"
|
|
69
|
+
? []
|
|
70
|
+
: Object.entries((group ?? {}) as Record<string, NamedInterface["settings"] | undefined>).flatMap(([name, settings]) =>
|
|
71
|
+
settings ? [{ name, kind: kind as NamedInterface["kind"], settings }] : []
|
|
72
|
+
)
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Every port, then every other interface, by key, with its settings. */
|
|
77
|
+
private static holders(device: Device): [string, Addressed][] {
|
|
78
|
+
return [
|
|
79
|
+
...Derive.ports(device),
|
|
80
|
+
...FortiOsNames.interfacesOf(device).map(({ name, settings }) => [name, settings] as [string, Addressed]),
|
|
81
|
+
];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** The sources `management.allow` names: its prefixes, or the members of the address set it names. */
|
|
85
|
+
static allowed(device: Device): readonly string[] {
|
|
86
|
+
const allow = device.management?.allow;
|
|
87
|
+
if (typeof allow !== "string") return allow ?? [];
|
|
88
|
+
|
|
89
|
+
return device.firewall?.address_sets?.[allow] ?? [];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** The one value a match field holds, or undefined. A list is one value per rule, which Circuit writes out before the platform reads it; `unsupported()` refuses a list of several. */
|
|
93
|
+
static single<T>(value: T | readonly T[] | undefined): T | undefined {
|
|
94
|
+
return Array.isArray(value) ? (value as readonly T[])[0] : (value as T | undefined);
|
|
58
95
|
}
|
|
59
96
|
|
|
60
97
|
/** Every address of a port or an interface. */
|
|
@@ -84,16 +121,12 @@ export class FortiOsNames {
|
|
|
84
121
|
|
|
85
122
|
/** The number of a VRF on the platform: its place among the device's VRFs, the default table being 0. */
|
|
86
123
|
static vrfNumber(device: Device, vrf: string | undefined): number {
|
|
87
|
-
return vrf ? Object.keys(device.vrfs ?? {}).indexOf(vrf) + 1 : 0;
|
|
124
|
+
return vrf ? Object.keys(device.routing?.vrfs ?? {}).indexOf(vrf) + 1 : 0;
|
|
88
125
|
}
|
|
89
126
|
|
|
90
127
|
/** Every port and interface that routes, by key, with its settings. A LAG member routes through its LAG. */
|
|
91
128
|
private static routedHolders(device: Device): [string, Addressed][] {
|
|
92
|
-
|
|
93
|
-
string,
|
|
94
|
-
Addressed | undefined,
|
|
95
|
-
][];
|
|
96
|
-
return holders.filter((holder): holder is [string, Addressed] => holder[1] !== undefined && !holder[1].lag);
|
|
129
|
+
return FortiOsNames.holders(device).filter(([, settings]) => !settings.in_lag);
|
|
97
130
|
}
|
|
98
131
|
|
|
99
132
|
/** The interface whose own network holds an address, the most specific when several do, in one routing table. */
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
import { Address } from "../../core/address";
|
|
4
4
|
import { Secrets } from "../../core/secrets";
|
|
5
|
-
import
|
|
5
|
+
import { Utils } from "../../core/utils";
|
|
6
|
+
import type { Device, Gre, Helper, Severity, StaticRoute, Vxlan } from "../../model";
|
|
6
7
|
import { Catalog } from "../catalog";
|
|
8
|
+
import { Derive } from "../derive";
|
|
7
9
|
import { FortiOsFirewall } from "./firewall";
|
|
8
10
|
import { FortiOsNames } from "./names";
|
|
9
11
|
import { FortiOsRouting } from "./routing";
|
|
@@ -13,7 +15,7 @@ import { FortiOsVdoms } from "./vdoms";
|
|
|
13
15
|
export type Section = (typeof FortiOsRender.SECTIONS)[number];
|
|
14
16
|
|
|
15
17
|
type RoutedSettings = {
|
|
16
|
-
addresses?: readonly (string | { address: string;
|
|
18
|
+
addresses?: readonly (string | { address: string; peer_address: string })[];
|
|
17
19
|
vrrp?: readonly { id: number; address: string; priority?: number; preempt?: boolean; interval?: number }[];
|
|
18
20
|
lldp?: boolean;
|
|
19
21
|
vrf?: string;
|
|
@@ -251,10 +253,10 @@ export class FortiOsRender {
|
|
|
251
253
|
|
|
252
254
|
if (management.https) services.push("https");
|
|
253
255
|
if (management.ssh) services.push("ssh");
|
|
254
|
-
if (
|
|
256
|
+
if (device.monitoring?.snmp) services.push("snmp");
|
|
255
257
|
if (management.http) services.push("http");
|
|
256
258
|
if (management.telnet) services.push("telnet");
|
|
257
|
-
if (management.
|
|
259
|
+
if (management.native_protocol) services.push("fgfm");
|
|
258
260
|
|
|
259
261
|
return services.join(" ");
|
|
260
262
|
}
|
|
@@ -289,7 +291,7 @@ export class FortiOsRender {
|
|
|
289
291
|
FortiOsRender.logging(root, device);
|
|
290
292
|
FortiOsRender.flowExport(root, device);
|
|
291
293
|
FortiOsRender.dhcpServers(section("system dhcp server"), device);
|
|
292
|
-
FortiOsRender.staticRoutes(section("router static"), section("router static6"), device);
|
|
294
|
+
FortiOsRender.staticRoutes(section("router static"), section("router static6"), device, device.routing?.static_routes ?? []);
|
|
293
295
|
FortiOsRouting.render(root, device);
|
|
294
296
|
FortiOsRender.helpers(section("system session-helper"), device);
|
|
295
297
|
FortiOsFirewall.render(root, device);
|
|
@@ -300,16 +302,13 @@ export class FortiOsRender {
|
|
|
300
302
|
|
|
301
303
|
/** A management VDOM's routes, and who reaches the device's own services through it, as sections of their own after everything else. */
|
|
302
304
|
private static managementVdom(root: FortiOsBlock, device: Device): void {
|
|
303
|
-
const management = device.vdoms?.management;
|
|
305
|
+
const management = device.system?.vdoms?.management;
|
|
304
306
|
if (!management) return;
|
|
305
307
|
|
|
306
308
|
const vdom = new FortiOsBlock();
|
|
307
309
|
for (const section of FortiOsVdoms.MANAGEMENT_SECTIONS) vdom.block(section);
|
|
308
310
|
|
|
309
|
-
FortiOsRender.staticRoutes(vdom.block("router static"), vdom.block("router static6"),
|
|
310
|
-
...device,
|
|
311
|
-
routing: { static: management.static ?? [] },
|
|
312
|
-
});
|
|
311
|
+
FortiOsRender.staticRoutes(vdom.block("router static"), vdom.block("router static6"), device, management.static_routes ?? []);
|
|
313
312
|
FortiOsFirewall.managementOnly(vdom, device);
|
|
314
313
|
|
|
315
314
|
for (const [section, block] of vdom.blocks) root.blocks.set(FortiOsVdoms.key(management.name, section), block);
|
|
@@ -317,8 +316,10 @@ export class FortiOsRender {
|
|
|
317
316
|
|
|
318
317
|
/** A certificate with its key is the device's own; one without is only trusted. */
|
|
319
318
|
private static certificates(local: FortiOsBlock, trusted: FortiOsBlock, device: Device): void {
|
|
320
|
-
for (const [name, certificate] of Object.entries(device.certificates ?? {})) {
|
|
321
|
-
|
|
319
|
+
for (const [name, certificate] of Object.entries(device.system?.certificates ?? {})) {
|
|
320
|
+
if (!certificate) continue;
|
|
321
|
+
|
|
322
|
+
const text = FortiOsTree.quote(Utils.projectFile(certificate.certificate_file).trim());
|
|
322
323
|
|
|
323
324
|
if (certificate.private_key)
|
|
324
325
|
local
|
|
@@ -348,7 +349,7 @@ export class FortiOsRender {
|
|
|
348
349
|
if (management.http) block.set("admin-https-redirect", "disable");
|
|
349
350
|
if (device.system?.banner) block.set("pre-login-banner", "enable");
|
|
350
351
|
|
|
351
|
-
if (device.lldp === true) block.set("lldp-transmission", "enable").set("lldp-reception", "enable");
|
|
352
|
+
if (device.switching?.lldp === true) block.set("lldp-transmission", "enable").set("lldp-reception", "enable");
|
|
352
353
|
|
|
353
354
|
const vdom = FortiOsVdoms.managementOf(device);
|
|
354
355
|
if (vdom) block.set("vdom-mode", "multi-vdom").set("management-vdom", FortiOsTree.quote(vdom));
|
|
@@ -356,7 +357,8 @@ export class FortiOsRender {
|
|
|
356
357
|
|
|
357
358
|
/** Each user, with the profile its role takes. */
|
|
358
359
|
private static users(profiles: FortiOsBlock, admins: FortiOsBlock, device: Device): void {
|
|
359
|
-
const
|
|
360
|
+
const users = Object.entries(device.system?.users ?? {}).flatMap(([name, user]) => (user ? [[name, user] as const] : []));
|
|
361
|
+
const roles = new Set(users.map(([, user]) => user.role));
|
|
360
362
|
|
|
361
363
|
// An operator changes everything but the system's own settings, which hold the users; read-only changes nothing.
|
|
362
364
|
for (const role of ["operator", "read-only"] as const) {
|
|
@@ -367,7 +369,7 @@ export class FortiOsRender {
|
|
|
367
369
|
profile.set(group, role === "operator" && group !== "sysgrp" ? "read-write" : "read");
|
|
368
370
|
}
|
|
369
371
|
|
|
370
|
-
for (const [name, user] of
|
|
372
|
+
for (const [name, user] of users) {
|
|
371
373
|
const admin = admins.entry(name);
|
|
372
374
|
|
|
373
375
|
admin.set("accprofile", FortiOsTree.quote(FortiOsRender.PROFILE[user.role]));
|
|
@@ -384,7 +386,7 @@ export class FortiOsRender {
|
|
|
384
386
|
const access = FortiOsRender.allowAccess(device);
|
|
385
387
|
|
|
386
388
|
for (const port of Catalog.ports(device.platform, device.model)) {
|
|
387
|
-
const settings = device.
|
|
389
|
+
const settings = device.interfaces?.ethernet?.[port];
|
|
388
390
|
const entry = block.entry(Catalog.spell(device.platform, device.model, port));
|
|
389
391
|
|
|
390
392
|
entry.set("vdom", FortiOsTree.quote(FortiOsVdoms.vdomOf(device, port)));
|
|
@@ -402,18 +404,18 @@ export class FortiOsRender {
|
|
|
402
404
|
if (speed) entry.set("speed", speed);
|
|
403
405
|
|
|
404
406
|
// A port in a LAG has no redirects of its own: `command parse error`.
|
|
405
|
-
if (settings.
|
|
407
|
+
if (settings.in_lag) continue;
|
|
406
408
|
|
|
407
409
|
FortiOsRender.redirects(entry, device);
|
|
408
410
|
if (settings.mtu) entry.set("mtu-override", "enable").set("mtu", settings.mtu);
|
|
409
411
|
FortiOsRender.routed(entry, device, port, settings, access);
|
|
410
412
|
}
|
|
411
413
|
|
|
412
|
-
for (const [name, iface] of Object.entries(device.interfaces ?? {})) {
|
|
413
|
-
if (iface
|
|
414
|
+
for (const [name, iface] of Object.entries(device.interfaces?.lag ?? {})) {
|
|
415
|
+
if (!iface) continue;
|
|
414
416
|
|
|
415
|
-
const members =
|
|
416
|
-
.filter(([, port]) => port
|
|
417
|
+
const members = Derive.ports(device)
|
|
418
|
+
.filter(([, port]) => port.in_lag === name)
|
|
417
419
|
.map(([port]) => Catalog.spell(device.platform, device.model, port));
|
|
418
420
|
|
|
419
421
|
const entry = block.entry(name);
|
|
@@ -427,14 +429,14 @@ export class FortiOsRender {
|
|
|
427
429
|
FortiOsRender.routed(entry, device, name, iface, access);
|
|
428
430
|
}
|
|
429
431
|
|
|
430
|
-
for (const [name, iface] of Object.entries(device.interfaces ?? {})) {
|
|
431
|
-
if (iface
|
|
432
|
+
for (const [name, iface] of Object.entries(device.interfaces?.vlan_interface ?? {})) {
|
|
433
|
+
if (!iface) continue;
|
|
432
434
|
|
|
433
435
|
const parent = FortiOsRender.vlanParent(device, iface.vlan);
|
|
434
436
|
const entry = block.entry(name);
|
|
435
437
|
entry.set("vdom", FortiOsTree.quote(FortiOsVdoms.vdomOf(device, name)));
|
|
436
438
|
if (parent) entry.set("interface", FortiOsTree.quote(FortiOsNames.nameOf(device, parent)));
|
|
437
|
-
entry.set("vlanid", device.vlans?.[iface.vlan]?.id ?? 0);
|
|
439
|
+
entry.set("vlanid", device.switching?.vlans?.[iface.vlan]?.id ?? 0);
|
|
438
440
|
const domain = FortiOsNames.forwardDomainOf(device, name);
|
|
439
441
|
if (domain) entry.set("forward-domain", domain);
|
|
440
442
|
if (iface.description) entry.set("description", FortiOsTree.quote(iface.description));
|
|
@@ -443,8 +445,8 @@ export class FortiOsRender {
|
|
|
443
445
|
FortiOsRender.routed(entry, device, name, iface, access);
|
|
444
446
|
}
|
|
445
447
|
|
|
446
|
-
for (const [name, iface] of Object.entries(device.interfaces ?? {})) {
|
|
447
|
-
if (iface
|
|
448
|
+
for (const [name, iface] of Object.entries(device.interfaces?.loopback ?? {})) {
|
|
449
|
+
if (!iface) continue;
|
|
448
450
|
|
|
449
451
|
const entry = block.entry(name);
|
|
450
452
|
entry.set("vdom", FortiOsTree.quote(FortiOsVdoms.vdomOf(device, name)));
|
|
@@ -455,31 +457,34 @@ export class FortiOsRender {
|
|
|
455
457
|
}
|
|
456
458
|
|
|
457
459
|
// A tunnel's interface is created by its own table, which names the interface it leaves through.
|
|
458
|
-
for (const
|
|
459
|
-
if (
|
|
460
|
+
for (const { name, kind, settings } of FortiOsNames.interfacesOf(device)) {
|
|
461
|
+
if (kind !== "gre" && kind !== "vxlan") continue;
|
|
462
|
+
|
|
463
|
+
const iface = settings as Gre | Vxlan;
|
|
464
|
+
const vxlan = kind === "vxlan" ? (settings as Vxlan) : undefined;
|
|
460
465
|
|
|
461
|
-
const outgoing = FortiOsNames.holderOf(device, iface.
|
|
466
|
+
const outgoing = FortiOsNames.holderOf(device, iface.local_address);
|
|
462
467
|
const via = outgoing ? FortiOsTree.quote(FortiOsNames.nameOf(device, outgoing)) : '""';
|
|
463
468
|
|
|
464
469
|
// Between IPv6 endpoints the family is stated first: the addresses of the other family are not settings until it is.
|
|
465
|
-
const ipv6 = Address.family(iface.
|
|
470
|
+
const ipv6 = Address.family(iface.remote_address) === "ipv6";
|
|
466
471
|
|
|
467
|
-
if (
|
|
472
|
+
if (!vxlan) {
|
|
468
473
|
const tunnel = root.block("system gre-tunnel").entry(name).set("interface", via);
|
|
469
|
-
if (ipv6) tunnel.set("ip-version", 6).set("remote-gw6", iface.
|
|
470
|
-
else tunnel.set("remote-gw", iface.
|
|
474
|
+
if (ipv6) tunnel.set("ip-version", 6).set("remote-gw6", iface.remote_address).set("local-gw6", iface.local_address);
|
|
475
|
+
else tunnel.set("remote-gw", iface.remote_address).set("local-gw", iface.local_address);
|
|
471
476
|
} else {
|
|
472
|
-
const tunnel = root.block("system vxlan").entry(name).set("interface", via).set("vni",
|
|
473
|
-
if (ipv6) tunnel.set("ip-version", "ipv6-unicast").set("remote-ip6", FortiOsTree.quote(
|
|
474
|
-
else tunnel.set("remote-ip", FortiOsTree.quote(
|
|
475
|
-
if (
|
|
477
|
+
const tunnel = root.block("system vxlan").entry(name).set("interface", via).set("vni", vxlan.vni);
|
|
478
|
+
if (ipv6) tunnel.set("ip-version", "ipv6-unicast").set("remote-ip6", FortiOsTree.quote(vxlan.remote_address));
|
|
479
|
+
else tunnel.set("remote-ip", FortiOsTree.quote(vxlan.remote_address));
|
|
480
|
+
if (vxlan.port !== undefined && vxlan.port !== 4789) tunnel.set("dstport", vxlan.port);
|
|
476
481
|
}
|
|
477
482
|
|
|
478
483
|
const entry = block.entry(name);
|
|
479
484
|
entry.set("vdom", FortiOsTree.quote(FortiOsVdoms.vdomOf(device, name)));
|
|
480
|
-
entry.set("type",
|
|
485
|
+
entry.set("type", vxlan ? "vxlan" : "tunnel");
|
|
481
486
|
entry.set("interface", via);
|
|
482
|
-
if (
|
|
487
|
+
if (vxlan?.mac) entry.set("macaddr", vxlan.mac.toLowerCase());
|
|
483
488
|
if (iface.description) entry.set("description", FortiOsTree.quote(iface.description));
|
|
484
489
|
if (iface.mtu) entry.set("mtu-override", "enable").set("mtu", iface.mtu);
|
|
485
490
|
FortiOsRender.redirects(entry, device);
|
|
@@ -489,14 +494,14 @@ export class FortiOsRender {
|
|
|
489
494
|
|
|
490
495
|
/** The platform sends ICMP redirects unless an interface says otherwise; the config sends them only when asked. */
|
|
491
496
|
private static redirects(entry: FortiOsBlock, device: Device): void {
|
|
492
|
-
if (!device.system?.
|
|
497
|
+
if (!device.system?.ip_stack?.icmp_redirects) entry.set("icmp-send-redirect", "disable");
|
|
493
498
|
}
|
|
494
499
|
|
|
495
500
|
/** The one port or LAG that carries a VLAN tagged, which its VLAN interface sits on. `unsupported()` refuses any other case. */
|
|
496
501
|
static vlanParent(device: Device, vlan: string): string | undefined {
|
|
497
502
|
const carriers = [
|
|
498
|
-
...
|
|
499
|
-
...Object.entries(device.interfaces ?? {}).filter(([,
|
|
503
|
+
...Derive.ports(device).filter(([, port]) => !port.in_lag && port.trunk_vlans?.includes(vlan)),
|
|
504
|
+
...Object.entries(device.interfaces?.lag ?? {}).filter(([, lag]) => lag?.trunk_vlans?.includes(vlan)),
|
|
500
505
|
];
|
|
501
506
|
|
|
502
507
|
return carriers.length === 1 ? carriers[0]![0] : undefined;
|
|
@@ -522,11 +527,11 @@ export class FortiOsRender {
|
|
|
522
527
|
if (vrf) entry.set("vrf", vrf);
|
|
523
528
|
|
|
524
529
|
// A tunnel holds an IPv4 address as a host and its peer with the link's mask.
|
|
525
|
-
const peer = settings.addresses?.find((item): item is { address: string;
|
|
530
|
+
const peer = settings.addresses?.find((item): item is { address: string; peer_address: string } => typeof item !== "string");
|
|
526
531
|
|
|
527
532
|
if (peer) {
|
|
528
533
|
entry.set("ip", `${Address.host(peer.address)} 255.255.255.255`);
|
|
529
|
-
entry.set("remote-ip", `${peer.
|
|
534
|
+
entry.set("remote-ip", `${peer.peer_address} ${Address.netmask(Address.split(peer.address).len)}`);
|
|
530
535
|
entry.set("allowaccess", access);
|
|
531
536
|
} else if (ipv4.length) {
|
|
532
537
|
entry.set("ip", FortiOsNames.subnet(ipv4[0]!));
|
|
@@ -544,7 +549,7 @@ export class FortiOsRender {
|
|
|
544
549
|
);
|
|
545
550
|
}
|
|
546
551
|
|
|
547
|
-
const relays = Object.values(device.dhcp_relay ?? {}).filter((relay) => relay.interface === name);
|
|
552
|
+
const relays = Object.values(device.services?.dhcp_relay ?? {}).filter((relay) => relay.interface === name);
|
|
548
553
|
const relayed = relays.flatMap((relay) => relay.servers);
|
|
549
554
|
const relayed4 = relayed.filter((server) => Address.family(server) === "ipv4");
|
|
550
555
|
const relayed6 = relayed.filter((server) => Address.family(server) === "ipv6");
|
|
@@ -589,15 +594,16 @@ export class FortiOsRender {
|
|
|
589
594
|
}
|
|
590
595
|
|
|
591
596
|
// LLDP follows the device unless a port says otherwise, or the device lists where it runs.
|
|
592
|
-
const
|
|
597
|
+
const lldp = device.switching?.lldp;
|
|
598
|
+
const listed = typeof lldp === "object" && lldp.interfaces.includes(name);
|
|
593
599
|
if (settings.lldp === false) entry.set("lldp-transmission", "disable").set("lldp-reception", "disable");
|
|
594
|
-
else if (listed || (settings.lldp === true &&
|
|
600
|
+
else if (listed || (settings.lldp === true && lldp !== true))
|
|
595
601
|
entry.set("lldp-transmission", "enable").set("lldp-reception", "enable");
|
|
596
602
|
|
|
597
603
|
// A tunnel is not sampled: its traffic is, on the interface it leaves through.
|
|
598
|
-
const flow = device.flow_export;
|
|
599
|
-
const tunnel =
|
|
600
|
-
const sampled = flow && !tunnel && (!flow.interfaces || flow.interfaces.includes(name
|
|
604
|
+
const flow = device.monitoring?.flow_export;
|
|
605
|
+
const tunnel = FortiOsNames.isTunnel(device, name);
|
|
606
|
+
const sampled = flow && !tunnel && (!flow.interfaces || flow.interfaces.includes(name));
|
|
601
607
|
|
|
602
608
|
if (sampled && flow.protocol === "sflow") {
|
|
603
609
|
entry.set("sflow-sampler", "enable");
|
|
@@ -619,7 +625,8 @@ export class FortiOsRender {
|
|
|
619
625
|
if (ipv6[1]) block.set("ip6-secondary", ipv6[1]);
|
|
620
626
|
|
|
621
627
|
// Recursive, the default mode, answers from what the device has resolved itself.
|
|
622
|
-
if (device.
|
|
628
|
+
if (device.services?.dns_server)
|
|
629
|
+
for (const name of FortiOsNames.addressed(device)) servers.entry(FortiOsNames.nameOf(device, name));
|
|
623
630
|
}
|
|
624
631
|
|
|
625
632
|
/** The servers the device keeps time from, and the interfaces it answers time queries on when it serves them. */
|
|
@@ -635,7 +642,7 @@ export class FortiOsRender {
|
|
|
635
642
|
servers.forEach((server, index) => list.entry(index + 1).set("server", FortiOsTree.quote(server)));
|
|
636
643
|
}
|
|
637
644
|
|
|
638
|
-
if (
|
|
645
|
+
if (device.services?.ntp_server) {
|
|
639
646
|
block.set("server-mode", "enable");
|
|
640
647
|
block.set("interface", FortiOsTree.quoteAll(FortiOsNames.addressed(device).map((name) => FortiOsNames.nameOf(device, name))));
|
|
641
648
|
}
|
|
@@ -643,7 +650,7 @@ export class FortiOsRender {
|
|
|
643
650
|
|
|
644
651
|
/** SNMP version 2c with one community, guarded by its hosts, and version 3 users. */
|
|
645
652
|
private static snmp(root: FortiOsBlock, device: Device): void {
|
|
646
|
-
const snmp = device.
|
|
653
|
+
const snmp = device.monitoring?.snmp;
|
|
647
654
|
if (!snmp) return;
|
|
648
655
|
|
|
649
656
|
const sysinfo = root.block("system snmp sysinfo");
|
|
@@ -655,7 +662,7 @@ export class FortiOsRender {
|
|
|
655
662
|
const community = root.block("system snmp community").entry(1);
|
|
656
663
|
community.set("name", FortiOsTree.quote(Secrets.marker(snmp.community)));
|
|
657
664
|
|
|
658
|
-
const allow = snmp.allow ?? device
|
|
665
|
+
const allow = snmp.allow ?? FortiOsNames.allowed(device);
|
|
659
666
|
const ipv4 = allow.filter((prefix) => Address.family(Address.host(prefix)) === "ipv4");
|
|
660
667
|
const ipv6 = allow.filter((prefix) => Address.family(Address.host(prefix)) === "ipv6");
|
|
661
668
|
ipv4.forEach((prefix, index) =>
|
|
@@ -701,7 +708,7 @@ export class FortiOsRender {
|
|
|
701
708
|
// The disk keeps logs unless told not to; the config keeps them only in memory.
|
|
702
709
|
root.block("log disk setting").set("status", "disable");
|
|
703
710
|
|
|
704
|
-
(
|
|
711
|
+
(device.monitoring?.syslog ?? []).slice(0, FortiOsRender.COLLECTORS).forEach((collector, index) => {
|
|
705
712
|
const number = index === 0 ? "" : String(index + 1);
|
|
706
713
|
const setting = root.block(`log syslogd${number} setting`);
|
|
707
714
|
setting.set("status", "enable");
|
|
@@ -714,7 +721,7 @@ export class FortiOsRender {
|
|
|
714
721
|
|
|
715
722
|
/** One collector, and sampling on each interface it observes. */
|
|
716
723
|
private static flowExport(root: FortiOsBlock, device: Device): void {
|
|
717
|
-
const flow = device.flow_export;
|
|
724
|
+
const flow = device.monitoring?.flow_export;
|
|
718
725
|
const collector = flow?.collectors[0];
|
|
719
726
|
if (!flow || !collector) return;
|
|
720
727
|
|
|
@@ -730,7 +737,7 @@ export class FortiOsRender {
|
|
|
730
737
|
}
|
|
731
738
|
|
|
732
739
|
private static dhcpServers(block: FortiOsBlock, device: Device): void {
|
|
733
|
-
Object.values(device.
|
|
740
|
+
Object.values(device.services?.dhcp_server ?? {}).forEach((server, index) => {
|
|
734
741
|
const entry = block.entry(index + 1);
|
|
735
742
|
if (server.lease_time !== undefined && server.lease_time !== 604800) entry.set("lease-time", server.lease_time);
|
|
736
743
|
entry.set("default-gateway", server.gateway);
|
|
@@ -750,10 +757,10 @@ export class FortiOsRender {
|
|
|
750
757
|
}
|
|
751
758
|
|
|
752
759
|
/** Each route by its position. A route goes in the routing table of the interface it leaves through. A default route is the platform's default destination and is not printed. */
|
|
753
|
-
private static staticRoutes(ipv4: FortiOsBlock, ipv6: FortiOsBlock, device: Device): void {
|
|
760
|
+
private static staticRoutes(ipv4: FortiOsBlock, ipv6: FortiOsBlock, device: Device, routes: readonly StaticRoute[]): void {
|
|
754
761
|
const counters = { ipv4: 0, ipv6: 0 };
|
|
755
762
|
|
|
756
|
-
for (const route of
|
|
763
|
+
for (const route of routes) {
|
|
757
764
|
const family = Address.family(Address.split(route.prefix).addr);
|
|
758
765
|
const entry = (family === "ipv4" ? ipv4 : ipv6).entry(++counters[family]);
|
|
759
766
|
const prefix = FortiOsNames.network(route.prefix);
|
|
@@ -761,10 +768,10 @@ export class FortiOsRender {
|
|
|
761
768
|
if (family === "ipv6" && prefix !== "::/0") entry.set("dst", prefix);
|
|
762
769
|
if (family === "ipv4" && prefix !== "0.0.0.0/0") entry.set("dst", FortiOsNames.subnet(prefix));
|
|
763
770
|
|
|
764
|
-
if (route.
|
|
771
|
+
if (route.next_hop) entry.set("gateway", route.next_hop);
|
|
765
772
|
|
|
766
773
|
// In transparent mode a route names no interface: it carries only the management address's own traffic.
|
|
767
|
-
const outgoing = route.interface ?? (route.
|
|
774
|
+
const outgoing = route.interface ?? (route.next_hop ? FortiOsNames.interfaceFor(device, route.next_hop, route.vrf) : undefined);
|
|
768
775
|
if (outgoing && !route.blackhole && !FortiOsNames.isTransparent(device))
|
|
769
776
|
entry.set("device", FortiOsTree.quote(FortiOsNames.nameOf(device, outgoing)));
|
|
770
777
|
|
|
@@ -793,7 +800,7 @@ export class FortiOsRender {
|
|
|
793
800
|
private static helpers(block: FortiOsBlock, device: Device): void {
|
|
794
801
|
let number = 0;
|
|
795
802
|
|
|
796
|
-
for (const helper of device.firewall?.
|
|
803
|
+
for (const helper of device.firewall?.conntrack_helpers ?? []) {
|
|
797
804
|
for (const [name, protocol, port] of FortiOsRender.HELPERS[helper] ?? [])
|
|
798
805
|
block.entry(++number).set("name", name).set("protocol", protocol).set("port", port);
|
|
799
806
|
}
|