fix: pass --statedir alongside --state= for SSM-backed state - #102
Conversation
When ssm_state_enabled = true, the module appends --state=arn:aws:ssm:... to tailscaled's flags so the daemon's state is persisted in SSM Parameter Store across instance replacements. With Go's flag.Parse() last-wins semantics, this --state=arn:... overrides the systemd unit's default --state=/var/lib/tailscale/tailscaled.state, which is the intended behavior. However, when --state= points at a portable store (arn:..., kube:..., mem:), tailscaled refuses to use the local filesystem as its 'var root' for sidecar files unless --statedir is also given explicitly. Without it, the in-process Tailscale SSH server can't persist host keys and silently stays disabled, along with taildrop, TKA (network-lock), and the per-profile cache. The journal reports: warning: unable to get SSH host keys, SSH will appear as disabled for this node: no var root for ssh keys taildrop: no Taildrop directory configured cannot fetch existing TKA state; no state directory for network-lock The Tailscale rpm/deb already configures StateDirectory=tailscale on the systemd unit (so /var/lib/tailscale exists with the right perms), so pinning --statedir there preserves the package's expectations and re-enables SSH/taildrop/TKA without any other changes.
--statedir alongside --state= for SSM-backed state--statedir alongside --state= for SSM-backed state
📝 WalkthroughWalkthroughThis PR extends the Tailscale daemon configuration in Estimated code review effort🎯 2 (Simple) | ⏱️ ~8 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@main.tf`:
- Line 23: The tailscaled_extra_flags currently concatenates
var.tailscaled_extra_flags before the module-injected local.ssm_state_flag and
local.ssm_statedir_flag which makes the module flags override caller-provided
--statedir (last-wins); change the concat order so the module flags come first
and var.tailscaled_extra_flags come last (i.e., concat([local.ssm_state_flag,
local.ssm_statedir_flag], var.tailscaled_extra_flags)) and keep the existing
compact(...) and join(...) so caller-supplied flags take precedence when
var.ssm_state_enabled is true.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
Reorder tailscaled extra flags so caller-provided flags are applied last, matching tailscaled's last-wins parsing behavior. Add a lifecycle precondition to fail plans when `ssm_state_enabled = true` and caller flags include `--state` or `--statedir`, preventing silent misconfiguration of module-managed SSM state.
what
ssm_state_enabled = true, the module now also passes--statedir=/var/lib/tailscaletotailscaledalongside the existing--state=arn:aws:ssm:…flag.--statedirviavar.tailscaled_extra_flagsare unaffected (Go'sflag.Parseis last-wins).why
--state=accepts portable URLs (arn:…,kube:…,mem:) that can hold the daemon's persistent prefs/keys outside the local filesystem — which is exactly whatssm_state_enabledopts callers into so node identity survives ASG instance replacement.--state=points at a portable store,tailscaledrefuses to use the local filesystem as its "var root" for sidecar files unless--statediris given explicitly. Without it, several features silently break:Hostinfo.SSH_HostKeysstays empty, the control plane reports the node as not-SSH-capable, and inboundssh/tailscale sshconnections hang at the banner exchange (TCP handshake succeeds, no SSH banner is ever sent).taildrop: no Taildrop directory configured).cannot fetch existing TKA state; no state directory for network-lock).profile data directory: profile local data storage unavailable).tailscale up --sshsucceeds,tailscale debug prefsshowsRunSSH: true, and the daemon advertises thecap/sshcapability — but the SSH server is never actually serving. Every consumer ofssm_state_enabledis exposed to it.StateDirectory=tailscaleon its systemd unit (i.e./var/lib/tailscaleexists with the right ownership/permissions), so pinning--statedirto that path keeps the daemon aligned with the packaging defaults and re-enables SSH, taildrop, and TKA with no other configuration required.references
tailscaledflag reference — see--stateand--statedir.unable to get SSH host keysintailscale/tailscale— the exact message produced when--statediris missing under a portable state store.Summary by CodeRabbit
Release Notes