From 5a24bbb13817607cf11eec0fd1f6cf4f2c07b8a6 Mon Sep 17 00:00:00 2001 From: Lexman Date: Fri, 26 Jul 2019 19:53:40 -0700 Subject: [PATCH] also flush nilNamespace when a namespace is flushed in the identity/oidc backend (#7203) * also flush nilNamespace when a namespace is flushed * adds test cases with nilNamespace.ID * adds a test case * adds a test for oidcCache.Flush * fixed a typo in an error message --- vault/identity_store_oidc.go | 9 +-- vault/identity_store_oidc_test.go | 102 ++++++++++++++++++++++++------ 2 files changed, 87 insertions(+), 24 deletions(-) diff --git a/vault/identity_store_oidc.go b/vault/identity_store_oidc.go index d7c2d20036..90c7e605f7 100644 --- a/vault/identity_store_oidc.go +++ b/vault/identity_store_oidc.go @@ -1564,14 +1564,15 @@ func (c *oidcCache) SetDefault(ns *namespace.Namespace, key string, obj interfac func (c *oidcCache) Flush(ns *namespace.Namespace) { for itemKey := range c.c.Items() { - if isNamespacedKey(itemKey, ns.ID) { + if isTargetNamespacedKey(itemKey, []string{nilNamespace.ID, ns.ID}) { c.c.Delete(itemKey) } } } -// isNamespacedKey returns true for a properly constructed namespaced key (::) where is nsID -func isNamespacedKey(nskey, nsID string) bool { +// isTargetNamespacedKey returns true for a properly constructed namespaced key (::) +// where matches any targeted nsID +func isTargetNamespacedKey(nskey string, nsTargets []string) bool { split := strings.Split(nskey, ":") - return len(split) >= 3 && split[1] == nsID + return len(split) >= 3 && strutil.StrListContains(nsTargets, split[1]) } diff --git a/vault/identity_store_oidc_test.go b/vault/identity_store_oidc_test.go index 173875b16a..384e5695de 100644 --- a/vault/identity_store_oidc_test.go +++ b/vault/identity_store_oidc_test.go @@ -14,6 +14,7 @@ import ( "github.com/hashicorp/vault/helper/namespace" "github.com/hashicorp/vault/sdk/framework" "github.com/hashicorp/vault/sdk/logical" + gocache "github.com/patrickmn/go-cache" "gopkg.in/square/go-jose.v2" "gopkg.in/square/go-jose.v2/jwt" ) @@ -971,36 +972,97 @@ func TestOIDC_Path_Introspect(t *testing.T) { } } -func TestOIDC_isNamespacedKey(t *testing.T) { +func TestOIDC_isTargetNamespacedKey(t *testing.T) { tests := []struct { - nsid string - nskey string - expected bool + nsTargets []string + nskey string + expected bool }{ - {"nsid", "v0:nsid:key", true}, - {"nsid", "v0:nsid:", true}, - {"nsid", "v0:nsid", false}, - {"nsid", "v0:", false}, - {"nsid", "v0", false}, - {"nsid", "", false}, - {"nsid1", "v0:nsid2:key", false}, - {"nsid1", "nsid1:nsid2:nsid1", false}, - {"nsid1", "nsid1:nsid1:nsid1", true}, - {"nsid", "nsid:nsid:nsid:nsid:nsid:nsid", true}, - {"nsid", ":::", false}, - {"", ":::", true}, // "" is a valid key for cache.Set/Get - {"nsid1", "nsid0:nsid1:nsid0:nsid1:nsid0:nsid1", true}, - {"nsid0", "nsid0:nsid1:nsid0:nsid1:nsid0:nsid1", false}, + {[]string{"nsid"}, "v0:nsid:key", true}, + {[]string{"nsid"}, "v0:nsid:", true}, + {[]string{"nsid"}, "v0:nsid", false}, + {[]string{"nsid"}, "v0:", false}, + {[]string{"nsid"}, "v0", false}, + {[]string{"nsid"}, "", false}, + {[]string{"nsid1"}, "v0:nsid2:key", false}, + {[]string{"nsid1"}, "nsid1:nsid2:nsid1", false}, + {[]string{"nsid1"}, "nsid1:nsid1:nsid1", true}, + {[]string{"nsid"}, "nsid:nsid:nsid:nsid:nsid:nsid", true}, + {[]string{"nsid"}, ":::", false}, + {[]string{""}, ":::", true}, // "" is a valid key for cache.Set/Get + {[]string{"nsid1"}, "nsid0:nsid1:nsid0:nsid1:nsid0:nsid1", true}, + {[]string{"nsid0"}, "nsid0:nsid1:nsid0:nsid1:nsid0:nsid1", false}, + {[]string{"nsid0", "nsid1"}, "v0:nsid2:key", false}, + {[]string{"nsid0", "nsid1", "nsid2", "nsid3", "nsid4"}, "v0:nsid3:key", true}, + {[]string{"nsid0", "nsid1", "nsid2", "nsid3", "nsid4"}, "nsid0:nsid1:nsid2:nsid3:nsid4:nsid5", true}, + {[]string{"nsid0", "nsid1", "nsid2", "nsid3", "nsid4"}, "nsid4:nsid5:nsid6:nsid7:nsid8:nsid9", false}, + {[]string{"nsid0", "nsid0", "nsid0", "nsid0", "nsid0"}, "nsid0:nsid0:nsid0:nsid0:nsid0:nsid0", true}, + {[]string{"nsid1", "nsid1", "nsid2", "nsid2"}, "nsid0:nsid0:nsid0:nsid0:nsid0:nsid0", false}, + {[]string{"nsid1", "nsid1", "nsid2", "nsid2"}, "nsid0:nsid0:nsid0:nsid0:nsid0:nsid0", false}, } for _, test := range tests { - actual := isNamespacedKey(test.nskey, test.nsid) + actual := isTargetNamespacedKey(test.nskey, test.nsTargets) if test.expected != actual { - t.Fatalf("expected %t but got %t for nsid: %q and nskey: %q", test.expected, actual, test.nsid, test.nskey) + t.Fatalf("expected %t but got %t for nstargets: %q and nskey: %q", test.expected, actual, test.nsTargets, test.nskey) } } } +func TestOIDC_Flush(t *testing.T) { + c := newOIDCCache() + ns := []*namespace.Namespace{ + nilNamespace, //ns[0] is nilNamespace + &namespace.Namespace{ID: "ns1"}, + &namespace.Namespace{ID: "ns2"}, + } + + // populateNs populates cache by ns with some data + populateNs := func() { + for i := range ns { + for _, val := range []string{"keyA", "keyB", "keyC"} { + c.SetDefault(ns[i], val, struct{}{}) + } + } + } + + // validate verifies that cache items exist or do not exist based on their namespaced key + verify := func(items map[string]gocache.Item, expect, doNotExpect []*namespace.Namespace) { + for _, expectNs := range expect { + found := false + for i := range items { + if isTargetNamespacedKey(i, []string{expectNs.ID}) { + found = true + break + } + } + if !found { + t.Fatalf("Expected cache to contain an entry with a namespaced key for namespace: %q but did not find one", expectNs.ID) + } + } + + for _, doNotExpectNs := range doNotExpect { + for i := range items { + if isTargetNamespacedKey(i, []string{doNotExpectNs.ID}) { + t.Fatalf("Did not expect cache to contain an entry with a namespaced key for namespace: %q but found the key: %q", doNotExpectNs.ID, i) + } + } + } + } + + // flushing ns1 should flush ns1 and nilNamespace but not ns2 + populateNs() + c.Flush(ns[1]) + items := c.c.Items() + verify(items, []*namespace.Namespace{ns[2]}, []*namespace.Namespace{ns[0], ns[1]}) + + // flushing nilNamespace should flush nilNamespace but not ns1 or ns2 + populateNs() + c.Flush(ns[0]) + items = c.c.Items() + verify(items, []*namespace.Namespace{ns[1], ns[2]}, []*namespace.Namespace{ns[0]}) +} + // some helpers func expectSuccess(t *testing.T, resp *logical.Response, err error) { t.Helper()