@ebowwa/hetzner 0.1.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 (46) hide show
  1. package/actions.js +802 -0
  2. package/actions.ts +1053 -0
  3. package/auth.js +35 -0
  4. package/auth.ts +37 -0
  5. package/bootstrap/FIREWALL.md +326 -0
  6. package/bootstrap/KERNEL-HARDENING.md +258 -0
  7. package/bootstrap/SECURITY-INTEGRATION.md +281 -0
  8. package/bootstrap/TESTING.md +301 -0
  9. package/bootstrap/cloud-init.js +279 -0
  10. package/bootstrap/cloud-init.ts +394 -0
  11. package/bootstrap/firewall.js +279 -0
  12. package/bootstrap/firewall.ts +342 -0
  13. package/bootstrap/genesis.js +406 -0
  14. package/bootstrap/genesis.ts +518 -0
  15. package/bootstrap/index.js +35 -0
  16. package/bootstrap/index.ts +71 -0
  17. package/bootstrap/kernel-hardening.js +266 -0
  18. package/bootstrap/kernel-hardening.test.ts +230 -0
  19. package/bootstrap/kernel-hardening.ts +272 -0
  20. package/bootstrap/security-audit.js +118 -0
  21. package/bootstrap/security-audit.ts +124 -0
  22. package/bootstrap/ssh-hardening.js +182 -0
  23. package/bootstrap/ssh-hardening.ts +192 -0
  24. package/client.js +137 -0
  25. package/client.ts +177 -0
  26. package/config.js +5 -0
  27. package/config.ts +5 -0
  28. package/errors.js +270 -0
  29. package/errors.ts +371 -0
  30. package/index.js +28 -0
  31. package/index.ts +55 -0
  32. package/package.json +56 -0
  33. package/pricing.js +284 -0
  34. package/pricing.ts +422 -0
  35. package/schemas.js +660 -0
  36. package/schemas.ts +765 -0
  37. package/server-status.ts +81 -0
  38. package/servers.js +424 -0
  39. package/servers.ts +568 -0
  40. package/ssh-keys.js +90 -0
  41. package/ssh-keys.ts +122 -0
  42. package/ssh-setup.ts +218 -0
  43. package/types.js +96 -0
  44. package/types.ts +389 -0
  45. package/volumes.js +172 -0
  46. package/volumes.ts +229 -0
