From fd2c0f033e283db45425b2d1dc8fe2b5f10f1b13 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Wed, 7 Oct 2015 15:30:54 -0400 Subject: [PATCH] Add the ability for warnings to be added to responses. These are marshalled into JSON or displayed from the CLI depending on the output mode. This allows conferring information such as "no such policy exists" when creating a token -- not an error, but something the user should be aware of. Fixes #676 --- api/secret.go | 5 +++++ command/format.go | 9 +++++++++ http/logical.go | 6 +++++- logical/response.go | 22 ++++++++++++++++++++++ vault/token_store.go | 23 +++++++++++++++++++++++ 5 files changed, 64 insertions(+), 1 deletion(-) diff --git a/api/secret.go b/api/secret.go index 49d0112c34..bc52f78b72 100644 --- a/api/secret.go +++ b/api/secret.go @@ -15,6 +15,11 @@ type Secret struct { // is arbitrary and up to the secret backend. Data map[string]interface{} `json:"data"` + // Warnings contains any warnings related to the operation. These + // are not issues that caused the command to fail, but that the + // client should be aware of. + Warnings []string `json:"warnings"` + // Auth, if non-nil, means that there was authentication information // attached to this response. Auth *SecretAuth `json:"auth,omitempty"` diff --git a/command/format.go b/command/format.go index d0bd7a142a..3df863a716 100644 --- a/command/format.go +++ b/command/format.go @@ -43,6 +43,7 @@ func outputFormatTable(ui cli.Ui, s *api.Secret, whitespace bool) int { config.Prefix = "" input := make([]string, 0, 5) + input = append(input, fmt.Sprintf("Key %s Value", config.Delim)) if s.LeaseDuration > 0 { @@ -71,6 +72,14 @@ func outputFormatTable(ui cli.Ui, s *api.Secret, whitespace bool) int { input = append(input, fmt.Sprintf("%s %s %v", k, config.Delim, v)) } + if len(s.Warnings) != 0 { + input = append(input, "") + input = append(input, "The following warnings were generated:") + for _, warning := range s.Warnings { + input = append(input, fmt.Sprintf("* %s", warning)) + } + } + ui.Output(columnize.Format(input, config)) return 0 } diff --git a/http/logical.go b/http/logical.go index f5fa2326c4..ce03b384f4 100644 --- a/http/logical.go +++ b/http/logical.go @@ -96,7 +96,10 @@ func respondLogical(w http.ResponseWriter, r *http.Request, path string, dataOnl return } - logicalResp := &LogicalResponse{Data: resp.Data} + logicalResp := &LogicalResponse{ + Data: resp.Data, + Warnings: resp.GetWarnings(), + } if resp.Secret != nil { logicalResp.LeaseID = resp.Secret.LeaseID logicalResp.Renewable = resp.Secret.Renewable @@ -196,6 +199,7 @@ type LogicalResponse struct { Renewable bool `json:"renewable"` LeaseDuration int `json:"lease_duration"` Data map[string]interface{} `json:"data"` + Warnings []string `json:"warnings"` Auth *Auth `json:"auth"` } diff --git a/logical/response.go b/logical/response.go index 8af0b3de51..79619aa7d4 100644 --- a/logical/response.go +++ b/logical/response.go @@ -40,6 +40,28 @@ type Response struct { // This is only valid for credential backends. This will be blanked // for any logical backend and ignored. Redirect string + + // Warnings allow operations or backends to return warnings in response + // to user actions without failing the action outright. + // Making it private helps ensure that it is easy for various parts of + // Vault (backend, core, etc.) to add warnings without accidentally + // replacing what exists. + warnings []string +} + +func (r *Response) AddWarning(warning string) { + if r.warnings == nil { + r.warnings = make([]string, 0, 1) + } + r.warnings = append(r.warnings, warning) +} + +func (r *Response) GetWarnings() []string { + return r.warnings +} + +func (r *Response) ClearWarnings() { + r.warnings = make([]string, 0, 1) } // IsError returns true if this response seems to indicate an error. diff --git a/vault/token_store.go b/vault/token_store.go index cdc41a1423..f72f9d9d93 100644 --- a/vault/token_store.go +++ b/vault/token_store.go @@ -46,6 +46,8 @@ type TokenStore struct { expiration *ExpirationManager cubbyholeBackend *CubbyholeBackend + + policyLookupFunc func() ([]string, error) } // NewTokenStore is used to construct a token store that is @@ -59,6 +61,10 @@ func NewTokenStore(c *Core, config *logical.BackendConfig) (*TokenStore, error) view: view, } + if c.policy != nil { + t.policyLookupFunc = c.policy.ListPolicies + } + // Setup the salt salt, err := salt.NewSalt(view, &salt.Config{ HashFunc: salt.SHA1Hash, @@ -636,6 +642,23 @@ func (ts *TokenStore) handleCreate( }, } + if ts.policyLookupFunc != nil { + availPolicies, err := ts.policyLookupFunc() + if err == nil { + policies := map[string]bool{} + if availPolicies != nil && len(availPolicies) > 0 { + for _, p := range availPolicies { + policies[p] = true + } + } + for _, p := range te.Policies { + if !policies[p] { + resp.AddWarning(fmt.Sprintf("policy \"%s\" does not exist", p)) + } + } + } + } + return resp, nil }