diff --git a/builtin/credential/approle/path_role.go b/builtin/credential/approle/path_role.go index c2a4949190ad2..696396b2fde6d 100644 --- a/builtin/credential/approle/path_role.go +++ b/builtin/credential/approle/path_role.go @@ -75,8 +75,10 @@ type roleIDStorageEntry struct { // role//period - For updating the param // role//role-id - For fetching the role_id of an role // role//secret-id - For issuing a secret_id against an role, also to list the secret_id_accessorss -// role//secret-id/ - For reading the properties of, or deleting a secret_id // role//custom-secret-id - For assigning a custom SecretID against an role +// role//secret-id/ - For reading the properties of, or deleting a secret_id +// role//secret-id-accessor/ - For reading the +// properties of, or deleting a secret_id, using the accessor of secret_id. func rolePaths(b *backend) []*framework.Path { return []*framework.Path{ &framework.Path{ @@ -363,7 +365,28 @@ formatted string containing the metadata in key value pairs.`, HelpDescription: strings.TrimSpace(roleHelp["role-secret-id"][1]), }, &framework.Path{ - Pattern: "role/" + framework.GenericNameRegex("role_name") + "/secret-id/" + framework.GenericNameRegex("secret_id_accessor"), + Pattern: "role/" + + framework.GenericNameRegex("role_name") + "/secret-id/" + framework.GenericNameRegex("secret_id"), + Fields: map[string]*framework.FieldSchema{ + "role_name": &framework.FieldSchema{ + Type: framework.TypeString, + Description: "Name of the role.", + }, + "secret_id": &framework.FieldSchema{ + Type: framework.TypeString, + Description: "SecretID attached to the role.", + }, + }, + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.ReadOperation: b.pathRoleSecretIDSecretIDRead, + logical.DeleteOperation: b.pathRoleSecretIDSecretIDDelete, + }, + HelpSynopsis: strings.TrimSpace(roleHelp["role-secret-id-secret-id"][0]), + HelpDescription: strings.TrimSpace(roleHelp["role-secret-id-secret-id"][1]), + }, + &framework.Path{ + Pattern: "role/" + + framework.GenericNameRegex("role_name") + "/secret-id-accessor/" + framework.GenericNameRegex("secret_id_accessor"), Fields: map[string]*framework.FieldSchema{ "role_name": &framework.FieldSchema{ Type: framework.TypeString, @@ -742,21 +765,18 @@ func (b *backend) pathRoleDelete(req *logical.Request, data *framework.FieldData } // Returns the properties of the SecretID -func (b *backend) pathRoleSecretIDAccessorRead(req *logical.Request, data *framework.FieldData) (*logical.Response, error) { +func (b *backend) pathRoleSecretIDSecretIDRead(req *logical.Request, data *framework.FieldData) (*logical.Response, error) { roleName := data.Get("role_name").(string) if roleName == "" { return logical.ErrorResponse("missing role_name"), nil } - secretIDAccessor := data.Get("secret_id_accessor").(string) - if secretIDAccessor == "" { - return logical.ErrorResponse("missing secret_id_accessor"), nil + secretID := data.Get("secret_id").(string) + if secretID == "" { + return logical.ErrorResponse("missing secret_id"), nil } - // SecretID is indexed based on HMACed roleName and HMACed SecretID. - // Get the role details to fetch the RoleID and accessor to get - // the HMACed SecretID. - + // Fetch the role role, err := b.roleEntry(req.Storage, strings.ToLower(roleName)) if err != nil { return nil, err @@ -765,27 +785,31 @@ func (b *backend) pathRoleSecretIDAccessorRead(req *logical.Request, data *frame return nil, fmt.Errorf("role %s does not exist", roleName) } - accessorEntry, err := b.secretIDAccessorEntry(req.Storage, secretIDAccessor) + // Create the HMAC of the secret ID using the per-role HMAC key + secretIDHMAC, err := createHMAC(role.HMACKey, secretID) if err != nil { - return nil, err - } - if accessorEntry == nil { - return nil, fmt.Errorf("failed to find accessor entry for secret_id_accessor:%s\n", secretIDAccessor) + return nil, fmt.Errorf("failed to create HMAC of secret_id: %s", err) } + // Create the HMAC of the roleName using the per-role HMAC key roleNameHMAC, err := createHMAC(role.HMACKey, roleName) if err != nil { return nil, fmt.Errorf("failed to create HMAC of role_name: %s", err) } - entryIndex := fmt.Sprintf("secret_id/%s/%s", roleNameHMAC, accessorEntry.SecretIDHMAC) + // Create the index at which the secret_id would've been stored + entryIndex := fmt.Sprintf("secret_id/%s/%s", roleNameHMAC, secretIDHMAC) - lock := b.secretIDLock(accessorEntry.SecretIDHMAC) + return b.secretIDCommon(req.Storage, entryIndex, secretIDHMAC) +} + +func (b *backend) secretIDCommon(s logical.Storage, entryIndex, secretIDHMAC string) (*logical.Response, error) { + lock := b.secretIDLock(secretIDHMAC) lock.RLock() defer lock.RUnlock() result := secretIDStorageEntry{} - if entry, err := req.Storage.Get(entryIndex); err != nil { + if entry, err := s.Get(entryIndex); err != nil { return nil, err } else if entry == nil { return nil, nil @@ -810,6 +834,107 @@ func (b *backend) pathRoleSecretIDAccessorRead(req *logical.Request, data *frame }, nil } +func (b *backend) pathRoleSecretIDSecretIDDelete(req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + roleName := data.Get("role_name").(string) + if roleName == "" { + return logical.ErrorResponse("missing role_name"), nil + } + + secretID := data.Get("secret_id").(string) + if secretID == "" { + return logical.ErrorResponse("missing secret_id"), nil + } + + role, err := b.roleEntry(req.Storage, strings.ToLower(roleName)) + if err != nil { + return nil, err + } + if role == nil { + return nil, fmt.Errorf("role %s does not exist", roleName) + } + + secretIDHMAC, err := createHMAC(role.HMACKey, secretID) + if err != nil { + return nil, fmt.Errorf("failed to create HMAC of secret_id: %s", err) + } + + roleNameHMAC, err := createHMAC(role.HMACKey, roleName) + if err != nil { + return nil, fmt.Errorf("failed to create HMAC of role_name: %s", err) + } + + entryIndex := fmt.Sprintf("secret_id/%s/%s", roleNameHMAC, secretIDHMAC) + + lock := b.secretIDLock(secretIDHMAC) + lock.Lock() + defer lock.Unlock() + + result := secretIDStorageEntry{} + if entry, err := req.Storage.Get(entryIndex); err != nil { + return nil, err + } else if entry == nil { + return nil, nil + } else if err := entry.DecodeJSON(&result); err != nil { + return nil, err + } + + accessorEntryIndex := "accessor/" + b.salt.SaltID(result.SecretIDAccessor) + + // Delete the accessor of the SecretID first + if err := req.Storage.Delete(accessorEntryIndex); err != nil { + return nil, fmt.Errorf("failed to delete accessor storage entry: %s", err) + } + + // Delete the storage entry that corresponds to the SecretID + if err := req.Storage.Delete(entryIndex); err != nil { + return nil, fmt.Errorf("failed to delete SecretID: %s", err) + } + + return nil, nil +} + +// Returns the properties of the SecretID +func (b *backend) pathRoleSecretIDAccessorRead(req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + roleName := data.Get("role_name").(string) + if roleName == "" { + return logical.ErrorResponse("missing role_name"), nil + } + + secretIDAccessor := data.Get("secret_id_accessor").(string) + if secretIDAccessor == "" { + return logical.ErrorResponse("missing secret_id_accessor"), nil + } + + // SecretID is indexed based on HMACed roleName and HMACed SecretID. + // Get the role details to fetch the RoleID and accessor to get + // the HMACed SecretID. + + role, err := b.roleEntry(req.Storage, strings.ToLower(roleName)) + if err != nil { + return nil, err + } + if role == nil { + return nil, fmt.Errorf("role %s does not exist", roleName) + } + + accessorEntry, err := b.secretIDAccessorEntry(req.Storage, secretIDAccessor) + if err != nil { + return nil, err + } + if accessorEntry == nil { + return nil, fmt.Errorf("failed to find accessor entry for secret_id_accessor:%s\n", secretIDAccessor) + } + + roleNameHMAC, err := createHMAC(role.HMACKey, roleName) + if err != nil { + return nil, fmt.Errorf("failed to create HMAC of role_name: %s", err) + } + + entryIndex := fmt.Sprintf("secret_id/%s/%s", roleNameHMAC, accessorEntry.SecretIDHMAC) + + return b.secretIDCommon(req.Storage, entryIndex, accessorEntry.SecretIDHMAC) +} + func (b *backend) pathRoleSecretIDAccessorDelete(req *logical.Request, data *framework.FieldData) (*logical.Response, error) { roleName := data.Get("role_name").(string) if roleName == "" { @@ -1592,8 +1717,13 @@ that are generated against the role using 'role//secret-id' or 'role//custom-secret-id' endpoints.`, ``, }, - "role-secret-id-accessor": { + "role-secret-id-secret-id": { "Read or delete a issued secret_id", + `This endpoint is used to either read the properties of a +secret_id associated to a role or to invalidate it.`, + }, + "role-secret-id-accessor": { + "Read or delete a issued secret_id, using its accessor", `This is particularly useful to clean-up the non-expiring 'secret_id's. The list operation on the 'role//secret-id' endpoint will return the 'secret_id_accessor's. This endpoint can be used to read the properties diff --git a/builtin/credential/approle/path_role_test.go b/builtin/credential/approle/path_role_test.go index baed802d31e33..b728d182d6670 100644 --- a/builtin/credential/approle/path_role_test.go +++ b/builtin/credential/approle/path_role_test.go @@ -155,6 +155,52 @@ func TestAppRole_RoleSecretIDReadDelete(t *testing.T) { var err error b, storage := createBackendWithStorage(t) + createRole(t, b, storage, "role1", "a,b") + secretIDCreateReq := &logical.Request{ + Operation: logical.UpdateOperation, + Storage: storage, + Path: "role/role1/secret-id", + } + resp, err = b.HandleRequest(secretIDCreateReq) + if err != nil || (resp != nil && resp.IsError()) { + t.Fatalf("err:%v resp:%#v", err, resp) + } + secretID := resp.Data["secret_id"].(string) + + secretIDReq := &logical.Request{ + Operation: logical.ReadOperation, + Storage: storage, + Path: "role/role1/secret-id/" + secretID, + } + resp, err = b.HandleRequest(secretIDReq) + if err != nil || (resp != nil && resp.IsError()) { + t.Fatalf("err:%v resp:%#v", err, resp) + } + if resp.Data == nil { + t.Fatal(err) + } + + secretIDReq.Operation = logical.DeleteOperation + resp, err = b.HandleRequest(secretIDReq) + if err != nil || (resp != nil && resp.IsError()) { + t.Fatalf("err:%v resp:%#v", err, resp) + } + + secretIDReq.Operation = logical.ReadOperation + resp, err = b.HandleRequest(secretIDReq) + if resp != nil && resp.IsError() { + t.Fatalf("error response:%#v", err, resp) + } + if err != nil { + t.Fatal(err) + } +} + +func TestAppRole_RoleSecretIDAccessorReadDelete(t *testing.T) { + var resp *logical.Response + var err error + b, storage := createBackendWithStorage(t) + createRole(t, b, storage, "role1", "a,b") secretIDReq := &logical.Request{ Operation: logical.UpdateOperation, @@ -180,7 +226,7 @@ func TestAppRole_RoleSecretIDReadDelete(t *testing.T) { hmacReq := &logical.Request{ Operation: logical.ReadOperation, Storage: storage, - Path: "role/role1/secret-id/" + hmacSecretID, + Path: "role/role1/secret-id-accessor/" + hmacSecretID, } resp, err = b.HandleRequest(hmacReq) if err != nil || (resp != nil && resp.IsError()) { diff --git a/website/source/docs/auth/approle.html.md b/website/source/docs/auth/approle.html.md index 295e430bfd682..365a042041440 100644 --- a/website/source/docs/auth/approle.html.md +++ b/website/source/docs/auth/approle.html.md @@ -545,7 +545,76 @@ $ curl -XPOST -H "X-Vault-Token:xxx" "http://127.0.0.1:8200/v1/auth/approle/logi -### /auth/approle/role/[role_name]/secret-id/ +### /auth/approle/role/[role_name]/secret-id/ +#### GET +
+
Description
+
+ Reads out the properties of a SecretID. +
+ +
Method
+
`GET`
+ +
URL
+
`/auth/approle/role/[role_name]/secret-id/`
+ +
Parameters
+
+ None. +
+ +
Returns
+
+ +```javascript +{ + "auth": null, + "warnings": null, + "wrap_info": null, + "data": { + "secret_id_ttl": 600, + "secret_id_num_uses": 40, + "secret_id_accessor": "5e222f10-278d-a829-4e74-10d71977bb53", + "metadata": {}, + "last_updated_time": "2016-06-29T05:31:09.407042587Z", + "expiration_time": "2016-06-29T05:41:09.407042587Z", + "creation_time": "2016-06-29T05:31:09.407042587Z" + }, + "lease_duration": 0, + "renewable": false, + "lease_id": "" +} +``` + +
+
+ +#### DELETE +
+
Description
+
+ Deletes a SecretID. +
+ +
Method
+
`DELETE`
+ +
URL
+
`/auth/approle/role/[role_name]/secret-id/`
+ +
Parameters
+
+ None. +
+ +
Returns
+
+ `204` response code. +
+
+ +### /auth/approle/role/[role_name]/secret-id-accessor/ #### GET
Description
@@ -558,7 +627,7 @@ $ curl -XPOST -H "X-Vault-Token:xxx" "http://127.0.0.1:8200/v1/auth/approle/logi
`GET`
URL
-
`/auth/approle/role/[role_name]/secret-id/`
+
`/auth/approle/role/[role_name]/secret-id-accessor/`
Parameters
@@ -602,7 +671,7 @@ $ curl -XPOST -H "X-Vault-Token:xxx" "http://127.0.0.1:8200/v1/auth/approle/logi
`DELETE`
URL
-
`/auth/approle/role/[role_name]/secret-id/`
+
`/auth/approle/role/[role_name]/secret-id-accessor/`
Parameters