Skip to content

Commit

Permalink
Merge pull request #1754 from hashicorp/secret-id-read-delete
Browse files Browse the repository at this point in the history
Seperate endpoints for read/delete using secret-id and accessor
  • Loading branch information
vishalnayak committed Aug 21, 2016
2 parents 826146f + 7d772e4 commit 0dd95f0
Show file tree
Hide file tree
Showing 3 changed files with 268 additions and 23 deletions.
168 changes: 149 additions & 19 deletions builtin/credential/approle/path_role.go
Expand Up @@ -75,8 +75,10 @@ type roleIDStorageEntry struct {
// role/<role_name>/period - For updating the param
// role/<role_name>/role-id - For fetching the role_id of an role
// role/<role_name>/secret-id - For issuing a secret_id against an role, also to list the secret_id_accessorss
// role/<role_name>/secret-id/<secret_id_accessor> - For reading the properties of, or deleting a secret_id
// role/<role_name>/custom-secret-id - For assigning a custom SecretID against an role
// role/<role_name>/secret-id/<secret_id> - For reading the properties of, or deleting a secret_id
// role/<role_name>/secret-id-accessor/<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{
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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 == "" {
Expand Down Expand Up @@ -1592,8 +1717,13 @@ that are generated against the role using 'role/<role_name>/secret-id' or
'role/<role_name>/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/<role_name>/secret-id' endpoint will return
the 'secret_id_accessor's. This endpoint can be used to read the properties
Expand Down
48 changes: 47 additions & 1 deletion builtin/credential/approle/path_role_test.go
Expand Up @@ -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,
Expand All @@ -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()) {
Expand Down
75 changes: 72 additions & 3 deletions website/source/docs/auth/approle.html.md
Expand Up @@ -545,7 +545,76 @@ $ curl -XPOST -H "X-Vault-Token:xxx" "http://127.0.0.1:8200/v1/auth/approle/logi
</dd>
</dl>

### /auth/approle/role/[role_name]/secret-id/<secret_id_accessor>
### /auth/approle/role/[role_name]/secret-id/<secret_id>
#### GET
<dl class="api">
<dt>Description</dt>
<dd>
Reads out the properties of a SecretID.
</dd>

<dt>Method</dt>
<dd>`GET`</dd>

<dt>URL</dt>
<dd>`/auth/approle/role/[role_name]/secret-id/<secret_id>`</dd>

<dt>Parameters</dt>
<dd>
None.
</dd>

<dt>Returns</dt>
<dd>

```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": ""
}
```

</dd>
</dl>

#### DELETE
<dl class="api">
<dt>Description</dt>
<dd>
Deletes a SecretID.
</dd>

<dt>Method</dt>
<dd>`DELETE`</dd>

<dt>URL</dt>
<dd>`/auth/approle/role/[role_name]/secret-id/<secret_id>`</dd>

<dt>Parameters</dt>
<dd>
None.
</dd>

<dt>Returns</dt>
<dd>
`204` response code.
</dd>
</dl>

### /auth/approle/role/[role_name]/secret-id-accessor/<secret_id_accessor>
#### GET
<dl class="api">
<dt>Description</dt>
Expand All @@ -558,7 +627,7 @@ $ curl -XPOST -H "X-Vault-Token:xxx" "http://127.0.0.1:8200/v1/auth/approle/logi
<dd>`GET`</dd>

<dt>URL</dt>
<dd>`/auth/approle/role/[role_name]/secret-id/<secret_id_accessor>`</dd>
<dd>`/auth/approle/role/[role_name]/secret-id-accessor/<secret_id_accessor>`</dd>

<dt>Parameters</dt>
<dd>
Expand Down Expand Up @@ -602,7 +671,7 @@ $ curl -XPOST -H "X-Vault-Token:xxx" "http://127.0.0.1:8200/v1/auth/approle/logi
<dd>`DELETE`</dd>

<dt>URL</dt>
<dd>`/auth/approle/role/[role_name]/secret-id/<secret_id_accessor>`</dd>
<dd>`/auth/approle/role/[role_name]/secret-id-accessor/<secret_id_accessor>`</dd>

<dt>Parameters</dt>
<dd>
Expand Down

0 comments on commit 0dd95f0

Please sign in to comment.