WIP: rebase fork onto upstream/main (v1.103.0) #15

Closed
codinget wants to merge 670 commits from webnet into save/webnet-2026-07-29
9 changed files with 194 additions and 10 deletions
Showing only changes of commit 7355116c05 - Show all commits
+5 -2
View File
@@ -243,8 +243,11 @@ func (s *tpmStore) WriteState(k ipn.StateKey, bs []byte) error {
if bytes.Equal(s.cache[k], bs) {
return nil
}
s.cache[k] = bytes.Clone(bs)
if bs == nil {
delete(s.cache, k)
} else {
s.cache[k] = bytes.Clone(bs)
}
return s.writeSealed()
}
+30
View File
@@ -8683,3 +8683,33 @@ func TestPeerRoutesCGNATCollapse(t *testing.T) {
t.Errorf("with subnet: got %v; want %v", got, want)
}
}
func TestResetAuthClearsMachineKey(t *testing.T) {
store := new(mem.Store)
// Write a machine key to the store.
machineKey := key.NewMachine()
keyText, _ := machineKey.MarshalText()
if err := store.WriteState(ipn.MachineKeyStateKey, keyText); err != nil {
t.Fatal(err)
}
// Verify key is readable.
if bs, err := store.ReadState(ipn.MachineKeyStateKey); err != nil {
t.Fatalf("ReadState before clear: %v", err)
} else if len(bs) == 0 {
t.Fatal("machine key is empty before clear")
}
// Clear the key the same way ResetAuth does.
if err := ipn.WriteState(store, ipn.MachineKeyStateKey, nil); err != nil {
t.Fatal(err)
}
// Verify key is gone. It should return ErrStateNotExist,
// not nil/nil which would cause initMachineKeyLocked to
// fail with "invalid key ... doesn't have expected type prefix".
if _, err := store.ReadState(ipn.MachineKeyStateKey); err != ipn.ErrStateNotExist {
t.Fatalf("ReadState after clear: got err %v, want ErrStateNotExist", err)
}
}
+44
View File
@@ -1185,3 +1185,47 @@ func (k *failingHardwareAttestationKey) UnmarshalJSON([]byte) error {
k.unmarshalCalled = true
return errors.New("failed to unmarshal attestation key!")
}
func TestDeleteProfileClearsState(t *testing.T) {
store := new(mem.Store)
pm, err := newProfileManagerWithGOOS(store, logger.Discard, health.NewTracker(eventbustest.NewBus(t)), "linux")
if err != nil {
t.Fatal(err)
}
pm.SetCurrentUserID("user1")
pm.SwitchToNewProfile()
p := pm.CurrentPrefs().AsStruct()
p.Persist = &persist.Persist{
NodeID: "node1",
PrivateNodeKey: key.NewNode(),
UserProfile: tailcfg.UserProfile{
ID: 1,
LoginName: "user@example.com",
},
}
if err := pm.SetPrefs(p.View(), ipn.NetworkProfile{}); err != nil {
t.Fatal(err)
}
profileKey := pm.currentProfile.Key()
if profileKey == "" {
t.Fatal("profile key is empty")
}
// Verify profile state exists in store.
if _, err := store.ReadState(profileKey); err != nil {
t.Fatalf("ReadState before delete: %v", err)
}
profileID := pm.currentProfile.ID()
if err := pm.DeleteProfile(profileID); err != nil {
t.Fatal(err)
}
// Verify profile state is deleted from store.
if _, err := store.ReadState(profileKey); err != ipn.ErrStateNotExist {
t.Fatalf("ReadState after delete: got err %v, want ErrStateNotExist", err)
}
}
+3 -1
View File
@@ -92,7 +92,9 @@ type StateStore interface {
// ReadState returns the bytes associated with ID. Returns (nil,
// ErrStateNotExist) if the ID doesn't have associated state.
ReadState(id StateKey) ([]byte, error)
// WriteState saves bs as the state associated with ID.
// WriteState saves bs as the state associated with ID. If bs is nil,
// the state associated with ID is deleted, and a subsequent ReadState
// for ID will return ErrStateNotExist.
//
// Callers should generally use the ipn.WriteState wrapper func
// instead, which only writes if the value is different from what's
+26
View File
@@ -147,6 +147,9 @@ func (s *Store) WriteState(id ipn.StateKey, bs []byte) (err error) {
s.memory.WriteState(ipn.StateKey(sanitizeKey(id)), bs)
}
}()
if bs == nil {
return s.removeSecretField(string(id), s.secretName)
}
return s.updateSecret(map[string][]byte{string(id): bs}, s.secretName)
}
@@ -339,6 +342,29 @@ func (s *Store) updateSecret(data map[string][]byte, secretName string) (err err
return nil
}
func (s *Store) removeSecretField(key, secretName string) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
if s.canPatchSecret(secretName) {
return s.client.JSONPatchResource(ctx, secretName, kubeclient.TypeSecrets, []kubeclient.JSONPatch{
{
Op: "remove",
Path: "/data/" + sanitizeKey(ipn.StateKey(key)),
},
})
}
// No patch permissions, use UPDATE: get the secret, delete the key, update.
secret, err := s.client.GetSecret(ctx, secretName)
if err != nil {
return fmt.Errorf("error getting Secret %s: %w", secretName, err)
}
delete(secret.Data, sanitizeKey(ipn.StateKey(key)))
if err := s.client.UpdateSecret(ctx, secret); err != nil {
return fmt.Errorf("error updating Secret %s: %w", secretName, err)
}
return nil
}
func (s *Store) loadState() (err error) {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
+40 -5
View File
@@ -172,6 +172,32 @@ func TestWriteState(t *testing.T) {
},
allowPatch: true,
},
{
name: "delete_with_patch",
initial: map[string][]byte{
"foo": []byte("bar"),
"baz": []byte("quux"),
},
key: "foo",
value: nil,
wantData: map[string][]byte{
"baz": []byte("quux"),
},
allowPatch: true,
},
{
name: "delete_with_update",
initial: map[string][]byte{
"foo": []byte("bar"),
"baz": []byte("quux"),
},
key: "foo",
value: nil,
wantData: map[string][]byte{
"baz": []byte("quux"),
},
allowPatch: false,
},
}
for _, tt := range tests {
@@ -208,6 +234,9 @@ func TestWriteState(t *testing.T) {
} else if p.Op == "add" && strings.HasPrefix(p.Path, "/data/") {
key := strings.TrimPrefix(p.Path, "/data/")
secret[key] = p.Value.([]byte)
} else if p.Op == "remove" && strings.HasPrefix(p.Path, "/data/") {
key := strings.TrimPrefix(p.Path, "/data/")
delete(secret, key)
}
}
return nil
@@ -234,11 +263,17 @@ func TestWriteState(t *testing.T) {
// Verify memory store was updated
got, err := s.memory.ReadState(ipn.StateKey(sanitizeKey(string(tt.key))))
if err != nil {
t.Errorf("reading from memory store: %v", err)
}
if !cmp.Equal(got, tt.value) {
t.Errorf("memory store key %q = %v, want %v", tt.key, got, tt.value)
if tt.value == nil {
if err != ipn.ErrStateNotExist {
t.Errorf("reading deleted key from memory store: got err %v, want ErrStateNotExist", err)
}
} else {
if err != nil {
t.Errorf("reading from memory store: %v", err)
}
if !cmp.Equal(got, tt.value) {
t.Errorf("memory store key %q = %v, want %v", tt.key, got, tt.value)
}
}
})
}
+5 -1
View File
@@ -49,7 +49,11 @@ func (s *Store) WriteState(id ipn.StateKey, bs []byte) error {
if s.cache == nil {
s.cache = map[ipn.StateKey][]byte{}
}
s.cache[id] = bytes.Clone(bs)
if bs == nil {
delete(s.cache, id)
} else {
s.cache[id] = bytes.Clone(bs)
}
return nil
}
+5 -1
View File
@@ -214,7 +214,11 @@ func (s *FileStore) WriteState(id ipn.StateKey, bs []byte) error {
if bytes.Equal(s.cache[id], bs) {
return nil
}
s.cache[id] = bytes.Clone(bs)
if bs == nil {
delete(s.cache, id)
} else {
s.cache[id] = bytes.Clone(bs)
}
bs, err := json.MarshalIndent(s.cache, "", " ")
if err != nil {
return err
+36
View File
@@ -135,11 +135,41 @@ func testStoreSemantics(t *testing.T, store ipn.StateStore) {
}
}
func testStoreDeleteSemantics(t *testing.T, store ipn.StateStore) {
t.Helper()
// Write a key, verify it exists.
if err := store.WriteState("delme", []byte("val")); err != nil {
t.Fatalf("WriteState: %v", err)
}
if bs, err := store.ReadState("delme"); err != nil {
t.Fatalf("ReadState after write: %v", err)
} else if string(bs) != "val" {
t.Fatalf("ReadState after write: got %q, want %q", bs, "val")
}
// Delete by writing nil.
if err := store.WriteState("delme", nil); err != nil {
t.Fatalf("WriteState(nil): %v", err)
}
// Read should return ErrStateNotExist.
if _, err := store.ReadState("delme"); err != ipn.ErrStateNotExist {
t.Fatalf("ReadState after delete: got err %v, want ErrStateNotExist", err)
}
// Delete of a non-existent key should not error.
if err := store.WriteState("never-existed", nil); err != nil {
t.Fatalf("WriteState(nil) on non-existent key: %v", err)
}
}
func TestMemoryStore(t *testing.T) {
tstest.PanicOnLog()
store := new(mem.Store)
testStoreSemantics(t, store)
testStoreDeleteSemantics(t, store)
}
func TestFileStore(t *testing.T) {
@@ -154,6 +184,7 @@ func TestFileStore(t *testing.T) {
}
testStoreSemantics(t, store)
testStoreDeleteSemantics(t, store)
// Build a brand new file store and check that both IDs written
// above are still there.
@@ -176,4 +207,9 @@ func TestFileStore(t *testing.T) {
t.Errorf("reading %q (2nd store): got %q, want %q", key, bs, want)
}
}
// Verify deleted key is still gone after reload.
if _, err := store.ReadState("delme"); err != ipn.ErrStateNotExist {
t.Fatalf("reading deleted key from reloaded store: got err %v, want ErrStateNotExist", err)
}
}