From 1a62fb64c212144e16b02f82c4a19fc8c8d9fa61 Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Sun, 21 Aug 2016 14:42:49 -0400 Subject: [PATCH 1/2] Seperate endpoints for read/delete using secret-id and accessor --- builtin/credential/approle/path_role.go | 159 ++++++++++++++++++- builtin/credential/approle/path_role_test.go | 48 +++++- website/source/docs/auth/approle.html.md | 75 ++++++++- 3 files changed, 275 insertions(+), 7 deletions(-) diff --git a/builtin/credential/approle/path_role.go b/builtin/credential/approle/path_role.go index c2a4949190ad2..a738a0072677b 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, @@ -741,6 +764,131 @@ func (b *backend) pathRoleDelete(req *logical.Request, data *framework.FieldData return nil, nil } +// Returns the properties of the SecretID +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 + } + + secretID := data.Get("secret_id").(string) + if secretID == "" { + return logical.ErrorResponse("missing secret_id"), nil + } + + // Fetch the role + 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) + } + + // Create the HMAC of the secret ID using the per-role HMAC key + secretIDHMAC, err := createHMAC(role.HMACKey, secretID) + if err != nil { + 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) + } + + // Create the index at which the secret_id would've been stored + entryIndex := fmt.Sprintf("secret_id/%s/%s", roleNameHMAC, secretIDHMAC) + + lock := b.secretIDLock(secretIDHMAC) + lock.RLock() + defer lock.RUnlock() + + 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 + } + + result.SecretIDTTL /= time.Second + d := structs.New(result).Map() + + // Converting the time values to RFC3339Nano format. + // + // Map() from 'structs' package formats time in RFC3339Nano. + // In order to not break the API due to a modification in the + // third party package, converting the time values again. + d["creation_time"] = (d["creation_time"].(time.Time)).Format(time.RFC3339Nano) + d["expiration_time"] = (d["expiration_time"].(time.Time)).Format(time.RFC3339Nano) + d["last_updated_time"] = (d["last_updated_time"].(time.Time)).Format(time.RFC3339Nano) + + return &logical.Response{ + Data: d, + }, 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) @@ -1592,8 +1740,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
From 7d772e445fd1b0d1d8857e36f6681241eb04ce19 Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Sun, 21 Aug 2016 15:46:11 -0400 Subject: [PATCH 2/2] Extract out common code --- builtin/credential/approle/path_role.go | 35 +++++-------------------- 1 file changed, 6 insertions(+), 29 deletions(-) diff --git a/builtin/credential/approle/path_role.go b/builtin/credential/approle/path_role.go index a738a0072677b..696396b2fde6d 100644 --- a/builtin/credential/approle/path_role.go +++ b/builtin/credential/approle/path_role.go @@ -800,12 +800,16 @@ func (b *backend) pathRoleSecretIDSecretIDRead(req *logical.Request, data *frame // Create the index at which the secret_id would've been stored entryIndex := fmt.Sprintf("secret_id/%s/%s", roleNameHMAC, 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 @@ -928,34 +932,7 @@ func (b *backend) pathRoleSecretIDAccessorRead(req *logical.Request, data *frame entryIndex := fmt.Sprintf("secret_id/%s/%s", roleNameHMAC, accessorEntry.SecretIDHMAC) - lock := b.secretIDLock(accessorEntry.SecretIDHMAC) - lock.RLock() - defer lock.RUnlock() - - 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 - } - - result.SecretIDTTL /= time.Second - d := structs.New(result).Map() - - // Converting the time values to RFC3339Nano format. - // - // Map() from 'structs' package formats time in RFC3339Nano. - // In order to not break the API due to a modification in the - // third party package, converting the time values again. - d["creation_time"] = (d["creation_time"].(time.Time)).Format(time.RFC3339Nano) - d["expiration_time"] = (d["expiration_time"].(time.Time)).Format(time.RFC3339Nano) - d["last_updated_time"] = (d["last_updated_time"].(time.Time)).Format(time.RFC3339Nano) - - return &logical.Response{ - Data: d, - }, nil + return b.secretIDCommon(req.Storage, entryIndex, accessorEntry.SecretIDHMAC) } func (b *backend) pathRoleSecretIDAccessorDelete(req *logical.Request, data *framework.FieldData) (*logical.Response, error) {