Add LIST support to sys/policies/password (#12787)

* Add read support to sys/policies/password

Closes https://github.com/hashicorp/vault/issues/12562

* Add changelog

* Empty commit to trigger CI

* Add optional /

Co-authored-by: Calvin Leung Huang <1883212+calvn@users.noreply.github.com>

* Use a ListOperation

Co-authored-by: Calvin Leung Huang <1883212+calvn@users.noreply.github.com>
This commit is contained in:
Rémi Lapeyre 2022-01-24 22:42:14 +01:00 committed by GitHub
parent 6474e73c97
commit d6a4a3b53c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 162 additions and 0 deletions

3
changelog/12787.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
core: Add support to list password policies at `sys/policies/password`
```

View File

@ -2345,6 +2345,16 @@ const (
maxPasswordLength = 100 maxPasswordLength = 100
) )
// handlePoliciesPasswordList returns the list of password policies
func (*SystemBackend) handlePoliciesPasswordList(ctx context.Context, req *logical.Request, data *framework.FieldData) (resp *logical.Response, err error) {
keys, err := req.Storage.List(ctx, "password_policy/")
if err != nil {
return nil, err
}
return logical.ListResponse(keys), nil
}
// handlePoliciesPasswordSet saves/updates password policies // handlePoliciesPasswordSet saves/updates password policies
func (*SystemBackend) handlePoliciesPasswordSet(ctx context.Context, req *logical.Request, data *framework.FieldData) (resp *logical.Response, err error) { func (*SystemBackend) handlePoliciesPasswordSet(ctx context.Context, req *logical.Request, data *framework.FieldData) (resp *logical.Response, err error) {
policyName := data.Get("name").(string) policyName := data.Get("name").(string)

View File

@ -1615,6 +1615,17 @@ func (b *SystemBackend) policyPaths() []*framework.Path {
HelpDescription: strings.TrimSpace(sysHelp["policy"][1]), HelpDescription: strings.TrimSpace(sysHelp["policy"][1]),
}, },
{
Pattern: "policies/password/?$",
Operations: map[logical.Operation]framework.OperationHandler{
logical.ListOperation: &framework.PathOperation{
Callback: b.handlePoliciesPasswordList,
Summary: "List the existing password policies.",
},
},
},
{ {
Pattern: "policies/password/(?P<name>.+)/generate$", Pattern: "policies/password/(?P<name>.+)/generate$",

View File

@ -3645,6 +3645,107 @@ func TestHandlePoliciesPasswordDelete(t *testing.T) {
} }
} }
func TestHandlePoliciesPasswordList(t *testing.T) {
type testCase struct {
storage logical.Storage
expectErr bool
expectedResp *logical.Response
}
tests := map[string]testCase{
"no policies": {
storage: new(logical.InmemStorage),
expectedResp: &logical.Response{
Data: map[string]interface{}{},
},
},
"one policy": {
storage: makeStorage(t,
&logical.StorageEntry{
Key: getPasswordPolicyKey("testpolicy"),
Value: toJson(t,
passwordPolicyConfig{
HCLPolicy: "length = 18\n" +
"rule \"charset\" {\n" +
" charset=\"ABCDEFGHIJ\"\n" +
"}",
}),
},
),
expectedResp: &logical.Response{
Data: map[string]interface{}{
"keys": []string{"testpolicy"},
},
},
},
"two policies": {
storage: makeStorage(t,
&logical.StorageEntry{
Key: getPasswordPolicyKey("testpolicy"),
Value: toJson(t,
passwordPolicyConfig{
HCLPolicy: "length = 18\n" +
"rule \"charset\" {\n" +
" charset=\"ABCDEFGHIJ\"\n" +
"}",
}),
},
&logical.StorageEntry{
Key: getPasswordPolicyKey("unrelated_policy"),
Value: toJson(t,
passwordPolicyConfig{
HCLPolicy: "length = 20\n" +
"rule \"charset\" {\n" +
" charset=\"abcdefghij\"\n" +
"}",
}),
},
),
expectedResp: &logical.Response{
Data: map[string]interface{}{
"keys": []string{
"testpolicy",
"unrelated_policy",
},
},
},
},
"storage failure": {
storage: new(logical.InmemStorage).FailList(true),
expectErr: true,
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
defer cancel()
req := &logical.Request{
Storage: test.storage,
}
b := &SystemBackend{}
actualResp, err := b.handlePoliciesPasswordList(ctx, req, nil)
if test.expectErr && err == nil {
t.Fatalf("err expected, got nil")
}
if !test.expectErr && err != nil {
t.Fatalf("no error expected, got: %s", err)
}
if !reflect.DeepEqual(actualResp, test.expectedResp) {
t.Fatalf("Actual response: %#v\nExpected response: %#v", actualResp, test.expectedResp)
}
})
}
}
func TestHandlePoliciesPasswordGenerate(t *testing.T) { func TestHandlePoliciesPasswordGenerate(t *testing.T) {
t.Run("errors", func(t *testing.T) { t.Run("errors", func(t *testing.T) {
type testCase struct { type testCase struct {

View File

@ -77,6 +77,43 @@ rule "charset" {
$ vault write sys/policies/password/my-policy policy=@my-policy.hcl $ vault write sys/policies/password/my-policy policy=@my-policy.hcl
``` ```
## List Password Policies
This endpoints list the password policies.
| Method | Path |
| :------ | :--------------------------------- |
| `LIST` | `/sys/policies/password` |
| `GET` | `/sys/policies/password?list=true` |
### Sample Request
```shell
$ curl \
--header "X-Vault-Token: ..." \
--request LIST \
http://127.0.0.1:8200/v1/sys/policies/password
```
### Sample Response
```json
{
"request_id": "58e2540f-8c51-6390-46de-38e279e75468",
"lease_id": "",
"renewable": false,
"lease_duration": 0,
"data": {
"keys": [
"my-policy"
]
},
"wrap_info": null,
"warnings": null,
"auth": null
}
```
## Read Password Policy ## Read Password Policy
This endpoint retrieves information about the named password policy. This endpoint retrieves information about the named password policy.