Skip to content

secure-network: fix intermittent DNS failure on Ubuntu runners (resolv.conf symlink)#175

Open
jeresig wants to merge 1 commit intomainfrom
secure-network-symlink-fix
Open

secure-network: fix intermittent DNS failure on Ubuntu runners (resolv.conf symlink)#175
jeresig wants to merge 1 commit intomainfrom
secure-network-symlink-fix

Conversation

@jeresig
Copy link
Copy Markdown
Member

@jeresig jeresig commented Apr 13, 2026

Summary

  • Fixes intermittent DNS failures on GitHub-hosted runners where secure-network would set up Unbound correctly but DNS queries would still fail
  • Root cause: on Ubuntu 24.04, /etc/resolv.conf is a symlink to /run/systemd/resolve/stub-resolv.conf on tmpfs. Writing through the symlink put our nameserver 127.0.0.1 on tmpfs, where chattr +i is a no-op — so systemd-resolved could silently overwrite the file back to nameserver 127.0.0.53
  • Fix: break the symlink before writing, so /etc/resolv.conf becomes a regular file on /etc (ext4), where chattr +i actually locks it

How the failure manifested

Post-job diagnostics (added in #171) showed:

  • Unbound was running fine (not crashed)
  • /etc/resolv.conf contained nameserver 127.0.0.53 (systemd-resolved's stub) instead of nameserver 127.0.0.1
  • DNS test: connection refused to 127.0.0.53#53 — systemd-resolved was stopped but iptables also REJECTs queries to 127.0.0.53 (only 127.0.0.1:53 is allowed)

Why it was intermittent

systemd-resolved only rewrites its stub file in response to network events (DHCP renewals, interface changes, etc.). Most runs complete without such an event in the narrow window between our writeFileSync and the failed chattr +i, so it usually works. Occasionally a runner gets a network event mid-job and the file is overwritten.

Test plan

  • Run a workflow that uses secure-network and confirm the new log line appears: Removed /etc/resolv.conf symlink (was managed by systemd-resolved).
  • Post-job diagnostics should now show nameserver 127.0.0.1 in /etc/resolv.conf

🤖 Generated with Claude Code

…overwrite on Ubuntu runners

On GitHub-hosted Ubuntu runners, /etc/resolv.conf is a symlink to
/run/systemd/resolve/stub-resolv.conf on tmpfs. Writing through the
symlink let systemd-resolved silently overwrite our nameserver change,
and chattr +i was a no-op on tmpfs. This caused intermittent DNS
failures: DNS queries went to 127.0.0.53 (systemd-resolved's stub),
which was blocked by iptables and not listening anyway.

Fix: remove any symlink before writing, so /etc/resolv.conf becomes a
real file on /etc (ext4) that chattr can lock.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Copy link
Copy Markdown

@claude claude bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 13, 2026

🦋 Changeset detected

Latest commit: 4bdf82b

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
secure-network Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@khan-actions-bot khan-actions-bot requested review from a team, jaredly and kevinb-khan and removed request for a team April 13, 2026 21:24
@khan-actions-bot
Copy link
Copy Markdown
Contributor

Gerald

Required Reviewers
  • @Khan/github-actions for changes to .changeset/secure-network-symlink-fix.md

Don't want to be involved in this pull request? Comment #removeme and we won't notify you of further changes.

Copy link
Copy Markdown
Contributor

@jaredly jaredly left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems fine

Comment on lines +561 to +563
} catch (_) {
/* not a symlink or doesn't exist — proceed */
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this catch feels a little over-general -- if we want the "doesn't exist" case, we could fo fs.existsSync(path) && fs.lstatSync(path).isSymbolicLink()

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.

3 participants