@@ -0,0 +1,279 @@
1
+ /**
2
+ * UFW Firewall Cloud-Init Components
3
+ *
4
+ * Composable cloud-init blocks for securing servers with UFW (Uncomplicated Firewall).
5
+ * Includes: default deny incoming, allow outgoing, rate limiting, and logging.
6
+ *
7
+ * Background: Hetzner public IPs face constant scanning and brute-force attacks.
8
+ * UFW provides a simple interface to iptables/nftables with secure defaults.
9
+ *
10
+ * This module is imported by cloud-init.ts (seed/worker nodes) and genesis.ts
11
+ * (control plane) so every new server gets firewall protection at first boot.
12
+ *
13
+ * Three composable functions return cloud-init line arrays for splicing into
14
+ * the appropriate YAML sections:
15
+ * - ufwFirewallPackages() → packages: section
16
+ * - ufwFirewallWriteFiles() → write_files: section
17
+ * - ufwFirewallRunCmd() → runcmd: section
18
+ *
19
+ * Security Policy:
20
+ * - Default: deny incoming, allow outgoing (stateful)
21
+ * - SSH (22): rate limited to prevent brute-force
22
+ * - HTTP/HTTPS (80/443): allowed for web services
23
+ * - Node Agent (8911): allowed for internal communication
24
+ * - Tailscale (41641): allowed for VPN
25
+ * - Logging: enabled with rate limiting to prevent log flooding
26
+ */
27
+ /**
28
+ * Default firewall options for Genesis control plane servers.
29
+ */
30
+ export const DEFAULT_UFW_GENESIS_OPTIONS = {
31
+ allowSSHFrom: [], // Empty = allow from anywhere
32
+ allowHTTP: true,
33
+ allowHTTPS: true,
34
+ allowNodeAgent: false, // Genesis doesn't run node-agent
35
+ verboseLogging: false,
36
+ };
37
+ /**
38
+ * Default firewall options for worker/seed servers.
39
+ */
40
+ export const DEFAULT_UFW_WORKER_OPTIONS = {
41
+ allowSSHFrom: [], // Empty = allow from anywhere
42
+ allowHTTP: false,
43
+ allowHTTPS: false,
44
+ allowNodeAgent: true, // Workers run node-agent on port 8911
45
+ verboseLogging: false,
46
+ };
47
+ /**
48
+ * Packages required for UFW firewall.
49
+ * Returns cloud-init YAML lines for the `packages:` section.
50
+ *
51
+ * - ufw: Uncomplicated Firewall interface to iptables/nftables
52
+ */
53
+ export function ufwFirewallPackages() {
54
+ return [
55
+ " - ufw",
56
+ ];
57
+ }
58
+ /**
59
+ * Files to write at first boot for UFW firewall configuration.
60
+ * Returns cloud-init YAML lines for the `write_files:` section.
61
+ *
62
+ * Drops 2 files onto the server:
63
+ *
64
+ * 1. /etc/ufw/before.rules
65
+ * - Custom before rules for stateful firewall behavior
66
+ * - Allows loopback, established/related connections
67
+ * - Drops invalid packets early
68
+ *
69
+ * 2. /etc/ufw/sysctl.conf
70
+ * - Enables kernel network security parameters
71
+ * - IP spoofing protection
72
+ * - ICMP redirect protection
73
+ * - Log martian packets
74
+ */
75
+ export function ufwFirewallWriteFiles(options = {}) {
76
+ const lines = [];
77
+ // 1. UFW before.rules - stateful firewall rules applied before UFW rules
78
+ lines.push(" # UFW before.rules - stateful firewall and network security");
79
+ lines.push(" - path: /etc/ufw/before.rules");
80
+ lines.push(" owner: root:root");
81
+ lines.push(" permissions: '0644'");
82
+ lines.push(" content: |");
83
+ lines.push(" #");
84
+ lines.push(" # UFW before.rules - applied before UFW rules");
85
+ lines.push(" #");
86
+ lines.push(" # Start with the standard configuration");
87
+ lines.push(" *filter");
88
+ lines.push(" :ufw-before-input - [0:0]");
89
+ lines.push(" :ufw-before-output - [0:0]");
90
+ lines.push(" :ufw-before-forward - [0:0]");
91
+ lines.push(" :ufw-not-local - [0:0]");
92
+ lines.push(" # End of lines to adjust");
93
+ lines.push("");
94
+ lines.push(" # Allow all on loopback");
95
+ lines.push(" -A ufw-before-input -i lo -j ACCEPT");
96
+ lines.push(" -A ufw-before-output -o lo -j ACCEPT");
97
+ lines.push("");
98
+ lines.push(" # Drop invalid packets");
99
+ lines.push(" -A ufw-before-input -m conntrack --ctstate INVALID -j DROP");
100
+ lines.push("");
101
+ lines.push(" # Allow established and related connections");
102
+ lines.push(" -A ufw-before-input -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT");
103
+ lines.push(" -A ufw-before-output -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT");
104
+ lines.push("");
105
+ lines.push(" # Allow ICMP messages (required for PMTU discovery)");
106
+ lines.push(" -A ufw-before-input -p icmp --icmp-type destination-unreachable -j ACCEPT");
107
+ lines.push(" -A ufw-before-input -p icmp --icmp-type time-exceeded -j ACCEPT");
108
+ lines.push(" -A ufw-before-input -p icmp --icmp-type parameter-problem -j ACCEPT");
109
+ lines.push(" -A ufw-before-input -p icmp --icmp-type echo-request -j ACCEPT");
110
+ lines.push("");
111
+ lines.push(" # Drop packets with bogus TCP flags (potential scan)");
112
+ lines.push(" -A ufw-before-input -p tcp --tcp-flags ALL NONE -j DROP");
113
+ lines.push(" -A ufw-before-input -p tcp --tcp-flags ALL ALL -j DROP");
114
+ lines.push("");
115
+ lines.push(" # Commit changes");
116
+ lines.push(" COMMIT");
117
+ lines.push("");
118
+ // 2. UFW sysctl.conf - kernel network security parameters
119
+ lines.push(" # UFW sysctl.conf - kernel network hardening");
120
+ lines.push(" - path: /etc/ufw/sysctl.conf");
121
+ lines.push(" owner: root:root");
122
+ lines.push(" permissions: '0644'");
123
+ lines.push(" content: |");
124
+ lines.push(" #");
125
+ lines.push(" # UFW sysctl.conf - kernel network security parameters");
126
+ lines.push(" #");
127
+ lines.push("");
128
+ lines.push(" # IP spoofing protection");
129
+ lines.push(" net/ipv4/conf/all/rp_filter=1");
130
+ lines.push(" net/ipv4/conf/default/rp_filter=1");
131
+ lines.push("");
132
+ lines.push(" # Ignore ICMP redirect messages");
133
+ lines.push(" net/ipv4/conf/all/accept_redirects=0");
134
+ lines.push(" net/ipv6/conf/all/accept_redirects=0");
135
+ lines.push(" net/ipv4/conf/default/accept_redirects=0");
136
+ lines.push(" net/ipv6/conf/default/accept_redirects=0");
137
+ lines.push("");
138
+ lines.push(" # Ignore send redirects");
139
+ lines.push(" net/ipv4/conf/all/send_redirects=0");
140
+ lines.push(" net/ipv4/conf/default/send_redirects=0");
141
+ lines.push("");
142
+ lines.push(" # Log martian packets (packets with impossible addresses)");
143
+ lines.push(" net/ipv4/conf/all/log_martians=1");
144
+ lines.push(" net/ipv4/conf/default/log_martians=1");
145
+ lines.push("");
146
+ lines.push(" # SYN cookies protection (SYN flood mitigation)");
147
+ lines.push(" net/ipv4/tcp_syncookies=1");
148
+ lines.push("");
149
+ lines.push(" # Source address verification (spoofing protection)");
150
+ lines.push(" net/ipv4/conf/all/secure_redirects=1");
151
+ lines.push(" net/ipv4/conf/default/secure_redirects=1");
152
+ lines.push("");
153
+ return lines;
154
+ }
155
+ /**
156
+ * Commands to configure and activate UFW firewall at first boot.
157
+ * Returns cloud-init YAML lines for the `runcmd:` section.
158
+ *
159
+ * Order matters:
160
+ * 1. Set default policies (deny incoming, allow outgoing)
161
+ * 2. Allow loopback interface
162
+ * 3. Allow SSH with rate limiting (prevents brute-force)
163
+ * 4. Allow HTTP/HTTPS if enabled
164
+ * 5. Allow Node Agent port if enabled
165
+ * 6. Allow Tailscale port (required for VPN)
166
+ * 7. Enable logging with rate limiting
167
+ * 8. Enable and reload UFW
168
+ * 9. Display firewall status
169
+ */
170
+ export function ufwFirewallRunCmd(options = {}) {
171
+ const { allowSSHFrom = [], allowHTTP = true, allowHTTPS = true, allowNodeAgent = false, additionalPorts = [], verboseLogging = false, } = options;
172
+ const lines = [];
173
+ lines.push(" # UFW Firewall: Configure and enable secure firewall");
174
+ lines.push("");
175
+ // Set default policies
176
+ lines.push(" # Set default policies: deny incoming, allow outgoing");
177
+ lines.push(" - ufw --force reset");
178
+ lines.push(" - ufw default deny incoming");
179
+ lines.push(" - ufw default allow outgoing");
180
+ lines.push(" - ufw default deny forwarded");
181
+ lines.push("");
182
+ // Allow loopback
183
+ lines.push(" # Allow loopback interface");
184
+ lines.push(" - ufw allow in on lo");
185
+ lines.push("");
186
+ // Allow SSH with rate limiting
187
+ if (allowSSHFrom.length === 0) {
188
+ // Allow SSH from anywhere with rate limiting
189
+ lines.push(" # Allow SSH with rate limiting (6 connections in 30 seconds)");
190
+ lines.push(" - ufw limit 22/tcp comment 'Rate-limited SSH'");
191
+ }
192
+ else {
193
+ // Allow SSH from specific IPs/CIDRs
194
+ for (const source of allowSSHFrom) {
195
+ lines.push(` - ufw allow from ${source} to any port 22 proto tcp comment 'SSH from ${source}'`);
196
+ }
197
+ }
198
+ lines.push("");
199
+ // Allow HTTP if enabled
200
+ if (allowHTTP) {
201
+ lines.push(" # Allow HTTP");
202
+ lines.push(" - ufw allow 80/tcp comment 'HTTP'");
203
+ }
204
+ // Allow HTTPS if enabled
205
+ if (allowHTTPS) {
206
+ lines.push(" # Allow HTTPS");
207
+ lines.push(" - ufw allow 443/tcp comment 'HTTPS'");
208
+ }
209
+ lines.push("");
210
+ // Allow Node Agent port if enabled
211
+ if (allowNodeAgent) {
212
+ lines.push(" # Allow Node Agent (internal communication)");
213
+ lines.push(" - ufw allow 8911/tcp comment 'Node Agent'");
214
+ lines.push("");
215
+ }
216
+ // Allow Tailscale (required for VPN functionality)
217
+ lines.push(" # Allow Tailscale VPN");
218
+ lines.push(" - ufw allow 41641/udp comment 'Tailscale'");
219
+ lines.push("");
220
+ // Additional ports
221
+ if (additionalPorts.length > 0) {
222
+ lines.push(" # Additional custom ports");
223
+ for (const portConfig of additionalPorts) {
224
+ const protocol = portConfig.protocol || "tcp";
225
+ const comment = portConfig.comment || `Custom port ${portConfig.port}`;
226
+ lines.push(` - ufw allow ${portConfig.port}/${protocol} comment '${comment}'`);
227
+ }
228
+ lines.push("");
229
+ }
230
+ // Configure logging
231
+ if (verboseLogging) {
232
+ lines.push(" # Enable verbose logging");
233
+ lines.push(" - ufw logging on");
234
+ lines.push(" - ufw logging high");
235
+ }
236
+ else {
237
+ lines.push(" # Enable rate-limited logging (prevent log flooding)");
238
+ lines.push(" - ufw logging on");
239
+ lines.push(" - ufw logging low");
240
+ }
241
+ lines.push("");
242
+ // Enable and reload UFW
243
+ lines.push(" # Enable and reload UFW");
244
+ lines.push(" - ufw --force enable");
245
+ lines.push(" - ufw reload");
246
+ lines.push("");
247
+ // Display status
248
+ lines.push(" # Display firewall status");
249
+ lines.push(" - ufw status verbose > /var/log/ufw-bootstrap-status.log");
250
+ lines.push("");
251
+ return lines;
252
+ }
253
+ /**
254
+ * Generate complete UFW firewall configuration for Genesis servers.
255
+ *
256
+ * @param options - UFW firewall options (uses DEFAULT_UFW_GENESIS_OPTIONS if not provided)
257
+ * @returns Object with packages, writeFiles, and runCmd arrays
258
+ */
259
+ export function generateUFWFirewallForGenesis(options = DEFAULT_UFW_GENESIS_OPTIONS) {
260
+ return {
261
+ packages: ufwFirewallPackages(),
262
+ writeFiles: ufwFirewallWriteFiles(options),
263
+ runCmd: ufwFirewallRunCmd(options),
264
+ };
265
+ }
266
+ /**
267
+ * Generate complete UFW firewall configuration for worker/seed servers.
268
+ *
269
+ * @param options - UFW firewall options (uses DEFAULT_UFW_WORKER_OPTIONS if not provided)
270
+ * @returns Object with packages, writeFiles, and runCmd arrays
271
+ */
272
+ export function generateUFWFirewallForWorker(options = DEFAULT_UFW_WORKER_OPTIONS) {
273
+ return {
274
+ packages: ufwFirewallPackages(),
275
+ writeFiles: ufwFirewallWriteFiles(options),
276
+ runCmd: ufwFirewallRunCmd(options),
277
+ };
278
+ }
279
+ //# sourceMappingURL=firewall.js.map
@@ -0,0 +1,342 @@
1
+ /**
2
+ * UFW Firewall Cloud-Init Components
3
+ *
4
+ * Composable cloud-init blocks for securing servers with UFW (Uncomplicated Firewall).
5
+ * Includes: default deny incoming, allow outgoing, rate limiting, and logging.
6
+ *
7
+ * Background: Hetzner public IPs face constant scanning and brute-force attacks.
8
+ * UFW provides a simple interface to iptables/nftables with secure defaults.
9
+ *
10
+ * This module is imported by cloud-init.ts (seed/worker nodes) and genesis.ts
11
+ * (control plane) so every new server gets firewall protection at first boot.
12
+ *
13
+ * Three composable functions return cloud-init line arrays for splicing into
14
+ * the appropriate YAML sections:
15
+ * - ufwFirewallPackages() → packages: section
16
+ * - ufwFirewallWriteFiles() → write_files: section
17
+ * - ufwFirewallRunCmd() → runcmd: section
18
+ *
19
+ * Security Policy:
20
+ * - Default: deny incoming, allow outgoing (stateful)
21
+ * - SSH (22): rate limited to prevent brute-force
22
+ * - HTTP/HTTPS (80/443): allowed for web services
23
+ * - Node Agent (8911): allowed for internal communication
24
+ * - Tailscale (41641): allowed for VPN
25
+ * - Logging: enabled with rate limiting to prevent log flooding
26
+ */
27
+
28
+ /**
29
+ * UFW firewall configuration options.
30
+ */
31
+ export interface UFWFirewallOptions {
32
+ /** Allow SSH from specific IPs/CIDRs (default: allow from anywhere) */
33
+ allowSSHFrom?: string[];
34
+
35
+ /** Allow HTTP (port 80) */
36
+ allowHTTP?: boolean;
37
+
38
+ /** Allow HTTPS (port 443) */
39
+ allowHTTPS?: boolean;
40
+
41
+ /** Allow Node Agent port (8911) */
42
+ allowNodeAgent?: boolean;
43
+
44
+ /** Additional ports to allow */
45
+ additionalPorts?: Array<{ port: number; protocol?: string; comment?: string }>;
46
+
47
+ /** Enable verbose logging (default: rate-limited logging) */
48
+ verboseLogging?: boolean;
49
+ }
50
+
51
+ /**
52
+ * Default firewall options for Genesis control plane servers.
53
+ */
54
+ export const DEFAULT_UFW_GENESIS_OPTIONS: UFWFirewallOptions = {
55
+ allowSSHFrom: [], // Empty = allow from anywhere
56
+ allowHTTP: true,
57
+ allowHTTPS: true,
58
+ allowNodeAgent: false, // Genesis doesn't run node-agent
59
+ verboseLogging: false,
60
+ };
61
+
62
+ /**
63
+ * Default firewall options for worker/seed servers.
64
+ */
65
+ export const DEFAULT_UFW_WORKER_OPTIONS: UFWFirewallOptions = {
66
+ allowSSHFrom: [], // Empty = allow from anywhere
67
+ allowHTTP: false,
68
+ allowHTTPS: false,
69
+ allowNodeAgent: true, // Workers run node-agent on port 8911
70
+ verboseLogging: false,
71
+ };
72
+
73
+ /**
74
+ * Packages required for UFW firewall.
75
+ * Returns cloud-init YAML lines for the `packages:` section.
76
+ *
77
+ * - ufw: Uncomplicated Firewall interface to iptables/nftables
78
+ */
79
+ export function ufwFirewallPackages(): string[] {
80
+ return [
81
+ " - ufw",
82
+ ];
83
+ }
84
+
85
+ /**
86
+ * Files to write at first boot for UFW firewall configuration.
87
+ * Returns cloud-init YAML lines for the `write_files:` section.
88
+ *
89
+ * Drops 2 files onto the server:
90
+ *
91
+ * 1. /etc/ufw/before.rules
92
+ * - Custom before rules for stateful firewall behavior
93
+ * - Allows loopback, established/related connections
94
+ * - Drops invalid packets early
95
+ *
96
+ * 2. /etc/ufw/sysctl.conf
97
+ * - Enables kernel network security parameters
98
+ * - IP spoofing protection
99
+ * - ICMP redirect protection
100
+ * - Log martian packets
101
+ */
102
+ export function ufwFirewallWriteFiles(options: UFWFirewallOptions = {}): string[] {
103
+ const lines: string[] = [];
104
+
105
+ // 1. UFW before.rules - stateful firewall rules applied before UFW rules
106
+ lines.push(" # UFW before.rules - stateful firewall and network security");
107
+ lines.push(" - path: /etc/ufw/before.rules");
108
+ lines.push(" owner: root:root");
109
+ lines.push(" permissions: '0644'");
110
+ lines.push(" content: |");
111
+ lines.push(" #");
112
+ lines.push(" # UFW before.rules - applied before UFW rules");
113
+ lines.push(" #");
114
+ lines.push(" # Start with the standard configuration");
115
+ lines.push(" *filter");
116
+ lines.push(" :ufw-before-input - [0:0]");
117
+ lines.push(" :ufw-before-output - [0:0]");
118
+ lines.push(" :ufw-before-forward - [0:0]");
119
+ lines.push(" :ufw-not-local - [0:0]");
120
+ lines.push(" # End of lines to adjust");
121
+ lines.push("");
122
+ lines.push(" # Allow all on loopback");
123
+ lines.push(" -A ufw-before-input -i lo -j ACCEPT");
124
+ lines.push(" -A ufw-before-output -o lo -j ACCEPT");
125
+ lines.push("");
126
+ lines.push(" # Drop invalid packets");
127
+ lines.push(" -A ufw-before-input -m conntrack --ctstate INVALID -j DROP");
128
+ lines.push("");
129
+ lines.push(" # Allow established and related connections");
130
+ lines.push(" -A ufw-before-input -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT");
131
+ lines.push(" -A ufw-before-output -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT");
132
+ lines.push("");
133
+ lines.push(" # Allow ICMP messages (required for PMTU discovery)");
134
+ lines.push(" -A ufw-before-input -p icmp --icmp-type destination-unreachable -j ACCEPT");
135
+ lines.push(" -A ufw-before-input -p icmp --icmp-type time-exceeded -j ACCEPT");
136
+ lines.push(" -A ufw-before-input -p icmp --icmp-type parameter-problem -j ACCEPT");
137
+ lines.push(" -A ufw-before-input -p icmp --icmp-type echo-request -j ACCEPT");
138
+ lines.push("");
139
+ lines.push(" # Drop packets with bogus TCP flags (potential scan)");
140
+ lines.push(" -A ufw-before-input -p tcp --tcp-flags ALL NONE -j DROP");
141
+ lines.push(" -A ufw-before-input -p tcp --tcp-flags ALL ALL -j DROP");
142
+ lines.push("");
143
+ lines.push(" # Commit changes");
144
+ lines.push(" COMMIT");
145
+ lines.push("");
146
+
147
+ // 2. UFW sysctl.conf - kernel network security parameters
148
+ lines.push(" # UFW sysctl.conf - kernel network hardening");
149
+ lines.push(" - path: /etc/ufw/sysctl.conf");
150
+ lines.push(" owner: root:root");
151
+ lines.push(" permissions: '0644'");
152
+ lines.push(" content: |");
153
+ lines.push(" #");
154
+ lines.push(" # UFW sysctl.conf - kernel network security parameters");
155
+ lines.push(" #");
156
+ lines.push("");
157
+ lines.push(" # IP spoofing protection");
158
+ lines.push(" net/ipv4/conf/all/rp_filter=1");
159
+ lines.push(" net/ipv4/conf/default/rp_filter=1");
160
+ lines.push("");
161
+ lines.push(" # Ignore ICMP redirect messages");
162
+ lines.push(" net/ipv4/conf/all/accept_redirects=0");
163
+ lines.push(" net/ipv6/conf/all/accept_redirects=0");
164
+ lines.push(" net/ipv4/conf/default/accept_redirects=0");
165
+ lines.push(" net/ipv6/conf/default/accept_redirects=0");
166
+ lines.push("");
167
+ lines.push(" # Ignore send redirects");
168
+ lines.push(" net/ipv4/conf/all/send_redirects=0");
169
+ lines.push(" net/ipv4/conf/default/send_redirects=0");
170
+ lines.push("");
171
+ lines.push(" # Log martian packets (packets with impossible addresses)");
172
+ lines.push(" net/ipv4/conf/all/log_martians=1");
173
+ lines.push(" net/ipv4/conf/default/log_martians=1");
174
+ lines.push("");
175
+ lines.push(" # SYN cookies protection (SYN flood mitigation)");
176
+ lines.push(" net/ipv4/tcp_syncookies=1");
177
+ lines.push("");
178
+ lines.push(" # Source address verification (spoofing protection)");
179
+ lines.push(" net/ipv4/conf/all/secure_redirects=1");
180
+ lines.push(" net/ipv4/conf/default/secure_redirects=1");
181
+ lines.push("");
182
+
183
+ return lines;
184
+ }
185
+
186
+ /**
187
+ * Commands to configure and activate UFW firewall at first boot.
188
+ * Returns cloud-init YAML lines for the `runcmd:` section.
189
+ *
190
+ * Order matters:
191
+ * 1. Set default policies (deny incoming, allow outgoing)
192
+ * 2. Allow loopback interface
193
+ * 3. Allow SSH with rate limiting (prevents brute-force)
194
+ * 4. Allow HTTP/HTTPS if enabled
195
+ * 5. Allow Node Agent port if enabled
196
+ * 6. Allow Tailscale port (required for VPN)
197
+ * 7. Enable logging with rate limiting
198
+ * 8. Enable and reload UFW
199
+ * 9. Display firewall status
200
+ */
201
+ export function ufwFirewallRunCmd(options: UFWFirewallOptions = {}): string[] {
202
+ const {
203
+ allowSSHFrom = [],
204
+ allowHTTP = true,
205
+ allowHTTPS = true,
206
+ allowNodeAgent = false,
207
+ additionalPorts = [],
208
+ verboseLogging = false,
209
+ } = options;
210
+
211
+ const lines: string[] = [];
212
+
213
+ lines.push(" # UFW Firewall: Configure and enable secure firewall");
214
+ lines.push("");
215
+
216
+ // Set default policies
217
+ lines.push(" # Set default policies: deny incoming, allow outgoing");
218
+ lines.push(" - ufw --force reset");
219
+ lines.push(" - ufw default deny incoming");
220
+ lines.push(" - ufw default allow outgoing");
221
+ lines.push(" - ufw default deny forwarded");
222
+ lines.push("");
223
+
224
+ // Allow loopback
225
+ lines.push(" # Allow loopback interface");
226
+ lines.push(" - ufw allow in on lo");
227
+ lines.push("");
228
+
229
+ // Allow SSH with rate limiting
230
+ if (allowSSHFrom.length === 0) {
231
+ // Allow SSH from anywhere with rate limiting
232
+ lines.push(" # Allow SSH with rate limiting (6 connections in 30 seconds)");
233
+ lines.push(" - ufw limit 22/tcp comment 'Rate-limited SSH'");
234
+ } else {
235
+ // Allow SSH from specific IPs/CIDRs
236
+ for (const source of allowSSHFrom) {
237
+ lines.push(` - ufw allow from ${source} to any port 22 proto tcp comment 'SSH from ${source}'`);
238
+ }
239
+ }
240
+ lines.push("");
241
+
242
+ // Allow HTTP if enabled
243
+ if (allowHTTP) {
244
+ lines.push(" # Allow HTTP");
245
+ lines.push(" - ufw allow 80/tcp comment 'HTTP'");
246
+ }
247
+
248
+ // Allow HTTPS if enabled
249
+ if (allowHTTPS) {
250
+ lines.push(" # Allow HTTPS");
251
+ lines.push(" - ufw allow 443/tcp comment 'HTTPS'");
252
+ }
253
+ lines.push("");
254
+
255
+ // Allow Node Agent port if enabled
256
+ if (allowNodeAgent) {
257
+ lines.push(" # Allow Node Agent (internal communication)");
258
+ lines.push(" - ufw allow 8911/tcp comment 'Node Agent'");
259
+ lines.push("");
260
+ }
261
+
262
+ // Allow Tailscale (required for VPN functionality)
263
+ lines.push(" # Allow Tailscale VPN");
264
+ lines.push(" - ufw allow 41641/udp comment 'Tailscale'");
265
+ lines.push("");
266
+
267
+ // Additional ports
268
+ if (additionalPorts.length > 0) {
269
+ lines.push(" # Additional custom ports");
270
+ for (const portConfig of additionalPorts) {
271
+ const protocol = portConfig.protocol || "tcp";
272
+ const comment = portConfig.comment || `Custom port ${portConfig.port}`;
273
+ lines.push(` - ufw allow ${portConfig.port}/${protocol} comment '${comment}'`);
274
+ }
275
+ lines.push("");
276
+ }
277
+
278
+ // Configure logging
279
+ if (verboseLogging) {
280
+ lines.push(" # Enable verbose logging");
281
+ lines.push(" - ufw logging on");
282
+ lines.push(" - ufw logging high");
283
+ } else {
284
+ lines.push(" # Enable rate-limited logging (prevent log flooding)");
285
+ lines.push(" - ufw logging on");
286
+ lines.push(" - ufw logging low");
287
+ }
288
+ lines.push("");
289
+
290
+ // Enable and reload UFW
291
+ lines.push(" # Enable and reload UFW");
292
+ lines.push(" - ufw --force enable");
293
+ lines.push(" - ufw reload");
294
+ lines.push("");
295
+
296
+ // Display status
297
+ lines.push(" # Display firewall status");
298
+ lines.push(" - ufw status verbose > /var/log/ufw-bootstrap-status.log");
299
+ lines.push("");
300
+
301
+ return lines;
302
+ }
303
+
304
+ /**
305
+ * Generate complete UFW firewall configuration for Genesis servers.
306
+ *
307
+ * @param options - UFW firewall options (uses DEFAULT_UFW_GENESIS_OPTIONS if not provided)
308
+ * @returns Object with packages, writeFiles, and runCmd arrays
309
+ */
310
+ export function generateUFWFirewallForGenesis(
311
+ options: UFWFirewallOptions = DEFAULT_UFW_GENESIS_OPTIONS,
312
+ ): {
313
+ packages: string[];
314
+ writeFiles: string[];
315
+ runCmd: string[];
316
+ } {
317
+ return {
318
+ packages: ufwFirewallPackages(),
319
+ writeFiles: ufwFirewallWriteFiles(options),
320
+ runCmd: ufwFirewallRunCmd(options),
321
+ };
322
+ }
323
+
324
+ /**
325
+ * Generate complete UFW firewall configuration for worker/seed servers.
326
+ *
327
+ * @param options - UFW firewall options (uses DEFAULT_UFW_WORKER_OPTIONS if not provided)
328
+ * @returns Object with packages, writeFiles, and runCmd arrays
329
+ */
330
+ export function generateUFWFirewallForWorker(
331
+ options: UFWFirewallOptions = DEFAULT_UFW_WORKER_OPTIONS,
332
+ ): {
333
+ packages: string[];
334
+ writeFiles: string[];
335
+ runCmd: string[];
336
+ } {
337
+ return {
338
+ packages: ufwFirewallPackages(),
339
+ writeFiles: ufwFirewallWriteFiles(options),
340
+ runCmd: ufwFirewallRunCmd(options),
341
+ };
342
+ }