From b1549d8efd243c51b06a3867fa3926769727ac4b Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Fri, 3 Jun 2016 16:34:59 -0400 Subject: [PATCH] Added listing of app and group secrets --- builtin/credential/appgroup/path_app.go | 127 +++++++++++++++++- builtin/credential/appgroup/path_group.go | 118 +++++++++++++++- .../credential/appgroup/path_supergroup.go | 4 +- builtin/credential/appgroup/secret_id.go | 28 ++-- 4 files changed, 257 insertions(+), 20 deletions(-) diff --git a/builtin/credential/appgroup/path_app.go b/builtin/credential/appgroup/path_app.go index ef518da2c958e..3c5975454e013 100644 --- a/builtin/credential/appgroup/path_app.go +++ b/builtin/credential/appgroup/path_app.go @@ -242,7 +242,7 @@ func appPaths(b *backend) []*framework.Path { HelpDescription: strings.TrimSpace(appHelp["app-selector-id"][1]), }, &framework.Path{ - Pattern: "app/" + framework.GenericNameRegex("app_name") + "/secret-id$", + Pattern: "app/" + framework.GenericNameRegex("app_name") + "/secret-id/?$", Fields: map[string]*framework.FieldSchema{ "app_name": &framework.FieldSchema{ Type: framework.TypeString, @@ -251,10 +251,30 @@ func appPaths(b *backend) []*framework.Path { }, Callbacks: map[logical.Operation]framework.OperationFunc{ logical.ReadOperation: b.pathAppSecretIDRead, + logical.ListOperation: b.pathAppSecretIDList, }, HelpSynopsis: strings.TrimSpace(appHelp["app-secret-id"][0]), HelpDescription: strings.TrimSpace(appHelp["app-secret-id"][1]), }, + &framework.Path{ + Pattern: "app/" + framework.GenericNameRegex("app_name") + "/secret-id/" + framework.GenericNameRegex("secret_id_hmac"), + Fields: map[string]*framework.FieldSchema{ + "app_name": &framework.FieldSchema{ + Type: framework.TypeString, + Description: "Name of the App.", + }, + "secret_id_hmac": &framework.FieldSchema{ + Type: framework.TypeString, + Description: "HMAC of the secret ID", + }, + }, + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.ReadOperation: b.pathAppSecretIDHMACRead, + logical.DeleteOperation: b.pathAppSecretIDHMACDelete, + }, + HelpSynopsis: strings.TrimSpace(appHelp["app-secret-id-hmac"][0]), + HelpDescription: strings.TrimSpace(appHelp["app-secret-id-hmac"][1]), + }, &framework.Path{ Pattern: "app/" + framework.GenericNameRegex("app_name") + "/custom-secret-id$", Fields: map[string]*framework.FieldSchema{ @@ -299,6 +319,34 @@ func (b *backend) pathAppList( return logical.ListResponse(apps), nil } +// pathAppSecretIDList is used to list all the Apps registered with the backend. +func (b *backend) pathAppSecretIDList(req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + appName := data.Get("app_name").(string) + if appName == "" { + return logical.ErrorResponse("missing app_name"), nil + } + + app, err := b.appEntry(req.Storage, strings.ToLower(appName)) + if err != nil { + return nil, err + } + if app == nil { + return logical.ErrorResponse(fmt.Sprintf("app %s does not exist", appName)), nil + } + + // Get the "custom" lock + lock := b.secretIDLock("") + lock.RLock() + defer lock.RUnlock() + + secrets, err := req.Storage.List(fmt.Sprintf("secret_id/%s/", b.salt.SaltID(app.SelectorID))) + if err != nil { + return nil, err + } + + return logical.ListResponse(secrets), nil +} + // setAppEntry grabs a write lock and stores the options on an App into the storage func (b *backend) setAppEntry(s logical.Storage, appName string, app *appStorageEntry) error { b.appLock.Lock() @@ -491,6 +539,74 @@ func (b *backend) pathAppBindSecretIDUpdate(req *logical.Request, data *framewor } } +func (b *backend) pathAppSecretIDHMACRead(req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + appName := data.Get("app_name").(string) + if appName == "" { + return logical.ErrorResponse("missing app_name"), nil + } + + hashedSecretID := data.Get("secret_id_hmac").(string) + if hashedSecretID == "" { + return logical.ErrorResponse("missing secret_id_hmac"), nil + } + + app, err := b.appEntry(req.Storage, strings.ToLower(appName)) + if err != nil { + return nil, err + } + if app == nil { + return nil, fmt.Errorf("app %s does not exist", appName) + } + + entryIndex := fmt.Sprintf("secret_id/%s/%s", b.salt.SaltID(app.SelectorID), hashedSecretID) + + lock := b.secretIDLock(hashedSecretID) + 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 + } + + respData := structs.New(result).Map() + return &logical.Response{ + Data: respData, + }, nil +} + +func (b *backend) pathAppSecretIDHMACDelete(req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + appName := data.Get("app_name").(string) + if appName == "" { + return logical.ErrorResponse("missing app_name"), nil + } + + hashedSecretID := data.Get("secret_id_hmac").(string) + if hashedSecretID == "" { + return logical.ErrorResponse("missing secret_id_hmac"), nil + } + + app, err := b.appEntry(req.Storage, strings.ToLower(appName)) + if err != nil { + return nil, err + } + if app == nil { + return nil, fmt.Errorf("app %s does not exist", appName) + } + + entryIndex := fmt.Sprintf("secret_id/%s/%s", b.salt.SaltID(app.SelectorID), hashedSecretID) + + lock := b.secretIDLock(hashedSecretID) + lock.Lock() + defer lock.Unlock() + + return nil, req.Storage.Delete(entryIndex) +} + func (b *backend) pathAppBindSecretIDRead(req *logical.Request, data *framework.FieldData) (*logical.Response, error) { appName := data.Get("app_name").(string) if appName == "" { @@ -955,6 +1071,15 @@ that are generated against the App using 'app//secret-id' or 'app//custom-secret-id' endpoints.`, ``, }, + "app-secret-id-hmac": { + "Read or delete a issued secret_id", + `This is particularly useful to clean-up the non-expiring 'secret_id's. +The list operation on the 'app//secret-id' endpoint will return +the HMACed 'secret_id's. This endpoint can be used to read the properties +of the secret. If the 'secret_idnum_uses' field in the response is 0, it represents +a non-expiring 'secret_id'. The same endpoint can be invoked again to delete +it.`, + }, "app-token-ttl": { `Duration in seconds, the lifetime of the token issued by using the SecretID that is generated against this App, before which the token needs to be renewed.`, diff --git a/builtin/credential/appgroup/path_group.go b/builtin/credential/appgroup/path_group.go index a2768609ae2ed..30208053a79d4 100644 --- a/builtin/credential/appgroup/path_group.go +++ b/builtin/credential/appgroup/path_group.go @@ -281,7 +281,7 @@ addition to those, a set of policies can be assigned using this. HelpDescription: strings.TrimSpace(groupHelp["group-selector-id"][1]), }, &framework.Path{ - Pattern: "group/" + framework.GenericNameRegex("group_name") + "/secret-id$", + Pattern: "group/" + framework.GenericNameRegex("group_name") + "/secret-id/?$", Fields: map[string]*framework.FieldSchema{ "group_name": &framework.FieldSchema{ Type: framework.TypeString, @@ -290,6 +290,26 @@ addition to those, a set of policies can be assigned using this. }, Callbacks: map[logical.Operation]framework.OperationFunc{ logical.ReadOperation: b.pathGroupSecretIDRead, + logical.ListOperation: b.pathGroupSecretIDList, + }, + HelpSynopsis: strings.TrimSpace(groupHelp["group-secret-id"][0]), + HelpDescription: strings.TrimSpace(groupHelp["group-secret-id"][1]), + }, + &framework.Path{ + Pattern: "group/" + framework.GenericNameRegex("group_name") + "/secret-id/" + framework.GenericNameRegex("secret_id_hmac"), + Fields: map[string]*framework.FieldSchema{ + "group_name": &framework.FieldSchema{ + Type: framework.TypeString, + Description: "Name of the Group.", + }, + "secret_id_hmac": &framework.FieldSchema{ + Type: framework.TypeString, + Description: "HMAC of the secret ID", + }, + }, + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.ReadOperation: b.pathGroupSecretIDHMACRead, + logical.DeleteOperation: b.pathGroupSecretIDHMACDelete, }, HelpSynopsis: strings.TrimSpace(groupHelp["group-secret-id"][0]), HelpDescription: strings.TrimSpace(groupHelp["group-secret-id"][1]), @@ -338,6 +358,34 @@ func (b *backend) pathGroupList( return logical.ListResponse(groups), nil } +// pathGroupSecretIDList is used to list all the Apps registered with the backend. +func (b *backend) pathGroupSecretIDList( + req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + // Get the "custom" lock + lock := b.secretIDLock("") + lock.RLock() + defer lock.RUnlock() + + groupName := data.Get("group_name").(string) + if groupName == "" { + return logical.ErrorResponse("missing group_name"), nil + } + + group, err := b.appEntry(req.Storage, strings.ToLower(groupName)) + if err != nil { + return nil, err + } + if group == nil { + return logical.ErrorResponse(fmt.Sprintf("group %s does not exist", groupName)), nil + } + + secrets, err := req.Storage.List(fmt.Sprintf("secret_id/%s", b.salt.SaltID(group.SelectorID))) + if err != nil { + return nil, err + } + return logical.ListResponse(secrets), nil +} + // setAppEntry grabs a write lock and stores the options on a Group into the storage func (b *backend) setGroupEntry(s logical.Storage, groupName string, group *groupStorageEntry) error { b.groupLock.Lock() @@ -586,6 +634,74 @@ func (b *backend) pathGroupBindSecretIDUpdate(req *logical.Request, data *framew } } +func (b *backend) pathGroupSecretIDHMACRead(req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + groupName := data.Get("group_name").(string) + if groupName == "" { + return logical.ErrorResponse("missing group_name"), nil + } + + hashedSecretID := data.Get("secret_id_hmac").(string) + if hashedSecretID == "" { + return logical.ErrorResponse("missing secret_id_hmac"), nil + } + + group, err := b.groupEntry(req.Storage, strings.ToLower(groupName)) + if err != nil { + return nil, err + } + if group == nil { + return nil, fmt.Errorf("group %s does not exist", groupName) + } + + entryIndex := fmt.Sprintf("secret_id/%s/%s", b.salt.SaltID(group.SelectorID), hashedSecretID) + + lock := b.secretIDLock(hashedSecretID) + 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 + } + + respData := structs.New(result).Map() + return &logical.Response{ + Data: respData, + }, nil +} + +func (b *backend) pathGroupSecretIDHMACDelete(req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + groupName := data.Get("group_name").(string) + if groupName == "" { + return logical.ErrorResponse("missing group_name"), nil + } + + hashedSecretID := data.Get("secret_id_hmac").(string) + if hashedSecretID == "" { + return logical.ErrorResponse("missing secret_id_hmac"), nil + } + + group, err := b.groupEntry(req.Storage, strings.ToLower(groupName)) + if err != nil { + return nil, err + } + if group == nil { + return nil, fmt.Errorf("group %s does not exist", groupName) + } + + entryIndex := fmt.Sprintf("secret_id/%s/%s", b.salt.SaltID(group.SelectorID), hashedSecretID) + + lock := b.secretIDLock(hashedSecretID) + lock.Lock() + defer lock.Unlock() + + return nil, req.Storage.Delete(entryIndex) +} + func (b *backend) pathGroupBindSecretIDRead(req *logical.Request, data *framework.FieldData) (*logical.Response, error) { groupName := data.Get("group_name").(string) if groupName == "" { diff --git a/builtin/credential/appgroup/path_supergroup.go b/builtin/credential/appgroup/path_supergroup.go index 64e7414d84591..da14d6b7319db 100644 --- a/builtin/credential/appgroup/path_supergroup.go +++ b/builtin/credential/appgroup/path_supergroup.go @@ -2,7 +2,6 @@ package appgroup import ( "fmt" - "log" "strings" "time" @@ -162,7 +161,7 @@ addition to those, a set of policies can be assigned using this. func (b *backend) setSuperGroupEntry(s logical.Storage, superGroupName string, superGroup *superGroupStorageEntry) error { b.superGroupLock.Lock() defer b.superGroupLock.Unlock() - log.Printf("reading supergroup: %s\n", superGroupName) + entry, err := logical.StorageEntryJSON("supergroup/"+strings.ToLower(superGroupName), superGroup) if err != nil { return err @@ -197,7 +196,6 @@ func (b *backend) superGroupEntry(s logical.Storage, superGroupName string) (*su b.superGroupLock.RLock() defer b.superGroupLock.RUnlock() - log.Printf("reading supergroup: %s\n", superGroupName) if entry, err := s.Get("supergroup/" + strings.ToLower(superGroupName)); err != nil { return nil, err } else if entry == nil { diff --git a/builtin/credential/appgroup/secret_id.go b/builtin/credential/appgroup/secret_id.go index f2f85c12fe0cc..3e7ac49ab0766 100644 --- a/builtin/credential/appgroup/secret_id.go +++ b/builtin/credential/appgroup/secret_id.go @@ -5,7 +5,6 @@ import ( "crypto/sha256" "encoding/hex" "fmt" - "log" "sync" "time" @@ -150,7 +149,6 @@ func (b *backend) validateSelectorID(s logical.Storage, selectorID string) (*val if selector == nil { return nil, fmt.Errorf("failed to find selector for selector_id:%s\n", selectorID) } - log.Printf("selector:%#v\n", selector) resp := &validationResponse{} switch selector.Type { @@ -257,7 +255,7 @@ func (b *backend) secretIDEntryValid(s logical.Storage, selectorID, secretID, hm } entryIndex := fmt.Sprintf("secret_id/%s/%s", b.salt.SaltID(selectorID), hashedSecretID) - lock := b.getSecretIDLock(secretID) + lock := b.secretIDLock(hashedSecretID) lock.RLock() result := secretIDStorageEntry{} @@ -337,11 +335,11 @@ func (b *backend) secretIDEntryValid(s logical.Storage, selectorID, secretID, hm return true, nil } -func (b *backend) getSecretIDLock(secretID string) *sync.RWMutex { +func (b *backend) secretIDLock(hashedSecretID string) *sync.RWMutex { var lock *sync.RWMutex var ok bool - if len(secretID) >= 2 { - lock, ok = b.secretIDLocksMap[secretID[0:2]] + if len(hashedSecretID) >= 2 { + lock, ok = b.secretIDLocksMap[hashedSecretID[0:2]] } if !ok || lock == nil { // Fall back for custom secret IDs @@ -365,14 +363,14 @@ func createHMAC(key, value string) (string, error) { // lock in the map of locks maintained at the backend. The index into the // map is the SecretID itself. During login, if the SecretID supplied is not // having a corresponding lock in the map, the login attempt fails. -func (b *backend) registerSecretIDEntry(s logical.Storage, selectorID, secretID, hmacKey string, secretIDEntry *secretIDStorageEntry) error { +func (b *backend) registerSecretIDEntry(s logical.Storage, selectorID, secretID, hmacKey string, secretEntry *secretIDStorageEntry) error { hashedSecretID, err := createHMAC(hmacKey, secretID) if err != nil { return fmt.Errorf("failed to create HMAC of secret_id: %s", err) } entryIndex := fmt.Sprintf("secret_id/%s/%s", b.salt.SaltID(selectorID), hashedSecretID) - lock := b.getSecretIDLock(secretID) + lock := b.secretIDLock(hashedSecretID) lock.RLock() entry, err := s.Get(entryIndex) @@ -404,21 +402,21 @@ func (b *backend) registerSecretIDEntry(s logical.Storage, selectorID, secretID, // Set the creation time for the SecretID currentTime := time.Now().UTC() - secretIDEntry.CreationTime = currentTime - secretIDEntry.LastUpdatedTime = currentTime + secretEntry.CreationTime = currentTime + secretEntry.LastUpdatedTime = currentTime // If SecretIDTTL is not specified or if it crosses the backend mount's limit, // cap the expiration to backend's max. Otherwise, use it to determine the // expiration time. - if secretIDEntry.SecretIDTTL < time.Duration(0) || secretIDEntry.SecretIDTTL > b.System().MaxLeaseTTL() { - secretIDEntry.ExpirationTime = currentTime.Add(b.System().MaxLeaseTTL()) - } else if secretIDEntry.SecretIDTTL != time.Duration(0) { + if secretEntry.SecretIDTTL < time.Duration(0) || secretEntry.SecretIDTTL > b.System().MaxLeaseTTL() { + secretEntry.ExpirationTime = currentTime.Add(b.System().MaxLeaseTTL()) + } else if secretEntry.SecretIDTTL != time.Duration(0) { // Set the ExpirationTime only if SecretIDTTL was set. SecretIDs should not // expire by default. - secretIDEntry.ExpirationTime = currentTime.Add(secretIDEntry.SecretIDTTL) + secretEntry.ExpirationTime = currentTime.Add(secretEntry.SecretIDTTL) } - if entry, err := logical.StorageEntryJSON(entryIndex, secretIDEntry); err != nil { + if entry, err := logical.StorageEntryJSON(entryIndex, secretEntry); err != nil { return err } else if err = s.Put(entry); err != nil { return err