2016-05-30 18:30:01 +00:00
|
|
|
package approle
|
|
|
|
|
|
|
|
import (
|
2018-01-08 18:31:38 +00:00
|
|
|
"context"
|
2016-05-30 18:30:01 +00:00
|
|
|
"fmt"
|
|
|
|
"sync/atomic"
|
|
|
|
"time"
|
|
|
|
|
2018-04-05 15:49:21 +00:00
|
|
|
"github.com/hashicorp/errwrap"
|
2016-05-30 18:30:01 +00:00
|
|
|
"github.com/hashicorp/go-multierror"
|
|
|
|
"github.com/hashicorp/vault/logical"
|
|
|
|
"github.com/hashicorp/vault/logical/framework"
|
|
|
|
)
|
|
|
|
|
|
|
|
func pathTidySecretID(b *backend) *framework.Path {
|
|
|
|
return &framework.Path{
|
|
|
|
Pattern: "tidy/secret-id$",
|
|
|
|
|
|
|
|
Callbacks: map[logical.Operation]framework.OperationFunc{
|
|
|
|
logical.UpdateOperation: b.pathTidySecretIDUpdate,
|
|
|
|
},
|
|
|
|
|
|
|
|
HelpSynopsis: pathTidySecretIDSyn,
|
|
|
|
HelpDescription: pathTidySecretIDDesc,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// tidySecretID is used to delete entries in the whitelist that are expired.
|
2018-01-19 06:44:44 +00:00
|
|
|
func (b *backend) tidySecretID(ctx context.Context, s logical.Storage) error {
|
2016-05-30 18:30:01 +00:00
|
|
|
grabbed := atomic.CompareAndSwapUint32(&b.tidySecretIDCASGuard, 0, 1)
|
|
|
|
if grabbed {
|
|
|
|
defer atomic.StoreUint32(&b.tidySecretIDCASGuard, 0)
|
|
|
|
} else {
|
|
|
|
return fmt.Errorf("SecretID tidy operation already running")
|
|
|
|
}
|
|
|
|
|
2018-04-23 20:02:55 +00:00
|
|
|
var result error
|
|
|
|
|
|
|
|
tidyFunc := func(secretIDPrefixToUse, accessorIDPrefixToUse string) error {
|
|
|
|
roleNameHMACs, err := s.List(ctx, secretIDPrefixToUse)
|
2018-04-23 14:51:55 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-05-30 18:30:01 +00:00
|
|
|
|
2018-04-23 20:02:55 +00:00
|
|
|
// List all the accessors and add them all to a map
|
|
|
|
accessorHashes, err := s.List(ctx, accessorIDPrefixToUse)
|
2016-05-30 18:30:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-04-23 20:02:55 +00:00
|
|
|
accessorMap := make(map[string]bool, len(accessorHashes))
|
|
|
|
for _, accessorHash := range accessorHashes {
|
|
|
|
accessorMap[accessorHash] = true
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, roleNameHMAC := range roleNameHMACs {
|
2016-05-30 18:30:01 +00:00
|
|
|
// roleNameHMAC will already have a '/' suffix. Don't append another one.
|
2018-04-23 20:02:55 +00:00
|
|
|
secretIDHMACs, err := s.List(ctx, fmt.Sprintf("%s%s", secretIDPrefixToUse, roleNameHMAC))
|
2016-05-30 18:30:01 +00:00
|
|
|
if err != nil {
|
2018-04-23 20:02:55 +00:00
|
|
|
return err
|
2016-05-30 18:30:01 +00:00
|
|
|
}
|
2018-04-23 20:02:55 +00:00
|
|
|
for _, secretIDHMAC := range secretIDHMACs {
|
|
|
|
// In order to avoid lock swroleing in case there is need to delete,
|
|
|
|
// grab the write lock.
|
|
|
|
lock := b.secretIDLock(secretIDHMAC)
|
|
|
|
lock.Lock()
|
|
|
|
// roleNameHMAC will already have a '/' suffix. Don't append another one.
|
|
|
|
entryIndex := fmt.Sprintf("%s%s%s", secretIDPrefixToUse, roleNameHMAC, secretIDHMAC)
|
|
|
|
secretIDEntry, err := s.Get(ctx, entryIndex)
|
|
|
|
if err != nil {
|
|
|
|
lock.Unlock()
|
|
|
|
return errwrap.Wrapf(fmt.Sprintf("error fetching SecretID %q: {{err}}", secretIDHMAC), err)
|
|
|
|
}
|
2016-05-30 18:30:01 +00:00
|
|
|
|
2018-04-23 20:02:55 +00:00
|
|
|
if secretIDEntry == nil {
|
|
|
|
result = multierror.Append(result, fmt.Errorf("entry for SecretID %q is nil", secretIDHMAC))
|
|
|
|
lock.Unlock()
|
|
|
|
continue
|
|
|
|
}
|
2016-05-30 18:30:01 +00:00
|
|
|
|
2018-04-23 20:02:55 +00:00
|
|
|
if secretIDEntry.Value == nil || len(secretIDEntry.Value) == 0 {
|
|
|
|
lock.Unlock()
|
|
|
|
return fmt.Errorf("found entry for SecretID %q but actual SecretID is empty", secretIDHMAC)
|
|
|
|
}
|
2016-05-30 18:30:01 +00:00
|
|
|
|
2018-04-23 20:02:55 +00:00
|
|
|
var result secretIDStorageEntry
|
|
|
|
if err := secretIDEntry.DecodeJSON(&result); err != nil {
|
2018-02-06 20:44:48 +00:00
|
|
|
lock.Unlock()
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-04-23 20:02:55 +00:00
|
|
|
// ExpirationTime not being set indicates non-expiring SecretIDs
|
|
|
|
if !result.ExpirationTime.IsZero() && time.Now().After(result.ExpirationTime) {
|
|
|
|
// Clean up the accessor of the secret ID first
|
|
|
|
err = b.deleteSecretIDAccessorEntry(ctx, s, result.SecretIDAccessor)
|
|
|
|
if err != nil {
|
|
|
|
lock.Unlock()
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := s.Delete(ctx, entryIndex); err != nil {
|
|
|
|
lock.Unlock()
|
|
|
|
return errwrap.Wrapf(fmt.Sprintf("error deleting SecretID %q from storage: {{err}}", secretIDHMAC), err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// At this point, the secret ID is not expired and is valid. Delete
|
|
|
|
// the corresponding accessor from the accessorMap. This will leave
|
|
|
|
// only the dangling accessors in the map which can then be cleaned
|
|
|
|
// up later.
|
|
|
|
salt, err := b.Salt(ctx)
|
|
|
|
if err != nil {
|
2016-05-30 18:30:01 +00:00
|
|
|
lock.Unlock()
|
2018-04-23 20:02:55 +00:00
|
|
|
return err
|
2016-05-30 18:30:01 +00:00
|
|
|
}
|
2018-04-23 20:02:55 +00:00
|
|
|
delete(accessorMap, salt.SaltID(result.SecretIDAccessor))
|
|
|
|
|
|
|
|
lock.Unlock()
|
2016-05-30 18:30:01 +00:00
|
|
|
}
|
2018-04-23 20:02:55 +00:00
|
|
|
}
|
2018-02-06 20:44:48 +00:00
|
|
|
|
2018-04-23 20:02:55 +00:00
|
|
|
// Accessor indexes were not getting cleaned up until 0.9.3. This is a fix
|
|
|
|
// to clean up the dangling accessor entries.
|
|
|
|
for accessorHash, _ := range accessorMap {
|
|
|
|
// Ideally, locking should be performed here. But for that, accessors
|
|
|
|
// are required in plaintext, which are not available. Hence performing
|
|
|
|
// a racy cleanup.
|
|
|
|
err = s.Delete(ctx, secretIDAccessorPrefix+accessorHash)
|
2018-02-06 20:44:48 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-05-30 18:30:01 +00:00
|
|
|
}
|
2018-04-23 20:02:55 +00:00
|
|
|
|
|
|
|
return nil
|
2016-05-30 18:30:01 +00:00
|
|
|
}
|
2018-02-06 20:44:48 +00:00
|
|
|
|
2018-04-23 20:02:55 +00:00
|
|
|
err := tidyFunc(secretIDPrefix, secretIDAccessorPrefix)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
err = tidyFunc(secretIDLocalPrefix, secretIDAccessorLocalPrefix)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2018-02-06 20:44:48 +00:00
|
|
|
}
|
|
|
|
|
2016-05-30 18:30:01 +00:00
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
// pathTidySecretIDUpdate is used to delete the expired SecretID entries
|
2018-01-08 18:31:38 +00:00
|
|
|
func (b *backend) pathTidySecretIDUpdate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
2018-01-19 06:44:44 +00:00
|
|
|
return nil, b.tidySecretID(ctx, req.Storage)
|
2016-05-30 18:30:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const pathTidySecretIDSyn = "Trigger the clean-up of expired SecretID entries."
|
2018-01-03 18:56:14 +00:00
|
|
|
const pathTidySecretIDDesc = `SecretIDs will have expiration time attached to them. The periodic function
|
2016-05-30 18:30:01 +00:00
|
|
|
of the backend will look for expired entries and delete them. This happens once in a minute. Invoking
|
|
|
|
this endpoint will trigger the clean-up action, without waiting for the backend's periodic function.`
|