Skip to content

fix(dns): write the file the stage names, not the symlink's target - #339

Open
ci-robbot wants to merge 1 commit into
mudler:masterfrom
ci-robbot:fix/835-dns-vfs-symlink
Open

ci-robbot wants to merge 1 commit into
mudler:masterfrom
ci-robbot:fix/835-dns-vfs-symlink

Conversation

@ci-robbot

Copy link
Copy Markdown
Contributor

Fixes the second half of kairos-io/kairos#835, the one left open when the initramfs systemd-resolved dracut module was handled: the dns: stage does not stick on a systemd host.

The bug

Build used os.WriteFile, which follows symlinks. Kairos points /etc/resolv.conf at systemd-resolved's runtime file on every systemd image (kairos-init/pkg/bundled/cloudconfigs/05_network.yaml:71 does rm /etc/resolv.conf && ln -s /run/systemd/resolve/resolv.conf /etc/resolv.conf), and most systemd distributions do the same on their own. So the nameservers went into a file resolved owns and regenerates, on a tmpfs. In an initramfs that directory does not exist yet, and the stage failed outright.

A/B with the same yaml, against a tree whose /etc/resolv.conf is that symlink:

before after
resolved's runtime file overwritten with the user's nameservers untouched
/etc/resolv.conf still a symlink, so the setting is not durable regular file, 0644, the nameservers
initramfs (no /run/systemd/resolve yet) level=fatal ... open /etc/resolv.conf: no such file or directory writes the file

Separately, the plugin took a vfs.FS and never used it. Handed a filesystem rooted elsewhere it still wrote the host's /etc/resolv.conf; the only reason the existing test passed was that it read the result back with os.Open. Every other file-writing plugin here goes through fs.

The fix

Write a sibling and Rename over the path. Rename acts on the link rather than its target, so the caller gets a regular file where it asked for one, resolved's file is left alone, and no resolver can read a half-written resolv.conf. Route the whole function through fs, and create the parent directory when it is missing.

Build gains an fs vfs.FS parameter. It has no callers outside this package.

Tests

Six specs in dns_test.go, covering the symlink, the dangling symlink, the vfs, the missing parent directory, and search/options. Mutation-checked one layer at a time: reverting fs.* to os.* reds all six, writing the path directly instead of renaming reds exactly the two symlink specs, dropping the MkdirAll reds exactly the parent-directory spec. Suite is back to its pre-existing baseline on this box (90 passed / 30 failed, the 30 being the layout and user specs that need mkfs and root).

Not in scope

Making the stage speak to systemd-resolved natively (resolvectl or a .network drop-in) instead of writing resolv.conf at all is a separate design call, and I have left it alone. This change only makes the plugin honour the path it was given.

os.WriteFile follows symlinks, and most systemd distributions ship
/etc/resolv.conf as a link into systemd-resolved's runtime directory.
The nameservers therefore landed in a file resolved owns and regenerates,
on a tmpfs, so the dns stage never stuck. In an initramfs that directory
does not exist yet and the whole stage failed with ENOENT.

Write a sibling and rename over the path instead. Rename acts on the link
rather than its target, so the caller gets a regular file where it asked
for one, resolved's file is left alone, and no reader can observe a
half-written resolv.conf.

The same function ignored the vfs.FS it was handed and went straight to
the os package, which wrote the host's /etc/resolv.conf no matter which
filesystem the executor was pointed at. Route it through fs, like every
other plugin, and create the parent directory when it is missing.

Signed-off-by: Ettore Di Giacinto <mudler@kairos.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants