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
This commit is contained in:
Lexman 2019-07-26 19:53:40 -07:00 committed by GitHub
parent d177fc7dd1
commit 5a24bbb138
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 87 additions and 24 deletions

View File

@ -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 (<version>:<nsID>:<key>) where <nsID> is nsID
func isNamespacedKey(nskey, nsID string) bool {
// isTargetNamespacedKey returns true for a properly constructed namespaced key (<version>:<nsID>:<key>)
// where <nsID> 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])
}

View File

@ -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()