Skip to content

Commit 8ec33f1

Browse files
committed
🐛 fix(runners): improve YAML output and DNS configuration
- Switch from pyyaml to ruamel-yaml for proper literal block scalar (|) output in generated configs (user-data, app_private_key fields) - Add OOM protection to runner manager services (OOMScoreAdjust=-900) - Enable systemd-resolved in base image for DNS caching via stub resolver - Remove redundant DNS override from cloud-init runcmd - let DHCP/ systemd-networkd handle DNS configuration (dnsmasq option 6) DNS flow now: app → 127.0.0.53 (systemd-resolved) → gateway (dnsmasq)
1 parent 8828bfe commit 8ec33f1

7 files changed

Lines changed: 82 additions & 59 deletions

File tree

images/docker/ubuntu-24.04/Dockerfile

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,36 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
158158
# Copy common overlay files (network, docker service drop-in)
159159
COPY overlay/common/etc /etc
160160

161+
# Enable systemd-resolved for DNS caching (without systemctl during build)
162+
# DNS servers are obtained via DHCP when using systemd-networkd (UseDNS=yes)
163+
RUN mkdir -p /etc/systemd/system/multi-user.target.wants \
164+
&& ln -sf /lib/systemd/system/systemd-resolved.service \
165+
/etc/systemd/system/multi-user.target.wants/systemd-resolved.service
166+
167+
# Configure resolv.conf symlink at runtime (cannot modify during build - Docker bind-mount)
168+
# This oneshot service runs before systemd-resolved and sets up the stub resolver symlink
169+
RUN printf '[Unit]\n\
170+
Description=Setup resolv.conf symlink for systemd-resolved\n\
171+
DefaultDependencies=no\n\
172+
Before=systemd-resolved.service\n\
173+
Before=network.target\n\
174+
\n\
175+
[Service]\n\
176+
Type=oneshot\n\
177+
RemainAfterExit=yes\n\
178+
ExecStart=/bin/sh -c "rm -f /etc/resolv.conf && ln -s /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf"\n\
179+
\n\
180+
[Install]\n\
181+
WantedBy=sysinit.target\n' > /etc/systemd/system/resolv-conf-symlink.service \
182+
&& mkdir -p /etc/systemd/system/sysinit.target.wants \
183+
&& ln -sf /etc/systemd/system/resolv-conf-symlink.service \
184+
/etc/systemd/system/sysinit.target.wants/resolv-conf-symlink.service
185+
186+
# Harden systemd-resolved for Firecracker VMs (disable unused features)
187+
RUN mkdir -p /etc/systemd/resolved.conf.d \
188+
&& printf '[Resolve]\nLLMNR=no\nMulticastDNS=no\nDNSSEC=no\nDNSOverTLS=no\n' \
189+
> /etc/systemd/resolved.conf.d/firecracker.conf
190+
161191
# Enable cloud-init services (runs before Docker starts)
162192
RUN systemctl enable cloud-init-local.service \
163193
cloud-init.service \

