mirror of
https://github.com/kopia/kopia.git
synced 2026-01-24 22:38:00 -05:00
revamped how vault configuration gets stored
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
@@ -10,8 +11,11 @@
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/kopia/kopia/storage/logging"
|
||||
|
||||
"github.com/bgentry/speakeasy"
|
||||
"github.com/kopia/kopia/repo"
|
||||
"github.com/kopia/kopia/storage"
|
||||
"github.com/kopia/kopia/vault"
|
||||
)
|
||||
|
||||
@@ -59,7 +63,7 @@ func repositoryOptionsFromFlags(extraOptions []repo.RepositoryOption) []repo.Rep
|
||||
}
|
||||
|
||||
if *traceStorage {
|
||||
opts = append(opts, repo.EnableLogging())
|
||||
opts = append(opts, repo.EnableLogging(logging.Prefix("[REPOSITORY] ")))
|
||||
}
|
||||
return opts
|
||||
}
|
||||
@@ -84,7 +88,7 @@ func vaultConfigFileName() string {
|
||||
}
|
||||
|
||||
func persistVaultConfig(v *vault.Vault) error {
|
||||
cfg, err := v.Token()
|
||||
cfg, err := v.Config()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -95,22 +99,50 @@ func persistVaultConfig(v *vault.Vault) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return ioutil.WriteFile(fname, []byte(cfg), 0600)
|
||||
f, err := os.Create(fname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return json.NewEncoder(f).Encode(cfg)
|
||||
}
|
||||
|
||||
func getPersistedVaultConfig() string {
|
||||
f, err := ioutil.ReadFile(vaultConfigFileName())
|
||||
func getPersistedVaultConfig() *vault.Config {
|
||||
f, err := os.Open(vaultConfigFileName())
|
||||
if err != nil {
|
||||
return ""
|
||||
return nil
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
vc := &vault.Config{}
|
||||
|
||||
if err := json.NewDecoder(f).Decode(vc); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return string(f)
|
||||
return vc
|
||||
}
|
||||
|
||||
func openVault() (*vault.Vault, error) {
|
||||
vc := getPersistedVaultConfig()
|
||||
if vc != "" {
|
||||
return vault.OpenWithToken(vc)
|
||||
if vc != nil {
|
||||
st, err := storage.NewStorage(vc.ConnectionInfo)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot open vault storage: %v", err)
|
||||
}
|
||||
|
||||
if *traceStorage {
|
||||
st = logging.NewWrapper(st, logging.Prefix("[VAULT] "))
|
||||
}
|
||||
|
||||
creds, err := vault.MasterKey(vc.Key)
|
||||
if err != nil {
|
||||
st.Close()
|
||||
return nil, fmt.Errorf("invalid vault config")
|
||||
}
|
||||
|
||||
return vault.Open(st, creds)
|
||||
}
|
||||
|
||||
if *vaultPath == "" {
|
||||
|
||||
@@ -167,9 +167,9 @@ func WriteBack(workerCount int) RepositoryOption {
|
||||
}
|
||||
|
||||
// EnableLogging is an RepositoryOption that causes all storage access to be logged.
|
||||
func EnableLogging() RepositoryOption {
|
||||
func EnableLogging(options ...logging.Option) RepositoryOption {
|
||||
return func(o *repository) error {
|
||||
o.storage = logging.NewWrapper(o.storage)
|
||||
o.storage = logging.NewWrapper(o.storage, options...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,8 @@
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/kopia/kopia/storage"
|
||||
|
||||
"github.com/kopia/kopia/internal/storagetesting"
|
||||
"github.com/kopia/kopia/storage"
|
||||
"github.com/kopia/kopia/storage/logging"
|
||||
|
||||
"testing"
|
||||
@@ -79,7 +78,7 @@ func TestCache(t *testing.T) {
|
||||
master := storagetesting.NewMapStorage(masterData)
|
||||
|
||||
var tr tracer
|
||||
master = logging.NewWrapperWithLogger(master, &tr)
|
||||
master = logging.NewWrapper(master, logging.Output(tr.Printf))
|
||||
|
||||
cache, err := NewWrapper(master, Options{CacheDir: tmpdir})
|
||||
defer cache.Close()
|
||||
|
||||
@@ -11,13 +11,14 @@
|
||||
type loggingStorage struct {
|
||||
base storage.Storage
|
||||
printf func(string, ...interface{})
|
||||
prefix string
|
||||
}
|
||||
|
||||
func (s *loggingStorage) BlockExists(id string) (bool, error) {
|
||||
t0 := time.Now()
|
||||
result, err := s.base.BlockExists(id)
|
||||
dt := time.Since(t0)
|
||||
s.printf(s.prefix()+"BlockExists(%#v)=%#v,%#v took %v", id, result, err, dt)
|
||||
s.printf(s.prefix+"BlockExists(%#v)=%#v,%#v took %v", id, result, err, dt)
|
||||
return result, err
|
||||
}
|
||||
|
||||
@@ -26,9 +27,9 @@ func (s *loggingStorage) GetBlock(id string) ([]byte, error) {
|
||||
result, err := s.base.GetBlock(id)
|
||||
dt := time.Since(t0)
|
||||
if len(result) < 20 {
|
||||
s.printf(s.prefix()+"GetBlock(%#v)=(%#v, %#v) took %v", id, result, err, dt)
|
||||
s.printf(s.prefix+"GetBlock(%#v)=(%#v, %#v) took %v", id, result, err, dt)
|
||||
} else {
|
||||
s.printf(s.prefix()+"GetBlock(%#v)=({%#v bytes}, %#v) took %v", id, len(result), err, dt)
|
||||
s.printf(s.prefix+"GetBlock(%#v)=({%#v bytes}, %#v) took %v", id, len(result), err, dt)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
@@ -38,7 +39,7 @@ func (s *loggingStorage) PutBlock(id string, data storage.ReaderWithLength, opti
|
||||
l := data.Len()
|
||||
err := s.base.PutBlock(id, data, options)
|
||||
dt := time.Since(t0)
|
||||
s.printf(s.prefix()+"PutBlock(%#v, options=%v, len=%v)=%#v took %v", id, options, l, err, dt)
|
||||
s.printf(s.prefix+"PutBlock(%#v, options=%v, len=%v)=%#v took %v", id, options, l, err, dt)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -46,7 +47,7 @@ func (s *loggingStorage) DeleteBlock(id string) error {
|
||||
t0 := time.Now()
|
||||
err := s.base.DeleteBlock(id)
|
||||
dt := time.Since(t0)
|
||||
s.printf(s.prefix()+"DeleteBlock(%#v)=%#v took %v", id, err, dt)
|
||||
s.printf(s.prefix+"DeleteBlock(%#v)=%#v took %v", id, err, dt)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -54,7 +55,7 @@ func (s *loggingStorage) ListBlocks(prefix string) chan (storage.BlockMetadata)
|
||||
t0 := time.Now()
|
||||
ch := s.base.ListBlocks(prefix)
|
||||
dt := time.Since(t0)
|
||||
s.printf(s.prefix()+"ListBlocks(%#v) took %v", prefix, dt)
|
||||
s.printf(s.prefix+"ListBlocks(%#v) took %v", prefix, dt)
|
||||
return ch
|
||||
}
|
||||
|
||||
@@ -62,7 +63,7 @@ func (s *loggingStorage) Flush() error {
|
||||
t0 := time.Now()
|
||||
err := s.base.Flush()
|
||||
dt := time.Since(t0)
|
||||
s.printf(s.prefix()+"Flush()=%#v took %v", err, dt)
|
||||
s.printf(s.prefix+"Flush()=%#v took %v", err, dt)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -70,14 +71,11 @@ func (s *loggingStorage) Close() error {
|
||||
t0 := time.Now()
|
||||
err := s.base.Close()
|
||||
dt := time.Since(t0)
|
||||
s.printf(s.prefix()+"Close()=%#v took %v", err, dt)
|
||||
s.printf(s.prefix+"Close()=%#v took %v", err, dt)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *loggingStorage) prefix() string {
|
||||
return "[STORAGE] "
|
||||
}
|
||||
|
||||
// Option modifies the behavior of logging storage wrapper.
|
||||
type Option func(s *loggingStorage)
|
||||
|
||||
// NewWrapper returns a Storage wrapper that logs all storage commands.
|
||||
@@ -86,6 +84,7 @@ func NewWrapper(wrapped storage.Storage, options ...Option) storage.Storage {
|
||||
for _, o := range options {
|
||||
o(s)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -95,3 +94,10 @@ func Output(outputFunc func(fmt string, args ...interface{})) Option {
|
||||
s.printf = outputFunc
|
||||
}
|
||||
}
|
||||
|
||||
// Prefix specifies prefix to be prepended to all log output.
|
||||
func Prefix(prefix string) Option {
|
||||
return func(s *loggingStorage) {
|
||||
s.prefix = prefix
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -262,32 +261,26 @@ func (v *Vault) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type vaultConfig struct {
|
||||
// Config represents JSON-compatible configuration of the vault connection, including vault key.
|
||||
type Config struct {
|
||||
ConnectionInfo storage.ConnectionInfo `json:"connection"`
|
||||
Key []byte `json:"key,omitempty"`
|
||||
}
|
||||
|
||||
// Token returns a persistent opaque string that encodes the configuration of vault storage
|
||||
// and its credentials in a way that can be later used to open the vault.
|
||||
func (v *Vault) Token() (string, error) {
|
||||
// Config returns a configuration of vault storage its credentials that's suitable
|
||||
// for storing in configuration file.
|
||||
func (v *Vault) Config() (*Config, error) {
|
||||
cip, ok := v.storage.(storage.ConnectionInfoProvider)
|
||||
if !ok {
|
||||
return "", errors.New("repository does not support persisting configuration")
|
||||
return nil, errors.New("repository does not support persisting configuration")
|
||||
}
|
||||
|
||||
ci := cip.ConnectionInfo()
|
||||
|
||||
vc := vaultConfig{
|
||||
return &Config{
|
||||
ConnectionInfo: ci,
|
||||
Key: v.masterKey,
|
||||
}
|
||||
|
||||
b, err := json.Marshal(&vc)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return base64.RawURLEncoding.EncodeToString(b), nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Remove deletes the specified vault item.
|
||||
@@ -394,32 +387,6 @@ func Open(vaultStorage storage.Storage, vaultCreds Credentials) (*Vault, error)
|
||||
return &v, nil
|
||||
}
|
||||
|
||||
// OpenWithToken opens a vault with storage configuration and credentials in the specified token.
|
||||
func OpenWithToken(token string) (*Vault, error) {
|
||||
b, err := base64.RawURLEncoding.DecodeString(token)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid vault base64 token: %v", err)
|
||||
}
|
||||
|
||||
var vc vaultConfig
|
||||
err = json.Unmarshal(b, &vc)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid vault json token: %v", err)
|
||||
}
|
||||
|
||||
st, err := storage.NewStorage(vc.ConnectionInfo)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot open vault storage: %v", err)
|
||||
}
|
||||
|
||||
creds, err := MasterKey(vc.Key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid vault token")
|
||||
}
|
||||
|
||||
return Open(st, creds)
|
||||
}
|
||||
|
||||
func isReservedName(itemID string) bool {
|
||||
switch itemID {
|
||||
case formatBlockID, repositoryConfigBlockID:
|
||||
|
||||
@@ -86,23 +86,18 @@ func verifyVault(t *testing.T, vaultPath string, repoPath string) {
|
||||
return
|
||||
}
|
||||
|
||||
tok1, err := v1.Token()
|
||||
cfg, err := v1.Config()
|
||||
if err != nil {
|
||||
t.Errorf("error getting token1 %v", err)
|
||||
}
|
||||
tok2, err := v2.Token()
|
||||
|
||||
cfg2, err := v2.Config()
|
||||
if err != nil {
|
||||
t.Errorf("error getting token2 %v", err)
|
||||
}
|
||||
|
||||
v3, err := OpenWithToken(tok1)
|
||||
if err != nil {
|
||||
t.Errorf("can't open vault with token: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if tok1 != tok2 {
|
||||
t.Errorf("tokens don't match: %v vs %v", tok1, tok2)
|
||||
if !reflect.DeepEqual(cfg, cfg2) {
|
||||
t.Errorf("configurations are different: %+v vs %+v", cfg, cfg2)
|
||||
}
|
||||
|
||||
_, err = Open(vaultStorage, otherVaultCreds)
|
||||
@@ -165,7 +160,7 @@ func verifyVault(t *testing.T, vaultPath string, repoPath string) {
|
||||
}
|
||||
|
||||
// Verify contents of vault items for both created and opened vault.
|
||||
for _, v := range []*Vault{v1, v2, v3} {
|
||||
for _, v := range []*Vault{v1, v2} {
|
||||
rf := v.RepositoryFormat()
|
||||
if !reflect.DeepEqual(rf, repoFormat) {
|
||||
t.Errorf("invalid repository format: %v, but got %v", repoFormat, rf)
|
||||
|
||||
Reference in New Issue
Block a user