diff --git a/logical/framework/path_map.go b/logical/framework/path_map.go index 1887ae73c84b..f9fa3a6b66b5 100644 --- a/logical/framework/path_map.go +++ b/logical/framework/path_map.go @@ -42,7 +42,7 @@ func (p *PathMap) init() { } // pathStruct returns the pathStruct for this mapping -func (p *PathMap) pathStruct(k string) *PathStruct { +func (p *PathMap) pathStruct(s logical.Storage, k string) (*PathStruct, error) { p.once.Do(p.init) // If we don't care about casing, store everything lowercase @@ -50,34 +50,90 @@ func (p *PathMap) pathStruct(k string) *PathStruct { k = strings.ToLower(k) } + // The original key before any salting + origKey := k + // If we have a salt, apply it before lookup salt := p.Salt + var err error if p.SaltFunc != nil { - salt, _ = p.SaltFunc() + salt, err = p.SaltFunc() + if err != nil { + return nil, err + } } if salt != nil { k = salt.SaltID(k) } - return &PathStruct{ - Name: fmt.Sprintf("map/%s/%s", p.Name, k), + finalName := fmt.Sprintf("map/%s/%s", p.Name, k) + ps := &PathStruct{ + Name: finalName, Schema: p.Schema, } + + // Check for unsalted version and upgrade if so + if k != origKey { + // Generate the unsalted name + unsaltedName := fmt.Sprintf("map/%s/%s", p.Name, origKey) + // Set the path struct to use the unsalted name + ps.Name = unsaltedName + // Ensure that no matter what happens what is returned is the final + // path + defer func() { + ps.Name = finalName + }() + val, err := ps.Get(s) + if err != nil { + return nil, err + } + // If not nil, we have an unsalted entry -- upgrade it + if val != nil { + // Set the path struct to use the desired final name + ps.Name = finalName + err = ps.Put(s, val) + if err != nil { + return nil, err + } + // Set it back to the old path and delete + ps.Name = unsaltedName + err = ps.Delete(s) + if err != nil { + return nil, err + } + // We'll set this in the deferred function but doesn't hurt here + ps.Name = finalName + } + } + + return ps, nil } // Get reads a value out of the mapping func (p *PathMap) Get(s logical.Storage, k string) (map[string]interface{}, error) { - return p.pathStruct(k).Get(s) + ps, err := p.pathStruct(s, k) + if err != nil { + return nil, err + } + return ps.Get(s) } // Put writes a value into the mapping func (p *PathMap) Put(s logical.Storage, k string, v map[string]interface{}) error { - return p.pathStruct(k).Put(s, v) + ps, err := p.pathStruct(s, k) + if err != nil { + return err + } + return ps.Put(s, v) } // Delete removes a value from the mapping func (p *PathMap) Delete(s logical.Storage, k string) error { - return p.pathStruct(k).Delete(s) + ps, err := p.pathStruct(s, k) + if err != nil { + return err + } + return ps.Delete(s) } // List reads the keys under a given path diff --git a/logical/framework/path_map_test.go b/logical/framework/path_map_test.go index 485f3bcde135..ce9215b9d7af 100644 --- a/logical/framework/path_map_test.go +++ b/logical/framework/path_map_test.go @@ -254,6 +254,39 @@ func TestPathMap_Salted(t *testing.T) { if v != nil { t.Fatalf("bad: %#v", v) } + + // Put in a non-salted version and make sure that after reading it's been + // upgraded + err = storage.Put(&logical.StorageEntry{ + Key: "struct/map/foo/b", + Value: []byte(`{"foo": "bar"}`), + }) + if err != nil { + t.Fatal("err: %v", err) + } + // A read should transparently upgrade + resp, err = b.HandleRequest(&logical.Request{ + Operation: logical.ReadOperation, + Path: "map/foo/b", + Storage: storage, + }) + if err != nil { + t.Fatal(err) + } + list, _ := storage.List("struct/map/foo/") + if len(list) != 1 { + t.Fatalf("unexpected number of entries left after upgrade; expected 1, got %d", len(list)) + } + found := false + for _, v := range list { + if v == salt.SaltID("b") { + found = true + break + } + } + if !found { + t.Fatal("did not find upgraded value") + } } func TestPathMap_SaltFunc(t *testing.T) { @@ -376,4 +409,37 @@ func TestPathMap_SaltFunc(t *testing.T) { if v != nil { t.Fatalf("bad: %#v", v) } + + // Put in a non-salted version and make sure that after reading it's been + // upgraded + err = storage.Put(&logical.StorageEntry{ + Key: "struct/map/foo/b", + Value: []byte(`{"foo": "bar"}`), + }) + if err != nil { + t.Fatal("err: %v", err) + } + // A read should transparently upgrade + resp, err = b.HandleRequest(&logical.Request{ + Operation: logical.ReadOperation, + Path: "map/foo/b", + Storage: storage, + }) + if err != nil { + t.Fatal(err) + } + list, _ := storage.List("struct/map/foo/") + if len(list) != 1 { + t.Fatalf("unexpected number of entries left after upgrade; expected 1, got %d", len(list)) + } + found := false + for _, v := range list { + if v == locSalt.SaltID("b") { + found = true + break + } + } + if !found { + t.Fatal("did not find upgraded value") + } }