diff --git a/cmd/kopia/config.go b/cmd/kopia/config.go index 48d289368..e1fac7ad2 100644 --- a/cmd/kopia/config.go +++ b/cmd/kopia/config.go @@ -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 == "" { diff --git a/repo/repository.go b/repo/repository.go index 2ba5fee32..02c6db12f 100644 --- a/repo/repository.go +++ b/repo/repository.go @@ -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 } } diff --git a/storage/caching/caching_storage_test.go b/storage/caching/caching_storage_test.go index 6a954741e..2a94cbb76 100644 --- a/storage/caching/caching_storage_test.go +++ b/storage/caching/caching_storage_test.go @@ -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() diff --git a/storage/logging/logging_storage.go b/storage/logging/logging_storage.go index af199f6b9..1a7b53ffd 100644 --- a/storage/logging/logging_storage.go +++ b/storage/logging/logging_storage.go @@ -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 + } +} diff --git a/vault/vault.go b/vault/vault.go index c56df91be..ff496e44c 100644 --- a/vault/vault.go +++ b/vault/vault.go @@ -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: diff --git a/vault/vault_test.go b/vault/vault_test.go index 93a899acf..8c9483a14 100644 --- a/vault/vault_test.go +++ b/vault/vault_test.go @@ -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)