@takodotid/circuit 0.34.3 → 0.35.0

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 (240) hide show
  1. package/README.md +13 -14
  2. package/dist/chunks/index-20kcjxjg.js +174 -0
  3. package/dist/chunks/index-2r4bbhac.js +977 -0
  4. package/dist/chunks/index-n7w3tg5y.js +149 -0
  5. package/dist/chunks/new-w3zys5y4.js +172 -0
  6. package/dist/chunks/server-gzsf5me5.js +259 -0
  7. package/dist/cli/index.js +11345 -9024
  8. package/dist/helpers/index.js +594 -0
  9. package/dist/index.js +9 -28
  10. package/dist/types/core/{addresses.d.ts → address-space.d.ts} +23 -7
  11. package/dist/types/core/address.d.ts +44 -0
  12. package/dist/types/core/cabling.d.ts +41 -0
  13. package/dist/types/core/cross-checks.d.ts +28 -0
  14. package/dist/types/core/define.d.ts +25 -3
  15. package/dist/types/core/project-files.d.ts +12 -0
  16. package/dist/types/core/schema-docs.d.ts +13 -4
  17. package/dist/types/core/secrets.d.ts +62 -21
  18. package/dist/types/core/{graph.d.ts → topology.d.ts} +32 -12
  19. package/dist/types/core/utils.d.ts +8 -0
  20. package/dist/types/core/validator.d.ts +19 -0
  21. package/dist/types/helpers/bgp.d.ts +97 -0
  22. package/dist/types/helpers/checks.d.ts +32 -0
  23. package/dist/types/helpers/exchange.d.ts +49 -0
  24. package/dist/types/helpers/filters.d.ts +51 -0
  25. package/dist/types/helpers/index.d.ts +10 -0
  26. package/dist/types/helpers/route-server.d.ts +52 -0
  27. package/dist/types/index.d.ts +5 -3
  28. package/dist/types/integrations/librenms.d.ts +60 -0
  29. package/dist/types/integrations/netbox.d.ts +89 -0
  30. package/dist/types/integrations/peeringdb.d.ts +24 -0
  31. package/dist/types/integrations/prometheus.d.ts +24 -0
  32. package/dist/types/integrations/registries.d.ts +8 -0
  33. package/dist/types/platforms/bgp-wait.d.ts +18 -0
  34. package/dist/types/platforms/catalog.d.ts +109 -0
  35. package/dist/types/platforms/derive.d.ts +17 -0
  36. package/dist/types/platforms/fortios/firewall.d.ts +45 -0
  37. package/dist/types/platforms/fortios/index.d.ts +134 -0
  38. package/dist/types/platforms/fortios/names.d.ts +52 -0
  39. package/dist/types/platforms/fortios/plan.d.ts +104 -0
  40. package/dist/types/platforms/fortios/render.d.ts +75 -0
  41. package/dist/types/platforms/fortios/routing.d.ts +56 -0
  42. package/dist/types/platforms/fortios/tree.d.ts +37 -0
  43. package/dist/types/platforms/index.d.ts +13 -0
  44. package/dist/types/platforms/junos/bgp.d.ts +44 -0
  45. package/dist/types/platforms/junos/index.d.ts +114 -0
  46. package/dist/types/platforms/junos/render.d.ts +154 -0
  47. package/dist/types/platforms/line-diff.d.ts +67 -0
  48. package/dist/types/platforms/platform.d.ts +120 -0
  49. package/dist/types/platforms/raisecom-ros/index.d.ts +71 -0
  50. package/dist/types/platforms/raisecom-ros/render.d.ts +41 -0
  51. package/dist/types/platforms/routeros/export.d.ts +59 -0
  52. package/dist/types/platforms/routeros/index.d.ts +96 -0
  53. package/dist/types/{adapters/devices → platforms}/routeros/models.d.ts +4 -0
  54. package/dist/types/platforms/routeros/plan.d.ts +54 -0
  55. package/dist/types/platforms/routeros/render.d.ts +240 -0
  56. package/dist/types/platforms/vrp/index.d.ts +83 -0
  57. package/dist/types/platforms/vrp/render.d.ts +96 -0
  58. package/dist/types/schema/common.d.ts +12 -6
  59. package/dist/types/schema/index.d.ts +1 -0
  60. package/dist/types/schema/interfaces.d.ts +14 -6
  61. package/dist/types/schema/policy.d.ts +18 -10
  62. package/dist/types/schema/routing.d.ts +14 -1
  63. package/dist/types/schema/system.d.ts +16 -6
  64. package/dist/types/schema/time-zones.d.ts +2 -0
  65. package/dist/types/transport/console.d.ts +79 -23
  66. package/dist/types/transport/path-check.d.ts +32 -0
  67. package/dist/types/transport/ssh.d.ts +17 -15
  68. package/package.json +17 -11
  69. package/src/cli/graph-format.ts +91 -0
  70. package/src/cli/index.ts +1074 -484
  71. package/src/cli/new.ts +208 -175
  72. package/src/cli/rename.ts +97 -91
  73. package/src/core/address-space.ts +320 -0
  74. package/src/core/address.ts +202 -0
  75. package/src/core/cabling.ts +154 -0
  76. package/src/core/cross-checks.ts +364 -0
  77. package/src/core/define.ts +26 -3
  78. package/src/core/project-files.ts +35 -0
  79. package/src/core/schema-docs.ts +138 -130
  80. package/src/core/secrets.ts +170 -76
  81. package/src/core/topology.ts +656 -0
  82. package/src/core/utils.ts +37 -0
  83. package/src/core/validator.ts +407 -0
  84. package/src/helpers/bgp.ts +301 -0
  85. package/src/helpers/checks.ts +178 -0
  86. package/src/helpers/exchange.ts +117 -0
  87. package/src/helpers/filters.ts +107 -0
  88. package/src/helpers/index.ts +12 -0
  89. package/src/helpers/route-server.ts +212 -0
  90. package/src/index.ts +6 -3
  91. package/src/integrations/librenms.ts +291 -0
  92. package/src/integrations/netbox.ts +620 -0
  93. package/src/integrations/peeringdb.ts +173 -0
  94. package/src/integrations/prometheus.ts +90 -0
  95. package/src/integrations/registries.ts +25 -0
  96. package/src/platforms/bgp-wait.ts +41 -0
  97. package/src/platforms/catalog.ts +69 -0
  98. package/src/platforms/derive.ts +117 -0
  99. package/src/{adapters/devices → platforms}/fortios/TRAPS.md +6 -0
  100. package/src/platforms/fortios/firewall.ts +377 -0
  101. package/src/platforms/fortios/index.ts +1060 -0
  102. package/src/platforms/fortios/names.ts +130 -0
  103. package/src/platforms/fortios/plan.ts +929 -0
  104. package/src/platforms/fortios/render.ts +779 -0
  105. package/src/platforms/fortios/routing.ts +476 -0
  106. package/src/platforms/fortios/tree.ts +129 -0
  107. package/src/platforms/index.ts +36 -0
  108. package/src/{adapters/devices → platforms}/junos/TRAPS.md +8 -0
  109. package/src/platforms/junos/bgp.ts +293 -0
  110. package/src/platforms/junos/index.ts +1006 -0
  111. package/src/platforms/junos/render.ts +967 -0
  112. package/src/platforms/line-diff.ts +244 -0
  113. package/src/platforms/platform.ts +122 -0
  114. package/src/platforms/raisecom-ros/TRAPS.md +40 -0
  115. package/src/platforms/raisecom-ros/index.ts +731 -0
  116. package/src/platforms/raisecom-ros/render.ts +328 -0
  117. package/src/{adapters/devices → platforms}/routeros/TRAPS.md +16 -2
  118. package/src/platforms/routeros/export.ts +342 -0
  119. package/src/platforms/routeros/index.ts +636 -0
  120. package/src/{adapters/devices → platforms}/routeros/models.ts +2 -0
  121. package/src/platforms/routeros/plan.ts +444 -0
  122. package/src/platforms/routeros/render.ts +1483 -0
  123. package/src/platforms/vrp/TRAPS.md +45 -0
  124. package/src/platforms/vrp/index.ts +652 -0
  125. package/src/platforms/vrp/render.ts +615 -0
  126. package/src/schema/common.ts +15 -6
  127. package/src/schema/index.ts +1 -0
  128. package/src/schema/interfaces.ts +15 -5
  129. package/src/schema/policy.ts +22 -10
  130. package/src/schema/routing.ts +16 -1
  131. package/src/schema/system.ts +16 -6
  132. package/src/schema/time-zones.ts +482 -0
  133. package/src/studio/data.ts +172 -168
  134. package/src/studio/git.ts +95 -90
  135. package/src/studio/public/app.js +161 -150
  136. package/src/studio/public/compare.js +219 -212
  137. package/src/studio/public/diff.js +77 -75
  138. package/src/studio/public/dom.js +118 -108
  139. package/src/studio/public/evaluate.js +584 -545
  140. package/src/studio/public/export.js +120 -118
  141. package/src/studio/public/graph.js +1343 -1266
  142. package/src/studio/public/net.js +115 -109
  143. package/src/studio/public/tables.js +1135 -1083
  144. package/src/studio/public/tools.js +561 -529
  145. package/src/studio/server.ts +202 -196
  146. package/src/transport/console.ts +363 -271
  147. package/src/transport/path-check.ts +91 -0
  148. package/src/transport/ssh.ts +98 -94
  149. package/templates/colocation/checks.ts +11 -6
  150. package/templates/colocation/edge-01.ts +9 -9
  151. package/templates/colocation/routing.ts +2 -2
  152. package/templates/colocation/tenancy.ts +3 -3
  153. package/templates/colocation/tor-01.ts +3 -3
  154. package/templates/edge-router/checks.ts +4 -4
  155. package/templates/edge-router/edge-01.ts +5 -5
  156. package/templates/edge-router/routing.ts +2 -2
  157. package/templates/project/AGENTS.md +5 -1
  158. package/templates/single-site/checks.ts +2 -2
  159. package/dist/chunks/index-47debt16.js +0 -956
  160. package/dist/chunks/index-v8zgr7wm.js +0 -128
  161. package/dist/chunks/new-ny89s7y7.js +0 -166
  162. package/dist/chunks/server-tjbb3rqc.js +0 -251
  163. package/dist/presets/index.js +0 -394
  164. package/dist/types/adapters/devices/catalog.d.ts +0 -86
  165. package/dist/types/adapters/devices/derive.d.ts +0 -7
  166. package/dist/types/adapters/devices/fortios/firewall.d.ts +0 -23
  167. package/dist/types/adapters/devices/fortios/index.d.ts +0 -58
  168. package/dist/types/adapters/devices/fortios/names.d.ts +0 -48
  169. package/dist/types/adapters/devices/fortios/plan.d.ts +0 -28
  170. package/dist/types/adapters/devices/fortios/render.d.ts +0 -38
  171. package/dist/types/adapters/devices/fortios/routing.d.ts +0 -32
  172. package/dist/types/adapters/devices/fortios/tree.d.ts +0 -33
  173. package/dist/types/adapters/devices/index.d.ts +0 -3
  174. package/dist/types/adapters/devices/junos/bgp.d.ts +0 -11
  175. package/dist/types/adapters/devices/junos/index.d.ts +0 -42
  176. package/dist/types/adapters/devices/junos/render.d.ts +0 -52
  177. package/dist/types/adapters/devices/lines.d.ts +0 -56
  178. package/dist/types/adapters/devices/raisecom-ros/index.d.ts +0 -28
  179. package/dist/types/adapters/devices/routeros/export.d.ts +0 -39
  180. package/dist/types/adapters/devices/routeros/index.d.ts +0 -30
  181. package/dist/types/adapters/devices/routeros/plan.d.ts +0 -4
  182. package/dist/types/adapters/devices/routeros/render.d.ts +0 -100
  183. package/dist/types/adapters/devices/types.d.ts +0 -40
  184. package/dist/types/adapters/devices/vrp/index.d.ts +0 -27
  185. package/dist/types/adapters/registries/index.d.ts +0 -3
  186. package/dist/types/adapters/registries/librenms.d.ts +0 -15
  187. package/dist/types/adapters/registries/peeringdb.d.ts +0 -16
  188. package/dist/types/adapters/registries/prometheus.d.ts +0 -16
  189. package/dist/types/core/across.d.ts +0 -4
  190. package/dist/types/core/addr.d.ts +0 -34
  191. package/dist/types/core/files.d.ts +0 -6
  192. package/dist/types/core/merge.d.ts +0 -4
  193. package/dist/types/core/time.d.ts +0 -2
  194. package/dist/types/core/validate.d.ts +0 -2
  195. package/dist/types/presets/index.d.ts +0 -183
  196. package/src/adapters/devices/catalog.ts +0 -41
  197. package/src/adapters/devices/derive.ts +0 -59
  198. package/src/adapters/devices/fortios/firewall.ts +0 -362
  199. package/src/adapters/devices/fortios/index.ts +0 -923
  200. package/src/adapters/devices/fortios/names.ts +0 -115
  201. package/src/adapters/devices/fortios/plan.ts +0 -888
  202. package/src/adapters/devices/fortios/render.ts +0 -768
  203. package/src/adapters/devices/fortios/routing.ts +0 -444
  204. package/src/adapters/devices/fortios/tree.ts +0 -121
  205. package/src/adapters/devices/index.ts +0 -9
  206. package/src/adapters/devices/junos/bgp.ts +0 -269
  207. package/src/adapters/devices/junos/index.ts +0 -862
  208. package/src/adapters/devices/junos/render.ts +0 -872
  209. package/src/adapters/devices/lines.ts +0 -212
  210. package/src/adapters/devices/raisecom-ros/TRAPS.md +0 -26
  211. package/src/adapters/devices/raisecom-ros/index.ts +0 -700
  212. package/src/adapters/devices/routeros/export.ts +0 -321
  213. package/src/adapters/devices/routeros/index.ts +0 -346
  214. package/src/adapters/devices/routeros/plan.ts +0 -398
  215. package/src/adapters/devices/routeros/render.ts +0 -1490
  216. package/src/adapters/devices/types.ts +0 -38
  217. package/src/adapters/devices/vrp/TRAPS.md +0 -26
  218. package/src/adapters/devices/vrp/index.ts +0 -865
  219. package/src/adapters/registries/index.ts +0 -19
  220. package/src/adapters/registries/librenms.ts +0 -92
  221. package/src/adapters/registries/peeringdb.ts +0 -169
  222. package/src/adapters/registries/prometheus.ts +0 -71
  223. package/src/cli/graph.ts +0 -89
  224. package/src/core/across.ts +0 -350
  225. package/src/core/addr.ts +0 -174
  226. package/src/core/addresses.ts +0 -316
  227. package/src/core/files.ts +0 -32
  228. package/src/core/graph.ts +0 -640
  229. package/src/core/merge.ts +0 -19
  230. package/src/core/time.ts +0 -15
  231. package/src/core/validate.ts +0 -348
  232. package/src/presets/index.ts +0 -579
  233. /package/dist/types/{adapters/devices → platforms}/fortios/models.d.ts +0 -0
  234. /package/dist/types/{adapters/devices → platforms}/junos/models.d.ts +0 -0
  235. /package/dist/types/{adapters/devices → platforms}/raisecom-ros/models.d.ts +0 -0
  236. /package/dist/types/{adapters/devices → platforms}/vrp/models.d.ts +0 -0
  237. /package/src/{adapters/devices → platforms}/fortios/models.ts +0 -0
  238. /package/src/{adapters/devices → platforms}/junos/models.ts +0 -0
  239. /package/src/{adapters/devices → platforms}/raisecom-ros/models.ts +0 -0
  240. /package/src/{adapters/devices → platforms}/vrp/models.ts +0 -0
