@takodotid/circuit 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/LICENSE +106 -0
  2. package/README.md +195 -0
  3. package/package.json +83 -0
  4. package/src/adapters/devices/catalog.ts +38 -0
  5. package/src/adapters/devices/derive.ts +27 -0
  6. package/src/adapters/devices/index.ts +7 -0
  7. package/src/adapters/devices/lines.ts +212 -0
  8. package/src/adapters/devices/raisecom-ros/TRAPS.md +22 -0
  9. package/src/adapters/devices/raisecom-ros/index.ts +603 -0
  10. package/src/adapters/devices/raisecom-ros/models.ts +7 -0
  11. package/src/adapters/devices/routeros/TRAPS.md +33 -0
  12. package/src/adapters/devices/routeros/export.ts +319 -0
  13. package/src/adapters/devices/routeros/index.ts +261 -0
  14. package/src/adapters/devices/routeros/models.ts +8 -0
  15. package/src/adapters/devices/routeros/plan.ts +370 -0
  16. package/src/adapters/devices/routeros/render.ts +1431 -0
  17. package/src/adapters/devices/types.ts +31 -0
  18. package/src/adapters/devices/vrp/TRAPS.md +22 -0
  19. package/src/adapters/devices/vrp/index.ts +742 -0
  20. package/src/adapters/devices/vrp/models.ts +4 -0
  21. package/src/adapters/registries/index.ts +19 -0
  22. package/src/adapters/registries/peeringdb.ts +169 -0
  23. package/src/cli/index.ts +295 -0
  24. package/src/core/addr.ts +145 -0
  25. package/src/core/define.ts +94 -0
  26. package/src/core/secrets.ts +68 -0
  27. package/src/core/time.ts +15 -0
  28. package/src/core/validate.ts +380 -0
  29. package/src/index.ts +6 -0
  30. package/src/schema/common.ts +32 -0
  31. package/src/schema/device.ts +95 -0
  32. package/src/schema/firewall.ts +128 -0
  33. package/src/schema/index.ts +7 -0
  34. package/src/schema/interfaces.ts +192 -0
  35. package/src/schema/policy.ts +88 -0
  36. package/src/schema/routing.ts +120 -0
  37. package/src/schema/system.ts +191 -0
  38. package/src/transport/ssh.ts +125 -0
