diff --git a/changelog/11596.txt b/changelog/11596.txt new file mode 100644 index 000000000000..3735ca0bf858 --- /dev/null +++ b/changelog/11596.txt @@ -0,0 +1,3 @@ +```release-note:bug +core (enterprise): Fix plugins mounted in namespaces being unable to use password policies +``` diff --git a/vault/dynamic_system_view.go b/vault/dynamic_system_view.go index 86e8b560ba73..c9e9c16cbe5e 100644 --- a/vault/dynamic_system_view.go +++ b/vault/dynamic_system_view.go @@ -336,11 +336,11 @@ func (d dynamicSystemView) GeneratePasswordFromPolicy(ctx context.Context, polic // Ensure there's a timeout on the context of some sort if _, hasTimeout := ctx.Deadline(); !hasTimeout { var cancel func() - ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second) + ctx, cancel = context.WithTimeout(ctx, 1*time.Second) defer cancel() } - policyCfg, err := retrievePasswordPolicy(ctx, d.core.systemBarrierView, policyName) + policyCfg, err := d.retrievePasswordPolicy(ctx, policyName) if err != nil { return "", fmt.Errorf("failed to retrieve password policy: %w", err) } diff --git a/vault/dynamic_system_view_test.go b/vault/dynamic_system_view_test.go index 2b5044d9f41c..b7861428cf91 100644 --- a/vault/dynamic_system_view_test.go +++ b/vault/dynamic_system_view_test.go @@ -2,7 +2,7 @@ package vault import ( "context" - "encoding/json" + "encoding/base64" "fmt" "reflect" "sort" @@ -16,6 +16,22 @@ import ( "github.com/hashicorp/vault/sdk/logical" ) +var testPolicyName = "testpolicy" +var rawTestPasswordPolicy = ` +length = 20 +rule "charset" { + charset = "abcdefghijklmnopqrstuvwxyz" + min_chars = 1 +} +rule "charset" { + charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + min_chars = 1 +} +rule "charset" { + charset = "0123456789" + min_chars = 1 +}` + func TestIdentity_BackendTemplating(t *testing.T) { var err error coreConfig := &CoreConfig{ @@ -157,47 +173,45 @@ func TestIdentity_BackendTemplating(t *testing.T) { } func TestDynamicSystemView_GeneratePasswordFromPolicy_successful(t *testing.T) { - policyName := "testpolicy" - rawPolicy := map[string]interface{}{ - "policy": `length = 20 -rule "charset" { - charset = "abcdefghijklmnopqrstuvwxyz" - min_chars = 1 -} -rule "charset" { - charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - min_chars = 1 -} -rule "charset" { - charset = "0123456789" - min_chars = 1 -}`, - } - marshalledPolicy, err := json.Marshal(rawPolicy) - if err != nil { - t.Fatalf("Unable to set up test: unable to marshal raw policy to JSON: %s", err) + var err error + coreConfig := &CoreConfig{ + DisableMlock: true, + DisableCache: true, + Logger: log.NewNullLogger(), + CredentialBackends: map[string]logical.Factory{}, } - testStorage := fakeBarrier{ - getEntry: &logical.StorageEntry{ - Key: getPasswordPolicyKey(policyName), - Value: marshalledPolicy, - }, - } + cluster := NewTestCluster(t, coreConfig, &TestClusterOptions{}) - dsv := dynamicSystemView{ - core: &Core{ - systemBarrierView: NewBarrierView(testStorage, "sys/"), - }, + cluster.Start() + defer cluster.Cleanup() + + core := cluster.Cores[0].Core + TestWaitActive(t, core) + + b64Policy := base64.StdEncoding.EncodeToString([]byte(rawTestPasswordPolicy)) + + path := fmt.Sprintf("sys/policies/password/%s", testPolicyName) + req := logical.TestRequest(t, logical.CreateOperation, path) + req.ClientToken = cluster.RootToken + req.Data["policy"] = b64Policy + + _, err = core.HandleRequest(namespace.RootContext(nil), req) + if err != nil { + t.Fatalf("err: %v", err) } + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) defer cancel() + ctx = namespace.RootContext(ctx) + dsv := dynamicSystemView{core: cluster.Cores[0].Core} + runeset := map[rune]bool{} runesFound := []rune{} for i := 0; i < 100; i++ { - actual, err := dsv.GeneratePasswordFromPolicy(ctx, policyName) + actual, err := dsv.GeneratePasswordFromPolicy(ctx, testPolicyName) if err != nil { t.Fatalf("no error expected, but got: %s", err) } @@ -220,12 +234,6 @@ rule "charset" { } } -type runes []rune - -func (r runes) Len() int { return len(r) } -func (r runes) Less(i, j int) bool { return r[i] < r[j] } -func (r runes) Swap(i, j int) { r[i], r[j] = r[j], r[i] } - func TestDynamicSystemView_GeneratePasswordFromPolicy_failed(t *testing.T) { type testCase struct { policyName string @@ -282,6 +290,12 @@ func TestDynamicSystemView_GeneratePasswordFromPolicy_failed(t *testing.T) { } } +type runes []rune + +func (r runes) Len() int { return len(r) } +func (r runes) Less(i, j int) bool { return r[i] < r[j] } +func (r runes) Swap(i, j int) { r[i], r[j] = r[j], r[i] } + type fakeBarrier struct { getEntry *logical.StorageEntry getErr error diff --git a/vault/password_policy_util.go b/vault/password_policy_util.go new file mode 100644 index 000000000000..133373677f1c --- /dev/null +++ b/vault/password_policy_util.go @@ -0,0 +1,33 @@ +// +build !enterprise + +package vault + +import ( + "context" + "encoding/json" + "fmt" +) + +const ( + passwordPolicySubPath = "password_policy/" +) + +// retrievePasswordPolicy retrieves a password policy from the logical storage +func (d dynamicSystemView) retrievePasswordPolicy(ctx context.Context, policyName string) (*passwordPolicyConfig, error) { + storage := d.core.systemBarrierView.SubView(passwordPolicySubPath) + entry, err := storage.Get(ctx, policyName) + if err != nil { + return nil, err + } + if entry == nil { + return nil, nil + } + + policyCfg := &passwordPolicyConfig{} + err = json.Unmarshal(entry.Value, &policyCfg) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal stored data: %w", err) + } + + return policyCfg, nil +}