Skip to content

Commit 15f74a4

Browse files
committed
cmd/tailmixd: retain WireGuard profile after state sorting
Reacquire an applied WireGuard profile by its stable ID after state normalization reorders the profile slice. Keep its state directory separately so secret cleanup cannot follow a stale profile pointer. Add a regression test that deterministically invalidates the original pointer by sorting the new profile ahead of an existing profile. Change-Id: I9009ffb6508576edb29ed5289d838e5cbff50a1e
1 parent cb8e210 commit 15f74a4

2 files changed

Lines changed: 50 additions & 4 deletions

File tree

‎cmd/tailmixd/wireguardapi.go‎

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ func (s *supervisor) ApplyWireGuard(_ context.Context, requested wireguardcfg.Co
7070
})
7171
configured, _ = profileByName(&next, config.Name)
7272
}
73+
profileID := configured.ID
74+
profileStateDir := configured.StateDir
7375

7476
secrets, err := completeWireGuardSecrets(config, supplied, oldSecrets, create)
7577
if err != nil {
@@ -78,14 +80,14 @@ func (s *supervisor) ApplyWireGuard(_ context.Context, requested wireguardcfg.Co
7880
if err := validateWireGuardKeyOwnership(config, secrets); err != nil {
7981
return controlapi.WireGuardProfile{}, controlapi.NewError("invalid_request", "%v", err)
8082
}
81-
secretFile, err := writeWireGuardSecrets(configured.StateDir, secrets)
83+
secretFile, err := writeWireGuardSecrets(profileStateDir, secrets)
8284
if err != nil {
8385
return controlapi.WireGuardProfile{}, fmt.Errorf("write WireGuard profile secrets: %w", err)
8486
}
8587
committed := false
8688
defer func() {
8789
if !committed {
88-
_ = removeWireGuardSecrets(configured.StateDir, secretFile)
90+
_ = removeWireGuardSecrets(profileStateDir, secretFile)
8991
}
9092
}()
9193

@@ -98,7 +100,10 @@ func (s *supervisor) ApplyWireGuard(_ context.Context, requested wireguardcfg.Co
98100
configured.MagicDNSSuffix = config.DNSSuffix
99101
configured.WireGuard = configPointer(config)
100102
configured.WireGuardSecretFile = secretFile
101-
state.Normalize(&next)
103+
configured, err = normalizeProfileByID(&next, profileID)
104+
if err != nil {
105+
return controlapi.WireGuardProfile{}, controlapi.NewError("invalid_state", "%v", err)
106+
}
102107

103108
var engine *tailmixprofile.WireGuardEngine
104109
if wasRunning {
@@ -138,7 +143,7 @@ func (s *supervisor) ApplyWireGuard(_ context.Context, requested wireguardcfg.Co
138143
}
139144
committed = true
140145
if oldSecretFile != "" && oldSecretFile != secretFile {
141-
_ = removeWireGuardSecrets(configured.StateDir, oldSecretFile)
146+
_ = removeWireGuardSecrets(profileStateDir, oldSecretFile)
142147
}
143148
return s.wireGuardProfileLocked(*configured)
144149
}
@@ -229,6 +234,24 @@ func beforeProfileRemoved(st state.State, profileID string) bool {
229234
return false
230235
}
231236

237+
func profileByID(st *state.State, profileID string) *state.Profile {
238+
for i := range st.Profiles {
239+
if st.Profiles[i].ID == profileID {
240+
return &st.Profiles[i]
241+
}
242+
}
243+
return nil
244+
}
245+
246+
func normalizeProfileByID(st *state.State, profileID string) (*state.Profile, error) {
247+
state.Normalize(st)
248+
configured := profileByID(st, profileID)
249+
if configured == nil {
250+
return nil, errors.New("profile disappeared during normalization")
251+
}
252+
return configured, nil
253+
}
254+
232255
func validateWireGuardKeyOwnership(config wireguardcfg.Config, secrets wireguardcfg.Secrets) error {
233256
if secrets.PrivateKey == nil {
234257
return errors.New("WireGuard private key is required")

‎cmd/tailmixd/wireguardapi_test.go‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,26 @@ func TestValidateWireGuardEndpointNamesRejectsProfileDNSRecursion(t *testing.T)
6262
t.Fatalf("rejected independent endpoint: %v", err)
6363
}
6464
}
65+
66+
func TestNormalizeProfileByIDReacquiresSortedProfile(t *testing.T) {
67+
config := wireguardcfg.Config{
68+
Version: wireguardcfg.Version, Name: "lab", DNSSuffix: "lab.example",
69+
Addresses: []netip.Addr{netip.MustParseAddr("10.80.0.1")},
70+
}
71+
st := state.State{Profiles: []state.Profile{
72+
{ID: "z_existing", Name: "existing", Kind: state.ProfileKindTailscale},
73+
{ID: "p_new", Name: "lab", Kind: state.ProfileKindWireGuard, WireGuard: &config},
74+
}}
75+
stale := &st.Profiles[1]
76+
77+
configured, err := normalizeProfileByID(&st, "p_new")
78+
if err != nil {
79+
t.Fatal(err)
80+
}
81+
if stale.ID != "z_existing" {
82+
t.Fatalf("test did not force the original pointer to become stale: points to %q", stale.ID)
83+
}
84+
if configured.ID != "p_new" || configured.WireGuard == nil || configured.WireGuard.Name != "lab" {
85+
t.Fatalf("reacquired profile = %+v", configured)
86+
}
87+
}

0 commit comments

Comments
 (0)