@@ -0,0 +1,191 @@
1
+ import type { IP, Prefix, Secret, Severity } from "./common";
2
+
3
+ /** Device-wide settings. */
4
+ export type System = {
5
+ /** IANA zone, for example `Asia/Jakarta`. UTC when absent. */
6
+ timezone?: string;
7
+ /** Shown at login. */
8
+ banner?: string;
9
+ /** Resolvers the device uses. */
10
+ dns?: {
11
+ /** Resolvers, in order of preference. */
12
+ servers: readonly IP[];
13
+ /** Answer queries from others. Off when absent. */
14
+ serve?: boolean;
15
+ };
16
+ /** Time the device keeps. */
17
+ ntp?: {
18
+ /** Time sources, by address or name. */
19
+ servers: readonly string[];
20
+ /** Answer time queries from others. Off when absent. */
21
+ serve?: boolean;
22
+ };
23
+ /** Where log messages go. */
24
+ logging?: {
25
+ /** Kept on the device's own storage, from `level` up. */
26
+ local?: {
27
+ /** The least severe message kept. */
28
+ level: Severity;
29
+ /** How many rotated files to keep. */
30
+ files?: number;
31
+ /** Lines in each file before it rotates. */
32
+ lines_per_file?: number;
33
+ };
34
+ /** Sent to syslog collectors, each from its own `level` up. */
35
+ remote?: readonly {
36
+ address: IP;
37
+ /** UDP port. 514 when absent. */
38
+ port?: number;
39
+ /** The least severe message sent. */
40
+ level: Severity;
41
+ }[];
42
+ };
43
+ /** Software release track the device updates from. */
44
+ release_channel?: string;
45
+ /** Read optic temperature, power and voltage. */
46
+ transceiver_monitoring?: boolean;
47
+ /** Host behaviour of the IP stack. */
48
+ ip?: {
49
+ /** Send ICMP redirects. Off when absent. */
50
+ icmp_redirects?: boolean;
51
+ /** Answer a TCP SYN flood with SYN cookies. Off when absent. */
52
+ syn_cookies?: boolean;
53
+ };
54
+ };
55
+
56
+ /** A login on the device. */
57
+ export type User = {
58
+ /** What the user may do. */
59
+ role: "admin" | "operator" | "read-only";
60
+ /** The login password. */
61
+ password: Secret;
62
+ /** Who the account is for. */
63
+ description?: string;
64
+ /** Public keys accepted instead of the password over SSH. */
65
+ ssh_keys?: readonly string[];
66
+ };
67
+
68
+ /** A way into the device. Absent means turned off. */
69
+ export type Service = {
70
+ /** Sources allowed. `management.allow` when absent. */
71
+ allow?: readonly Prefix[];
72
+ /** TCP or UDP port. The protocol's own when absent. */
73
+ port?: number;
74
+ };
75
+
76
+ /** One SNMPv3 user. */
77
+ export type SnmpUser = {
78
+ /** How messages are authenticated. */
79
+ auth: "sha1" | "sha256";
80
+ /** The authentication passphrase. */
81
+ auth_password: Secret;
82
+ /** How messages are encrypted. */
83
+ privacy: "aes128" | "des";
84
+ /** The encryption passphrase. */
85
+ privacy_password: Secret;
86
+ };
87
+
88
+ /** How the device is reached and managed. */
89
+ export type Management<I extends string, C extends string> = {
90
+ /** Sources allowed to every service that does not state its own. */
91
+ allow?: readonly Prefix[];
92
+ /** Secure shell. */
93
+ ssh?: Service & {
94
+ /** Also offer legacy ciphers, MACs and key exchanges. Off when absent. */
95
+ weak_crypto?: boolean;
96
+ /** Seconds a login may take. */
97
+ auth_timeout?: number;
98
+ /** Failed attempts allowed per connection. */
99
+ auth_retries?: number;
100
+ };
101
+ /** Unencrypted remote login. */
102
+ telnet?: Service;
103
+ /** Web interface, unencrypted. */
104
+ http?: Service;
105
+ /** Web interface over TLS. */
106
+ https?: Service & {
107
+ /** One of the device's `certificates`. */
108
+ certificate?: C;
109
+ };
110
+ /** The platform's own API, unencrypted. */
111
+ api?: Service;
112
+ /** The platform's own API over TLS. */
113
+ api_tls?: Service & {
114
+ /** One of the device's `certificates`. */
115
+ certificate?: C;
116
+ };
117
+ /** File transfer, unencrypted. */
118
+ ftp?: Service;
119
+ /** The platform's own management protocol. `interfaces` also allows it at layer 2 on those interfaces. */
120
+ native?: Service & { interfaces?: readonly I[] };
121
+ /** Polling by SNMP. */
122
+ snmp?: Service & {
123
+ /** Read-only community for version 2c. Version 2c is off when absent. */
124
+ community?: Secret;
125
+ /** Version 3 users, keyed by name. */
126
+ users?: Record<string, SnmpUser>;
127
+ /** Who to contact about the device. */
128
+ contact?: string;
129
+ /** Where the device is. */
130
+ location?: string;
131
+ };
132
+ /** Console login uses this password. The device's users when absent. */
133
+ console?: { password: Secret };
134
+ /** Password for privileged mode, on platforms that have one. */
135
+ privilege_password?: Secret;
136
+ };
137
+
138
+ /** A DHCP server for one network. */
139
+ export type DhcpServer<I extends string> = {
140
+ /** Where it answers. */
141
+ interface: I;
142
+ /** The network it serves. */
143
+ network: Prefix;
144
+ /** Default gateway handed out. */
145
+ gateway: IP;
146
+ /** Resolvers handed out. */
147
+ dns?: readonly IP[];
148
+ /** First and last address handed out. */
149
+ pool: readonly [IP, IP];
150
+ /** Seconds a lease lasts. */
151
+ lease_time?: number;
152
+ /** Addresses fixed to a MAC. */
153
+ reservations?: readonly {
154
+ address: IP;
155
+ mac: string;
156
+ /** Whose address it is. */
157
+ description?: string;
158
+ }[];
159
+ };
160
+
161
+ /** Forward DHCP requests heard on an interface to servers elsewhere. */
162
+ export type DhcpRelay<I extends string> = {
163
+ /** Where requests are heard. */
164
+ interface: I;
165
+ /** Where they are forwarded. */
166
+ servers: readonly IP[];
167
+ };
168
+
169
+ /** A certificate the device presents or trusts. */
170
+ export type Certificate = {
171
+ /** The certificate, PEM. Public, so it lives in the repository. */
172
+ certificate: string;
173
+ /** Its private key: a PEM file through `secretFile`, or PEM base64-encoded as one line through `secret`. A certificate only trusted, such as a CA, has none. */
174
+ private_key?: Secret;
175
+ };
176
+
177
+ /** Export flow records or packet samples to collectors. */
178
+ export type FlowExport<I extends string> = {
179
+ /** The record format. */
180
+ protocol: "sflow" | "netflow-v9" | "ipfix";
181
+ /** Where records are sent. */
182
+ collectors: readonly {
183
+ address: IP;
184
+ /** UDP port. 6343 for sFlow and 2055 otherwise when absent. */
185
+ port?: number;
186
+ }[];
187
+ /** Sample one packet in this many. Every packet when absent, on a platform that can export every packet. */
188
+ sampling?: number;
189
+ /** Where traffic is observed. Every interface when absent. */
190
+ interfaces?: readonly I[];
191
+ };
@@ -0,0 +1,125 @@
1
+ // SSH sessions to devices. Adapters choose how to talk to their platform; this holds what they share.
2
+
3
+ import { Client, type ClientChannel } from "ssh2";
4
+
5
+ export type Login = {
6
+ host: string;
7
+ port?: number;
8
+ user: string;
9
+ password: string;
10
+ /** Host key algorithms to accept, for a device that negotiates one it then signs wrongly. */
11
+ hostKeys?: string[];
12
+ };
13
+
14
+ /** Open a session. */
15
+ export function connect(login: Login): Promise<Client> {
16
+ return new Promise((resolve, reject) => {
17
+ const client = new Client();
18
+
19
+ client
20
+ .on("ready", () => resolve(client))
21
+ .on("error", reject)
22
+ .connect({
23
+ host: login.host,
24
+ port: login.port ?? 22,
25
+ username: login.user,
26
+ password: login.password,
27
+ readyTimeout: 30_000,
28
+ ...(login.hostKeys ? { algorithms: { serverHostKey: login.hostKeys as never } } : {}),
29
+ });
30
+ });
31
+ }
32
+
33
+ /** Run one command and return what it printed, stderr included. */
34
+ export function exec(client: Client, command: string, timeout = 120_000): Promise<string> {
35
+ return new Promise((resolve, reject) => {
36
+ client.exec(command, (error, stream) => {
37
+ if (error) return reject(error);
38
+
39
+ let output = "";
40
+ const append = (chunk: Buffer) => (output += chunk.toString("utf8"));
41
+ const timer = setTimeout(() => resolve(output.trim()), timeout);
42
+
43
+ stream.on("data", append);
44
+ stream.stderr.on("data", append);
45
+
46
+ stream.on("close", () => {
47
+ clearTimeout(timer);
48
+ resolve(output.trim());
49
+ });
50
+ });
51
+ });
52
+ }
53
+
54
+ /** An interactive shell, for platforms that take configuration only there. */
55
+ export function shell(client: Client, columns = 200, rows = 1000): Promise<ClientChannel> {
56
+ return new Promise((resolve, reject) => {
57
+ client.shell({ cols: columns, rows }, (error, channel) => (error ? reject(error) : resolve(channel)));
58
+ });
59
+ }
60
+
61
+ /** Collect output until `done` holds, then wait `settle` for stragglers, or give up at `timeout`. */
62
+ export function readUntil(channel: ClientChannel, done: (output: string) => boolean, timeout: number, settle = 400): Promise<string> {
63
+ return new Promise((resolve) => {
64
+ let output = "";
65
+ let finished = false;
66
+
67
+ const finish = () => {
68
+ if (finished) return;
69
+
70
+ finished = true;
71
+ channel.removeListener("data", onData);
72
+ resolve(output);
73
+ };
74
+
75
+ const onData = (chunk: Buffer) => {
76
+ output += chunk.toString("utf8");
77
+ if (done(output)) setTimeout(finish, settle);
78
+ };
79
+
80
+ channel.on("data", onData);
81
+ setTimeout(finish, timeout);
82
+ });
83
+ }
84
+
85
+ /** Send one line into an interactive shell and collect output until it goes quiet for `settle`. */
86
+ export function send(channel: ClientChannel, line: string, timeout = 20_000, settle = 1500): Promise<string> {
87
+ channel.write(line + "\n");
88
+
89
+ return new Promise((resolve) => {
90
+ let output = "";
91
+ let timer: ReturnType<typeof setTimeout>;
92
+
93
+ const finish = () => {
94
+ channel.removeListener("data", onData);
95
+ resolve(output);
96
+ };
97
+
98
+ const onData = (chunk: Buffer) => {
99
+ output += chunk.toString("utf8");
100
+ clearTimeout(timer);
101
+ timer = setTimeout(finish, settle);
102
+ };
103
+
104
+ channel.on("data", onData);
105
+ timer = setTimeout(finish, timeout);
106
+ });
107
+ }
108
+
109
+ export const sleep = (milliseconds: number) => new Promise((done) => setTimeout(done, milliseconds));
110
+
111
+ /** Write a file to the device over SFTP. */
112
+ export function upload(client: Client, path: string, content: string | Buffer): Promise<void> {
113
+ return new Promise((resolve, reject) => {
114
+ client.sftp((error, sftp) => {
115
+ if (error) return reject(error);
116
+
117
+ sftp.writeFile(path, content, (writeError) => {
118
+ sftp.end();
119
+
120
+ if (writeError) reject(writeError);
121
+ else resolve();
122
+ });
123
+ });
124
+ });
125
+ }