modules/fireactions/inject-secrets.py

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,19 @@
99
- Squid SSL bump CA certificate (only when needed)
1010
- Debug SSH key injection
1111
"""
12-
import yaml
12+
from ruamel.yaml import YAML
13+
from ruamel.yaml.scalarstring import LiteralScalarString
1314
import os
1415
import json
1516

16-
# Custom representer for multi-line strings (block scalar style)
17-
def str_representer(dumper, data):
18-
if "\n" in data:
19-
return dumper.represent_scalar("tag:yaml.org,2002:str", data, style="|")
20-
return dumper.represent_scalar("tag:yaml.org,2002:str", data)
21-
22-
yaml.add_representer(str, str_representer)
17+
# Initialize ruamel.yaml with round-trip mode for proper formatting
18+
yaml = YAML()
19+
yaml.preserve_quotes = True
20+
yaml.default_flow_style = False
2321

2422
# Read the base config
2523
with open("/etc/fireactions/config.yaml", "r") as f:
26-
config = yaml.safe_load(f)
24+
config = yaml.load(f)
2725

2826
# Inject GitHub secrets from files
2927
if "github" in config:
@@ -35,7 +33,7 @@ def str_representer(dumper, data):
3533
private_key_file = os.environ.get("PRIVATE_KEY_FILE", "")
3634
if private_key_file:
3735
with open(private_key_file, "r") as f:
38-
config["github"]["app_private_key"] = f.read()
36+
config["github"]["app_private_key"] = LiteralScalarString(f.read())
3937

4038
# Inject metadata into all pools
4139
if "pools" in config:
@@ -265,12 +263,9 @@ def str_representer(dumper, data):
265263
" fi",
266264
])
267265

268-
# DNS configuration (always, if gateway is set)
269-
if gateway:
270-
user_data_lines.extend([
271-
" # Set DNS to use host gateway (centralized DNS via dnsmasq)",
272-
f" - echo 'nameserver {gateway}' > /etc/resolv.conf",
273-
])
266+
# Note: DNS is automatically configured via DHCP (dnsmasq provides option 6)
267+
# systemd-networkd accepts DNS from DHCP (UseDNS=yes in 10-eth0.network)
268+
# systemd-resolved caches and forwards to the gateway
274269

275270
# Hostname from MMDS
276271
user_data_lines.extend([
@@ -282,7 +277,7 @@ def str_representer(dumper, data):
282277
" fi",
283278
])
284279

285-
user_data = '\n'.join(user_data_lines) + '\n'
280+
user_data = LiteralScalarString('\n'.join(user_data_lines) + '\n')
286281

287282
# Inject user-data into all pools (firecracker/metadata already initialized above)
288283
for pool in config["pools"]:
@@ -293,6 +288,6 @@ def str_representer(dumper, data):
293288

294289
# Write the final config
295290
with open("/run/fireactions/config.yaml", "w") as f:
296-
yaml.dump(config, f, default_flow_style=False, allow_unicode=True, sort_keys=False)
291+
yaml.dump(config, f)
297292

298293
print("Config with secrets written to /run/fireactions/config.yaml")

modules/fireactions/services.nix

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,7 @@ in
371371
) registryCacheCfg._internal.caCertPath
372372
}"
373373
374-
${pkgs.python3.withPackages (ps: [ ps.pyyaml ])}/bin/python3 ${./inject-secrets.py}
374+
${pkgs.python3.withPackages (ps: [ ps.ruamel-yaml ])}/bin/python3 ${./inject-secrets.py}
375375
376376
# Set proper permissions
377377
chown ${cfg.user}:${cfg.group} /run/fireactions/config.yaml
@@ -446,6 +446,10 @@ in
446446
# Working directory
447447
WorkingDirectory = cfg.dataDir;
448448

449+
# OOM protection - critical infrastructure service
450+
OOMScoreAdjust = -900;
451+
OOMPolicy = "continue";
452+
449453
#
450454
# Security hardening (built-in, always enabled)
451455
#

modules/fireglab/inject-secrets.py

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
- Squid SSL bump CA certificate (only when needed)
1212
- Debug SSH key injection
1313
"""
14-
import yaml
14+
from ruamel.yaml import YAML
15+
from ruamel.yaml.scalarstring import LiteralScalarString
1516
import os
1617
import json
1718

@@ -25,18 +26,14 @@ def read_secret_file(env_var):
2526
return None
2627

2728

28-
# Custom representer for multi-line strings (block scalar style)
29-
def str_representer(dumper, data):
30-
if "\n" in data:
31-
return dumper.represent_scalar("tag:yaml.org,2002:str", data, style="|")
32-
return dumper.represent_scalar("tag:yaml.org,2002:str", data)
33-
34-
35-
yaml.add_representer(str, str_representer)
29+
# Initialize ruamel.yaml with round-trip mode for proper formatting
30+
yaml = YAML()
31+
yaml.preserve_quotes = True
32+
yaml.default_flow_style = False
3633

3734
# Read the base config
3835
with open("/etc/fireglab/config.yaml", "r") as f:
39-
config = yaml.safe_load(f)
36+
config = yaml.load(f)
4037

4138
# Inject GitLab secrets from files
4239
if "gitlab" in config:
@@ -299,13 +296,9 @@ def str_representer(dumper, data):
299296
" fi",
300297
])
301298

302-
# DNS configuration
303-
# Use fireglab gateway for DNS (10.202.0.1) which forwards to main dnsmasq
304-
if fireglab_gateway:
305-
user_data_lines.extend([
306-
" # Set DNS to use fireglab gateway (centralized DNS via dnsmasq)",
307-
f" - echo 'nameserver {fireglab_gateway}' > /etc/resolv.conf",
308-
])
299+
# Note: DNS is automatically configured via DHCP (dnsmasq provides option 6)
300+
# systemd-networkd accepts DNS from DHCP (UseDNS=yes in 10-eth0.network)
301+
# systemd-resolved caches and forwards to the gateway
309302

310303
# Hostname from MMDS
311304
user_data_lines.extend([
@@ -317,7 +310,7 @@ def str_representer(dumper, data):
317310
" fi",
318311
])
319312

320-
user_data = '\n'.join(user_data_lines) + '\n'
313+
user_data = LiteralScalarString('\n'.join(user_data_lines) + '\n')
321314

322315
# Inject user-data into all pools
323316
for pool in config["pools"]:
@@ -329,6 +322,6 @@ def str_representer(dumper, data):
329322
# Write the modified config
330323
os.makedirs("/run/fireglab", exist_ok=True)
331324
with open("/run/fireglab/config.yaml", "w") as f:
332-
yaml.dump(config, f, default_flow_style=False, sort_keys=False)
325+
yaml.dump(config, f)
333326

