Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Seperate endpoints for read/delete using secret-id and accessor #1754

Merged
merged 2 commits into from Aug 21, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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