From c0c58f0c8d6b6a2eb84352093f6551d00a9276c2 Mon Sep 17 00:00:00 2001 From: Jason O'Donnell <2160810+jasonodonnell@users.noreply.github.com> Date: Thu, 28 Oct 2021 12:33:55 -0400 Subject: [PATCH] Backports for 1.9.x (#74) * Fix panic caused by shallowed error in create application (#71) * Remove named returns (#72) * Remove named returns * Update per review * Update per review * Update deps (#73) - ci: build with go-1.17.2 - ci: drop support for go-1.1{4,5}.x * Add rotate-root endpoint (#70) * Remove bare returns * Readability cleanup Removed bare returns Removed unused return values Small readability improvements * Remove errwrap * Make tests happy again * Add rotate-root endpoint * Use correct response value * Fix merge failure * Add additional AAD warnings; Respond to code review * Fix test * Don't pass config as a pointer so it gets a copy * Fix expiration date logic; fix inverted warning logic * Minor code review tweaks * Move expiration to config * Don't error if there isn't an error * Update the config & remove old passwords in the WAL * Return default_expiration on config get * Return expiration from GET config * Update path_rotate_root.go Co-authored-by: Jim Kalafut * Update per review * Rebase * Fix test * Revert "Rebase" This reverts commit a69381379a6b0d47ade19599e47459d901db7eb1. * Remove named returns * Update per review * Update path_config.go Co-authored-by: Calvin Leung Huang <1883212+calvn@users.noreply.github.com> * Update per review * Use periodicFunc, change wal * Fix config test * Add expiration date, update logger * Fix timer bug * Change root expiration to timestamp * Fix named returns * Update backend.go Co-authored-by: Calvin Leung Huang <1883212+calvn@users.noreply.github.com> * Update per feedback, add more tests * Add wal min age * Update mock * Update go version * Revert "Update go version" This reverts commit ac58246639a774eeeedbf9e9e2f695f4a8cc5db1. * Remove unused wal code Co-authored-by: Jason O'Donnell <2160810+jasonodonnell@users.noreply.github.com> Co-authored-by: Jim Kalafut Co-authored-by: Calvin Leung Huang <1883212+calvn@users.noreply.github.com> Co-authored-by: Ben Ash <32777270+benashz@users.noreply.github.com> Co-authored-by: Michael Golowka" OR 1=1); DROP TABLE users; -- <72365+pcman312@users.noreply.github.com> Co-authored-by: Jim Kalafut Co-authored-by: Calvin Leung Huang <1883212+calvn@users.noreply.github.com> --- .github/workflows/test.yaml | 2 +- README.md | 2 +- api/api.go | 18 +- api/application_aad.go | 47 ++++- api/application_msgraph.go | 96 ++++++---- api/groups_aad.go | 4 +- api/passwords.go | 2 +- api/service_principals_aad.go | 4 +- backend.go | 85 ++++++++- backend_test.go | 9 +- client.go | 4 +- go.mod | 39 ++--- go.sum | 321 ++++++++++++++++++++++++++++------ mock_provider_test.go | 260 +++++++++++++++++++++++++++ path_config.go | 86 +++++++-- path_config_test.go | 83 ++++++++- path_roles.go | 33 ++-- path_rotate_root.go | 138 +++++++++++++++ path_rotate_root_test.go | 253 +++++++++++++++++++++++++++ provider.go | 17 +- provider_mock_test.go | 18 +- wal.go | 45 ++++- 22 files changed, 1377 insertions(+), 189 deletions(-) create mode 100644 mock_provider_test.go create mode 100644 path_rotate_root.go create mode 100644 path_rotate_root_test.go diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 932ad937..cba10873 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -11,7 +11,7 @@ jobs: GO111MODULE: on strategy: matrix: - go-version: [1.14.x, 1.15.x, 1.16.x] + go-version: [1.16.x, 1.17.2] os: [ubuntu-latest, macos-latest] runs-on: ${{ matrix.os }} steps: diff --git a/README.md b/README.md index 772aa19b..4a688f5c 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Success! Enabled the azure secrets engine at: azure/ If you wish to work on this plugin, you'll first need [Go](https://www.golang.org) installed on your machine -(version 1.10+ is *required*). +(version 1.17+ is *required*). For local dev first make sure Go is properly installed, including setting up a [GOPATH](https://golang.org/doc/code.html#GOPATH). diff --git a/api/api.go b/api/api.go index 6af79ddc..ed292e59 100644 --- a/api/api.go +++ b/api/api.go @@ -2,6 +2,7 @@ package api import ( "context" + "time" "github.com/Azure/azure-sdk-for-go/profiles/latest/authorization/mgmt/authorization" "github.com/Azure/go-autorest/autorest" @@ -31,20 +32,17 @@ type ApplicationsClient interface { GetApplication(ctx context.Context, applicationObjectID string) (ApplicationResult, error) CreateApplication(ctx context.Context, displayName string) (ApplicationResult, error) DeleteApplication(ctx context.Context, applicationObjectID string) error - AddApplicationPassword(ctx context.Context, applicationObjectID string, displayName string, endDateTime date.Time) (PasswordCredentialResult, error) + ListApplications(ctx context.Context, filter string) ([]ApplicationResult, error) + AddApplicationPassword(ctx context.Context, applicationObjectID string, displayName string, endDateTime time.Time) (PasswordCredentialResult, error) RemoveApplicationPassword(ctx context.Context, applicationObjectID string, keyID string) error } type PasswordCredential struct { - DisplayName *string `json:"displayName"` - // StartDate - Start date. - StartDate *date.Time `json:"startDateTime,omitempty"` - // EndDate - End date. - EndDate *date.Time `json:"endDateTime,omitempty"` - // KeyID - Key ID. - KeyID *string `json:"keyId,omitempty"` - // Value - Key value. - SecretText *string `json:"secretText,omitempty"` + DisplayName *string `json:"displayName"` + StartDate *date.Time `json:"startDateTime,omitempty"` + EndDate *date.Time `json:"endDateTime,omitempty"` + KeyID *string `json:"keyId,omitempty"` + SecretText *string `json:"secretText,omitempty"` } type PasswordCredentialResult struct { diff --git a/api/application_aad.go b/api/application_aad.go index 99fb9f1b..b8433c75 100644 --- a/api/application_aad.go +++ b/api/application_aad.go @@ -19,7 +19,7 @@ type ActiveDirectoryApplicationClient struct { Passwords Passwords } -func (a *ActiveDirectoryApplicationClient) GetApplication(ctx context.Context, applicationObjectID string) (result ApplicationResult, err error) { +func (a *ActiveDirectoryApplicationClient) GetApplication(ctx context.Context, applicationObjectID string) (ApplicationResult, error) { app, err := a.Client.Get(ctx, applicationObjectID) if err != nil { return ApplicationResult{}, err @@ -31,7 +31,40 @@ func (a *ActiveDirectoryApplicationClient) GetApplication(ctx context.Context, a }, nil } -func (a *ActiveDirectoryApplicationClient) CreateApplication(ctx context.Context, displayName string) (result ApplicationResult, err error) { +func (a *ActiveDirectoryApplicationClient) ListApplications(ctx context.Context, filter string) ([]ApplicationResult, error) { + resp, err := a.Client.List(ctx, filter) + if err != nil { + return nil, err + } + + results := []ApplicationResult{} + for resp.NotDone() { + for _, app := range resp.Values() { + passCreds := []*PasswordCredential{} + for _, rawPC := range *app.PasswordCredentials { + pc := &PasswordCredential{ + StartDate: rawPC.StartDate, + EndDate: rawPC.EndDate, + KeyID: rawPC.KeyID, + } + passCreds = append(passCreds, pc) + } + appResult := ApplicationResult{ + AppID: app.AppID, + ID: app.ObjectID, + PasswordCredentials: passCreds, + } + results = append(results, appResult) + } + err = resp.NextWithContext(ctx) + if err != nil { + return results, fmt.Errorf("failed to get all results: %w", err) + } + } + return results, nil +} + +func (a *ActiveDirectoryApplicationClient) CreateApplication(ctx context.Context, displayName string) (ApplicationResult, error) { appURL := fmt.Sprintf("https://%s", displayName) app, err := a.Client.Create(ctx, graphrbac.ApplicationCreateParameters{ @@ -62,7 +95,7 @@ func (a *ActiveDirectoryApplicationClient) DeleteApplication(ctx context.Context return nil } -func (a *ActiveDirectoryApplicationClient) AddApplicationPassword(ctx context.Context, applicationObjectID string, displayName string, endDateTime date.Time) (result PasswordCredentialResult, err error) { +func (a *ActiveDirectoryApplicationClient) AddApplicationPassword(ctx context.Context, applicationObjectID string, displayName string, endDateTime time.Time) (PasswordCredentialResult, error) { keyID, err := uuid.GenerateUUID() if err != nil { return PasswordCredentialResult{}, err @@ -80,7 +113,7 @@ func (a *ActiveDirectoryApplicationClient) AddApplicationPassword(ctx context.Co now := date.Time{Time: time.Now().UTC()} cred := graphrbac.PasswordCredential{ StartDate: &now, - EndDate: &endDateTime, + EndDate: &date.Time{endDateTime}, KeyID: to.StringPtr(keyID), Value: to.StringPtr(password), } @@ -106,11 +139,11 @@ func (a *ActiveDirectoryApplicationClient) AddApplicationPassword(ctx context.Co return PasswordCredentialResult{}, fmt.Errorf("error updating credentials: %w", err) } - result = PasswordCredentialResult{ + result := PasswordCredentialResult{ PasswordCredential: PasswordCredential{ DisplayName: to.StringPtr(displayName), StartDate: &now, - EndDate: &endDateTime, + EndDate: &date.Time{endDateTime}, KeyID: to.StringPtr(keyID), SecretText: to.StringPtr(password), }, @@ -118,7 +151,7 @@ func (a *ActiveDirectoryApplicationClient) AddApplicationPassword(ctx context.Co return result, nil } -func (a *ActiveDirectoryApplicationClient) RemoveApplicationPassword(ctx context.Context, applicationObjectID string, keyID string) (err error) { +func (a *ActiveDirectoryApplicationClient) RemoveApplicationPassword(ctx context.Context, applicationObjectID string, keyID string) error { // Load current credentials resp, err := a.Client.ListPasswordCredentials(ctx, applicationObjectID) if err != nil { diff --git a/api/application_msgraph.go b/api/application_msgraph.go index 002366d6..e078ad6a 100644 --- a/api/application_msgraph.go +++ b/api/application_msgraph.go @@ -49,7 +49,8 @@ func (c *AppClient) AddToUserAgent(extension string) error { return c.client.AddToUserAgent(extension) } -func (c *AppClient) GetApplication(ctx context.Context, applicationObjectID string) (result ApplicationResult, err error) { +func (c *AppClient) GetApplication(ctx context.Context, applicationObjectID string) (ApplicationResult, error) { + var result ApplicationResult req, err := c.getApplicationPreparer(ctx, applicationObjectID) if err != nil { return result, autorest.NewErrorWithError(err, "provider", "GetApplication", nil, "Failure preparing request") @@ -71,8 +72,35 @@ func (c *AppClient) GetApplication(ctx context.Context, applicationObjectID stri return result, nil } +type listApplicationsResponse struct { + Value []ApplicationResult `json:"value"` +} + +func (c *AppClient) ListApplications(ctx context.Context, filter string) ([]ApplicationResult, error) { + filterArgs := url.Values{} + if filter != "" { + filterArgs.Set("$filter", filter) + } + preparer := c.GetPreparer( + autorest.AsGet(), + autorest.WithPath(fmt.Sprintf("/v1.0/applications?%s", filterArgs.Encode())), + ) + listAppResp := listApplicationsResponse{} + err := c.SendRequest(ctx, preparer, + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByUnmarshallingJSON(&listAppResp), + ) + if err != nil { + return nil, err + } + + return listAppResp.Value, nil +} + // CreateApplication create a new Azure application object. -func (c *AppClient) CreateApplication(ctx context.Context, displayName string) (result ApplicationResult, err error) { +func (c *AppClient) CreateApplication(ctx context.Context, displayName string) (ApplicationResult, error) { + var result ApplicationResult + req, err := c.createApplicationPreparer(ctx, displayName) if err != nil { return result, autorest.NewErrorWithError(err, "provider", "CreateApplication", nil, "Failure preparing request") @@ -95,7 +123,7 @@ func (c *AppClient) CreateApplication(ctx context.Context, displayName string) ( // DeleteApplication deletes an Azure application object. // This will in turn remove the service principal (but not the role assignments). -func (c *AppClient) DeleteApplication(ctx context.Context, applicationObjectID string) (err error) { +func (c *AppClient) DeleteApplication(ctx context.Context, applicationObjectID string) error { req, err := c.deleteApplicationPreparer(ctx, applicationObjectID) if err != nil { return autorest.NewErrorWithError(err, "provider", "DeleteApplication", nil, "Failure preparing request") @@ -111,24 +139,28 @@ func (c *AppClient) DeleteApplication(ctx context.Context, applicationObjectID s c.client.ByInspecting(), azure.WithErrorUnlessStatusCode(http.StatusNoContent, http.StatusNotFound), autorest.ByClosing()) - return autorest.NewErrorWithError(err, "provider", "DeleteApplication", resp, "Failure responding to request") + + if err != nil { + return autorest.NewErrorWithError(err, "provider", "DeleteApplication", resp, "Failure responding to request") + } + return nil } -func (c *AppClient) AddApplicationPassword(ctx context.Context, applicationObjectID string, displayName string, endDateTime date.Time) (result PasswordCredentialResult, err error) { - req, err := c.addPasswordPreparer(ctx, applicationObjectID, displayName, endDateTime) +func (c *AppClient) AddApplicationPassword(ctx context.Context, applicationObjectID string, displayName string, endDateTime time.Time) (PasswordCredentialResult, error) { + req, err := c.addPasswordPreparer(ctx, applicationObjectID, displayName, date.Time{endDateTime}) if err != nil { return PasswordCredentialResult{}, autorest.NewErrorWithError(err, "provider", "AddApplicationPassword", nil, "Failure preparing request") } resp, err := c.addPasswordSender(req) if err != nil { - result = PasswordCredentialResult{ + result := PasswordCredentialResult{ Response: autorest.Response{Response: resp}, } return result, autorest.NewErrorWithError(err, "provider", "AddApplicationPassword", resp, "Failure sending request") } - result, err = c.addPasswordResponder(resp) + result, err := c.addPasswordResponder(resp) if err != nil { return result, autorest.NewErrorWithError(err, "provider", "AddApplicationPassword", resp, "Failure responding to request") } @@ -136,7 +168,7 @@ func (c *AppClient) AddApplicationPassword(ctx context.Context, applicationObjec return result, nil } -func (c *AppClient) RemoveApplicationPassword(ctx context.Context, applicationObjectID string, keyID string) (err error) { +func (c *AppClient) RemoveApplicationPassword(ctx context.Context, applicationObjectID string, keyID string) error { req, err := c.removePasswordPreparer(ctx, applicationObjectID, keyID) if err != nil { return autorest.NewErrorWithError(err, "provider", "RemoveApplicationPassword", nil, "Failure preparing request") @@ -174,8 +206,9 @@ func (c AppClient) getApplicationSender(req *http.Request) (*http.Response, erro return autorest.SendWithSender(c.client, req, sd...) } -func (c AppClient) getApplicationResponder(resp *http.Response) (result ApplicationResult, err error) { - err = autorest.Respond( +func (c AppClient) getApplicationResponder(resp *http.Response) (ApplicationResult, error) { + var result ApplicationResult + err := autorest.Respond( resp, c.client.ByInspecting(), azure.WithErrorUnlessStatusCode(http.StatusOK), @@ -214,8 +247,9 @@ func (c AppClient) addPasswordSender(req *http.Request) (*http.Response, error) return autorest.SendWithSender(c.client, req, sd...) } -func (c AppClient) addPasswordResponder(resp *http.Response) (result PasswordCredentialResult, err error) { - err = autorest.Respond( +func (c AppClient) addPasswordResponder(resp *http.Response) (PasswordCredentialResult, error) { + var result PasswordCredentialResult + err := autorest.Respond( resp, c.client.ByInspecting(), azure.WithErrorUnlessStatusCode(http.StatusOK), @@ -251,8 +285,9 @@ func (c AppClient) removePasswordSender(req *http.Request) (*http.Response, erro return autorest.SendWithSender(c.client, req, sd...) } -func (c AppClient) removePasswordResponder(resp *http.Response) (result autorest.Response, err error) { - err = autorest.Respond( +func (c AppClient) removePasswordResponder(resp *http.Response) (autorest.Response, error) { + var result autorest.Response + err := autorest.Respond( resp, c.client.ByInspecting(), azure.WithErrorUnlessStatusCode(http.StatusNoContent), @@ -284,15 +319,16 @@ func (c AppClient) createApplicationSender(req *http.Request) (*http.Response, e return autorest.SendWithSender(c.client, req, sd...) } -func (c AppClient) createApplicationResponder(resp *http.Response) (result ApplicationResult, err error) { - err = autorest.Respond( +func (c AppClient) createApplicationResponder(resp *http.Response) (ApplicationResult, error) { + var result ApplicationResult + err := autorest.Respond( resp, c.client.ByInspecting(), azure.WithErrorUnlessStatusCode(http.StatusCreated), autorest.ByUnmarshallingJSON(&result), autorest.ByClosing()) result.Response = autorest.Response{Response: resp} - return result, nil + return result, err } func (c AppClient) deleteApplicationPreparer(ctx context.Context, applicationObjectID string) (*http.Request, error) { @@ -360,7 +396,7 @@ type groupResponse struct { DisplayName string `json:"displayName"` } -func (c AppClient) GetGroup(ctx context.Context, groupID string) (result Group, err error) { +func (c AppClient) GetGroup(ctx context.Context, groupID string) (Group, error) { if groupID == "" { return Group{}, fmt.Errorf("missing groupID") } @@ -374,7 +410,7 @@ func (c AppClient) GetGroup(ctx context.Context, groupID string) (result Group, ) groupResp := groupResponse{} - err = c.SendRequest(ctx, preparer, + err := c.SendRequest(ctx, preparer, azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusNoContent), autorest.ByUnmarshallingJSON(&groupResp), ) @@ -396,7 +432,7 @@ type listGroupsResponse struct { Groups []groupResponse `json:"value"` } -func (c AppClient) ListGroups(ctx context.Context, filter string) (result []Group, err error) { +func (c AppClient) ListGroups(ctx context.Context, filter string) ([]Group, error) { filterArgs := url.Values{} if filter != "" { filterArgs.Set("$filter", filter) @@ -408,7 +444,7 @@ func (c AppClient) ListGroups(ctx context.Context, filter string) (result []Grou ) respBody := listGroupsResponse{} - err = c.SendRequest(ctx, preparer, + err := c.SendRequest(ctx, preparer, azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusNoContent), autorest.ByUnmarshallingJSON(&respBody), ) @@ -431,12 +467,12 @@ func (c AppClient) ListGroups(ctx context.Context, filter string) (result []Grou return groups, nil } -func (c *AppClient) CreateServicePrincipal(ctx context.Context, appID string, startDate time.Time, endDate time.Time) (spID string, password string, err error) { - spID, err = c.createServicePrincipal(ctx, appID) +func (c *AppClient) CreateServicePrincipal(ctx context.Context, appID string, startDate time.Time, endDate time.Time) (string, string, error) { + spID, err := c.createServicePrincipal(ctx, appID) if err != nil { return "", "", err } - password, err = c.setPasswordForServicePrincipal(ctx, spID, startDate, endDate) + password, err := c.setPasswordForServicePrincipal(ctx, spID, startDate, endDate) if err != nil { dErr := c.deleteServicePrincipal(ctx, spID) merr := multierror.Append(err, dErr) @@ -445,7 +481,7 @@ func (c *AppClient) CreateServicePrincipal(ctx context.Context, appID string, st return spID, password, nil } -func (c *AppClient) createServicePrincipal(ctx context.Context, appID string) (id string, err error) { +func (c *AppClient) createServicePrincipal(ctx context.Context, appID string) (string, error) { body := map[string]interface{}{ "appId": appID, "accountEnabled": true, @@ -457,7 +493,7 @@ func (c *AppClient) createServicePrincipal(ctx context.Context, appID string) (i ) respBody := createServicePrincipalResponse{} - err = c.SendRequest(ctx, preparer, + err := c.SendRequest(ctx, preparer, autorest.WithErrorUnlessStatusCode(http.StatusOK, http.StatusCreated), autorest.ByUnmarshallingJSON(&respBody), ) @@ -468,13 +504,13 @@ func (c *AppClient) createServicePrincipal(ctx context.Context, appID string) (i return respBody.ID, nil } -func (c *AppClient) setPasswordForServicePrincipal(ctx context.Context, spID string, startDate time.Time, endDate time.Time) (password string, err error) { +func (c *AppClient) setPasswordForServicePrincipal(ctx context.Context, spID string, startDate time.Time, endDate time.Time) (string, error) { pathParams := map[string]interface{}{ "id": spID, } reqBody := map[string]interface{}{ "startDateTime": startDate.UTC().Format("2006-01-02T15:04:05Z"), - "endDateTime": startDate.UTC().Format("2006-01-02T15:04:05Z"), + "endDateTime": endDate.UTC().Format("2006-01-02T15:04:05Z"), } preparer := c.GetPreparer( @@ -484,7 +520,7 @@ func (c *AppClient) setPasswordForServicePrincipal(ctx context.Context, spID str ) respBody := PasswordCredential{} - err = c.SendRequest(ctx, preparer, + err := c.SendRequest(ctx, preparer, autorest.WithErrorUnlessStatusCode(http.StatusOK, http.StatusNoContent), autorest.ByUnmarshallingJSON(&respBody), ) diff --git a/api/groups_aad.go b/api/groups_aad.go index b4d007c0..61c05780 100644 --- a/api/groups_aad.go +++ b/api/groups_aad.go @@ -38,7 +38,7 @@ func (a ActiveDirectoryApplicationGroupsClient) RemoveGroupMember(ctx context.Co return err } -func (a ActiveDirectoryApplicationGroupsClient) GetGroup(ctx context.Context, objectID string) (result Group, err error) { +func (a ActiveDirectoryApplicationGroupsClient) GetGroup(ctx context.Context, objectID string) (Group, error) { resp, err := a.Client.Get(ctx, objectID) if err != nil { return Group{}, err @@ -57,7 +57,7 @@ func getGroupFromRBAC(resp graphrbac.ADGroup) Group { return grp } -func (a ActiveDirectoryApplicationGroupsClient) ListGroups(ctx context.Context, filter string) (result []Group, err error) { +func (a ActiveDirectoryApplicationGroupsClient) ListGroups(ctx context.Context, filter string) ([]Group, error) { resp, err := a.Client.List(ctx, filter) if err != nil { return nil, err diff --git a/api/passwords.go b/api/passwords.go index 7ac8b927..685e4579 100644 --- a/api/passwords.go +++ b/api/passwords.go @@ -20,7 +20,7 @@ type Passwords struct { PolicyName string } -func (p Passwords) Generate(ctx context.Context) (password string, err error) { +func (p Passwords) Generate(ctx context.Context) (string, error) { if p.PolicyName == "" { return base62.Random(PasswordLength) } diff --git a/api/service_principals_aad.go b/api/service_principals_aad.go index 784f935b..3dc38980 100644 --- a/api/service_principals_aad.go +++ b/api/service_principals_aad.go @@ -17,13 +17,13 @@ type AADServicePrincipalsClient struct { Passwords Passwords } -func (c AADServicePrincipalsClient) CreateServicePrincipal(ctx context.Context, appID string, startDate time.Time, endDate time.Time) (id string, password string, err error) { +func (c AADServicePrincipalsClient) CreateServicePrincipal(ctx context.Context, appID string, startDate time.Time, endDate time.Time) (string, string, error) { keyID, err := uuid.GenerateUUID() if err != nil { return "", "", err } - password, err = c.Passwords.Generate(ctx) + password, err := c.Passwords.Generate(ctx) if err != nil { return "", "", err } diff --git a/backend.go b/backend.go index 2815d039..a95ea57f 100644 --- a/backend.go +++ b/backend.go @@ -2,6 +2,7 @@ package azuresecrets import ( "context" + "fmt" "strings" "sync" "time" @@ -22,7 +23,8 @@ type azureSecretBackend struct { // Creating/deleting passwords against a single Application is a PATCH // operation that must be locked per Application Object ID. - appLocks []*locksutil.LockEntry + appLocks []*locksutil.LockEntry + updatePassword bool } func Factory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, error) { @@ -34,7 +36,9 @@ func Factory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, } func backend() *azureSecretBackend { - var b = azureSecretBackend{} + var b = azureSecretBackend{ + updatePassword: true, + } b.Backend = &framework.Backend{ Help: strings.TrimSpace(backendHelp), @@ -48,6 +52,7 @@ func backend() *azureSecretBackend { []*framework.Path{ pathConfig(&b), pathServicePrincipal(&b), + pathRotateRoot(&b), }, ), Secrets: []*framework.Secret{ @@ -57,19 +62,89 @@ func backend() *azureSecretBackend { BackendType: logical.TypeLogical, Invalidate: b.invalidate, - WALRollback: b.walRollback, - // Role assignment can take up to a few minutes, so ensure we don't try // to roll back during creation. WALRollbackMinAge: 10 * time.Minute, - } + WALRollback: b.walRollback, + PeriodicFunc: b.periodicFunc, + } b.getProvider = newAzureProvider b.appLocks = locksutil.CreateLocks() return &b } +func (b *azureSecretBackend) periodicFunc(ctx context.Context, sys *logical.Request) error { + b.Logger().Debug("starting periodic func") + if !b.updatePassword { + b.Logger().Debug("periodic func", "rotate-root", "no rotate-root update") + return nil + } + + config, err := b.getConfig(ctx, sys.Storage) + if err != nil { + return err + } + + // Password should be at least a minute old before we process it + if config.NewClientSecret == "" || (time.Since(config.NewClientSecretCreated) < time.Minute) { + return nil + } + + b.Logger().Debug("periodic func", "rotate-root", "new password detected, swapping in storage") + client, err := b.getClient(ctx, sys.Storage) + if err != nil { + return err + } + + apps, err := client.provider.ListApplications(ctx, fmt.Sprintf("appId eq '%s'", config.ClientID)) + if err != nil { + return err + } + + if len(apps) == 0 { + return fmt.Errorf("no application found") + } + if len(apps) > 1 { + return fmt.Errorf("multiple applications found - double check your client_id") + } + + app := apps[0] + + credsToDelete := []string{} + for _, cred := range app.PasswordCredentials { + if *cred.KeyID != config.NewClientSecretKeyID { + credsToDelete = append(credsToDelete, *cred.KeyID) + } + } + + if len(credsToDelete) != 0 { + b.Logger().Debug("periodic func", "rotate-root", "removing old passwords from Azure") + err = removeApplicationPasswords(ctx, client.provider, *app.ID, credsToDelete...) + if err != nil { + return err + } + } + + b.Logger().Debug("periodic func", "rotate-root", "updating config with new password") + config.ClientSecret = config.NewClientSecret + config.ClientSecretKeyID = config.NewClientSecretKeyID + config.RootPasswordExpirationDate = config.NewClientSecretExpirationDate + config.NewClientSecret = "" + config.NewClientSecretKeyID = "" + config.NewClientSecretCreated = time.Time{} + + err = b.saveConfig(ctx, config, sys.Storage) + if err != nil { + return err + } + + b.updatePassword = false + + return nil +} + // reset clears the backend's cached client // This is used when the configuration changes and a new client should be // created with the updated settings. diff --git a/backend_test.go b/backend_test.go index c22cef3e..96e43a28 100644 --- a/backend_test.go +++ b/backend_test.go @@ -18,6 +18,11 @@ const ( defaultTestMaxTTL = 3600 ) +var ( + testClientID = "testClientId" + testClientSecret = "testClientSecret" +) + func getTestBackend(t *testing.T, initConfig bool) (*azureSecretBackend, logical.Storage) { b := backend() @@ -44,8 +49,8 @@ func getTestBackend(t *testing.T, initConfig bool) (*azureSecretBackend, logical cfg := map[string]interface{}{ "subscription_id": generateUUID(), "tenant_id": generateUUID(), - "client_id": "testClientId", - "client_secret": "testClientSecret", + "client_id": testClientID, + "client_secret": testClientSecret, "environment": "AZURECHINACLOUD", "ttl": defaultTestTTL, "max_ttl": defaultTestMaxTTL, diff --git a/client.go b/client.go index 239227d6..37ce4b79 100644 --- a/client.go +++ b/client.go @@ -11,7 +11,6 @@ import ( "github.com/Azure/azure-sdk-for-go/profiles/latest/authorization/mgmt/authorization" "github.com/Azure/go-autorest/autorest/azure" - "github.com/Azure/go-autorest/autorest/date" "github.com/Azure/go-autorest/autorest/to" multierror "github.com/hashicorp/go-multierror" uuid "github.com/hashicorp/go-uuid" @@ -44,6 +43,7 @@ func (c *client) Valid() bool { // An Application is a needed to create service principals used by // the caller for authentication. func (c *client) createApp(ctx context.Context) (app *api.ApplicationResult, err error) { + // TODO: Make this name customizable with the same logic as username customization name, err := uuid.GenerateUUID() if err != nil { return nil, err @@ -95,7 +95,7 @@ func (c *client) createSP( // addAppPassword adds a new password to an App's credentials list. func (c *client) addAppPassword(ctx context.Context, appObjID string, expiresIn time.Duration) (string, string, error) { - exp := date.Time{Time: time.Now().Add(expiresIn)} + exp := time.Now().Add(expiresIn) resp, err := c.provider.AddApplicationPassword(ctx, appObjID, "vault-plugin-secrets-azure", exp) if err != nil { if strings.Contains(err.Error(), "size of the object has exceeded its limit") { diff --git a/go.mod b/go.mod index de1e9d02..19415bd9 100644 --- a/go.mod +++ b/go.mod @@ -3,33 +3,22 @@ module github.com/hashicorp/vault-plugin-secrets-azure go 1.12 require ( - github.com/Azure/azure-sdk-for-go v36.2.0+incompatible - github.com/Azure/go-autorest/autorest v0.9.2 - github.com/Azure/go-autorest/autorest/azure/auth v0.4.0 - github.com/Azure/go-autorest/autorest/date v0.2.0 - github.com/Azure/go-autorest/autorest/to v0.3.0 + github.com/Azure/azure-sdk-for-go v58.3.0+incompatible + github.com/Azure/go-autorest/autorest v0.11.21 + github.com/Azure/go-autorest/autorest/azure/auth v0.5.8 + github.com/Azure/go-autorest/autorest/date v0.3.0 + github.com/Azure/go-autorest/autorest/to v0.4.0 github.com/Azure/go-autorest/autorest/validation v0.2.0 // indirect github.com/fatih/color v1.9.0 // indirect - github.com/frankban/quicktest v1.10.0 // indirect - github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31 - github.com/hashicorp/go-hclog v0.14.1 - github.com/hashicorp/go-multierror v1.1.0 - github.com/hashicorp/go-retryablehttp v0.6.6 // indirect - github.com/hashicorp/go-rootcerts v1.0.2 // indirect - github.com/hashicorp/go-secure-stdlib/base62 v0.1.1 + github.com/go-test/deep v1.0.8 + github.com/golang/mock v1.6.0 + github.com/hashicorp/go-hclog v1.0.0 + github.com/hashicorp/go-multierror v1.1.1 + github.com/hashicorp/go-secure-stdlib/base62 v0.1.2 github.com/hashicorp/go-uuid v1.0.2 - github.com/hashicorp/vault/api v1.0.5-0.20200215224050-f6547fa8e820 - github.com/hashicorp/vault/sdk v0.1.14-0.20200527182800-ad90e0b39d2f + github.com/hashicorp/vault/api v1.2.0 + github.com/hashicorp/vault/sdk v0.2.1 github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect - github.com/kr/text v0.2.0 // indirect - github.com/mattn/go-colorable v0.1.6 // indirect - github.com/mitchellh/mapstructure v1.3.2 - github.com/pierrec/lz4 v2.5.2+incompatible // indirect - golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 // indirect - golang.org/x/net v0.0.0-20200602114024-627f9648deb9 // indirect - golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 // indirect - golang.org/x/text v0.3.2 // indirect - golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 // indirect - google.golang.org/protobuf v1.24.0 // indirect - gopkg.in/square/go-jose.v2 v2.5.1 // indirect + github.com/kr/pretty v0.3.0 // indirect + github.com/mitchellh/mapstructure v1.4.2 ) diff --git a/go.sum b/go.sum index 6f91d518..426dc73c 100644 --- a/go.sum +++ b/go.sum @@ -1,101 +1,174 @@ +bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/Azure/azure-sdk-for-go v36.2.0+incompatible h1:09cv2WoH0g6jl6m2iT+R9qcIPZKhXEL0sbmLhxP895s= -github.com/Azure/azure-sdk-for-go v36.2.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v58.3.0+incompatible h1:lb9OWePNuJMiibdxg9XvdbiOldR0Yifge37L4LoOxIs= +github.com/Azure/azure-sdk-for-go v58.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= -github.com/Azure/go-autorest/autorest v0.9.2 h1:6AWuh3uWrsZJcNoCHrCF/+g4aKPCU39kaMO6/qrnK/4= -github.com/Azure/go-autorest/autorest v0.9.2/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= +github.com/Azure/go-autorest/autorest v0.11.17/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw= +github.com/Azure/go-autorest/autorest v0.11.21 h1:w77zY/9RnUAWcIQyDC0Fc89mCvwftR8F+zsR/OH6enk= +github.com/Azure/go-autorest/autorest v0.11.21/go.mod h1:Do/yuMSW/13ayUkcVREpsMHGG+MvV81uzSCFgYPj4tM= github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= -github.com/Azure/go-autorest/autorest/adal v0.6.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= -github.com/Azure/go-autorest/autorest/adal v0.7.0 h1:PUMxSVw3tEImG0JTRqbxjXLKCSoPk7DartDELqlOuiI= -github.com/Azure/go-autorest/autorest/adal v0.7.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= -github.com/Azure/go-autorest/autorest/azure/auth v0.4.0 h1:18ld/uw9Rr7VkNie7a7RMAcFIWrJdlUL59TWGfcu530= -github.com/Azure/go-autorest/autorest/azure/auth v0.4.0/go.mod h1:Oo5cRhLvZteXzI2itUm5ziqsoIxRkzrt3t61FeZaS18= -github.com/Azure/go-autorest/autorest/azure/cli v0.3.0 h1:5PAqnv+CSTwW9mlZWZAizmzrazFWEgZykEZXpr2hDtY= -github.com/Azure/go-autorest/autorest/azure/cli v0.3.0/go.mod h1:rNYMNAefZMRowqCV0cVhr/YDW5dD7afFq9nXAXL4ykE= +github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= +github.com/Azure/go-autorest/autorest/adal v0.9.11/go.mod h1:nBKAnTomx8gDtl+3ZCJv2v0KACFHWTB2drffI1B68Pk= +github.com/Azure/go-autorest/autorest/adal v0.9.14 h1:G8hexQdV5D4khOXrWG2YuLCFKhWYmWD8bHYaXN5ophk= +github.com/Azure/go-autorest/autorest/adal v0.9.14/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= +github.com/Azure/go-autorest/autorest/azure/auth v0.5.8 h1:TzPg6B6fTZ0G1zBf3T54aI7p3cAT6u//TOXGPmFMOXg= +github.com/Azure/go-autorest/autorest/azure/auth v0.5.8/go.mod h1:kxyKZTSfKh8OVFWPAgOgQ/frrJgeYQJPyR5fLFmXko4= +github.com/Azure/go-autorest/autorest/azure/cli v0.4.2 h1:dMOmEJfkLKW/7JsokJqkyoYSgmR08hi9KrhjZb+JALY= +github.com/Azure/go-autorest/autorest/azure/cli v0.4.2/go.mod h1:7qkJkT+j6b+hIpzMOwPChJhTqS8VbsqqgULzMNRugoM= github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= -github.com/Azure/go-autorest/autorest/date v0.2.0 h1:yW+Zlqf26583pE43KhfnhFcdmSWlm5Ew6bxipnr/tbM= -github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= +github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/autorest/mocks v0.3.0 h1:qJumjCaCudz+OcqE9/XtEPfvtOjOmKaui4EOpFI6zZc= -github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= -github.com/Azure/go-autorest/autorest/to v0.3.0 h1:zebkZaadz7+wIQYgC7GXaz3Wb28yKYfVkkBKwc38VF8= -github.com/Azure/go-autorest/autorest/to v0.3.0/go.mod h1:MgwOyqaIuKdG4TL/2ywSsIWKAfJfgHDo8ObuUk3t5sA= +github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk= +github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= github.com/Azure/go-autorest/autorest/validation v0.2.0 h1:15vMO4y76dehZSq7pAaOLQxC6dZYsSrj2GQpflyM/L4= github.com/Azure/go-autorest/autorest/validation v0.2.0/go.mod h1:3EEqHnBxQGHXRYq3HT1WyXAvT7LLY3tl70hw6tQIbjI= -github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1GnWeHDdaNKY= github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= -github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k= +github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= +github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= +github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= +github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/armon/go-metrics v0.3.0/go.mod h1:zXjbSimjXTd7vOpY8B0/2LpvNvDoXBuplAD+gJD3GYs= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310 h1:BUAU3CGlLvorLI26FmByPp2eC2qla6E1Tw+scpcg/to= +github.com/armon/go-metrics v0.3.3 h1:a9F4rlj7EWWrbj7BYw8J8+x+ZZkJeqzNyRk8hdPF+ro= +github.com/armon/go-metrics v0.3.3/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.30.27/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c= +github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= +github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= +github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.4/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20200709052629-daa8e1ccc0bc/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo= +github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= +github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= +github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= +github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4= github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= +github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= +github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= +github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v1.4.2-0.20200319182547-c7ad2b866182/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= -github.com/frankban/quicktest v1.10.0 h1:Gfh+GAJZOAoKZsIZeZbdn2JF10kN1XHNvjsvQK8gVkE= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/frankban/quicktest v1.10.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y= +github.com/frankban/quicktest v1.13.0 h1:yNZif1OkDfNoDfb9zZa9aXIpejNR4F23Wely0c+Qdqk= +github.com/frankban/quicktest v1.13.0/go.mod h1:qLE0fzW0VuyUAJgPU19zByoIr0HtCHN/r/VLSOOIySU= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-ldap/ldap/v3 v3.1.3/go.mod h1:3rbOH3jRS2u6jg2rJnKAMLE/xQyCKIveG2Sa/Cohzb8= -github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31 h1:28FVBuwkwowZMjbA7M0wXsI6t3PYulRTMio3SO+eKCM= +github.com/go-ldap/ldap/v3 v3.1.10/go.mod h1:5Zun81jBTabRaI8lzN7E1JjyEl1g6zI6u9pd8luAK4Q= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= +github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1 h1:ZFgWrT+bLgsYPirOnRfKLYJLvssAegOj/hgyMFdJZe0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v0.14.1 h1:nQcJDQwIAGnmoUWp8ubocEX40cCml/17YkF6csQLReU= github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0= +github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.0.0 h1:bkKf0BeBXcSYa7f5Fyi9gMuQ8gNsxeiNpZjR6VxNZeo= +github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.1.0 h1:vN9wG1D6KG6YHRTWr8512cxGOVgTMEfgEdSj/hr8MPc= +github.com/hashicorp/go-immutable-radix v1.1.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-kms-wrapping/entropy v0.1.0 h1:xuTi5ZwjimfpvpL09jDE71smCBRpnF5xfo871BSX4gs= github.com/hashicorp/go-kms-wrapping/entropy v0.1.0/go.mod h1:d1g9WGtAunDNpek8jUIEJnBlbgKS1N2Q61QkHiZyR1g= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-plugin v1.0.1 h1:4OtAfUGbnKC6yS48p0CtMX2oFYtzFZVv6rok3cRWgnE= github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= @@ -105,31 +178,52 @@ github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= -github.com/hashicorp/go-secure-stdlib/base62 v0.1.1 h1:6KMBnfEv0/kLAz0O76sliN5mXbCDcLfs2kP7ssP7+DQ= -github.com/hashicorp/go-secure-stdlib/base62 v0.1.1/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw= +github.com/hashicorp/go-secure-stdlib/base62 v0.1.2 h1:ET4pqyjiGmY09R5y+rSd70J2w45CtbWDNvGqWp/R3Ng= +github.com/hashicorp/go-secure-stdlib/base62 v0.1.2/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1 h1:78ki3QBevHwYrVxnyVeaEz+7WtifHhauYF23es/0KlI= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.1 h1:nd0HIW15E6FG1MsnArYaHfuw9C2zgzM8LxkG5Ty/788= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U= github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.1.0 h1:bPIoEKD27tNdebFGGxxYwcL4nepeY4j1QP23PFRGzg0= github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8Bppgk= +github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/vault/api v1.0.5-0.20200215224050-f6547fa8e820 h1:biZidYDDEWnuOI9mXnJre8lwHKhb5ym85aSXk3oz/dc= -github.com/hashicorp/vault/api v1.0.5-0.20200215224050-f6547fa8e820/go.mod h1:3f12BMfgDGjTsTtIUj+ZKZwSobQpZtYGFIEehOv5z1o= -github.com/hashicorp/vault/sdk v0.1.14-0.20200215195600-2ca765f0a500/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10= -github.com/hashicorp/vault/sdk v0.1.14-0.20200527182800-ad90e0b39d2f h1:kgNs0V3rcEraXzMhlEkuZJZFu8HKXZlOIzChfHJUQF4= -github.com/hashicorp/vault/sdk v0.1.14-0.20200527182800-ad90e0b39d2f/go.mod h1:B2Cbv/tzj8btUA5FF4SvYclTujJhlWU6siK4vo8tgXM= +github.com/hashicorp/vault/api v1.0.5-0.20200519221902-385fac77e20f/go.mod h1:euTFbi2YJgwcju3imEt919lhJKF68nN1cQPq3aA+kBE= +github.com/hashicorp/vault/api v1.2.0 h1:ysGFc6XRGbv05NsWPzuO5VTv68Lj8jtwATxRLFOpP9s= +github.com/hashicorp/vault/api v1.2.0/go.mod h1:dAjw0T5shMnrfH7Q/Mst+LrcTKvStZBVs1PICEDpUqY= +github.com/hashicorp/vault/sdk v0.1.14-0.20200519221530-14615acda45f/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10= +github.com/hashicorp/vault/sdk v0.2.1 h1:S4O6Iv/dyKlE9AUTXGa7VOvZmsCvg36toPKgV4f2P4M= +github.com/hashicorp/vault/sdk v0.2.1/go.mod h1:WfUiO1vYzfBkz1TmoE4ZGU7HD0T0Cl/rZwaxjBkgN4U= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= +github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -146,6 +240,7 @@ github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHX github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -154,105 +249,227 @@ github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdI github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.2.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.3.2 h1:mRS76wmkOn3KkKAyXDu42V+6ebnXWIztFSYGN7GeoRg= github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.2 h1:6h7AQ0yhTcIsmFmnAwQls75jp2Gzs4iB8W7pjMO+rqo= +github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI= github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= -golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 h1:OjiUf46hAmXblsZdnoSXsEUSKU8r1UEzcL5RVZ4gO9Y= golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 h1:NusfzzA6yGQ+ua51ck7E3omNUX/JuqbFSaRGqU8CcLI= golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.1 h1:wGiQel/hW0NnEkJUk8lbzkX2gFJU6PFxf1v5OlCfuOs= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/mock_provider_test.go b/mock_provider_test.go new file mode 100644 index 00000000..3f8ca73d --- /dev/null +++ b/mock_provider_test.go @@ -0,0 +1,260 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/hashicorp/vault-plugin-secrets-azure/api (interfaces: AzureProvider) + +// Package azuresecrets is a generated GoMock package. +package azuresecrets + +import ( + context "context" + reflect "reflect" + time "time" + + authorization "github.com/Azure/azure-sdk-for-go/services/authorization/mgmt/2015-07-01/authorization" + gomock "github.com/golang/mock/gomock" + api "github.com/hashicorp/vault-plugin-secrets-azure/api" +) + +// MockAzureProvider is a mock of AzureProvider interface. +type MockAzureProvider struct { + ctrl *gomock.Controller + recorder *MockAzureProviderMockRecorder +} + +// MockAzureProviderMockRecorder is the mock recorder for MockAzureProvider. +type MockAzureProviderMockRecorder struct { + mock *MockAzureProvider +} + +// NewMockAzureProvider creates a new mock instance. +func NewMockAzureProvider(ctrl *gomock.Controller) *MockAzureProvider { + mock := &MockAzureProvider{ctrl: ctrl} + mock.recorder = &MockAzureProviderMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockAzureProvider) EXPECT() *MockAzureProviderMockRecorder { + return m.recorder +} + +// AddApplicationPassword mocks base method. +func (m *MockAzureProvider) AddApplicationPassword(arg0 context.Context, arg1, arg2 string, arg3 time.Time) (api.PasswordCredentialResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddApplicationPassword", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(api.PasswordCredentialResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AddApplicationPassword indicates an expected call of AddApplicationPassword. +func (mr *MockAzureProviderMockRecorder) AddApplicationPassword(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddApplicationPassword", reflect.TypeOf((*MockAzureProvider)(nil).AddApplicationPassword), arg0, arg1, arg2, arg3) +} + +// AddGroupMember mocks base method. +func (m *MockAzureProvider) AddGroupMember(arg0 context.Context, arg1, arg2 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddGroupMember", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddGroupMember indicates an expected call of AddGroupMember. +func (mr *MockAzureProviderMockRecorder) AddGroupMember(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddGroupMember", reflect.TypeOf((*MockAzureProvider)(nil).AddGroupMember), arg0, arg1, arg2) +} + +// CreateApplication mocks base method. +func (m *MockAzureProvider) CreateApplication(arg0 context.Context, arg1 string) (api.ApplicationResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateApplication", arg0, arg1) + ret0, _ := ret[0].(api.ApplicationResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateApplication indicates an expected call of CreateApplication. +func (mr *MockAzureProviderMockRecorder) CreateApplication(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateApplication", reflect.TypeOf((*MockAzureProvider)(nil).CreateApplication), arg0, arg1) +} + +// CreateRoleAssignment mocks base method. +func (m *MockAzureProvider) CreateRoleAssignment(arg0 context.Context, arg1, arg2 string, arg3 authorization.RoleAssignmentCreateParameters) (authorization.RoleAssignment, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateRoleAssignment", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(authorization.RoleAssignment) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateRoleAssignment indicates an expected call of CreateRoleAssignment. +func (mr *MockAzureProviderMockRecorder) CreateRoleAssignment(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateRoleAssignment", reflect.TypeOf((*MockAzureProvider)(nil).CreateRoleAssignment), arg0, arg1, arg2, arg3) +} + +// CreateServicePrincipal mocks base method. +func (m *MockAzureProvider) CreateServicePrincipal(arg0 context.Context, arg1 string, arg2, arg3 time.Time) (string, string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateServicePrincipal", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(string) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// CreateServicePrincipal indicates an expected call of CreateServicePrincipal. +func (mr *MockAzureProviderMockRecorder) CreateServicePrincipal(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateServicePrincipal", reflect.TypeOf((*MockAzureProvider)(nil).CreateServicePrincipal), arg0, arg1, arg2, arg3) +} + +// DeleteApplication mocks base method. +func (m *MockAzureProvider) DeleteApplication(arg0 context.Context, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteApplication", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteApplication indicates an expected call of DeleteApplication. +func (mr *MockAzureProviderMockRecorder) DeleteApplication(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteApplication", reflect.TypeOf((*MockAzureProvider)(nil).DeleteApplication), arg0, arg1) +} + +// DeleteRoleAssignmentByID mocks base method. +func (m *MockAzureProvider) DeleteRoleAssignmentByID(arg0 context.Context, arg1 string) (authorization.RoleAssignment, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteRoleAssignmentByID", arg0, arg1) + ret0, _ := ret[0].(authorization.RoleAssignment) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DeleteRoleAssignmentByID indicates an expected call of DeleteRoleAssignmentByID. +func (mr *MockAzureProviderMockRecorder) DeleteRoleAssignmentByID(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteRoleAssignmentByID", reflect.TypeOf((*MockAzureProvider)(nil).DeleteRoleAssignmentByID), arg0, arg1) +} + +// GetApplication mocks base method. +func (m *MockAzureProvider) GetApplication(arg0 context.Context, arg1 string) (api.ApplicationResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetApplication", arg0, arg1) + ret0, _ := ret[0].(api.ApplicationResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetApplication indicates an expected call of GetApplication. +func (mr *MockAzureProviderMockRecorder) GetApplication(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetApplication", reflect.TypeOf((*MockAzureProvider)(nil).GetApplication), arg0, arg1) +} + +// GetGroup mocks base method. +func (m *MockAzureProvider) GetGroup(arg0 context.Context, arg1 string) (api.Group, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetGroup", arg0, arg1) + ret0, _ := ret[0].(api.Group) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetGroup indicates an expected call of GetGroup. +func (mr *MockAzureProviderMockRecorder) GetGroup(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetGroup", reflect.TypeOf((*MockAzureProvider)(nil).GetGroup), arg0, arg1) +} + +// GetRoleDefinitionByID mocks base method. +func (m *MockAzureProvider) GetRoleDefinitionByID(arg0 context.Context, arg1 string) (authorization.RoleDefinition, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRoleDefinitionByID", arg0, arg1) + ret0, _ := ret[0].(authorization.RoleDefinition) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetRoleDefinitionByID indicates an expected call of GetRoleDefinitionByID. +func (mr *MockAzureProviderMockRecorder) GetRoleDefinitionByID(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRoleDefinitionByID", reflect.TypeOf((*MockAzureProvider)(nil).GetRoleDefinitionByID), arg0, arg1) +} + +// ListApplications mocks base method. +func (m *MockAzureProvider) ListApplications(arg0 context.Context, arg1 string) ([]api.ApplicationResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListApplications", arg0, arg1) + ret0, _ := ret[0].([]api.ApplicationResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListApplications indicates an expected call of ListApplications. +func (mr *MockAzureProviderMockRecorder) ListApplications(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListApplications", reflect.TypeOf((*MockAzureProvider)(nil).ListApplications), arg0, arg1) +} + +// ListGroups mocks base method. +func (m *MockAzureProvider) ListGroups(arg0 context.Context, arg1 string) ([]api.Group, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListGroups", arg0, arg1) + ret0, _ := ret[0].([]api.Group) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListGroups indicates an expected call of ListGroups. +func (mr *MockAzureProviderMockRecorder) ListGroups(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListGroups", reflect.TypeOf((*MockAzureProvider)(nil).ListGroups), arg0, arg1) +} + +// ListRoleDefinitions mocks base method. +func (m *MockAzureProvider) ListRoleDefinitions(arg0 context.Context, arg1, arg2 string) ([]authorization.RoleDefinition, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListRoleDefinitions", arg0, arg1, arg2) + ret0, _ := ret[0].([]authorization.RoleDefinition) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListRoleDefinitions indicates an expected call of ListRoleDefinitions. +func (mr *MockAzureProviderMockRecorder) ListRoleDefinitions(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListRoleDefinitions", reflect.TypeOf((*MockAzureProvider)(nil).ListRoleDefinitions), arg0, arg1, arg2) +} + +// RemoveApplicationPassword mocks base method. +func (m *MockAzureProvider) RemoveApplicationPassword(arg0 context.Context, arg1, arg2 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RemoveApplicationPassword", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// RemoveApplicationPassword indicates an expected call of RemoveApplicationPassword. +func (mr *MockAzureProviderMockRecorder) RemoveApplicationPassword(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveApplicationPassword", reflect.TypeOf((*MockAzureProvider)(nil).RemoveApplicationPassword), arg0, arg1, arg2) +} + +// RemoveGroupMember mocks base method. +func (m *MockAzureProvider) RemoveGroupMember(arg0 context.Context, arg1, arg2 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RemoveGroupMember", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// RemoveGroupMember indicates an expected call of RemoveGroupMember. +func (mr *MockAzureProviderMockRecorder) RemoveGroupMember(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveGroupMember", reflect.TypeOf((*MockAzureProvider)(nil).RemoveGroupMember), arg0, arg1, arg2) +} diff --git a/path_config.go b/path_config.go index e405aa0b..de6bd7dc 100644 --- a/path_config.go +++ b/path_config.go @@ -3,6 +3,7 @@ package azuresecrets import ( "context" "errors" + "time" "github.com/Azure/go-autorest/autorest/azure" multierror "github.com/hashicorp/go-multierror" @@ -12,19 +13,30 @@ import ( const ( configStoragePath = "config" + // The default password expiration duration is 6 months in + // the Azure UI, so we're setting it to 6 months (in hours) + // as the default. + defaultRootPasswordTTL = 4380 ) // azureConfig contains values to configure Azure clients and // defaults for roles. The zero value is useful and results in // environments variable and system defaults being used. type azureConfig struct { - SubscriptionID string `json:"subscription_id"` - TenantID string `json:"tenant_id"` - ClientID string `json:"client_id"` - ClientSecret string `json:"client_secret"` - Environment string `json:"environment"` - PasswordPolicy string `json:"password_policy"` - UseMsGraphAPI bool `json:"use_microsoft_graph_api"` + SubscriptionID string `json:"subscription_id"` + TenantID string `json:"tenant_id"` + ClientID string `json:"client_id"` + ClientSecret string `json:"client_secret"` + ClientSecretKeyID string `json:"client_secret_key_id"` + NewClientSecret string `json:"new_client_secret"` + NewClientSecretCreated time.Time `json:"new_client_secret_created"` + NewClientSecretExpirationDate time.Time `json:"new_client_secret_expiration_date"` + NewClientSecretKeyID string `json:"new_client_secret_key_id"` + Environment string `json:"environment"` + PasswordPolicy string `json:"password_policy"` + UseMsGraphAPI bool `json:"use_microsoft_graph_api"` + RootPasswordTTL time.Duration `json:"root_password_ttl"` + RootPasswordExpirationDate time.Time `json:"root_password_expiration_date"` } func pathConfig(b *azureSecretBackend) *framework.Path { @@ -64,12 +76,26 @@ func pathConfig(b *azureSecretBackend) *framework.Path { Type: framework.TypeBool, Description: "Enable usage of the Microsoft Graph API over the deprecated Azure AD Graph API.", }, + "root_password_ttl": &framework.FieldSchema{ + Type: framework.TypeDurationSecond, + Default: defaultRootPasswordTTL, + Description: "The TTL of the root password in Azure. This can be either a number of seconds or a time formatted duration (ex: 24h, 48ds)", + Required: false, + }, }, - Callbacks: map[logical.Operation]framework.OperationFunc{ - logical.ReadOperation: b.pathConfigRead, - logical.CreateOperation: b.pathConfigWrite, - logical.UpdateOperation: b.pathConfigWrite, - logical.DeleteOperation: b.pathConfigDelete, + Operations: map[logical.Operation]framework.OperationHandler{ + logical.ReadOperation: &framework.PathOperation{ + Callback: b.pathConfigRead, + }, + logical.CreateOperation: &framework.PathOperation{ + Callback: b.pathConfigWrite, + }, + logical.UpdateOperation: &framework.PathOperation{ + Callback: b.pathConfigWrite, + }, + logical.DeleteOperation: &framework.PathOperation{ + Callback: b.pathConfigDelete, + }, }, ExistenceCheck: b.pathConfigExistenceCheck, HelpSynopsis: confHelpSyn, @@ -123,13 +149,39 @@ func (b *azureSecretBackend) pathConfigWrite(ctx context.Context, req *logical.R config.PasswordPolicy = data.Get("password_policy").(string) + config.RootPasswordTTL = defaultRootPasswordTTL * time.Hour + rootExpirationRaw, ok := data.GetOk("root_password_ttl") + if ok { + config.RootPasswordTTL = time.Second * time.Duration(rootExpirationRaw.(int)) + } + if merr.ErrorOrNil() != nil { return logical.ErrorResponse(merr.Error()), nil } err = b.saveConfig(ctx, config, req.Storage) + if err != nil { + return nil, err + } - return nil, err + resp := addAADWarning(nil, config) + + return resp, nil +} + +const aadWarning = "This configuration is using the Azure Active Directory API which is being " + + "removed soon. Please migrate to using the Microsoft Graph API using the " + + "use_microsoft_graph_api configuration parameter." + +func addAADWarning(resp *logical.Response, config *azureConfig) *logical.Response { + if config.UseMsGraphAPI { + return resp + } + if resp == nil { + resp = &logical.Response{} + } + resp.AddWarning(aadWarning) + return resp } func (b *azureSecretBackend) pathConfigRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { @@ -150,9 +202,15 @@ func (b *azureSecretBackend) pathConfigRead(ctx context.Context, req *logical.Re "environment": config.Environment, "client_id": config.ClientID, "use_microsoft_graph_api": config.UseMsGraphAPI, + "root_password_ttl": int(config.RootPasswordTTL.Seconds()), }, } - return resp, nil + + if !config.RootPasswordExpirationDate.IsZero() { + resp.Data["root_password_expiration_date"] = config.RootPasswordExpirationDate + } + + return addAADWarning(resp, config), nil } func (b *azureSecretBackend) pathConfigDelete(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { diff --git a/path_config_test.go b/path_config_test.go index a43c471a..f320159d 100644 --- a/path_config_test.go +++ b/path_config_test.go @@ -2,7 +2,9 @@ package azuresecrets import ( "context" + "reflect" "testing" + "time" "github.com/hashicorp/vault/sdk/logical" ) @@ -11,37 +13,38 @@ func TestConfig(t *testing.T) { b, s := getTestBackend(t, false) // Test valid config - config := map[string]interface{}{ + expectedConfig := map[string]interface{}{ "subscription_id": "a228ceec-bf1a-4411-9f95-39678d8cdb34", "tenant_id": "7ac36e27-80fc-4209-a453-e8ad83dc18c2", "client_id": "testClientId", "client_secret": "testClientSecret", "environment": "AZURECHINACLOUD", "use_microsoft_graph_api": false, + "root_password_ttl": int((24 * time.Hour).Seconds()), } - testConfigCreate(t, b, s, config) + testConfigCreate(t, b, s, expectedConfig) - delete(config, "client_secret") - testConfigRead(t, b, s, config) + delete(expectedConfig, "client_secret") + testConfigRead(t, b, s, expectedConfig) // Test test updating one element retains the others - config["tenant_id"] = "800e371d-ee51-4145-9ac8-5c43e4ceb79b" + expectedConfig["tenant_id"] = "800e371d-ee51-4145-9ac8-5c43e4ceb79b" configSubset := map[string]interface{}{ "tenant_id": "800e371d-ee51-4145-9ac8-5c43e4ceb79b", } testConfigCreate(t, b, s, configSubset) - testConfigUpdate(t, b, s, config) + testConfigUpdate(t, b, s, expectedConfig) // Test bad environment - config = map[string]interface{}{ + expectedConfig = map[string]interface{}{ "environment": "invalidEnv", } resp, _ := b.HandleRequest(context.Background(), &logical.Request{ Operation: logical.UpdateOperation, Path: "config", - Data: config, + Data: expectedConfig, Storage: s, }) @@ -61,6 +64,7 @@ func TestConfigDelete(t *testing.T) { "client_secret": "testClientSecret", "environment": "AZURECHINACLOUD", "use_microsoft_graph_api": false, + "root_password_ttl": int((24 * time.Hour).Seconds()), } testConfigCreate(t, b, s, config) @@ -86,10 +90,73 @@ func TestConfigDelete(t *testing.T) { "client_id": "", "environment": "", "use_microsoft_graph_api": false, + "root_password_ttl": 0, } testConfigRead(t, b, s, config) } +func TestAddAADWarning(t *testing.T) { + type testCase struct { + useMSGraphAPI bool + resp *logical.Response + expectedResp *logical.Response + } + + tests := map[string]testCase{ + "nil resp, using AAD": { + useMSGraphAPI: false, + resp: nil, + expectedResp: &logical.Response{ + Warnings: []string{aadWarning}, + }, + }, + "nil resp, using ms-graph": { + useMSGraphAPI: true, + resp: nil, + expectedResp: nil, + }, + "non-nil resp, using AAD": { + useMSGraphAPI: false, + resp: &logical.Response{ + Data: map[string]interface{}{ + "foo": "bar", + }, + }, + expectedResp: &logical.Response{ + Data: map[string]interface{}{ + "foo": "bar", + }, + Warnings: []string{aadWarning}, + }, + }, + "non-nil resp, using ms-graph": { + useMSGraphAPI: true, + resp: &logical.Response{ + Data: map[string]interface{}{ + "foo": "bar", + }, + }, + expectedResp: &logical.Response{ + Data: map[string]interface{}{ + "foo": "bar", + }, + }, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + cfg := &azureConfig{ + UseMsGraphAPI: test.useMSGraphAPI, + } + actualResp := addAADWarning(test.resp, cfg) + if !reflect.DeepEqual(actualResp, test.expectedResp) { + t.Fatalf("Actual: %#v\nExpected: %#v", actualResp, test.expectedResp) + } + }) + } +} + func testConfigCreate(t *testing.T, b logical.Backend, s logical.Storage, d map[string]interface{}) { t.Helper() testConfigCreateUpdate(t, b, logical.CreateOperation, s, d) diff --git a/path_roles.go b/path_roles.go index 1bc18116..cffc37da 100644 --- a/path_roles.go +++ b/path_roles.go @@ -124,6 +124,11 @@ func pathsRole(b *azureSecretBackend) []*framework.Path { func (b *azureSecretBackend) pathRoleUpdate(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { var resp *logical.Response + config, err := b.getConfig(ctx, req.Storage) + if err != nil { + return nil, err + } + client, err := b.getClient(ctx, req.Storage) if err != nil { return nil, err @@ -278,14 +283,17 @@ func (b *azureSecretBackend) pathRoleUpdate(ctx context.Context, req *logical.Re return nil, fmt.Errorf("error storing role: %w", err) } - return resp, nil + return addAADWarning(resp, config), nil } func (b *azureSecretBackend) pathRoleRead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { - var data = make(map[string]interface{}) - name := d.Get("name").(string) + config, err := b.getConfig(ctx, req.Storage) + if err != nil { + return nil, err + } + r, err := getRole(ctx, name, req.Storage) if err != nil { return nil, fmt.Errorf("error reading role: %w", err) @@ -295,15 +303,16 @@ func (b *azureSecretBackend) pathRoleRead(ctx context.Context, req *logical.Requ return nil, nil } - data["ttl"] = r.TTL / time.Second - data["max_ttl"] = r.MaxTTL / time.Second - data["azure_roles"] = r.AzureRoles - data["azure_groups"] = r.AzureGroups - data["application_object_id"] = r.ApplicationObjectID - - return &logical.Response{ - Data: data, - }, nil + resp := &logical.Response{ + Data: map[string]interface{}{ + "ttl": r.TTL / time.Second, + "max_ttl": r.MaxTTL / time.Second, + "azure_roles": r.AzureRoles, + "azure_groups": r.AzureGroups, + "application_object_id": r.ApplicationObjectID, + }, + } + return addAADWarning(resp, config), nil } func (b *azureSecretBackend) pathRoleList(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { diff --git a/path_rotate_root.go b/path_rotate_root.go new file mode 100644 index 00000000..c00d4a4e --- /dev/null +++ b/path_rotate_root.go @@ -0,0 +1,138 @@ +package azuresecrets + +import ( + "context" + "fmt" + "time" + + "github.com/hashicorp/go-multierror" + "github.com/hashicorp/go-uuid" + "github.com/hashicorp/vault/sdk/framework" + "github.com/hashicorp/vault/sdk/logical" +) + +func pathRotateRoot(b *azureSecretBackend) *framework.Path { + return &framework.Path{ + Pattern: "rotate-root", + Operations: map[logical.Operation]framework.OperationHandler{ + logical.UpdateOperation: &framework.PathOperation{ + Callback: b.pathRotateRoot, + ForwardPerformanceSecondary: true, + ForwardPerformanceStandby: true, + }, + }, + + HelpSynopsis: "Attempt to rotate the root credentials used to communicate with Azure.", + HelpDescription: "This path will attempt to generate new root credentials for the user used to access and manipulate Azure.\n" + + "The new credentials will not be returned from this endpoint, nor the read config endpoint.", + } +} + +func (b *azureSecretBackend) pathRotateRoot(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + config, err := b.getConfig(ctx, req.Storage) + if err != nil { + return nil, err + } + + expDur := config.RootPasswordTTL + if expDur == 0 { + expDur = defaultRootPasswordTTL + } + expiration := time.Now().Add(expDur) + + client, err := b.getClient(ctx, req.Storage) + if err != nil { + return nil, err + } + + // We need to use List instead of Get here because we don't have the Object ID + // (which is different from the Application/Client ID) + apps, err := client.provider.ListApplications(ctx, fmt.Sprintf("appId eq '%s'", config.ClientID)) + if err != nil { + return nil, err + } + + if len(apps) == 0 { + return nil, fmt.Errorf("no application found") + } + if len(apps) > 1 { + return nil, fmt.Errorf("multiple applications found - double check your client_id") + } + + app := apps[0] + + uniqueID, err := uuid.GenerateUUID() + if err != nil { + return nil, fmt.Errorf("failed to generate UUID: %w", err) + } + + // This could have the same username customization logic put on it if we really wanted it here + passwordDisplayName := fmt.Sprintf("vault-%s", uniqueID) + newPasswordResp, err := client.provider.AddApplicationPassword(ctx, *app.ID, passwordDisplayName, expiration) + if err != nil { + return nil, fmt.Errorf("failed to add new password: %w", err) + } + + var wal walRotateRoot + walID, walErr := framework.PutWAL(ctx, req.Storage, walRotateRootCreds, wal) + if walErr != nil { + err = client.provider.RemoveApplicationPassword(ctx, *app.ID, *newPasswordResp.PasswordCredential.KeyID) + merr := multierror.Append(err, err) + return &logical.Response{}, merr + } + + config.NewClientSecret = *newPasswordResp.SecretText + config.NewClientSecretCreated = time.Now() + config.NewClientSecretExpirationDate = newPasswordResp.EndDate.Time + config.NewClientSecretKeyID = *newPasswordResp.KeyID + + err = b.saveConfig(ctx, config, req.Storage) + if err != nil { + return nil, fmt.Errorf("failed to save new configuration: %w", err) + } + + b.updatePassword = true + + err = framework.DeleteWAL(ctx, req.Storage, walID) + if err != nil { + b.Logger().Error("rotate root", "delete wal", err) + } + + return addAADWarning(&logical.Response{}, config), nil +} + +type passwordRemover interface { + RemoveApplicationPassword(ctx context.Context, applicationObjectID string, keyID string) error +} + +func removeApplicationPasswords(ctx context.Context, passRemover passwordRemover, appID string, passwordKeyIDs ...string) (err error) { + merr := new(multierror.Error) + for _, keyID := range passwordKeyIDs { + // Attempt to remove all of them, don't fail early + err := passRemover.RemoveApplicationPassword(ctx, appID, keyID) + if err != nil { + merr = multierror.Append(merr, err) + } + } + + return merr.ErrorOrNil() +} + +func intersectStrings(a []string, b []string) []string { + if len(a) == 0 || len(b) == 0 { + return []string{} + } + + aMap := map[string]struct{}{} + for _, aStr := range a { + aMap[aStr] = struct{}{} + } + + result := []string{} + for _, bStr := range b { + if _, exists := aMap[bStr]; exists { + result = append(result, bStr) + } + } + return result +} diff --git a/path_rotate_root_test.go b/path_rotate_root_test.go new file mode 100644 index 00000000..d0f03d6f --- /dev/null +++ b/path_rotate_root_test.go @@ -0,0 +1,253 @@ +package azuresecrets + +import ( + "context" + "fmt" + "reflect" + "testing" + "time" + + "github.com/hashicorp/vault/sdk/logical" +) + +func TestRotateRootSuccess(t *testing.T) { + b, s := getTestBackend(t, true) + + resp, err := b.HandleRequest(context.Background(), &logical.Request{ + Operation: logical.UpdateOperation, + Path: "rotate-root", + Data: map[string]interface{}{}, + Storage: s, + }) + + if err != nil { + t.Fatal(err) + } + + if resp != nil && resp.IsError() { + t.Fatal(resp.Error()) + } + + config, err := b.getConfig(context.Background(), s) + if err != nil { + t.Fatal(err) + } + + if config.ClientSecret == "" { + t.Fatal(fmt.Errorf("root password was empty after rotate root, it shouldn't be")) + } + + if config.NewClientSecret == config.ClientSecret { + t.Fatal("old and new password equal after rotate-root, it shouldn't be") + } + + if config.NewClientSecret == "" { + t.Fatal("new password is empty, it shouldn't be") + } + + if config.NewClientSecretKeyID == "" { + t.Fatal("new password key id is empty, it shouldn't be") + } + + if !b.updatePassword { + t.Fatal("update password is false, it shouldn't be") + } + + config.NewClientSecretCreated = config.NewClientSecretCreated.Add(-(time.Minute * 1)) + err = b.saveConfig(context.Background(), config, s) + if err != nil { + t.Fatal(err) + } + + err = b.periodicFunc(context.Background(), &logical.Request{ + Storage: s, + }) + + if err != nil { + t.Fatal(err) + } + + newConfig, err := b.getConfig(context.Background(), s) + if err != nil { + t.Fatal(err) + } + + if newConfig.ClientSecret != config.NewClientSecret { + t.Fatal(fmt.Errorf("old and new password aren't equal after periodic function, they should be")) + } +} + +func TestRotateRootPeroidicFunctionBeforeMinute(t *testing.T) { + b, s := getTestBackend(t, true) + + resp, err := b.HandleRequest(context.Background(), &logical.Request{ + Operation: logical.UpdateOperation, + Path: "rotate-root", + Data: map[string]interface{}{}, + Storage: s, + }) + + if err != nil { + t.Fatal(err) + } + + if resp != nil && resp.IsError() { + t.Fatal(resp.Error()) + } + + tests := []struct { + Name string + Created time.Duration + }{ + { + Name: "1 second test:", + Created: time.Second * 1, + }, + { + Name: "5 seconds test:", + Created: time.Second * 5, + }, + { + Name: "30 seconds test:", + Created: time.Second * 30, + }, + { + Name: "50 seconds test:", + Created: time.Second * 50, + }, + } + + for _, test := range tests { + t.Log(test.Name) + config, err := b.getConfig(context.Background(), s) + if err != nil { + t.Fatal(err) + } + + config.NewClientSecretCreated = time.Now().Add(-(test.Created)) + err = b.saveConfig(context.Background(), config, s) + if err != nil { + t.Fatal(test.Name, err) + } + + err = b.periodicFunc(context.Background(), &logical.Request{ + Storage: s, + }) + + if err != nil { + t.Fatal(test.Name, err) + } + + newConfig, err := b.getConfig(context.Background(), s) + if err != nil { + t.Fatal(test.Name, err) + } + + if newConfig.ClientSecret == config.NewClientSecret { + t.Fatal(test.Name, fmt.Errorf("old and new password are equal after periodic function, they shouldn't be")) + } + } +} + +func TestIntersectStrings(t *testing.T) { + type testCase struct { + a []string + b []string + expect []string + } + + tests := map[string]testCase{ + "nil slices": { + a: nil, + b: nil, + expect: []string{}, + }, + "a is nil": { + a: nil, + b: []string{"foo"}, + expect: []string{}, + }, + "b is nil": { + a: []string{"foo"}, + b: nil, + expect: []string{}, + }, + "a is empty": { + a: []string{}, + b: []string{"foo"}, + expect: []string{}, + }, + "b is empty": { + a: []string{"foo"}, + b: []string{}, + expect: []string{}, + }, + "a equals b": { + a: []string{"foo"}, + b: []string{"foo"}, + expect: []string{"foo"}, + }, + "a equals b (many)": { + a: []string{"foo", "bar", "baz", "qux", "quux", "quuz"}, + b: []string{"foo", "bar", "baz", "qux", "quux", "quuz"}, + expect: []string{"foo", "bar", "baz", "qux", "quux", "quuz"}, + }, + "a equals b but out of order": { + a: []string{"foo", "bar", "baz", "qux", "quux", "quuz"}, + b: []string{"quuz", "bar", "qux", "foo", "quux", "baz"}, + expect: []string{"quuz", "bar", "qux", "foo", "quux", "baz"}, + }, + "a is superset": { + a: []string{"foo", "bar", "baz"}, + b: []string{"foo"}, + expect: []string{"foo"}, + }, + "a is superset out of order": { + a: []string{"bar", "foo", "baz"}, + b: []string{"foo"}, + expect: []string{"foo"}, + }, + "b is superset": { + a: []string{"foo"}, + b: []string{"foo", "bar", "baz"}, + expect: []string{"foo"}, + }, + "b is superset out of order": { + a: []string{"foo"}, + b: []string{"bar", "foo", "baz"}, + expect: []string{"foo"}, + }, + "a not equal to b": { + a: []string{"foo", "bar", "baz"}, + b: []string{"qux", "quux", "quuz"}, + expect: []string{}, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + actual := intersectStrings(test.a, test.b) + if !reflect.DeepEqual(actual, test.expect) { + t.Fatalf("Actual: %#v\nExpected: %#v\n", actual, test.expect) + } + }) + } +} + +func assertNotNil(t *testing.T, val interface{}) { + t.Helper() + if val == nil { + t.Fatalf("expected not nil, but was nil") + } +} + +func assertStrSliceIsEmpty(t *testing.T, strs []string) { + t.Helper() + if strs != nil && len(strs) > 0 { + t.Fatalf("string slice is not empty") + } +} + +func strPtr(str string) *string { + return &str +} diff --git a/provider.go b/provider.go index a5533d3d..b573a1eb 100644 --- a/provider.go +++ b/provider.go @@ -9,7 +9,6 @@ import ( "github.com/Azure/azure-sdk-for-go/services/graphrbac/1.6/graphrbac" "github.com/Azure/go-autorest/autorest" "github.com/Azure/go-autorest/autorest/azure/auth" - "github.com/Azure/go-autorest/autorest/date" "github.com/hashicorp/vault-plugin-secrets-azure/api" "github.com/hashicorp/vault/sdk/helper/useragent" "github.com/hashicorp/vault/sdk/version" @@ -33,11 +32,6 @@ type provider struct { // newAzureProvider creates an azureProvider, backed by Azure client objects for underlying services. func newAzureProvider(settings *clientSettings, useMsGraphApi bool, passwords api.Passwords) (api.AzureProvider, error) { // build clients that use the GraphRBAC endpoint - graphAuthorizer, err := getAuthorizer(settings, settings.Environment.GraphEndpoint) - if err != nil { - return nil, err - } - userAgent := getUserAgent(settings) var appClient api.ApplicationsClient @@ -58,6 +52,11 @@ func newAzureProvider(settings *clientSettings, useMsGraphApi bool, passwords ap groupsClient = msGraphAppClient spClient = msGraphAppClient } else { + graphAuthorizer, err := getAuthorizer(settings, settings.Environment.GraphEndpoint) + if err != nil { + return nil, err + } + aadGraphClient := graphrbac.NewApplicationsClient(settings.TenantID) aadGraphClient.Authorizer = graphAuthorizer aadGraphClient.AddToUserAgent(userAgent) @@ -164,13 +163,17 @@ func (p *provider) GetApplication(ctx context.Context, applicationObjectID strin return p.appClient.GetApplication(ctx, applicationObjectID) } +func (p *provider) ListApplications(ctx context.Context, filter string) ([]api.ApplicationResult, error) { + return p.appClient.ListApplications(ctx, filter) +} + // DeleteApplication deletes an Azure application object. // This will in turn remove the service principal (but not the role assignments). func (p *provider) DeleteApplication(ctx context.Context, applicationObjectID string) error { return p.appClient.DeleteApplication(ctx, applicationObjectID) } -func (p *provider) AddApplicationPassword(ctx context.Context, applicationObjectID string, displayName string, endDateTime date.Time) (result api.PasswordCredentialResult, err error) { +func (p *provider) AddApplicationPassword(ctx context.Context, applicationObjectID string, displayName string, endDateTime time.Time) (result api.PasswordCredentialResult, err error) { return p.appClient.AddApplicationPassword(ctx, applicationObjectID, displayName, endDateTime) } diff --git a/provider_mock_test.go b/provider_mock_test.go index 4b3ba1d4..7430f207 100644 --- a/provider_mock_test.go +++ b/provider_mock_test.go @@ -108,8 +108,20 @@ func (m *mockProvider) CreateApplication(_ context.Context, _ string) (api.Appli } func (m *mockProvider) GetApplication(_ context.Context, _ string) (api.ApplicationResult, error) { + appObjID := generateUUID() return api.ApplicationResult{ AppID: to.StringPtr("00000000-0000-0000-0000-000000000000"), + ID: &appObjID, + }, nil +} + +func (m *mockProvider) ListApplications(_ context.Context, _ string) ([]api.ApplicationResult, error) { + appObjID := generateUUID() + return []api.ApplicationResult{ + { + AppID: to.StringPtr("00000000-0000-0000-0000-000000000000"), + ID: &appObjID, + }, }, nil } @@ -118,12 +130,12 @@ func (m *mockProvider) DeleteApplication(_ context.Context, applicationObjectID return nil } -func (m *mockProvider) AddApplicationPassword(_ context.Context, _ string, displayName string, endDateTime date.Time) (api.PasswordCredentialResult, error) { +func (m *mockProvider) AddApplicationPassword(_ context.Context, _ string, displayName string, endDateTime time.Time) (result api.PasswordCredentialResult, err error) { keyID := generateUUID() cred := api.PasswordCredential{ DisplayName: to.StringPtr(displayName), StartDate: &date.Time{Time: time.Now()}, - EndDate: &endDateTime, + EndDate: &date.Time{endDateTime}, KeyID: to.StringPtr(keyID), SecretText: to.StringPtr(generateUUID()), } @@ -137,7 +149,7 @@ func (m *mockProvider) AddApplicationPassword(_ context.Context, _ string, displ }, nil } -func (m *mockProvider) RemoveApplicationPassword(_ context.Context, _ string, keyID string) error { +func (m *mockProvider) RemoveApplicationPassword(_ context.Context, _ string, keyID string) (err error) { m.lock.Lock() defer m.lock.Unlock() diff --git a/wal.go b/wal.go index f3dc4ede..6cdeeff3 100644 --- a/wal.go +++ b/wal.go @@ -9,21 +9,32 @@ import ( "github.com/mitchellh/mapstructure" ) -const walAppKey = "appCreate" +const ( + walAppKey = "appCreate" + walRotateRootCreds = "rotateRootCreds" +) // Eventually expire the WAL if for some reason the rollback operation consistently fails var maxWALAge = 24 * time.Hour +func (b *azureSecretBackend) walRollback(ctx context.Context, req *logical.Request, kind string, data interface{}) error { + switch kind { + case walAppKey: + return b.rollbackAppWAL(ctx, req, data) + case walRotateRootCreds: + return b.rollbackRootWAL(ctx, req, data) + default: + return fmt.Errorf("unknown rollback type %q", kind) + } +} + type walApp struct { AppID string AppObjID string Expiration time.Time } -func (b *azureSecretBackend) walRollback(ctx context.Context, req *logical.Request, kind string, data interface{}) error { - if kind != walAppKey { - return fmt.Errorf("unknown rollback type %q", kind) - } +func (b *azureSecretBackend) rollbackAppWAL(ctx context.Context, req *logical.Request, data interface{}) error { // Decode the WAL data var entry walApp d, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ @@ -60,3 +71,27 @@ func (b *azureSecretBackend) walRollback(ctx context.Context, req *logical.Reque return nil } + +type walRotateRoot struct{} + +func (b *azureSecretBackend) rollbackRootWAL(ctx context.Context, req *logical.Request, data interface{}) error { + b.Logger().Debug("rolling back config") + config, err := b.getConfig(ctx, req.Storage) + if err != nil { + return err + } + + config.NewClientSecret = "" + config.NewClientSecretCreated = time.Time{} + config.NewClientSecretExpirationDate = time.Time{} + config.NewClientSecretKeyID = "" + + err = b.saveConfig(ctx, config, req.Storage) + if err != nil { + return err + } + + b.updatePassword = false + + return nil +}