Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 81 additions & 12 deletions pkg/identity/store/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"crypto/ed25519"
"encoding/json"
"os"
"path/filepath"

"github.com/pkg/errors"
"github.com/threefoldtech/zosbase/pkg/versioned"
Expand Down Expand Up @@ -39,19 +40,78 @@ func (f *FileStore) Kind() string {

func (f *FileStore) Set(key ed25519.PrivateKey) error {
seed := key.Seed()
return versioned.WriteFile(f.path, SeedVersion1, seed, 0400)
// write to primary location first
if err := versioned.WriteFile(f.path, SeedVersion1, seed, 0400); err != nil {
return err
}

// also mirror the seed to all mounted disks under /mnt/*/seed.txt
mirrors, _ := f.mirrorPaths()
for _, p := range mirrors {
_ = versioned.WriteFile(p, SeedVersion1, seed, 0400)
}
return nil
}

func (f *FileStore) Annihilate() error {
return os.Remove(f.path)
}

func (f *FileStore) Get() (ed25519.PrivateKey, error) {
version, data, err := versioned.ReadFile(f.path)
key, err := f.readKeyFrom(f.path)
if errors.Is(err, ErrKeyDoesNotExist) {
// try to recover from any mirrored copies on mounted disks
mirrors, _ := f.mirrorPaths()
for _, p := range mirrors {
if k, merr := f.readKeyFrom(p); merr == nil {
// write back to primary location for future boots
_ = os.MkdirAll(filepath.Dir(f.path), 0o700)
_ = versioned.WriteFile(f.path, SeedVersion1, k.Seed(), 0400)
// ensure the seed is present across all mirrors
_ = f.ensureMirrors(k.Seed())
return k, nil
}
}
}
if err == nil {
// ensure the seed is present across all mirrors on every successful read
_ = f.ensureMirrors(key.Seed())
}
return key, err
}

func (f *FileStore) Exists() (bool, error) {
if _, err := os.Stat(f.path); os.IsNotExist(err) {
return false, nil
} else if err != nil {
return false, errors.Wrap(err, "failed to check seed file")
}

return true, nil
}

// ensureMirrors guarantees that the seed file exists on all mirror locations under /mnt/*
// It is best-effort; failures for individual mirrors are ignored.
func (f *FileStore) ensureMirrors(seed []byte) error {
mirrors, err := f.mirrorPaths()
if err != nil {
return err
}
for _, p := range mirrors {
if _, statErr := os.Stat(p); os.IsNotExist(statErr) {
_ = versioned.WriteFile(p, SeedVersion1, seed, 0400)
}
}
return nil
}

// readKeyFrom reads and decodes an identity seed from a given path
// supporting both 1.0.0 (raw seed) and 1.1.0 (mnemonic json) formats.
func (f *FileStore) readKeyFrom(path string) (ed25519.PrivateKey, error) {
version, data, err := versioned.ReadFile(path)
if versioned.IsNotVersioned(err) {
// this is a compatibility code for seed files
// in case it does not have any version information
if err := versioned.WriteFile(f.path, SeedVersionLatest, data, 0400); err != nil {
// compatibility for old non-versioned seed files
if err := versioned.WriteFile(path, SeedVersionLatest, data, 0400); err != nil {
return nil, err
}
version = SeedVersion1
Expand All @@ -68,6 +128,7 @@ func (f *FileStore) Get() (ed25519.PrivateKey, error) {
if version.EQ(SeedVersion1) {
return keyFromSeed(data)
}

// it means we read json data instead of the secret
type Seed110Struct struct {
Mnemonics string `json:"mnemonic"`
Expand All @@ -85,12 +146,20 @@ func (f *FileStore) Get() (ed25519.PrivateKey, error) {
return keyFromSeed(seed)
}

func (f *FileStore) Exists() (bool, error) {
if _, err := os.Stat(f.path); os.IsNotExist(err) {
return false, nil
} else if err != nil {
return false, errors.Wrap(err, "failed to check seed file")
// mirrorPaths lists candidate seed paths under all mounted disks in /mnt/*
// The path is mirrored at: /mnt/<disk>/seed.txt (flat, no original directories)
func (f *FileStore) mirrorPaths() ([]string, error) {
entries, err := os.ReadDir("/mnt")
if err != nil {
return nil, err
}

return true, nil
var paths []string
for _, e := range entries {
if !e.IsDir() {
continue
}
name := filepath.Base(f.path) // usually "seed.txt"
paths = append(paths, filepath.Join("/mnt", e.Name(), name))
}
return paths, nil
}
Loading