334327
print("fireglab config generated at /run/fireglab/config.yaml")

modules/fireglab/services.nix

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,7 @@ in
353353
) registryCacheCfg._internal.caCertPath
354354
}"
355355
356-
${pkgs.python3.withPackages (ps: [ ps.pyyaml ])}/bin/python3 ${./inject-secrets.py}
356+
${pkgs.python3.withPackages (ps: [ ps.ruamel-yaml ])}/bin/python3 ${./inject-secrets.py}
357357
358358
chown ${cfg.user}:${cfg.group} /run/fireglab/config.yaml
359359
chmod 0640 /run/fireglab/config.yaml
@@ -427,6 +427,10 @@ in
427427
Restart = "always";
428428
RestartSec = "10s";
429429

430+
# OOM protection - critical infrastructure service
431+
OOMScoreAdjust = -900;
432+
OOMPolicy = "continue";
433+
430434
# Security hardening
431435
NoNewPrivileges = false;
432436
ProtectSystem = "strict";

modules/fireteact/inject-secrets.py

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
- Squid SSL bump CA certificate (only when needed)
1212
- Debug SSH key injection
1313
"""
14-
import yaml
14+
from ruamel.yaml import YAML
15+
from ruamel.yaml.scalarstring import LiteralScalarString
1516
import os
1617
import json
1718

@@ -25,18 +26,14 @@ def read_secret_file(env_var):
2526
return None
2627

2728

28-
# Custom representer for multi-line strings (block scalar style)
29-
def str_representer(dumper, data):
30-
if "\n" in data:
31-
return dumper.represent_scalar("tag:yaml.org,2002:str", data, style="|")
32-
return dumper.represent_scalar("tag:yaml.org,2002:str", data)
33-
34-
35-
yaml.add_representer(str, str_representer)
29+
# Initialize ruamel.yaml with round-trip mode for proper formatting
30+
yaml = YAML()
31+
yaml.preserve_quotes = True
32+
yaml.default_flow_style = False
3633

3734
# Read the base config
3835
with open("/etc/fireteact/config.yaml", "r") as f:
39-
config = yaml.safe_load(f)
36+
config = yaml.load(f)
4037

4138
# Inject Gitea secrets from files
4239
if "gitea" in config:
@@ -293,13 +290,9 @@ def str_representer(dumper, data):
293290
" fi",
294291
])
295292

296-
# DNS configuration
297-
# Use fireteact gateway for DNS (10.201.0.1) which forwards to main dnsmasq
298-
if fireteact_gateway:
299-
user_data_lines.extend([
300-
" # Set DNS to use fireteact gateway (centralized DNS via dnsmasq)",
301-
f" - echo 'nameserver {fireteact_gateway}' > /etc/resolv.conf",
302-
])
293+
# Note: DNS is automatically configured via DHCP (dnsmasq provides option 6)
294+
# systemd-networkd accepts DNS from DHCP (UseDNS=yes in 10-eth0.network)
295+
# systemd-resolved caches and forwards to the gateway
303296

304297
# Hostname from MMDS
305298
user_data_lines.extend([
@@ -311,7 +304,7 @@ def str_representer(dumper, data):
311304
" fi",
312305
])
313306

314-
user_data = '\n'.join(user_data_lines) + '\n'
307+
user_data = LiteralScalarString('\n'.join(user_data_lines) + '\n')
315308

316309
# Inject user-data into all pools
317310
for pool in config["pools"]:
@@ -323,6 +316,6 @@ def str_representer(dumper, data):
323316
# Write the modified config
324317
os.makedirs("/run/fireteact", exist_ok=True)
325318
with open("/run/fireteact/config.yaml", "w") as f:
326-
yaml.dump(config, f, default_flow_style=False, sort_keys=False)
319+
yaml.dump(config, f)
327320

328321
print("fireteact config generated at /run/fireteact/config.yaml")

modules/fireteact/services.nix

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,7 @@ in
353353
) registryCacheCfg._internal.caCertPath
354354
}"
355355
356-
${pkgs.python3.withPackages (ps: [ ps.pyyaml ])}/bin/python3 ${./inject-secrets.py}
356+
${pkgs.python3.withPackages (ps: [ ps.ruamel-yaml ])}/bin/python3 ${./inject-secrets.py}
357357
358358
chown ${cfg.user}:${cfg.group} /run/fireteact/config.yaml
359359
chmod 0640 /run/fireteact/config.yaml
@@ -427,6 +427,10 @@ in
427427
Restart = "always";
428428
RestartSec = "10s";
429429

430+
# OOM protection - critical infrastructure service
431+
OOMScoreAdjust = -900;
432+
OOMPolicy = "continue";
433+
430434
# Security hardening
431435
NoNewPrivileges = false;
432436
ProtectSystem = "strict";

0 commit comments

Comments
 (0)