Add wrap support to API/CLI

This commit is contained in:
Jeff Mitchell
2016-05-02 01:58:58 -04:00
parent 21c0e4ee42
commit ff4dc0b853
6 changed files with 63 additions and 12 deletions

View File

@@ -26,6 +26,7 @@ const EnvVaultClientCert = "VAULT_CLIENT_CERT"
const EnvVaultClientKey = "VAULT_CLIENT_KEY" const EnvVaultClientKey = "VAULT_CLIENT_KEY"
const EnvVaultInsecure = "VAULT_SKIP_VERIFY" const EnvVaultInsecure = "VAULT_SKIP_VERIFY"
const EnvVaultTLSServerName = "VAULT_TLS_SERVER_NAME" const EnvVaultTLSServerName = "VAULT_TLS_SERVER_NAME"
const EnvVaultWrapTTL = "VAULT_WRAP_TTL"
var ( var (
errRedirect = errors.New("redirect") errRedirect = errors.New("redirect")
@@ -39,6 +40,14 @@ type Config struct {
// HttpClient. // HttpClient.
Address string 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 // 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. // same values as http.DefaultClient. This is used to control redirect behavior.
HttpClient *http.Client HttpClient *http.Client
@@ -80,6 +89,7 @@ func (c *Config) ReadEnvironment() error {
var envCAPath string var envCAPath string
var envClientCert string var envClientCert string
var envClientKey string var envClientKey string
var envWrapTTL string
var envInsecure bool var envInsecure bool
var foundInsecure bool var foundInsecure bool
var envTLSServerName string var envTLSServerName string
@@ -103,6 +113,9 @@ func (c *Config) ReadEnvironment() error {
if v := os.Getenv(EnvVaultClientKey); v != "" { if v := os.Getenv(EnvVaultClientKey); v != "" {
envClientKey = v envClientKey = v
} }
if v := os.Getenv(EnvVaultWrapTTL); v != "" {
envWrapTTL = v
}
if v := os.Getenv(EnvVaultInsecure); v != "" { if v := os.Getenv(EnvVaultInsecure); v != "" {
var err error var err error
envInsecure, err = strconv.ParseBool(v) envInsecure, err = strconv.ParseBool(v)
@@ -141,6 +154,10 @@ func (c *Config) ReadEnvironment() error {
c.Address = envAddress c.Address = envAddress
} }
if envWrapTTL != "" {
c.WrapTTL = envWrapTTL
}
clientTLSConfig := c.HttpClient.Transport.(*http.Transport).TLSClientConfig clientTLSConfig := c.HttpClient.Transport.(*http.Transport).TLSClientConfig
if foundInsecure { if foundInsecure {
clientTLSConfig.InsecureSkipVerify = envInsecure clientTLSConfig.InsecureSkipVerify = envInsecure
@@ -172,7 +189,6 @@ type Client struct {
// automatically added to the client. Otherwise, you must manually call // automatically added to the client. Otherwise, you must manually call
// `SetToken()`. // `SetToken()`.
func NewClient(c *Config) (*Client, error) { func NewClient(c *Config) (*Client, error) {
u, err := url.Parse(c.Address) u, err := url.Parse(c.Address)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -235,6 +251,7 @@ func (c *Client) NewRequest(method, path string) *Request {
Path: path, Path: path,
}, },
ClientToken: c.token, ClientToken: c.token,
WrapTTL: c.config.WrapTTL,
Params: make(map[string][]string), Params: make(map[string][]string),
} }

View File

@@ -15,6 +15,7 @@ type Request struct {
URL *url.URL URL *url.URL
Params url.Values Params url.Values
ClientToken string ClientToken string
WrapTTL string
Obj interface{} Obj interface{}
Body io.Reader Body io.Reader
BodySize int64 BodySize int64
@@ -62,5 +63,9 @@ func (r *Request) ToHTTP() (*http.Request, error) {
req.Header.Set("X-Vault-Token", r.ClientToken) 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 return req, nil
} }

View File

@@ -23,6 +23,17 @@ type Secret struct {
// Auth, if non-nil, means that there was authentication information // Auth, if non-nil, means that there was authentication information
// attached to this response. // attached to this response.
Auth *SecretAuth `json:"auth,omitempty"` 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. // SecretAuth is the structure containing auth information if we have it.

View File

@@ -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)) keys := make([]string, 0, len(s.Data))
for k := range s.Data { for k := range s.Data {
keys = append(keys, k) keys = append(keys, k)

View File

@@ -46,6 +46,7 @@ type Meta struct {
flagCAPath string flagCAPath string
flagClientCert string flagClientCert string
flagClientKey string flagClientKey string
flagWrapTTL string
flagInsecure bool flagInsecure bool
// Queried if no token can be found // 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 // Build the client
client, err := api.NewClient(config) client, err := api.NewClient(config)
if err != nil { 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.flagCAPath, "ca-path", "", "")
f.StringVar(&m.flagClientCert, "client-cert", "", "") f.StringVar(&m.flagClientCert, "client-cert", "", "")
f.StringVar(&m.flagClientKey, "client-key", "", "") f.StringVar(&m.flagClientKey, "client-key", "", "")
f.StringVar(&m.flagWrapTTL, "wrap-ttl", "", "")
f.BoolVar(&m.flagInsecure, "insecure", false, "") f.BoolVar(&m.flagInsecure, "insecure", false, "")
f.BoolVar(&m.flagInsecure, "tls-skip-verify", 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 -tls-skip-verify Do not verify TLS certificate. This is highly
not recommended. Verification will also be skipped not recommended. Verification will also be skipped
if VAULT_SKIP_VERIFY is set. 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 return general
} }

View File

@@ -12,8 +12,9 @@ import (
) )
var ( var (
// Value for memoizing whether cubbyhole is mounted, e.g. if we are in normal operation and not test mode // Value for memoizing whether cubbyhole is mounted, e.g. if we are in
cubbyholeMounted *bool // normal operation and not test mode
cubbyholeMounted bool
// mutex to ensure the same // mutex to ensure the same
cubbyholeMountedMutex sync.Mutex 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 // 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 // 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. // normal operation.
if cubbyholeMounted == nil { if !cubbyholeMounted {
cubbyholeMountedMutex.Lock() cubbyholeMountedMutex.Lock()
cubbyholeMounted = new(bool)
// Ensure it wasn't changed by another goroutine // Ensure it wasn't changed by another goroutine
if cubbyholeMounted == nil { if !cubbyholeMounted {
if c.router.MatchingMount("cubbyhole") != "" { if c.router.MatchingMount("cubbyhole/") != "" {
*cubbyholeMounted = true cubbyholeMounted = true
} else {
*cubbyholeMounted = false
} }
} }
cubbyholeMountedMutex.Unlock() 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 // 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 // TTL was specified for the token, plus if cubbyhole is mounted (which
// will be the case normally) // 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 // 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 // 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{ wrappingResp := &logical.Response{
WrapInfo: logical.WrapInfo{ WrapInfo: logical.WrapInfo{
Token: resp.WrapInfo.Token, Token: resp.WrapInfo.Token,
TTL: resp.WrapInfo.TTL,
}, },
} }
wrappingResp.CloneWarnings(resp) wrappingResp.CloneWarnings(resp)