mirror of
https://github.com/tailscale/tailscale.git
synced 2025-06-29 11:38:39 +00:00
ipn/store: make StateStore.All optional (#16409)
This method is only needed to migrate between store.FileStore and tpm.tpmStore. We can make a runtime type assertion instead of implementing an unused method for every platform. Updates #15830 Signed-off-by: Andrew Lytvynov <awly@tailscale.com>
This commit is contained in:
parent
0a64e86a0d
commit
76b9afb54d
@ -10,7 +10,4 @@ export const sessionStateStorage: IPNStateStorage = {
|
|||||||
getState(id) {
|
getState(id) {
|
||||||
return window.sessionStorage[`ipn-state-${id}`] || ""
|
return window.sessionStorage[`ipn-state-${id}`] || ""
|
||||||
},
|
},
|
||||||
all() {
|
|
||||||
return JSON.stringify(window.sessionStorage)
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
1
cmd/tsconnect/src/types/wasm_js.d.ts
vendored
1
cmd/tsconnect/src/types/wasm_js.d.ts
vendored
@ -44,7 +44,6 @@ declare global {
|
|||||||
interface IPNStateStorage {
|
interface IPNStateStorage {
|
||||||
setState(id: string, value: string): void
|
setState(id: string, value: string): void
|
||||||
getState(id: string): string
|
getState(id: string): string
|
||||||
all(): string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type IPNConfig = {
|
type IPNConfig = {
|
||||||
|
@ -15,7 +15,6 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"iter"
|
|
||||||
"log"
|
"log"
|
||||||
"math/rand/v2"
|
"math/rand/v2"
|
||||||
"net"
|
"net"
|
||||||
@ -580,29 +579,6 @@ func (s *jsStateStore) WriteState(id ipn.StateKey, bs []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *jsStateStore) All() iter.Seq2[ipn.StateKey, []byte] {
|
|
||||||
return func(yield func(ipn.StateKey, []byte) bool) {
|
|
||||||
jsValue := s.jsStateStorage.Call("all")
|
|
||||||
if jsValue.String() == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
buf, err := hex.DecodeString(jsValue.String())
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var state map[string][]byte
|
|
||||||
if err := json.Unmarshal(buf, &state); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v := range state {
|
|
||||||
if !yield(ipn.StateKey(k), v) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func mapSlice[T any, M any](a []T, f func(T) M) []M {
|
func mapSlice[T any, M any](a []T, f func(T) M) []M {
|
||||||
n := make([]M, len(a))
|
n := make([]M, len(a))
|
||||||
for i, e := range a {
|
for i, e := range a {
|
||||||
|
@ -217,6 +217,10 @@ func (s *tpmStore) All() iter.Seq2[ipn.StateKey, []byte] {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure tpmStore implements store.ExportableStore for migration to/from
|
||||||
|
// store.FileStore.
|
||||||
|
var _ store.ExportableStore = (*tpmStore)(nil)
|
||||||
|
|
||||||
// The nested levels of encoding and encryption are confusing, so here's what's
|
// The nested levels of encoding and encryption are confusing, so here's what's
|
||||||
// going on in plain English.
|
// going on in plain English.
|
||||||
//
|
//
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"iter"
|
||||||
"maps"
|
"maps"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -20,8 +21,8 @@ import (
|
|||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"tailscale.com/ipn"
|
"tailscale.com/ipn"
|
||||||
"tailscale.com/ipn/store"
|
"tailscale.com/ipn/store"
|
||||||
"tailscale.com/ipn/store/mem"
|
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
|
"tailscale.com/util/mak"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPropToString(t *testing.T) {
|
func TestPropToString(t *testing.T) {
|
||||||
@ -251,7 +252,9 @@ func TestMigrateStateToTPM(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("migration failed: %v", err)
|
t.Fatalf("migration failed: %v", err)
|
||||||
}
|
}
|
||||||
gotContent := maps.Collect(s.All())
|
gotContent := maps.Collect(s.(interface {
|
||||||
|
All() iter.Seq2[ipn.StateKey, []byte]
|
||||||
|
}).All())
|
||||||
if diff := cmp.Diff(content, gotContent); diff != "" {
|
if diff := cmp.Diff(content, gotContent); diff != "" {
|
||||||
t.Errorf("unexpected content after migration, diff:\n%s", diff)
|
t.Errorf("unexpected content after migration, diff:\n%s", diff)
|
||||||
}
|
}
|
||||||
@ -285,7 +288,7 @@ func tpmSupported() bool {
|
|||||||
|
|
||||||
type mockTPMSealProvider struct {
|
type mockTPMSealProvider struct {
|
||||||
path string
|
path string
|
||||||
mem.Store
|
data map[ipn.StateKey][]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMockTPMSeal(logf logger.Logf, path string) (ipn.StateStore, error) {
|
func newMockTPMSeal(logf logger.Logf, path string) (ipn.StateStore, error) {
|
||||||
@ -293,7 +296,7 @@ func newMockTPMSeal(logf logger.Logf, path string) (ipn.StateStore, error) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("%q missing tpmseal: prefix", path)
|
return nil, fmt.Errorf("%q missing tpmseal: prefix", path)
|
||||||
}
|
}
|
||||||
s := &mockTPMSealProvider{path: path, Store: mem.Store{}}
|
s := &mockTPMSealProvider{path: path}
|
||||||
buf, err := os.ReadFile(path)
|
buf, err := os.ReadFile(path)
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
return s, s.flushState()
|
return s, s.flushState()
|
||||||
@ -312,24 +315,28 @@ func newMockTPMSeal(logf logger.Logf, path string) (ipn.StateStore, error) {
|
|||||||
if data.Key == "" || data.Nonce == "" {
|
if data.Key == "" || data.Nonce == "" {
|
||||||
return nil, fmt.Errorf("%q missing key or nonce", path)
|
return nil, fmt.Errorf("%q missing key or nonce", path)
|
||||||
}
|
}
|
||||||
for k, v := range data.Data {
|
s.data = data.Data
|
||||||
s.Store.WriteState(k, v)
|
|
||||||
}
|
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *mockTPMSealProvider) ReadState(k ipn.StateKey) ([]byte, error) {
|
||||||
|
return p.data[k], nil
|
||||||
|
}
|
||||||
|
|
||||||
func (p *mockTPMSealProvider) WriteState(k ipn.StateKey, v []byte) error {
|
func (p *mockTPMSealProvider) WriteState(k ipn.StateKey, v []byte) error {
|
||||||
if err := p.Store.WriteState(k, v); err != nil {
|
mak.Set(&p.data, k, v)
|
||||||
return err
|
|
||||||
}
|
|
||||||
return p.flushState()
|
return p.flushState()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *mockTPMSealProvider) All() iter.Seq2[ipn.StateKey, []byte] {
|
||||||
|
return maps.All(p.data)
|
||||||
|
}
|
||||||
|
|
||||||
func (p *mockTPMSealProvider) flushState() error {
|
func (p *mockTPMSealProvider) flushState() error {
|
||||||
data := map[string]any{
|
data := map[string]any{
|
||||||
"key": "foo",
|
"key": "foo",
|
||||||
"nonce": "bar",
|
"nonce": "bar",
|
||||||
"data": maps.Collect(p.Store.All()),
|
"data": p.data,
|
||||||
}
|
}
|
||||||
buf, err := json.Marshal(data)
|
buf, err := json.Marshal(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -8,7 +8,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"iter"
|
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
@ -84,11 +83,6 @@ type StateStore interface {
|
|||||||
// instead, which only writes if the value is different from what's
|
// instead, which only writes if the value is different from what's
|
||||||
// already in the store.
|
// already in the store.
|
||||||
WriteState(id StateKey, bs []byte) error
|
WriteState(id StateKey, bs []byte) error
|
||||||
// All returns an iterator over all StateStore keys. Using ReadState or
|
|
||||||
// WriteState is not safe while iterating and can lead to a deadlock.
|
|
||||||
// The order of keys in the iterator is not specified and may change
|
|
||||||
// between runs.
|
|
||||||
All() iter.Seq2[StateKey, []byte]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteState is a wrapper around store.WriteState that only writes if
|
// WriteState is a wrapper around store.WriteState that only writes if
|
||||||
|
@ -10,7 +10,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"iter"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
@ -254,7 +253,3 @@ func (s *awsStore) persistState() error {
|
|||||||
_, err = s.ssmClient.PutParameter(context.TODO(), in)
|
_, err = s.ssmClient.PutParameter(context.TODO(), in)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *awsStore) All() iter.Seq2[ipn.StateKey, []byte] {
|
|
||||||
return s.memory.All()
|
|
||||||
}
|
|
||||||
|
@ -7,7 +7,6 @@ package kubestore
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"iter"
|
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
@ -429,7 +428,3 @@ func sanitizeKey[T ~string](k T) string {
|
|||||||
return '_'
|
return '_'
|
||||||
}, string(k))
|
}, string(k))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) All() iter.Seq2[ipn.StateKey, []byte] {
|
|
||||||
return s.memory.All()
|
|
||||||
}
|
|
||||||
|
@ -7,7 +7,6 @@ package mem
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"iter"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
xmaps "golang.org/x/exp/maps"
|
xmaps "golang.org/x/exp/maps"
|
||||||
@ -86,16 +85,3 @@ func (s *Store) ExportToJSON() ([]byte, error) {
|
|||||||
}
|
}
|
||||||
return json.MarshalIndent(s.cache, "", " ")
|
return json.MarshalIndent(s.cache, "", " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) All() iter.Seq2[ipn.StateKey, []byte] {
|
|
||||||
return func(yield func(ipn.StateKey, []byte) bool) {
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
|
|
||||||
for k, v := range s.cache {
|
|
||||||
if !yield(k, v) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -235,6 +235,23 @@ func (s *FileStore) All() iter.Seq2[ipn.StateKey, []byte] {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure FileStore implements ExportableStore for migration to/from
|
||||||
|
// tpm.tpmStore.
|
||||||
|
var _ ExportableStore = (*FileStore)(nil)
|
||||||
|
|
||||||
|
// ExportableStore is an ipn.StateStore that can export all of its contents.
|
||||||
|
// This interface is optional to implement, and used for migrating the state
|
||||||
|
// between different store implementations.
|
||||||
|
type ExportableStore interface {
|
||||||
|
ipn.StateStore
|
||||||
|
|
||||||
|
// All returns an iterator over all store keys. Using ReadState or
|
||||||
|
// WriteState is not safe while iterating and can lead to a deadlock. The
|
||||||
|
// order of keys in the iterator is not specified and may change between
|
||||||
|
// runs.
|
||||||
|
All() iter.Seq2[ipn.StateKey, []byte]
|
||||||
|
}
|
||||||
|
|
||||||
func maybeMigrateLocalStateFile(logf logger.Logf, path string) error {
|
func maybeMigrateLocalStateFile(logf logger.Logf, path string) error {
|
||||||
path, toTPM := strings.CutPrefix(path, TPMPrefix)
|
path, toTPM := strings.CutPrefix(path, TPMPrefix)
|
||||||
|
|
||||||
@ -297,10 +314,15 @@ func maybeMigrateLocalStateFile(logf logger.Logf, path string) error {
|
|||||||
}
|
}
|
||||||
defer os.Remove(tmpPath)
|
defer os.Remove(tmpPath)
|
||||||
|
|
||||||
|
fromExp, ok := from.(ExportableStore)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%T does not implement the exportableStore interface", from)
|
||||||
|
}
|
||||||
|
|
||||||
// Copy all the items. This is pretty inefficient, because both stores
|
// Copy all the items. This is pretty inefficient, because both stores
|
||||||
// write the file to disk for each WriteState, but that's ok for a one-time
|
// write the file to disk for each WriteState, but that's ok for a one-time
|
||||||
// migration.
|
// migration.
|
||||||
for k, v := range from.All() {
|
for k, v := range fromExp.All() {
|
||||||
if err := to.WriteState(k, v); err != nil {
|
if err := to.WriteState(k, v); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user