From ff4dc0b853b9968d05341678f67fa5af86028488 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Mon, 2 May 2016 01:58:58 -0400 Subject: [PATCH] Add wrap support to API/CLI --- api/client.go | 19 ++++++++++++++++++- api/request.go | 5 +++++ api/secret.go | 11 +++++++++++ command/format.go | 5 +++++ meta/meta.go | 14 ++++++++++++++ vault/request_handling.go | 21 ++++++++++----------- 6 files changed, 63 insertions(+), 12 deletions(-) diff --git a/api/client.go b/api/client.go index 9746d2ac7c..8df555bced 100644 --- a/api/client.go +++ b/api/client.go @@ -26,6 +26,7 @@ const EnvVaultClientCert = "VAULT_CLIENT_CERT" const EnvVaultClientKey = "VAULT_CLIENT_KEY" const EnvVaultInsecure = "VAULT_SKIP_VERIFY" const EnvVaultTLSServerName = "VAULT_TLS_SERVER_NAME" +const EnvVaultWrapTTL = "VAULT_WRAP_TTL" var ( errRedirect = errors.New("redirect") @@ -39,6 +40,14 @@ type Config struct { // HttpClient. Address string + // WrapTTL, if specified, asks the Vault server to return the normal + // response wrapped in the cubbyhole of a token, with the TTL of the token + // being set to the lesser of this value or a value requested by the + // backend originating the response. Specified either as a number of + // seconds, or a string duration with a "s", "m", or "h" suffix for + // "seconds", "minutes", or "hours" respectively. + WrapTTL string + // HttpClient is the HTTP client to use, which will currently always have the // same values as http.DefaultClient. This is used to control redirect behavior. HttpClient *http.Client @@ -80,6 +89,7 @@ func (c *Config) ReadEnvironment() error { var envCAPath string var envClientCert string var envClientKey string + var envWrapTTL string var envInsecure bool var foundInsecure bool var envTLSServerName string @@ -103,6 +113,9 @@ func (c *Config) ReadEnvironment() error { if v := os.Getenv(EnvVaultClientKey); v != "" { envClientKey = v } + if v := os.Getenv(EnvVaultWrapTTL); v != "" { + envWrapTTL = v + } if v := os.Getenv(EnvVaultInsecure); v != "" { var err error envInsecure, err = strconv.ParseBool(v) @@ -141,6 +154,10 @@ func (c *Config) ReadEnvironment() error { c.Address = envAddress } + if envWrapTTL != "" { + c.WrapTTL = envWrapTTL + } + clientTLSConfig := c.HttpClient.Transport.(*http.Transport).TLSClientConfig if foundInsecure { clientTLSConfig.InsecureSkipVerify = envInsecure @@ -172,7 +189,6 @@ type Client struct { // automatically added to the client. Otherwise, you must manually call // `SetToken()`. func NewClient(c *Config) (*Client, error) { - u, err := url.Parse(c.Address) if err != nil { return nil, err @@ -235,6 +251,7 @@ func (c *Client) NewRequest(method, path string) *Request { Path: path, }, ClientToken: c.token, + WrapTTL: c.config.WrapTTL, Params: make(map[string][]string), } diff --git a/api/request.go b/api/request.go index f44deff832..8f22dd5725 100644 --- a/api/request.go +++ b/api/request.go @@ -15,6 +15,7 @@ type Request struct { URL *url.URL Params url.Values ClientToken string + WrapTTL string Obj interface{} Body io.Reader BodySize int64 @@ -62,5 +63,9 @@ func (r *Request) ToHTTP() (*http.Request, error) { req.Header.Set("X-Vault-Token", r.ClientToken) } + if len(r.WrapTTL) != 0 { + req.Header.Set("X-Vault-Wrap-TTL", r.WrapTTL) + } + return req, nil } diff --git a/api/secret.go b/api/secret.go index fd46bd9c71..40a186cb8f 100644 --- a/api/secret.go +++ b/api/secret.go @@ -23,6 +23,17 @@ type Secret struct { // Auth, if non-nil, means that there was authentication information // attached to this response. Auth *SecretAuth `json:"auth,omitempty"` + + // WrapInfo, if non-nil, means that the initial response was wrapped in the + // cubbyhole of the given token (which has a TTL of the given number of + // seconds) + WrapInfo *SecretWrapInfo `json:"wrap_info,omitempty"` +} + +// SecretWrapInfo contains wrapping information if we have it. +type SecretWrapInfo struct { + Token string `json:"token"` + TTL int `json:"ttl"` } // SecretAuth is the structure containing auth information if we have it. diff --git a/command/format.go b/command/format.go index 93b812312e..bdfa8cb82c 100644 --- a/command/format.go +++ b/command/format.go @@ -152,6 +152,11 @@ func (t TableFormatter) OutputSecret(ui cli.Ui, secret, s *api.Secret) error { } } + if s.WrapInfo != nil { + input = append(input, fmt.Sprintf("wrapping_token: %s %s", config.Delim, s.WrapInfo.Token)) + input = append(input, fmt.Sprintf("wrapping_token_ttl: %s %d", config.Delim, s.WrapInfo.TTL)) + } + keys := make([]string, 0, len(s.Data)) for k := range s.Data { keys = append(keys, k) diff --git a/meta/meta.go b/meta/meta.go index 036acc1632..44eb7a4cf9 100644 --- a/meta/meta.go +++ b/meta/meta.go @@ -46,6 +46,7 @@ type Meta struct { flagCAPath string flagClientCert string flagClientKey string + flagWrapTTL string flagInsecure bool // Queried if no token can be found @@ -103,6 +104,10 @@ func (m *Meta) Client() (*api.Client, error) { } } + if m.flagWrapTTL != "" { + config.WrapTTL = m.flagWrapTTL + } + // Build the client client, err := api.NewClient(config) if err != nil { @@ -155,6 +160,7 @@ func (m *Meta) FlagSet(n string, fs FlagSetFlags) *flag.FlagSet { f.StringVar(&m.flagCAPath, "ca-path", "", "") f.StringVar(&m.flagClientCert, "client-cert", "", "") f.StringVar(&m.flagClientKey, "client-key", "", "") + f.StringVar(&m.flagWrapTTL, "wrap-ttl", "", "") f.BoolVar(&m.flagInsecure, "insecure", false, "") f.BoolVar(&m.flagInsecure, "tls-skip-verify", false, "") } @@ -271,6 +277,14 @@ func GeneralOptionsUsage() string { -tls-skip-verify Do not verify TLS certificate. This is highly not recommended. Verification will also be skipped if VAULT_SKIP_VERIFY is set. + + -wrap-ttl Indiciates that the response should be wrapped in a + cubbyhole token with the requested TTL. The response + will live at "/response" in the cubbyhole of the + returned token with a key of "response" and can be + parsed as a normal API Secret. The backend can also + request wrapping; the lesser of the values is used. + May also be specified via VAULT_WRAP_TTL. ` return general } diff --git a/vault/request_handling.go b/vault/request_handling.go index 6974479029..76febfc347 100644 --- a/vault/request_handling.go +++ b/vault/request_handling.go @@ -12,8 +12,9 @@ import ( ) var ( - // Value for memoizing whether cubbyhole is mounted, e.g. if we are in normal operation and not test mode - cubbyholeMounted *bool + // Value for memoizing whether cubbyhole is mounted, e.g. if we are in + // normal operation and not test mode + cubbyholeMounted bool // mutex to ensure the same cubbyholeMountedMutex sync.Mutex @@ -61,17 +62,14 @@ func (c *Core) HandleRequest(req *logical.Request) (resp *logical.Response, err // In order to wrap, we need cubbyhole to be mounted, so we ensure that // cubbyhole is actually mounted, as it may not be during tests. We memoize - // this response, since cubbyhole cannot be mounted or unmounted during + // a true response, since cubbyhole cannot be mounted or unmounted during // normal operation. - if cubbyholeMounted == nil { + if !cubbyholeMounted { cubbyholeMountedMutex.Lock() - cubbyholeMounted = new(bool) // Ensure it wasn't changed by another goroutine - if cubbyholeMounted == nil { - if c.router.MatchingMount("cubbyhole") != "" { - *cubbyholeMounted = true - } else { - *cubbyholeMounted = false + if !cubbyholeMounted { + if c.router.MatchingMount("cubbyhole/") != "" { + cubbyholeMounted = true } } cubbyholeMountedMutex.Unlock() @@ -80,7 +78,7 @@ func (c *Core) HandleRequest(req *logical.Request) (resp *logical.Response, err // We are wrapping if there is anything to wrap (not a nil response) and a // TTL was specified for the token, plus if cubbyhole is mounted (which // will be the case normally) - wrapping := *cubbyholeMounted && resp != nil && resp.WrapInfo.TTL != 0 + wrapping := cubbyholeMounted && resp != nil && resp.WrapInfo.TTL != 0 // If we are wrapping, the first part happens before auditing so that // resp.WrapInfo.Token can contain the HMAC'd wrapping token ID in the @@ -150,6 +148,7 @@ func (c *Core) HandleRequest(req *logical.Request) (resp *logical.Response, err wrappingResp := &logical.Response{ WrapInfo: logical.WrapInfo{ Token: resp.WrapInfo.Token, + TTL: resp.WrapInfo.TTL, }, } wrappingResp.CloneWarnings(resp)