@@ -1,362 +0,0 @@
1
- // Packet filtering as policies through the device and local-in policies to it, who reaches the device's own services, NAT as central source NAT and virtual IPs, and stateless ACLs on ports.
2
-
3
- import { host } from "../../../core/addr";
4
- import type { AclRule, Device, Family, FilterMatch, FilterRule, NatRule, Ports, Protocol } from "../../../schema";
5
- import { familyOfPrefix, isTransparent, nameOf, network, subnet, withLength } from "./names";
6
- import { Block, quote, quoteAll, UNSET } from "./tree";
7
-
8
- /** IP protocol numbers, for a protocol the platform names only by number. */
9
- export const PROTOCOL_NUMBER: Record<Protocol, number> = { tcp: 6, udp: 17, icmp: 1, gre: 47, esp: 50, ah: 51, ospf: 89, vrrp: 112 };
10
-
11
- /** The device's own services, by the config's name, with the protocol and port each listens on unless the config moves it. */
12
- const SERVICES = {
13
- ssh: { protocol: "tcp", port: 22 },
14
- https: { protocol: "tcp", port: 443 },
15
- http: { protocol: "tcp", port: 80 },
16
- telnet: { protocol: "tcp", port: 23 },
17
- snmp: { protocol: "udp", port: 161 },
18
- native: { protocol: "tcp", port: 541 },
19
- } as const;
20
-
21
- /** NAT rules of both directions, which central NAT needs turned on. */
22
- export function natRules(device: Device): NatRule<string, string>[] {
23
- const nat = device.firewall?.nat;
24
- return [...(nat?.source ?? []), ...(nat?.destination ?? [])];
25
- }
26
-
27
- /** Which families a rule applies to: its own, its addresses', or both. */
28
- export function familiesOf(device: Device, match: FilterMatch<string, string> | undefined): Family[] {
29
- if (match?.family) return [match.family];
30
-
31
- const prefixes = [match?.src, match?.dst].filter((prefix): prefix is string => prefix !== undefined);
32
- if (prefixes.length) return [...new Set(prefixes.map(familyOfPrefix))];
33
-
34
- let families: Family[] = ["ipv4", "ipv6"];
35
-
36
- for (const set of [match?.src_set, match?.dst_set]) {
37
- if (!set) continue;
38
-
39
- const present = new Set((device.firewall?.address_sets?.[set] ?? []).map(familyOfPrefix));
40
- families = families.filter((family) => present.has(family));
41
- }
42
-
43
- return families;
44
- }
45
-
46
- /** A port or ports as the platform writes them in a service: `22`, `1000-2000`. */
47
- export function portList(ports: Ports | undefined): string[] {
48
- if (ports === undefined) return [];
49
-
50
- const list = Array.isArray(ports) ? ports : [ports as number | { min: number; max: number }];
51
- return list.map((port) => (typeof port === "number" ? String(port) : `${port.min}-${port.max}`));
52
- }
53
-
54
- /** Every protocol, as a service of Circuit's own, for a device in transparent mode. */
55
- export const ALL_IN_TRANSPARENT = "circuit-all";
56
-
57
- /** The service a rule matches, by name, with what it holds. `ALL` when the rule names no protocol and no port; ports without a protocol are TCP's and UDP's. */
58
- export function serviceOf(match: FilterMatch<string, string> | undefined, family: Family): { name: string; block?: Block } {
59
- const protocol = match?.protocol;
60
- const destinations = portList(match?.dst_port);
61
- const sources = portList(match?.src_port);
62
-
63
- if (!protocol && !destinations.length && !sources.length) return { name: "ALL" };
64
-
65
- if (!protocol || protocol === "tcp" || protocol === "udp") {
66
- const ranges = (destinations.length ? destinations : ["1-65535"]).flatMap((destination) =>
67
- sources.length ? sources.map((source) => `${destination}:${source}`) : [destination]
68
- );
69
-
70
- const protocols = protocol ? [protocol] : (["tcp", "udp"] as const);
71
- const name = `${protocols.join("+")}/${destinations.join(",") || "any"}${sources.length ? ` from ${sources.join(",")}` : ""}`;
72
- const block = new Block();
73
- for (const each of protocols) block.set(`${each}-portrange`, ranges.join(" "));
74
- return { name, block };
75
- }
76
-
77
- if (protocol === "icmp") {
78
- const kind = family === "ipv4" ? "ICMP" : "ICMP6";
79
- return { name: kind.toLowerCase(), block: new Block().set("protocol", kind).set("icmptype", UNSET) };
80
- }
81
-
82
- const number = PROTOCOL_NUMBER[protocol];
83
- return { name: `protocol/${number}`, block: new Block().set("protocol", "IP").set("protocol-number", number) };
84
- }
85
-
86
- /** A rule the platform keeps without being told: replies to a session it let through, and packets that belong to one, pass on their own. */
87
- export function isImplied(rule: FilterRule<string, string>): boolean {
88
- const state = rule.match?.state;
89
- return (
90
- rule.action === "accept" &&
91
- state !== undefined &&
92
- state.length > 0 &&
93
- state.every((one) => one === "established" || one === "related")
94
- );
95
- }
96
-
97
- /** The address objects, groups and services rules name, each created once. */
98
- class Objects {
99
- constructor(
100
- private readonly root: Block,
101
- private readonly device: Device
102
- ) {}
103
-
104
- /** The address a rule names on one side: one prefix, a set, or everything. */
105
- address(prefix: string | undefined, set: string | undefined, family: Family): string {
106
- if (set) return this.group(set, this.device.firewall?.address_sets?.[set] ?? [], family);
107
- if (!prefix) return "all";
108
-
109
- return this.prefix(prefix, family);
110
- }
111
-
112
- private prefix(prefix: string, family: Family): string {
113
- const name = network(prefix);
114
-
115
- if (family === "ipv4") this.root.block("firewall address").entry(name).set("subnet", subnet(name));
116
- else this.root.block("firewall address6").entry(name).set("ip6", name);
117
-
118
- return name;
119
- }
120
-
121
- /** A group of the members of one family, named once for both families. */
122
- group(name: string, members: readonly string[], family: Family): string {
123
- const names = members.filter((member) => familyOfPrefix(member) === family).map((member) => this.prefix(member, family));
124
-
125
- const table = family === "ipv4" ? "firewall addrgrp" : "firewall addrgrp6";
126
- this.root
127
- .block(table)
128
- .entry(name)
129
- .set("member", quoteAll([...new Set(names)]));
130
- return name;
131
- }
132
-
133
- /** A pool of one address, for source NAT to it. */
134
- pool(address: string): void {
135
- this.root.block("firewall ippool").entry(address).set("startip", address).set("endip", address);
136
- }
137
-
138
- service(match: FilterMatch<string, string> | undefined, family: Family): string {
139
- const { name, block } = serviceOf(match, family);
140
-
141
- // Transparent mode deletes the factory's services, `ALL` with them, so every protocol is a service of Circuit's own there.
142
- if (name === "ALL" && isTransparent(this.device)) {
143
- this.root.block("firewall service custom").entry(ALL_IN_TRANSPARENT).set("protocol", "IP");
144
- return ALL_IN_TRANSPARENT;
145
- }
146
-
147
- if (!block) return name;
148
-
149
- const entry = this.root.block("firewall service custom").entry(name);
150
- for (const [key, value] of block.settings) entry.set(key, value);
151
- return name;
152
- }
153
- }
154
-
155
- /** Packet filtering, who reaches the device, NAT and stateless ACLs. */
156
- export function firewall(root: Block, device: Device): void {
157
- const fw = device.firewall;
158
- const objects = new Objects(root, device);
159
-
160
- const forward = fw?.filter?.forward;
161
- const policies = { ipv4: 0, ipv6: 0 };
162
-
163
- for (const rule of forward?.rules ?? []) {
164
- if (isImplied(rule)) continue;
165
-
166
- for (const family of familiesOf(device, rule.match)) {
167
- const table = root.block(family === "ipv4" ? "firewall policy" : "firewall policy6");
168
- policy(table.entry(++policies[family]), objects, device, rule, family);
169
- }
170
- }
171
-
172
- // A packet no rule matched is accepted unless the chain drops it; the platform's own last word is deny.
173
- if (forward?.default !== "drop") {
174
- for (const family of ["ipv4", "ipv6"] as const) {
175
- const table = root.block(family === "ipv4" ? "firewall policy" : "firewall policy6");
176
- policy(table.entry(++policies[family]), objects, device, { action: "accept" }, family);
177
- }
178
- }
179
-
180
- const localIns = { ipv4: 0, ipv6: 0 };
181
- const localInEntry = (family: Family) =>
182
- root.block(family === "ipv4" ? "firewall local-in-policy" : "firewall local-in-policy6").entry(++localIns[family]);
183
-
184
- management(localInEntry, objects, device);
185
-
186
- const input = fw?.filter?.input;
187
-
188
- for (const rule of input?.rules ?? []) {
189
- if (isImplied(rule)) continue;
190
-
191
- for (const family of familiesOf(device, rule.match)) localIn(localInEntry(family), objects, device, rule, family);
192
- }
193
-
194
- if (input?.default === "drop") {
195
- for (const family of ["ipv4", "ipv6"] as const) localIn(localInEntry(family), objects, device, { action: "drop" }, family);
196
- }
197
-
198
- (fw?.nat?.source ?? []).forEach((rule, index) =>
199
- sourceNat(root.block("firewall central-snat-map").entry(index + 1), objects, device, rule)
200
- );
201
- (fw?.nat?.destination ?? []).forEach((rule, index) =>
202
- destinationNat(root.block("firewall vip").entry(`dnat-${index + 1}`), device, rule)
203
- );
204
-
205
- acls(root, objects, device);
206
- }
207
-
208
- /**
209
- * Who reaches the device's own services: each service with sources to allow accepts them and refuses everyone else, ahead of the input chain. Services sharing their sources share the policies, named after `management.allow` or the service.
210
- */
211
- function management(entry: (family: Family) => Block, objects: Objects, device: Device): void {
212
- const config = device.management ?? {};
213
- const guards = new Map<string, { sources: readonly string[]; services: string[] }>();
214
-
215
- for (const [service, listener] of Object.entries(SERVICES) as [keyof typeof SERVICES, (typeof SERVICES)[keyof typeof SERVICES]][]) {
216
- const settings = config[service];
217
- const sources = settings?.allow ?? config.allow;
218
- if (!settings || !sources?.length) continue;
219
-
220
- const name = settings.allow ? `circuit-${service}` : "circuit-management";
221
- const guard = guards.get(name) ?? { sources, services: [] };
222
- guard.services.push(objects.service({ protocol: listener.protocol, dst_port: settings.port ?? listener.port }, "ipv4"));
223
- guards.set(name, guard);
224
- }
225
-
226
- for (const [name, guard] of guards) {
227
- for (const family of ["ipv4", "ipv6"] as const) {
228
- const services = quoteAll(guard.services);
229
-
230
- if (guard.sources.some((source) => familyOfPrefix(source) === family)) {
231
- const accept = entry(family);
232
- accept.set("intf", quote("any"));
233
- accept.set("srcaddr", quote(objects.group(name, guard.sources, family)));
234
- accept.set("dstaddr", quote("all"));
235
- accept.set("action", "accept");
236
- accept.set("service", services);
237
- accept.set("schedule", quote("always"));
238
- accept.set("comments", quote("Management from allowed sources"));
239
- }
240
-
241
- const deny = entry(family);
242
- deny.set("intf", quote("any"));
243
- deny.set("srcaddr", quote("all"));
244
- deny.set("dstaddr", quote("all"));
245
- deny.set("action", "deny");
246
- deny.set("service", services);
247
- deny.set("schedule", quote("always"));
248
- deny.set("comments", quote("Management from anywhere else"));
249
- }
250
- }
251
- }
252
-
253
- /** A policy for traffic through the device. A rejection answers the sender; the platform's deny is silent otherwise. */
254
- function policy(entry: Block, objects: Objects, device: Device, rule: FilterRule<string, string>, family: Family): void {
255
- const match = rule.match;
256
-
257
- entry.set("srcintf", quote(match?.in_interface ? nameOf(device, match.in_interface) : "any"));
258
- entry.set("dstintf", quote(match?.out_interface ? nameOf(device, match.out_interface) : "any"));
259
- entry.set("srcaddr", quote(objects.address(match?.src, match?.src_set, family)));
260
- entry.set("dstaddr", quote(objects.address(match?.dst, match?.dst_set, family)));
261
- entry.set("action", rule.action === "accept" ? "accept" : "deny");
262
- entry.set("schedule", quote("always"));
263
- entry.set("service", quote(objects.service(match, family)));
264
-
265
- // The device gives a deny policy `logtraffic disable` of its own accord.
266
- if (rule.action !== "accept") entry.set("logtraffic", "disable");
267
- if (rule.action === "accept" && !device.hardware_offload && !rule.offload) entry.set("auto-asic-offload", "disable");
268
- if (rule.description) entry.set("comments", quote(rule.description));
269
- if (rule.action === "reject") entry.set("send-deny-packet", "enable");
270
- }
271
-
272
- /** A local-in policy, for traffic to the device itself. */
273
- function localIn(entry: Block, objects: Objects, device: Device, rule: FilterRule<string, string>, family: Family): void {
274
- const match = rule.match;
275
-
276
- entry.set("intf", quote(match?.in_interface ? nameOf(device, match.in_interface) : "any"));
277
- entry.set("srcaddr", quote(objects.address(match?.src, match?.src_set, family)));
278
- entry.set("dstaddr", quote(objects.address(match?.dst, match?.dst_set, family)));
279
- entry.set("action", rule.action === "accept" ? "accept" : "deny");
280
- entry.set("service", quote(objects.service(match, family)));
281
- entry.set("schedule", quote("always"));
282
- if (rule.description) entry.set("comments", quote(rule.description));
283
- }
284
-
285
- /** Source NAT: to the outgoing interface's address, or to a pool of one address. */
286
- function sourceNat(entry: Block, objects: Objects, device: Device, rule: NatRule<string, string>): void {
287
- const match = rule.match;
288
-
289
- entry.set("orig-addr", quote(objects.address(match?.src, match?.src_set, "ipv4")));
290
- entry.set("srcintf", quote(match?.in_interface ? nameOf(device, match.in_interface) : "any"));
291
- entry.set("dst-addr", quote(objects.address(match?.dst, match?.dst_set, "ipv4")));
292
- entry.set("dstintf", quote(match?.out_interface ? nameOf(device, match.out_interface) : "any"));
293
- if (match?.protocol) entry.set("protocol", PROTOCOL_NUMBER[match.protocol]);
294
-
295
- const ports = portList(match?.src_port);
296
-
297
- if (rule.action === "snat") {
298
- objects.pool(rule.to);
299
- entry.set("nat-ippool", quote(rule.to));
300
- // A pool keeps the source port only when told which.
301
- if (ports.length) entry.set("orig-port", ports[0]!).set("nat-port", rule.to_port ?? ports[0]!);
302
- } else if (ports.length) {
303
- entry.set("orig-port", ports[0]!);
304
- }
305
-
306
- if (rule.description) entry.set("comments", quote(rule.description));
307
- }
308
-
309
- /** Destination NAT, as a virtual IP: the address it answers on, and the port it forwards when the rule names one. */
310
- function destinationNat(entry: Block, device: Device, rule: NatRule<string, string>): void {
311
- if (rule.action !== "dnat") return;
312
-
313
- const match = rule.match;
314
- if (rule.description) entry.set("comment", quote(rule.description));
315
- if (match?.dst) entry.set("extip", host(withLength(match.dst)));
316
- entry.set("extintf", quote(match?.in_interface ? nameOf(device, match.in_interface) : "any"));
317
-
318
- const sources = match?.src ? [match.src] : match?.src_set ? (device.firewall?.address_sets?.[match.src_set] ?? []) : [];
319
- if (sources.length) entry.set("src-filter", quoteAll(sources.map(network)));
320
-
321
- const ports = portList(match?.dst_port);
322
- if (ports.length) entry.set("portforward", "enable");
323
- entry.set("mappedip", quote(rule.to));
324
-
325
- if (ports.length) {
326
- if (match?.protocol === "udp") entry.set("protocol", "udp");
327
- entry.set("extport", ports[0]!);
328
- entry.set("mappedport", rule.to_port ?? ports[0]!);
329
- }
330
- }
331
-
332
- /**
333
- * A port's ACL as the platform's stateless ACLs, which only drop: each rule that drops becomes one, of each family it applies to. What no rule drops passes, so the rules that accept after the last drop are already there; `unsupported()` refuses one before a drop.
334
- */
335
- function acls(root: Block, objects: Objects, device: Device): void {
336
- const counters = { ipv4: 0, ipv6: 0 };
337
-
338
- for (const [port, settings] of Object.entries(device.ports ?? {})) {
339
- if (!settings?.acl) continue;
340
-
341
- for (const rule of device.acls?.[settings.acl] ?? []) {
342
- if (rule.action !== "drop") continue;
343
-
344
- const match = rule.match as FilterMatch<string, string> | undefined;
345
-
346
- for (const family of familiesOf(device, match)) {
347
- const entry = root.block(family === "ipv4" ? "firewall acl" : "firewall acl6").entry(++counters[family]);
348
- if (rule.description) entry.set("comments", quote(rule.description));
349
- entry.set("interface", quote(nameOf(device, port)));
350
- entry.set("srcaddr", quote(objects.address(match?.src, undefined, family)));
351
- entry.set("dstaddr", quote(objects.address(match?.dst, undefined, family)));
352
- entry.set("service", quote(objects.service(match, family)));
353
- }
354
- }
355
- }
356
- }
357
-
358
- /** Whether a port's ACL drops before it accepts: an accept before a drop is an exception the platform's ACLs cannot hold. */
359
- export function acceptsBeforeDrop(rules: readonly AclRule<string>[]): boolean {
360
- const lastDrop = rules.map((rule) => rule.action).lastIndexOf("drop");
361
- return rules.slice(0, Math.max(0, lastDrop)).some((rule) => rule.action === "accept");
362
- }