@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
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
// Every key Circuit takes, at run time, so `circuit validate` stops on one it doesn't know. One source: the types. Each shape here is typed
|
|
2
|
+
// `ShapeOf<the type>`, which needs every key of the type and allows no other, so a key added to, renamed in or removed from a type
|
|
3
|
+
// fails `tsc` until its shape says the same. The two can't drift.
|
|
4
|
+
|
|
5
|
+
import type { CommunityScheme } from "./communities";
|
|
6
|
+
import type { AnyDevice } from "./device";
|
|
7
|
+
import type { FilterRule, NatRule } from "./firewall";
|
|
8
|
+
import type { Checks, Integrations, NetworkSecrets, NetworkSettings, Prefixes, Site } from "./network";
|
|
9
|
+
import type { CallRule, PolicyRule, RouteMatch, RouteRule, RouteSet, RoutingRule } from "./routing";
|
|
10
|
+
import type { CommunitiesExport, CommunitiesImport } from "./communities";
|
|
11
|
+
import type { AclRule } from "./switching";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Where keys may be: `0` holds none Circuit checks (a number, a name, a list of names); a map lists its keys, each with its own shape;
|
|
15
|
+
* `{ "*": shape }` takes any key, such as a VLAN's name; `[shape]` is a list of that item.
|
|
16
|
+
*/
|
|
17
|
+
export type Shape = 0 | readonly [Shape] | { readonly [key: string]: Shape };
|
|
18
|
+
|
|
19
|
+
type Prim = string | number | boolean | bigint | symbol | null | undefined;
|
|
20
|
+
type AllKeys<U> = U extends unknown ? keyof U : never;
|
|
21
|
+
type ValueAt<U, K> = U extends unknown ? (K extends keyof U ? U[K] : never) : never;
|
|
22
|
+
type Item<T> = Extract<T, readonly unknown[]> extends readonly (infer E)[] ? E : never;
|
|
23
|
+
|
|
24
|
+
/** A map with any key (an index signature, such as VLANs by name), or one with keys written out. A key typed `never` is no key. */
|
|
25
|
+
type ObjectShape<O> =
|
|
26
|
+
{} extends Required<O>
|
|
27
|
+
? { readonly "*": ShapeOf<O[keyof O]> }
|
|
28
|
+
: { readonly [K in AllKeys<O> as [NonNullable<ValueAt<O, K>>] extends [never] ? never : K]-?: ShapeOf<ValueAt<O, K>> };
|
|
29
|
+
|
|
30
|
+
/** The shape of type `T`: what each shape here must be, exactly. A list of plain values is `0`, as a plain value. */
|
|
31
|
+
export type ShapeOf<T> = [Exclude<T, Prim>] extends [never]
|
|
32
|
+
? 0
|
|
33
|
+
: [Exclude<T, Prim | readonly unknown[]>] extends [never]
|
|
34
|
+
? ShapeOf<Item<T>> extends 0
|
|
35
|
+
? 0
|
|
36
|
+
: readonly [ShapeOf<Item<T>>]
|
|
37
|
+
: ObjectShape<Exclude<T, Prim | readonly unknown[]>>;
|
|
38
|
+
|
|
39
|
+
const SECRET = { secret: 0, secret_file: 0 } as const;
|
|
40
|
+
const RANGE = { min: 0, max: 0 } as const;
|
|
41
|
+
|
|
42
|
+
// ---------- rules, each kind on its own ----------
|
|
43
|
+
|
|
44
|
+
export const FILTER_MATCH = {
|
|
45
|
+
family: 0,
|
|
46
|
+
protocol: 0,
|
|
47
|
+
src: 0,
|
|
48
|
+
dst: 0,
|
|
49
|
+
src_address_set: 0,
|
|
50
|
+
dst_address_set: 0,
|
|
51
|
+
src_port: RANGE,
|
|
52
|
+
dst_port: RANGE,
|
|
53
|
+
in_interface: 0,
|
|
54
|
+
out_interface: 0,
|
|
55
|
+
in_interface_set: 0,
|
|
56
|
+
out_interface_set: 0,
|
|
57
|
+
connection_state: 0,
|
|
58
|
+
tcp_flags: { set: 0, unset: 0 },
|
|
59
|
+
} as const;
|
|
60
|
+
|
|
61
|
+
export const FILTER_RULE = { description: 0, if: FILTER_MATCH, then: 0, fast_path: 0 } as const satisfies ShapeOf<
|
|
62
|
+
FilterRule<string, string, string>
|
|
63
|
+
>;
|
|
64
|
+
export const NAT_RULE = { description: 0, if: FILTER_MATCH, then: 0, to_address: 0, to_port: 0 } as const satisfies ShapeOf<
|
|
65
|
+
NatRule<string, string, string>
|
|
66
|
+
>;
|
|
67
|
+
export const ACL_RULE = {
|
|
68
|
+
description: 0,
|
|
69
|
+
if: { family: 0, protocol: 0, src: 0, dst: 0, src_port: RANGE, dst_port: RANGE, vlan: 0 },
|
|
70
|
+
then: 0,
|
|
71
|
+
rate: 0,
|
|
72
|
+
} as const satisfies ShapeOf<AclRule<string>>;
|
|
73
|
+
export const ROUTING_RULE = {
|
|
74
|
+
description: 0,
|
|
75
|
+
match: { src: 0, dst: 0, in_interface: 0 },
|
|
76
|
+
routing_table: 0,
|
|
77
|
+
fallback: 0,
|
|
78
|
+
} as const satisfies ShapeOf<RoutingRule<string, string>>;
|
|
79
|
+
|
|
80
|
+
export const ROUTE_MATCH = {
|
|
81
|
+
family: 0,
|
|
82
|
+
prefix: 0,
|
|
83
|
+
prefix_set: 0,
|
|
84
|
+
prefix_length: RANGE,
|
|
85
|
+
as_path_length: RANGE,
|
|
86
|
+
as_path_contains: 0,
|
|
87
|
+
rpki: 0,
|
|
88
|
+
standard_community: 0,
|
|
89
|
+
large_community: 0,
|
|
90
|
+
} as const satisfies ShapeOf<RouteMatch<string>>;
|
|
91
|
+
export const ROUTE_SET = {
|
|
92
|
+
local_pref: 0,
|
|
93
|
+
med: 0,
|
|
94
|
+
prepend: 0,
|
|
95
|
+
standard_communities: 0,
|
|
96
|
+
add_standard_communities: 0,
|
|
97
|
+
remove_standard_communities: 0,
|
|
98
|
+
large_communities: 0,
|
|
99
|
+
add_large_communities: 0,
|
|
100
|
+
remove_large_communities: 0,
|
|
101
|
+
next_hop: 0,
|
|
102
|
+
preferred_source: 0,
|
|
103
|
+
blackhole: 0,
|
|
104
|
+
} as const satisfies ShapeOf<RouteSet>;
|
|
105
|
+
/** A route policy rule of each kind: on routes, a call, a communities import, a communities export. */
|
|
106
|
+
export const ROUTE_RULE = { description: 0, if: ROUTE_MATCH, set: ROUTE_SET, then: 0 } as const satisfies ShapeOf<RouteRule<string>>;
|
|
107
|
+
export const CALL_RULE = { call: 0 } as const satisfies ShapeOf<CallRule<string>>;
|
|
108
|
+
export const COMMUNITIES_IMPORT = { communities: 0, accept_requests: 0, add_tags: 0 } as const satisfies ShapeOf<CommunitiesImport<string>>;
|
|
109
|
+
export const COMMUNITIES_EXPORT = { communities: 0, keep_ours: 0, send_tags: 0 } as const satisfies ShapeOf<CommunitiesExport>;
|
|
110
|
+
export const POLICY_RULE = { ...ROUTE_RULE, ...CALL_RULE, ...COMMUNITIES_IMPORT, ...COMMUNITIES_EXPORT } as const satisfies ShapeOf<
|
|
111
|
+
PolicyRule<string, string>
|
|
112
|
+
>;
|
|
113
|
+
|
|
114
|
+
/** What `then` may be in each kind of rule. */
|
|
115
|
+
export const ACTIONS = {
|
|
116
|
+
filter: ["accept", "drop", "reject"],
|
|
117
|
+
nat: ["masquerade", "snat", "dnat"],
|
|
118
|
+
acl: ["accept", "drop"],
|
|
119
|
+
policy: ["accept", "reject"],
|
|
120
|
+
} as const;
|
|
121
|
+
|
|
122
|
+
type Same<A, B> = [A] extends [B] ? ([B] extends [A] ? true : false) : false;
|
|
123
|
+
// Each list is exactly what its type's `then` takes: `tsc` fails here otherwise.
|
|
124
|
+
true satisfies Same<(typeof ACTIONS.filter)[number], FilterRule<string, string, string>["then"]> &
|
|
125
|
+
Same<(typeof ACTIONS.nat)[number], NatRule<string, string, string>["then"]> &
|
|
126
|
+
Same<(typeof ACTIONS.acl)[number], AclRule<string>["then"]> &
|
|
127
|
+
Same<(typeof ACTIONS.policy)[number], NonNullable<RouteRule<string>["then"]>>;
|
|
128
|
+
|
|
129
|
+
// ---------- a device ----------
|
|
130
|
+
|
|
131
|
+
const SERVICE = { allow: 0, port: 0 } as const;
|
|
132
|
+
const STATIC_ROUTE = {
|
|
133
|
+
prefix: 0,
|
|
134
|
+
next_hop: 0,
|
|
135
|
+
interface: 0,
|
|
136
|
+
blackhole: 0,
|
|
137
|
+
bfd: 0,
|
|
138
|
+
distance: 0,
|
|
139
|
+
vrf: 0,
|
|
140
|
+
routing_table: 0,
|
|
141
|
+
description: 0,
|
|
142
|
+
} as const;
|
|
143
|
+
const ROUTED = {
|
|
144
|
+
addresses: [{ address: 0, peer_address: 0 }],
|
|
145
|
+
ipv6_ra: 0,
|
|
146
|
+
hardware_offload: 0,
|
|
147
|
+
vrrp: [{ id: 0, address: 0, priority: 0, preempt: 0, interval: 0 }],
|
|
148
|
+
vrf: 0,
|
|
149
|
+
exchange: { name: 0, speed: 0, peeringdb_ixlan: 0 },
|
|
150
|
+
} as const;
|
|
151
|
+
const THRESHOLD = { pps: 0, percent: 0 } as const;
|
|
152
|
+
const SWITCHED = {
|
|
153
|
+
access_vlan: 0,
|
|
154
|
+
trunk_vlans: 0,
|
|
155
|
+
native_vlan: 0,
|
|
156
|
+
stp: { edge: 0, cost: 0, priority: 0 },
|
|
157
|
+
storm_control: { broadcast: THRESHOLD, multicast: THRESHOLD, unknown_unicast: THRESHOLD },
|
|
158
|
+
mac_limit: 0,
|
|
159
|
+
ethertypes: 0,
|
|
160
|
+
} as const;
|
|
161
|
+
const BASE = { ...ROUTED, description: 0, mtu: 0 } as const;
|
|
162
|
+
const NEIGHBOR_SETTINGS = {
|
|
163
|
+
remote_as: 0,
|
|
164
|
+
description: 0,
|
|
165
|
+
local_address: 0,
|
|
166
|
+
import_policy: 0,
|
|
167
|
+
export_policy: 0,
|
|
168
|
+
role: 0,
|
|
169
|
+
multihop: 0,
|
|
170
|
+
password: SECRET,
|
|
171
|
+
max_prefixes: 0,
|
|
172
|
+
hold_time: 0,
|
|
173
|
+
keepalive: 0,
|
|
174
|
+
address_families: 0,
|
|
175
|
+
passive: 0,
|
|
176
|
+
bfd: 0,
|
|
177
|
+
routing_table: 0,
|
|
178
|
+
communities: { groups: 0, blackhole: 0 },
|
|
179
|
+
} as const;
|
|
180
|
+
const CHAIN = { default_action: 0, rules: [FILTER_RULE] } as const;
|
|
181
|
+
|
|
182
|
+
/** Every key a device may have, its parts merged in. */
|
|
183
|
+
export const DEVICE = {
|
|
184
|
+
name: 0,
|
|
185
|
+
site: 0,
|
|
186
|
+
role: 0,
|
|
187
|
+
platform: 0,
|
|
188
|
+
model: 0,
|
|
189
|
+
connection: {
|
|
190
|
+
host: 0,
|
|
191
|
+
user: 0,
|
|
192
|
+
port: 0,
|
|
193
|
+
console: { jump: { host: 0, user: 0, password: SECRET, port: 0, cloudflared: 0 }, port: 0, baud: 0, password_only: 0 },
|
|
194
|
+
},
|
|
195
|
+
system: {
|
|
196
|
+
timezone: 0,
|
|
197
|
+
banner: 0,
|
|
198
|
+
release_channel: 0,
|
|
199
|
+
ip_stack: { icmp_redirects: 0, syn_cookies: 0 },
|
|
200
|
+
alarms: { "*": { severity: 0 } },
|
|
201
|
+
dns: { servers: 0 },
|
|
202
|
+
ntp: { servers: 0 },
|
|
203
|
+
logging: { local: { level: 0, files: 0, lines_per_file: 0 } },
|
|
204
|
+
hardware_offload: 0,
|
|
205
|
+
users: { "*": { role: 0, password: SECRET, description: 0, ssh_keys: 0 } },
|
|
206
|
+
certificates: { "*": { certificate_file: 0, private_key: SECRET } },
|
|
207
|
+
// A VDOM's routes have no VRF.
|
|
208
|
+
vdoms: {
|
|
209
|
+
management: {
|
|
210
|
+
name: 0,
|
|
211
|
+
interfaces: 0,
|
|
212
|
+
static_routes: [
|
|
213
|
+
{ prefix: 0, next_hop: 0, interface: 0, blackhole: 0, bfd: 0, distance: 0, routing_table: 0, description: 0 },
|
|
214
|
+
],
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
management: {
|
|
219
|
+
allow: 0,
|
|
220
|
+
interfaces: 0,
|
|
221
|
+
ssh: { ...SERVICE, weak_crypto: 0, auth_timeout: 0, auth_retries: 0 },
|
|
222
|
+
telnet: SERVICE,
|
|
223
|
+
http: SERVICE,
|
|
224
|
+
https: { ...SERVICE, certificate: 0 },
|
|
225
|
+
api: SERVICE,
|
|
226
|
+
api_tls: { ...SERVICE, certificate: 0 },
|
|
227
|
+
ftp: SERVICE,
|
|
228
|
+
native_protocol: { ...SERVICE, interfaces: 0 },
|
|
229
|
+
console: { password: SECRET },
|
|
230
|
+
privilege_password: SECRET,
|
|
231
|
+
},
|
|
232
|
+
monitoring: {
|
|
233
|
+
snmp: {
|
|
234
|
+
...SERVICE,
|
|
235
|
+
community: SECRET,
|
|
236
|
+
users: { "*": { auth: 0, auth_password: SECRET, privacy: 0, privacy_password: SECRET } },
|
|
237
|
+
contact: 0,
|
|
238
|
+
location: 0,
|
|
239
|
+
},
|
|
240
|
+
syslog: [{ address: 0, port: 0, level: 0 }],
|
|
241
|
+
flow_export: { protocol: 0, collectors: [{ address: 0, port: 0 }], sampling: 0, interfaces: 0 },
|
|
242
|
+
transceivers: 0,
|
|
243
|
+
},
|
|
244
|
+
interfaces: {
|
|
245
|
+
ethernet: {
|
|
246
|
+
"*": {
|
|
247
|
+
...ROUTED,
|
|
248
|
+
...SWITCHED,
|
|
249
|
+
description: 0,
|
|
250
|
+
mtu: 0,
|
|
251
|
+
speed: 0,
|
|
252
|
+
lldp: 0,
|
|
253
|
+
ingress_acl: 0,
|
|
254
|
+
in_lag: 0,
|
|
255
|
+
link: { device: 0, port: 0, through: 0 },
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
lag: { "*": { ...BASE, ...SWITCHED, mode: 0, id: 0 } },
|
|
259
|
+
vlan_interface: { "*": { ...BASE, vlan: 0, mac: 0 } },
|
|
260
|
+
bridge: { "*": { ...BASE, member_interfaces: 0 } },
|
|
261
|
+
loopback: { "*": BASE },
|
|
262
|
+
gre: { "*": { ...BASE, local_address: 0, remote_address: 0 } },
|
|
263
|
+
vxlan: { "*": { ...BASE, vni: 0, local_address: 0, remote_address: 0, port: 0, mac: 0 } },
|
|
264
|
+
wireguard: {
|
|
265
|
+
"*": {
|
|
266
|
+
...BASE,
|
|
267
|
+
listen_port: 0,
|
|
268
|
+
endpoint: 0,
|
|
269
|
+
private_key: SECRET,
|
|
270
|
+
peers: [{ name: 0, public_key: 0, allowed_addresses: 0, endpoint: 0, keepalive: 0, client_allowed_addresses: 0 }],
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
switching: {
|
|
275
|
+
vlans: { "*": { id: 0, description: 0, untrusted: 0 } },
|
|
276
|
+
stp: { mode: 0, priority: 0 },
|
|
277
|
+
lldp: { interfaces: 0 },
|
|
278
|
+
acls: { "*": [ACL_RULE] },
|
|
279
|
+
},
|
|
280
|
+
services: {
|
|
281
|
+
dhcp_server: {
|
|
282
|
+
"*": {
|
|
283
|
+
interface: 0,
|
|
284
|
+
network: 0,
|
|
285
|
+
gateway: 0,
|
|
286
|
+
dns: 0,
|
|
287
|
+
pool: 0,
|
|
288
|
+
lease_time: 0,
|
|
289
|
+
reservations: [{ address: 0, mac: 0, description: 0 }],
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
dhcp_relay: { "*": { interface: 0, servers: 0 } },
|
|
293
|
+
dns_server: 0,
|
|
294
|
+
ntp_server: 0,
|
|
295
|
+
},
|
|
296
|
+
routing: {
|
|
297
|
+
router_id: 0,
|
|
298
|
+
tables: { "*": { description: 0 } },
|
|
299
|
+
vrfs: { "*": { description: 0 } },
|
|
300
|
+
policy_routing: [ROUTING_RULE],
|
|
301
|
+
static_routes: [STATIC_ROUTE],
|
|
302
|
+
bgp: {
|
|
303
|
+
asn: 0,
|
|
304
|
+
originated_prefixes: 0,
|
|
305
|
+
neighbor_groups: { "*": NEIGHBOR_SETTINGS },
|
|
306
|
+
neighbors: { "*": { ...NEIGHBOR_SETTINGS, address: 0, neighbor_group: 0 } },
|
|
307
|
+
},
|
|
308
|
+
ospf: {
|
|
309
|
+
address_families: 0,
|
|
310
|
+
areas: { "*": { interfaces: { "*": { cost: 0, passive: 0, network: 0, bfd: 0 } } } },
|
|
311
|
+
redistribute: 0,
|
|
312
|
+
},
|
|
313
|
+
bfd: { interval: 0, multiplier: 0 },
|
|
314
|
+
rpki: [{ address: 0, port: 0, description: 0 }],
|
|
315
|
+
policies: { "*": [POLICY_RULE] },
|
|
316
|
+
prefix_sets: { "*": { prefixes: { "*": 0 }, source: { registry: 0, query: 0, family: 0 } } },
|
|
317
|
+
},
|
|
318
|
+
firewall: {
|
|
319
|
+
address_sets: { "*": 0 },
|
|
320
|
+
interface_sets: { "*": 0 },
|
|
321
|
+
filter: { input: CHAIN, forward: CHAIN, output: CHAIN },
|
|
322
|
+
nat: { source: [NAT_RULE], destination: [NAT_RULE] },
|
|
323
|
+
conntrack_helpers: 0,
|
|
324
|
+
untracked: 0,
|
|
325
|
+
},
|
|
326
|
+
} as const satisfies ShapeOf<AnyDevice>;
|
|
327
|
+
|
|
328
|
+
// ---------- the network ----------
|
|
329
|
+
|
|
330
|
+
const OPTION = { code: 0, description: 0 } as const;
|
|
331
|
+
/** Every option of a community scheme. */
|
|
332
|
+
export const COMMUNITIES = {
|
|
333
|
+
asn: 0,
|
|
334
|
+
groups: { "*": { value: 0, description: 0 } },
|
|
335
|
+
learned_from_group: OPTION,
|
|
336
|
+
learned_at_site: OPTION,
|
|
337
|
+
learned_from_as: OPTION,
|
|
338
|
+
do_not_announce_to_as: OPTION,
|
|
339
|
+
do_not_announce_to_group: OPTION,
|
|
340
|
+
prepend: { once: OPTION, twice: OPTION, three_times: OPTION },
|
|
341
|
+
blackhole: OPTION,
|
|
342
|
+
custom_descriptions: { "*": 0 },
|
|
343
|
+
} as const satisfies ShapeOf<CommunityScheme>;
|
|
344
|
+
|
|
345
|
+
/** Circuit's built-in checks, each with its settings. Any other name in `checks` is one of the project's own. */
|
|
346
|
+
export const CHECKS = {
|
|
347
|
+
untrusted_vlans: { filtering_devices: 0, mixed_links: 0 },
|
|
348
|
+
exports_end_in_reject: 0,
|
|
349
|
+
imports_end_in_accept_or_reject: 0,
|
|
350
|
+
native_vlans_are_dead_ends: 0,
|
|
351
|
+
tunnels_outside_offered: 0,
|
|
352
|
+
same_vlan_ids: 0,
|
|
353
|
+
} as const satisfies ShapeOf<Checks<string>>;
|
|
354
|
+
|
|
355
|
+
/** Every key `circuit()` takes. `checks` is looked at apart: a project's own check has a name of its own. */
|
|
356
|
+
export const NETWORK = {
|
|
357
|
+
asn: 0,
|
|
358
|
+
prefixes: { "*": { "*": 0 } } satisfies ShapeOf<Prefixes>,
|
|
359
|
+
communities: COMMUNITIES,
|
|
360
|
+
sites: { "*": { description: 0, number: 0 } satisfies ShapeOf<Site> },
|
|
361
|
+
checks: 0,
|
|
362
|
+
integrations: {
|
|
363
|
+
peeringdb: { api_key: SECRET },
|
|
364
|
+
librenms: { url: 0, api_token: SECRET },
|
|
365
|
+
netbox: { url: 0, api_token: SECRET },
|
|
366
|
+
} satisfies ShapeOf<Integrations>,
|
|
367
|
+
secrets: { file: 0 } satisfies ShapeOf<NetworkSecrets>,
|
|
368
|
+
} as const satisfies { readonly [K in keyof NetworkSettings<{}, number>]-?: Shape };
|
|
369
|
+
|
|
370
|
+
// `satisfies` misses a key too many that comes from a spread or another constant. Each side assignable to the other doesn't: every key
|
|
371
|
+
// of a shape is required, so a shape with one more key, at any depth, is not the type's. `tsc` fails here then.
|
|
372
|
+
true satisfies Same<typeof DEVICE, ShapeOf<AnyDevice>> &
|
|
373
|
+
Same<typeof FILTER_RULE, ShapeOf<FilterRule<string, string, string>>> &
|
|
374
|
+
Same<typeof NAT_RULE, ShapeOf<NatRule<string, string, string>>> &
|
|
375
|
+
Same<typeof ACL_RULE, ShapeOf<AclRule<string>>> &
|
|
376
|
+
Same<typeof ROUTING_RULE, ShapeOf<RoutingRule<string, string>>> &
|
|
377
|
+
Same<typeof ROUTE_RULE, ShapeOf<RouteRule<string>>> &
|
|
378
|
+
Same<typeof CALL_RULE, ShapeOf<CallRule<string>>> &
|
|
379
|
+
Same<typeof COMMUNITIES_IMPORT, ShapeOf<CommunitiesImport<string>>> &
|
|
380
|
+
Same<typeof COMMUNITIES_EXPORT, ShapeOf<CommunitiesExport>> &
|
|
381
|
+
Same<typeof COMMUNITIES, ShapeOf<CommunityScheme>> &
|
|
382
|
+
Same<typeof CHECKS, ShapeOf<Checks<string>>> &
|
|
383
|
+
Same<typeof NETWORK.integrations, ShapeOf<Integrations>> &
|
|
384
|
+
Same<typeof NETWORK.sites, ShapeOf<Readonly<Record<string, Site>>>> &
|
|
385
|
+
Same<typeof NETWORK.prefixes, ShapeOf<Prefixes>> &
|
|
386
|
+
Same<keyof typeof NETWORK, keyof NetworkSettings<{}, number>>;
|
|
387
|
+
|
|
388
|
+
// ---------- the check ----------
|
|
389
|
+
|
|
390
|
+
/** A key Circuit doesn't take: where it is, dot by dot (`firewall.filter.input.rules[3].if`), the key, and the keys taken there. */
|
|
391
|
+
export type UnknownKey = { path: string; key: string; known: string[] };
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Every key in `value` that `shape` doesn't take. A list met where the shape is a map is a list of such maps (a port range, or several).
|
|
395
|
+
* A plain value met where the shape is a map is one the type allows beside it, such as an address written alone.
|
|
396
|
+
*/
|
|
397
|
+
export function unknownKeys(value: unknown, shape: Shape, path = ""): UnknownKey[] {
|
|
398
|
+
if (shape === 0 || !value || typeof value !== "object") return [];
|
|
399
|
+
if (Array.isArray(value)) {
|
|
400
|
+
const item: Shape = Array.isArray(shape) ? (shape as readonly [Shape])[0] : shape;
|
|
401
|
+
return value.flatMap((v, i) => unknownKeys(v, item, `${path}[${i}]`));
|
|
402
|
+
}
|
|
403
|
+
if (Array.isArray(shape)) return [];
|
|
404
|
+
const map = shape as { readonly [key: string]: Shape };
|
|
405
|
+
const at = (key: string) => (path ? `${path}.${key}` : key);
|
|
406
|
+
if (Object.hasOwn(map, "*")) return Object.entries(value).flatMap(([k, v]) => unknownKeys(v, map["*"]!, at(k)));
|
|
407
|
+
return Object.entries(value).flatMap(([k, v]) =>
|
|
408
|
+
Object.hasOwn(map, k) ? unknownKeys(v, map[k]!, at(k)) : [{ path, key: k, known: Object.keys(map) }]
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/** The closest of `names`, for "Did you mean": at most two letters off, or a quarter of a long name. */
|
|
413
|
+
export function closest(name: string, names: Iterable<string>): string | undefined {
|
|
414
|
+
const distance = (a: string, b: string) => {
|
|
415
|
+
let row = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
416
|
+
for (let i = 1; i <= a.length; i++) {
|
|
417
|
+
const next = [i];
|
|
418
|
+
for (let j = 1; j <= b.length; j++)
|
|
419
|
+
next[j] = Math.min(row[j]! + 1, next[j - 1]! + 1, row[j - 1]! + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
420
|
+
row = next;
|
|
421
|
+
}
|
|
422
|
+
return row[b.length]!;
|
|
423
|
+
};
|
|
424
|
+
let best: [string, number] | undefined;
|
|
425
|
+
for (const n of names)
|
|
426
|
+
if (distance(name, n) <= Math.max(2, name.length / 4) && (!best || distance(name, n) < best[1])) best = [n, distance(name, n)];
|
|
427
|
+
return best?.[0];
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/** What to say about an unknown key: "Did you mean" when one is close, else the keys taken there. */
|
|
431
|
+
export function unknownKeyHelp({ key, known }: UnknownKey): string {
|
|
432
|
+
const hint = closest(key, known);
|
|
433
|
+
return hint ? `Did you mean ${hint}?` : `It takes: ${known.join(", ")}.`;
|
|
434
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// How people and tools log in: `management.ssh`, `.https`, `.api`, `.native_protocol`, `.console`, and the rest. SNMP polling is `monitoring.snmp`.
|
|
2
|
+
|
|
3
|
+
import type { Prefix, Secret } from "./common";
|
|
4
|
+
|
|
5
|
+
/** A way into the device. Absent means turned off. */
|
|
6
|
+
export type Service = {
|
|
7
|
+
/** Sources allowed. `management.allow` when absent. */
|
|
8
|
+
allow?: readonly Prefix[];
|
|
9
|
+
/** TCP or UDP port. The protocol's own when absent. */
|
|
10
|
+
port?: number;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/** How the device is reached and managed. `I` is the device's ports and interfaces, `C` its certificates, `AS` its address sets. */
|
|
14
|
+
export type Management<I extends string, C extends string, AS extends string = string> = {
|
|
15
|
+
/**
|
|
16
|
+
* Sources allowed to every service that does not set its own: a list, or one of `firewall.address_sets` by name, so a device that
|
|
17
|
+
* filters on those sources too writes them once. Circuit writes the set's addresses here.
|
|
18
|
+
*/
|
|
19
|
+
allow?: readonly Prefix[] | AS;
|
|
20
|
+
/** Some of the device's ports and interfaces: the ones the services answer on; every other one answers ping alone. Every one with an address when absent. */
|
|
21
|
+
interfaces?: readonly I[];
|
|
22
|
+
/** Secure shell. */
|
|
23
|
+
ssh?: Service & {
|
|
24
|
+
/** Also offer legacy ciphers, MACs and key exchanges. Off when absent. */
|
|
25
|
+
weak_crypto?: boolean;
|
|
26
|
+
/** Seconds a login may take. */
|
|
27
|
+
auth_timeout?: number;
|
|
28
|
+
/** Failed attempts allowed per connection. */
|
|
29
|
+
auth_retries?: number;
|
|
30
|
+
};
|
|
31
|
+
/** Unencrypted remote login. */
|
|
32
|
+
telnet?: Service;
|
|
33
|
+
/** Web interface, unencrypted. */
|
|
34
|
+
http?: Service;
|
|
35
|
+
/** Web interface over TLS. */
|
|
36
|
+
https?: Service & {
|
|
37
|
+
/** One of `system.certificates`. */
|
|
38
|
+
certificate?: C;
|
|
39
|
+
};
|
|
40
|
+
/** The platform's own API, unencrypted. */
|
|
41
|
+
api?: Service;
|
|
42
|
+
/** The platform's own API over TLS. */
|
|
43
|
+
api_tls?: Service & {
|
|
44
|
+
/** One of `system.certificates`. */
|
|
45
|
+
certificate?: C;
|
|
46
|
+
};
|
|
47
|
+
/** File transfer, unencrypted. */
|
|
48
|
+
ftp?: Service;
|
|
49
|
+
/** The platform's own management protocol, such as Winbox. `interfaces` also allows it at layer 2 on those interfaces. */
|
|
50
|
+
native_protocol?: Service & {
|
|
51
|
+
/** Some of the device's ports and interfaces. */
|
|
52
|
+
interfaces?: readonly I[];
|
|
53
|
+
};
|
|
54
|
+
/** Console login uses this password. The device's users when absent. */
|
|
55
|
+
console?: {
|
|
56
|
+
/** The console password. */
|
|
57
|
+
password: Secret;
|
|
58
|
+
};
|
|
59
|
+
/** Password for privileged mode, on platforms that have one. */
|
|
60
|
+
privilege_password?: Secret;
|
|
61
|
+
};
|