From de6d2c524430287c699aaa898c1325da6afea539 Mon Sep 17 00:00:00 2001 From: Niall Sheridan Date: Wed, 20 Jun 2018 22:39:07 +0100 Subject: Update dependencies --- vendor/github.com/hashicorp/vault/api/SPEC.md | 611 --------------------- vendor/github.com/hashicorp/vault/api/client.go | 423 ++++++++++---- vendor/github.com/hashicorp/vault/api/logical.go | 87 ++- vendor/github.com/hashicorp/vault/api/renewer.go | 127 +++-- vendor/github.com/hashicorp/vault/api/request.go | 102 +++- vendor/github.com/hashicorp/vault/api/response.go | 8 +- vendor/github.com/hashicorp/vault/api/secret.go | 247 ++++++++- vendor/github.com/hashicorp/vault/api/ssh_agent.go | 46 +- vendor/github.com/hashicorp/vault/api/sys_audit.go | 13 +- vendor/github.com/hashicorp/vault/api/sys_auth.go | 49 +- .../hashicorp/vault/api/sys_capabilities.go | 8 +- .../hashicorp/vault/api/sys_generate_root.go | 51 +- .../github.com/hashicorp/vault/api/sys_health.go | 20 +- .../github.com/hashicorp/vault/api/sys_mounts.go | 57 +- .../github.com/hashicorp/vault/api/sys_plugins.go | 117 ++++ .../github.com/hashicorp/vault/api/sys_policy.go | 10 +- vendor/github.com/hashicorp/vault/api/sys_rekey.go | 147 ++++- vendor/github.com/hashicorp/vault/api/sys_seal.go | 18 +- .../vault/helper/compressutil/compress.go | 9 +- .../hashicorp/vault/helper/hclutil/hcl.go | 36 ++ .../hashicorp/vault/helper/jsonutil/json.go | 5 +- .../hashicorp/vault/helper/parseutil/parseutil.go | 98 ++++ .../hashicorp/vault/helper/strutil/strutil.go | 327 +++++++++++ 23 files changed, 1689 insertions(+), 927 deletions(-) delete mode 100644 vendor/github.com/hashicorp/vault/api/SPEC.md create mode 100644 vendor/github.com/hashicorp/vault/api/sys_plugins.go create mode 100644 vendor/github.com/hashicorp/vault/helper/hclutil/hcl.go create mode 100644 vendor/github.com/hashicorp/vault/helper/strutil/strutil.go (limited to 'vendor/github.com/hashicorp/vault') diff --git a/vendor/github.com/hashicorp/vault/api/SPEC.md b/vendor/github.com/hashicorp/vault/api/SPEC.md deleted file mode 100644 index 15345f3..0000000 --- a/vendor/github.com/hashicorp/vault/api/SPEC.md +++ /dev/null @@ -1,611 +0,0 @@ -FORMAT: 1A - -# vault - -The Vault API gives you full access to the Vault project. - -If you're browsing this API specifiction in GitHub or in raw -format, please excuse some of the odd formatting. This document -is in api-blueprint format that is read by viewers such as -Apiary. - -## Sealed vs. Unsealed - -Whenever an individual Vault server is started, it is started -in the _sealed_ state. In this state, it knows where its data -is located, but the data is encrypted and Vault doesn't have the -encryption keys to access it. Before Vault can operate, it must -be _unsealed_. - -**Note:** Sealing/unsealing has no relationship to _authentication_ -which is separate and still required once the Vault is unsealed. - -Instead of being sealed with a single key, we utilize -[Shamir's Secret Sharing](http://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing) -to shard a key into _n_ parts such that _t_ parts are required -to reconstruct the original key, where `t <= n`. This means that -Vault itself doesn't know the original key, and no single person -has the original key (unless `n = 1`, or `t` parts are given to -a single person). - -Unsealing is done via an unauthenticated -[unseal API](#reference/seal/unseal/unseal). This API takes a single -master shard and progresses the unsealing process. Once all shards -are given, the Vault is either unsealed or resets the unsealing -process if the key was invalid. - -The entire seal/unseal state is server-wide. This allows multiple -distinct operators to use the unseal API (or more likely the -`vault unseal` command) from separate computers/networks and never -have to transmit their key in order to unseal the vault in a -distributed fashion. - -## Transport - -The API is expected to be accessed over a TLS connection at -all times, with a valid certificate that is verified by a well -behaved client. - -## Authentication - -Once the Vault is unsealed, every other operation requires -authentication. There are multiple methods for authentication -that can be enabled (see -[authentication](#reference/authentication)). - -Authentication is done with the login endpoint. The login endpoint -returns an access token that is set as the `X-Vault-Token` header. - -## Help - -To retrieve the help for any API within Vault, including mounted -backends, credential providers, etc. then append `?help=1` to any -URL. If you have valid permission to access the path, then the help text -will be returned with the following structure: - - { - "help": "help text" - } - -## Error Response - -A common JSON structure is always returned to return errors: - - { - "errors": [ - "message", - "another message" - ] - } - -This structure will be sent down for any non-20x HTTP status. - -## HTTP Status Codes - -The following HTTP status codes are used throughout the API. - -- `200` - Success with data. -- `204` - Success, no data returned. -- `400` - Invalid request, missing or invalid data. -- `403` - Forbidden, your authentication details are either - incorrect or you don't have access to this feature. -- `404` - Invalid path. This can both mean that the path truly - doesn't exist or that you don't have permission to view a - specific path. We use 404 in some cases to avoid state leakage. -- `429` - Rate limit exceeded. Try again after waiting some period - of time. -- `500` - Internal server error. An internal error has occurred, - try again later. If the error persists, report a bug. -- `503` - Vault is down for maintenance or is currently sealed. - Try again later. - -# Group Initialization - -## Initialization [/sys/init] -### Initialization Status [GET] -Returns the status of whether the vault is initialized or not. The -vault doesn't have to be unsealed for this operation. - -+ Response 200 (application/json) - - { - "initialized": true - } - -### Initialize [POST] -Initialize the vault. This is an unauthenticated request to initially -setup a new vault. Although this is unauthenticated, it is still safe: -data cannot be in vault prior to initialization, and any future -authentication will fail if you didn't initialize it yourself. -Additionally, once initialized, a vault cannot be reinitialized. - -This API is the only time Vault will ever be aware of your keys, and -the only time the keys will ever be returned in one unit. Care should -be taken to ensure that the output of this request is never logged, -and that the keys are properly distributed. - -The response also contains the initial root token that can be used -as authentication in order to initially configure Vault once it is -unsealed. Just as with the unseal keys, this is the only time Vault is -ever aware of this token. - -+ Request (application/json) - - { - "secret_shares": 5, - "secret_threshold": 3, - } - -+ Response 200 (application/json) - - { - "keys": ["one", "two", "three"], - "root_token": "foo" - } - -# Group Seal/Unseal - -## Seal Status [/sys/seal-status] -### Seal Status [GET] -Returns the status of whether the vault is currently -sealed or not, as well as the progress of unsealing. - -The response has the following attributes: - -- sealed (boolean) - If true, the vault is sealed. Otherwise, - it is unsealed. -- t (int) - The "t" value for the master key, or the number - of shards needed total to unseal the vault. -- n (int) - The "n" value for the master key, or the total - number of shards of the key distributed. -- progress (int) - The number of master key shards that have - been entered so far towards unsealing the vault. - -+ Response 200 (application/json) - - { - "sealed": true, - "t": 3, - "n": 5, - "progress": 1 - } - -## Seal [/sys/seal] -### Seal [PUT] -Seal the vault. - -Sealing the vault locks Vault from any future operations on any -secrets or system configuration until the vault is once again -unsealed. Internally, sealing throws away the keys to access the -encrypted vault data, so Vault is unable to access the data without -unsealing to get the encryption keys. - -+ Response 204 - -## Unseal [/sys/unseal] -### Unseal [PUT] -Unseal the vault. - -Unseal the vault by entering a portion of the master key. The -response object will tell you if the unseal is complete or -only partial. - -If the vault is already unsealed, this does nothing. It is -not an error, the return value just says the vault is unsealed. -Due to the architecture of Vault, we cannot validate whether -any portion of the unseal key given is valid until all keys -are inputted, therefore unsealing an already unsealed vault -is still a success even if the input key is invalid. - -+ Request (application/json) - - { - "key": "value" - } - -+ Response 200 (application/json) - - { - "sealed": true, - "t": 3, - "n": 5, - "progress": 1 - } - -# Group Authentication - -## List Auth Methods [/sys/auth] -### List all auth methods [GET] -Lists all available authentication methods. - -This returns the name of the authentication method as well as -a human-friendly long-form help text for the method that can be -shown to the user as documentation. - -+ Response 200 (application/json) - - { - "token": { - "type": "token", - "description": "Token authentication" - }, - "oauth": { - "type": "oauth", - "description": "OAuth authentication" - } - } - -## Single Auth Method [/sys/auth/{id}] - -+ Parameters - + id (required, string) ... The ID of the auth method. - -### Enable an auth method [PUT] -Enables an authentication method. - -The body of the request depends on the authentication method -being used. Please reference the documentation for the specific -authentication method you're enabling in order to determine what -parameters you must give it. - -If an authentication method is already enabled, then this can be -used to change the configuration, including even the type of -the configuration. - -+ Request (application/json) - - { - "type": "type", - "key": "value", - "key2": "value2" - } - -+ Response 204 - -### Disable an auth method [DELETE] -Disables an authentication method. Previously authenticated sessions -are immediately invalidated. - -+ Response 204 - -# Group Policies - -Policies are named permission sets that identities returned by -credential stores are bound to. This separates _authentication_ -from _authorization_. - -## Policies [/sys/policy] -### List all Policies [GET] - -List all the policies. - -+ Response 200 (application/json) - - { - "policies": ["root"] - } - -## Single Policy [/sys/policy/{id}] - -+ Parameters - + id (required, string) ... The name of the policy - -### Upsert [PUT] - -Create or update a policy with the given ID. - -+ Request (application/json) - - { - "rules": "HCL" - } - -+ Response 204 - -### Delete [DELETE] - -Delete a policy with the given ID. Any identities bound to this -policy will immediately become "deny all" despite already being -authenticated. - -+ Response 204 - -# Group Mounts - -Logical backends are mounted at _mount points_, similar to -filesystems. This allows you to mount the "aws" logical backend -at the "aws-us-east" path, so all access is at `/aws-us-east/keys/foo` -for example. This enables multiple logical backends to be enabled. - -## Mounts [/sys/mounts] -### List all mounts [GET] - -Lists all the active mount points. - -+ Response 200 (application/json) - - { - "aws": { - "type": "aws", - "description": "AWS" - }, - "pg": { - "type": "postgresql", - "description": "PostgreSQL dynamic users" - } - } - -## Single Mount [/sys/mounts/{path}] -### New Mount [POST] - -Mount a logical backend to a new path. - -Configuration for this new backend is done via the normal -read/write mechanism once it is mounted. - -+ Request (application/json) - - { - "type": "aws", - "description": "EU AWS tokens" - } - -+ Response 204 - -### Unmount [DELETE] - -Unmount a mount point. - -+ Response 204 - -## Remount [/sys/remount] -### Remount [POST] - -Move an already-mounted backend to a new path. - -+ Request (application/json) - - { - "from": "aws", - "to": "aws-east" - } - -+ Response 204 - -# Group Audit Backends - -Audit backends are responsible for shuttling the audit logs that -Vault generates to a durable system for future querying. By default, -audit logs are not stored anywhere. - -## Audit Backends [/sys/audit] -### List Enabled Audit Backends [GET] - -List all the enabled audit backends - -+ Response 200 (application/json) - - { - "file": { - "type": "file", - "description": "Send audit logs to a file", - "options": {} - } - } - -## Single Audit Backend [/sys/audit/{path}] - -+ Parameters - + path (required, string) ... The path where the audit backend is mounted - -### Enable [PUT] - -Enable an audit backend. - -+ Request (application/json) - - { - "type": "file", - "description": "send to a file", - "options": { - "path": "/var/log/vault.audit.log" - } - } - -+ Response 204 - -### Disable [DELETE] - -Disable an audit backend. - -+ Request (application/json) - -+ Response 204 - -# Group Secrets - -## Generic [/{mount}/{path}] - -This group documents the general format of reading and writing -to Vault. The exact structure of the keyspace is defined by the -logical backends in use, so documentation related to -a specific backend should be referenced for details on what keys -and routes are expected. - -The path for examples are `/prefix/path`, but in practice -these will be defined by the backends that are mounted. For -example, reading an AWS key might be at the `/aws/root` path. -These paths are defined by the logical backends. - -+ Parameters - + mount (required, string) ... The mount point for the - logical backend. Example: `aws`. - + path (optional, string) ... The path within the backend - to read or write data. - -### Read [GET] - -Read data from vault. - -The data read from the vault can either be a secret or -arbitrary configuration data. The type of data returned -depends on the path, and is defined by the logical backend. - -If the return value is a secret, then the return structure -is a mixture of arbitrary key/value along with the following -fields which are guaranteed to exist: - -- `lease_id` (string) - A unique ID used for renewal and - revocation. - -- `renewable` (bool) - If true, then this key can be renewed. - If a key can't be renewed, then a new key must be requested - after the lease duration period. - -- `lease_duration` (int) - The time in seconds that a secret is - valid for before it must be renewed. - -- `lease_duration_max` (int) - The maximum amount of time in - seconds that a secret is valid for. This will always be - greater than or equal to `lease_duration`. The difference - between this and `lease_duration` is an overlap window - where multiple keys may be valid. - -If the return value is not a secret, then the return structure -is an arbitrary JSON object. - -+ Response 200 (application/json) - - { - "lease_id": "UUID", - "lease_duration": 3600, - "key": "value" - } - -### Write [PUT] - -Write data to vault. - -The behavior and arguments to the write are defined by -the logical backend. - -+ Request (application/json) - - { - "key": "value" - } - -+ Response 204 - -# Group Lease Management - -## Renew Key [/sys/renew/{id}] - -+ Parameters - + id (required, string) ... The `lease_id` of the secret - to renew. - -### Renew [PUT] - -+ Response 200 (application/json) - - { - "lease_id": "...", - "lease_duration": 3600, - "access_key": "foo", - "secret_key": "bar" - } - -## Revoke Key [/sys/revoke/{id}] - -+ Parameters - + id (required, string) ... The `lease_id` of the secret - to revoke. - -### Revoke [PUT] - -+ Response 204 - -# Group Backend: AWS - -## Root Key [/aws/root] -### Set the Key [PUT] - -Set the root key that the logical backend will use to create -new secrets, IAM policies, etc. - -+ Request (application/json) - - { - "access_key": "key", - "secret_key": "key", - "region": "us-east-1" - } - -+ Response 204 - -## Policies [/aws/policies] -### List Policies [GET] - -List all the policies that can be used to create keys. - -+ Response 200 (application/json) - - [{ - "name": "root", - "description": "Root access" - }, { - "name": "web-deploy", - "description": "Enough permissions to deploy the web app." - }] - -## Single Policy [/aws/policies/{name}] - -+ Parameters - + name (required, string) ... Name of the policy. - -### Read [GET] - -Read a policy. - -+ Response 200 (application/json) - - { - "policy": "base64-encoded policy" - } - -### Upsert [PUT] - -Create or update a policy. - -+ Request (application/json) - - { - "policy": "base64-encoded policy" - } - -+ Response 204 - -### Delete [DELETE] - -Delete the policy with the given name. - -+ Response 204 - -## Generate Access Keys [/aws/keys/{policy}] -### Create [GET] - -This generates a new keypair for the given policy. - -+ Parameters - + policy (required, string) ... The policy under which to create - the key pair. - -+ Response 200 (application/json) - - { - "lease_id": "...", - "lease_duration": 3600, - "access_key": "foo", - "secret_key": "bar" - } diff --git a/vendor/github.com/hashicorp/vault/api/client.go b/vendor/github.com/hashicorp/vault/api/client.go index 0204cec..8f0d3f8 100644 --- a/vendor/github.com/hashicorp/vault/api/client.go +++ b/vendor/github.com/hashicorp/vault/api/client.go @@ -1,6 +1,7 @@ package api import ( + "context" "crypto/tls" "fmt" "net" @@ -12,13 +13,15 @@ import ( "strings" "sync" "time" + "unicode" - "golang.org/x/net/http2" - + "github.com/hashicorp/errwrap" "github.com/hashicorp/go-cleanhttp" + retryablehttp "github.com/hashicorp/go-retryablehttp" "github.com/hashicorp/go-rootcerts" "github.com/hashicorp/vault/helper/parseutil" - "github.com/sethgrid/pester" + "golang.org/x/net/http2" + "golang.org/x/time/rate" ) const EnvVaultAddress = "VAULT_ADDR" @@ -32,6 +35,8 @@ const EnvVaultTLSServerName = "VAULT_TLS_SERVER_NAME" const EnvVaultWrapTTL = "VAULT_WRAP_TTL" const EnvVaultMaxRetries = "VAULT_MAX_RETRIES" const EnvVaultToken = "VAULT_TOKEN" +const EnvVaultMFA = "VAULT_MFA" +const EnvRateLimit = "VAULT_RATE_LIMIT" // WrappingLookupFunc is a function that, given an HTTP verb and a path, // returns an optional string duration to be used for response wrapping (e.g. @@ -42,24 +47,42 @@ type WrappingLookupFunc func(operation, path string) string // Config is used to configure the creation of the client. type Config struct { + modifyLock sync.RWMutex + // Address is the address of the Vault server. This should be a complete // URL such as "http://vault.example.com". If you need a custom SSL // cert or want to enable insecure mode, you need to specify a custom // HttpClient. Address 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 is the HTTP client to use. Vault sets sane defaults for the + // http.Client and its associated http.Transport created in DefaultConfig. + // If you must modify Vault's defaults, it is suggested that you start with + // that client and modify as needed rather than start with an empty client + // (or http.DefaultClient). HttpClient *http.Client - redirectSetup sync.Once - - // MaxRetries controls the maximum number of times to retry when a 5xx error - // occurs. Set to 0 or less to disable retrying. Defaults to 0. + // MaxRetries controls the maximum number of times to retry when a 5xx + // error occurs. Set to 0 to disable retrying. Defaults to 2 (for a total + // of three tries). MaxRetries int // Timeout is for setting custom timeout parameter in the HttpClient Timeout time.Duration + + // If there is an error when creating the configuration, this will be the + // error + Error error + + // The Backoff function to use; a default is used if not provided + Backoff retryablehttp.Backoff + + // Limiter is the rate limiter used by the client. + // If this pointer is nil, then there will be no limit set. + // In contrast, if this pointer is set, even to an empty struct, + // then that limiter will be used. Note that an empty Limiter + // is equivalent blocking all events. + Limiter *rate.Limiter } // TLSConfig contains the parameters needed to configure TLS on the HTTP client @@ -92,60 +115,94 @@ type TLSConfig struct { // // The default Address is https://127.0.0.1:8200, but this can be overridden by // setting the `VAULT_ADDR` environment variable. +// +// If an error is encountered, this will return nil. func DefaultConfig() *Config { config := &Config{ Address: "https://127.0.0.1:8200", HttpClient: cleanhttp.DefaultClient(), } config.HttpClient.Timeout = time.Second * 60 + transport := config.HttpClient.Transport.(*http.Transport) transport.TLSHandshakeTimeout = 10 * time.Second transport.TLSClientConfig = &tls.Config{ MinVersion: tls.VersionTLS12, } + if err := http2.ConfigureTransport(transport); err != nil { + config.Error = err + return config + } - if v := os.Getenv(EnvVaultAddress); v != "" { - config.Address = v + if err := config.ReadEnvironment(); err != nil { + config.Error = err + return config + } + + // Ensure redirects are not automatically followed + // Note that this is sane for the API client as it has its own + // redirect handling logic (and thus also for command/meta), + // but in e.g. http_test actual redirect handling is necessary + config.HttpClient.CheckRedirect = func(req *http.Request, via []*http.Request) error { + // Returning this value causes the Go net library to not close the + // response body and to nil out the error. Otherwise retry clients may + // try three times on every redirect because it sees an error from this + // function (to prevent redirects) passing through to it. + return http.ErrUseLastResponse } + config.Backoff = retryablehttp.LinearJitterBackoff + config.MaxRetries = 2 + return config } -// ConfigureTLS takes a set of TLS configurations and applies those to the the HTTP client. +// ConfigureTLS takes a set of TLS configurations and applies those to the the +// HTTP client. func (c *Config) ConfigureTLS(t *TLSConfig) error { if c.HttpClient == nil { c.HttpClient = DefaultConfig().HttpClient } + clientTLSConfig := c.HttpClient.Transport.(*http.Transport).TLSClientConfig var clientCert tls.Certificate foundClientCert := false - if t.CACert != "" || t.CAPath != "" || t.ClientCert != "" || t.ClientKey != "" || t.Insecure { - if t.ClientCert != "" && t.ClientKey != "" { - var err error - clientCert, err = tls.LoadX509KeyPair(t.ClientCert, t.ClientKey) - if err != nil { - return err - } - foundClientCert = true - } else if t.ClientCert != "" || t.ClientKey != "" { - return fmt.Errorf("Both client cert and client key must be provided") + + switch { + case t.ClientCert != "" && t.ClientKey != "": + var err error + clientCert, err = tls.LoadX509KeyPair(t.ClientCert, t.ClientKey) + if err != nil { + return err } + foundClientCert = true + case t.ClientCert != "" || t.ClientKey != "": + return fmt.Errorf("both client cert and client key must be provided") } - clientTLSConfig := c.HttpClient.Transport.(*http.Transport).TLSClientConfig - rootConfig := &rootcerts.Config{ - CAFile: t.CACert, - CAPath: t.CAPath, - } - if err := rootcerts.ConfigureTLS(clientTLSConfig, rootConfig); err != nil { - return err + if t.CACert != "" || t.CAPath != "" { + rootConfig := &rootcerts.Config{ + CAFile: t.CACert, + CAPath: t.CAPath, + } + if err := rootcerts.ConfigureTLS(clientTLSConfig, rootConfig); err != nil { + return err + } } - clientTLSConfig.InsecureSkipVerify = t.Insecure + if t.Insecure { + clientTLSConfig.InsecureSkipVerify = true + } if foundClientCert { - clientTLSConfig.Certificates = []tls.Certificate{clientCert} + // We use this function to ignore the server's preferential list of + // CAs, otherwise any CA used for the cert auth backend must be in the + // server's CA pool + clientTLSConfig.GetClientCertificate = func(*tls.CertificateRequestInfo) (*tls.Certificate, error) { + return &clientCert, nil + } } + if t.TLSServerName != "" { clientTLSConfig.ServerName = t.TLSServerName } @@ -153,9 +210,8 @@ func (c *Config) ConfigureTLS(t *TLSConfig) error { return nil } -// ReadEnvironment reads configuration information from the -// environment. If there is an error, no configuration value -// is updated. +// ReadEnvironment reads configuration information from the environment. If +// there is an error, no configuration value is updated. func (c *Config) ReadEnvironment() error { var envAddress string var envCACert string @@ -166,6 +222,7 @@ func (c *Config) ReadEnvironment() error { var envInsecure bool var envTLSServerName string var envMaxRetries *uint64 + var limit *rate.Limiter // Parse the environment variables if v := os.Getenv(EnvVaultAddress); v != "" { @@ -190,10 +247,17 @@ func (c *Config) ReadEnvironment() error { if v := os.Getenv(EnvVaultClientKey); v != "" { envClientKey = v } + if v := os.Getenv(EnvRateLimit); v != "" { + rateLimit, burstLimit, err := parseRateLimit(v) + if err != nil { + return err + } + limit = rate.NewLimiter(rate.Limit(rateLimit), burstLimit) + } if t := os.Getenv(EnvVaultClientTimeout); t != "" { clientTimeout, err := parseutil.ParseDurationSecond(t) if err != nil { - return fmt.Errorf("Could not parse %s", EnvVaultClientTimeout) + return fmt.Errorf("could not parse %q", EnvVaultClientTimeout) } envClientTimeout = clientTimeout } @@ -201,7 +265,7 @@ func (c *Config) ReadEnvironment() error { var err error envInsecure, err = strconv.ParseBool(v) if err != nil { - return fmt.Errorf("Could not parse VAULT_SKIP_VERIFY") + return fmt.Errorf("could not parse VAULT_SKIP_VERIFY") } } if v := os.Getenv(EnvVaultTLSServerName); v != "" { @@ -217,6 +281,12 @@ func (c *Config) ReadEnvironment() error { TLSServerName: envTLSServerName, Insecure: envInsecure, } + + c.modifyLock.Lock() + defer c.modifyLock.Unlock() + + c.Limiter = limit + if err := c.ConfigureTLS(t); err != nil { return err } @@ -226,7 +296,7 @@ func (c *Config) ReadEnvironment() error { } if envMaxRetries != nil { - c.MaxRetries = int(*envMaxRetries) + 1 + c.MaxRetries = int(*envMaxRetries) } if envClientTimeout != 0 { @@ -236,70 +306,76 @@ func (c *Config) ReadEnvironment() error { return nil } -// Client is the client to the Vault API. Create a client with -// NewClient. +func parseRateLimit(val string) (rate float64, burst int, err error) { + + _, err = fmt.Sscanf(val, "%f:%d", &rate, &burst) + if err != nil { + rate, err = strconv.ParseFloat(val, 64) + if err != nil { + err = fmt.Errorf("%v was provided but incorrectly formatted", EnvRateLimit) + } + burst = int(rate) + } + + return rate, burst, err + +} + +// Client is the client to the Vault API. Create a client with NewClient. type Client struct { + modifyLock sync.RWMutex addr *url.URL config *Config token string headers http.Header wrappingLookupFunc WrappingLookupFunc + mfaCreds []string + policyOverride bool } // NewClient returns a new client for the given configuration. // +// If the configuration is nil, Vault will use configuration from +// DefaultConfig(), which is the recommended starting configuration. +// // If the environment variable `VAULT_TOKEN` is present, the token will be // automatically added to the client. Otherwise, you must manually call // `SetToken()`. func NewClient(c *Config) (*Client, error) { + def := DefaultConfig() + if def == nil { + return nil, fmt.Errorf("could not create/read default configuration") + } + if def.Error != nil { + return nil, errwrap.Wrapf("error encountered setting up default configuration: {{err}}", def.Error) + } + if c == nil { - c = DefaultConfig() - if err := c.ReadEnvironment(); err != nil { - return nil, fmt.Errorf("error reading environment: %v", err) - } + c = def } + c.modifyLock.Lock() + defer c.modifyLock.Unlock() + u, err := url.Parse(c.Address) if err != nil { return nil, err } if c.HttpClient == nil { - c.HttpClient = DefaultConfig().HttpClient + c.HttpClient = def.HttpClient } if c.HttpClient.Transport == nil { - c.HttpClient.Transport = cleanhttp.DefaultTransport() + c.HttpClient.Transport = def.HttpClient.Transport } - if tp, ok := c.HttpClient.Transport.(*http.Transport); ok { - if err := http2.ConfigureTransport(tp); err != nil { - return nil, err - } - } - - redirFunc := func() { - // Ensure redirects are not automatically followed - // Note that this is sane for the API client as it has its own - // redirect handling logic (and thus also for command/meta), - // but in e.g. http_test actual redirect handling is necessary - c.HttpClient.CheckRedirect = func(req *http.Request, via []*http.Request) error { - // Returning this value causes the Go net library to not close the - // response body and to nil out the error. Otherwise pester tries - // three times on every redirect because it sees an error from this - // function (to prevent redirects) passing through to it. - return http.ErrUseLastResponse - } - } - - c.redirectSetup.Do(redirFunc) - client := &Client{ addr: u, config: c, } if token := os.Getenv(EnvVaultToken); token != "" { - client.SetToken(token) + client.token = token } return client, nil @@ -309,72 +385,181 @@ func NewClient(c *Config) (*Client, error) { // "://:". Setting this on a client will override the // value of VAULT_ADDR environment variable. func (c *Client) SetAddress(addr string) error { - var err error - if c.addr, err = url.Parse(addr); err != nil { - return fmt.Errorf("failed to set address: %v", err) + c.modifyLock.Lock() + defer c.modifyLock.Unlock() + + parsedAddr, err := url.Parse(addr) + if err != nil { + return errwrap.Wrapf("failed to set address: {{err}}", err) } + c.addr = parsedAddr return nil } // Address returns the Vault URL the client is configured to connect to func (c *Client) Address() string { + c.modifyLock.RLock() + defer c.modifyLock.RUnlock() + return c.addr.String() } +// SetLimiter will set the rate limiter for this client. +// This method is thread-safe. +// rateLimit and burst are specified according to https://godoc.org/golang.org/x/time/rate#NewLimiter +func (c *Client) SetLimiter(rateLimit float64, burst int) { + c.modifyLock.RLock() + c.config.modifyLock.Lock() + defer c.config.modifyLock.Unlock() + c.modifyLock.RUnlock() + + c.config.Limiter = rate.NewLimiter(rate.Limit(rateLimit), burst) +} + // SetMaxRetries sets the number of retries that will be used in the case of certain errors func (c *Client) SetMaxRetries(retries int) { + c.modifyLock.RLock() + c.config.modifyLock.Lock() + defer c.config.modifyLock.Unlock() + c.modifyLock.RUnlock() + c.config.MaxRetries = retries } // SetClientTimeout sets the client request timeout func (c *Client) SetClientTimeout(timeout time.Duration) { + c.modifyLock.RLock() + c.config.modifyLock.Lock() + defer c.config.modifyLock.Unlock() + c.modifyLock.RUnlock() + c.config.Timeout = timeout } +// CurrentWrappingLookupFunc sets a lookup function that returns desired wrap TTLs +// for a given operation and path +func (c *Client) CurrentWrappingLookupFunc() WrappingLookupFunc { + c.modifyLock.RLock() + defer c.modifyLock.RUnlock() + + return c.wrappingLookupFunc +} + // SetWrappingLookupFunc sets a lookup function that returns desired wrap TTLs // for a given operation and path func (c *Client) SetWrappingLookupFunc(lookupFunc WrappingLookupFunc) { + c.modifyLock.Lock() + defer c.modifyLock.Unlock() + c.wrappingLookupFunc = lookupFunc } +// SetMFACreds sets the MFA credentials supplied either via the environment +// variable or via the command line. +func (c *Client) SetMFACreds(creds []string) { + c.modifyLock.Lock() + defer c.modifyLock.Unlock() + + c.mfaCreds = creds +} + // Token returns the access token being used by this client. It will // return the empty string if there is no token set. func (c *Client) Token() string { + c.modifyLock.RLock() + defer c.modifyLock.RUnlock() + return c.token } // SetToken sets the token directly. This won't perform any auth // verification, it simply sets the token properly for future requests. func (c *Client) SetToken(v string) { + c.modifyLock.Lock() + defer c.modifyLock.Unlock() + c.token = v } // ClearToken deletes the token if it is set or does nothing otherwise. func (c *Client) ClearToken() { + c.modifyLock.Lock() + defer c.modifyLock.Unlock() + c.token = "" } // SetHeaders sets the headers to be used for future requests. func (c *Client) SetHeaders(headers http.Header) { + c.modifyLock.Lock() + defer c.modifyLock.Unlock() + c.headers = headers } -// Clone creates a copy of this client. +// SetBackoff sets the backoff function to be used for future requests. +func (c *Client) SetBackoff(backoff retryablehttp.Backoff) { + c.modifyLock.RLock() + c.config.modifyLock.Lock() + defer c.config.modifyLock.Unlock() + c.modifyLock.RUnlock() + + c.config.Backoff = backoff +} + +// Clone creates a new client with the same configuration. Note that the same +// underlying http.Client is used; modifying the client from more than one +// goroutine at once may not be safe, so modify the client as needed and then +// clone. func (c *Client) Clone() (*Client, error) { - return NewClient(c.config) + c.modifyLock.RLock() + c.config.modifyLock.RLock() + config := c.config + c.modifyLock.RUnlock() + + newConfig := &Config{ + Address: config.Address, + HttpClient: config.HttpClient, + MaxRetries: config.MaxRetries, + Timeout: config.Timeout, + Backoff: config.Backoff, + Limiter: config.Limiter, + } + config.modifyLock.RUnlock() + + return NewClient(newConfig) +} + +// SetPolicyOverride sets whether requests should be sent with the policy +// override flag to request overriding soft-mandatory Sentinel policies (both +// RGPs and EGPs) +func (c *Client) SetPolicyOverride(override bool) { + c.modifyLock.Lock() + defer c.modifyLock.Unlock() + + c.policyOverride = override } // NewRequest creates a new raw request object to query the Vault server // configured for this client. This is an advanced method and generally // doesn't need to be called externally. func (c *Client) NewRequest(method, requestPath string) *Request { + c.modifyLock.RLock() + addr := c.addr + token := c.token + mfaCreds := c.mfaCreds + wrappingLookupFunc := c.wrappingLookupFunc + headers := c.headers + policyOverride := c.policyOverride + c.modifyLock.RUnlock() + // if SRV records exist (see https://tools.ietf.org/html/draft-andrews-http-srv-02), lookup the SRV // record and take the highest match; this is not designed for high-availability, just discovery - var host string = c.addr.Host - if c.addr.Port() == "" { + var host string = addr.Host + if addr.Port() == "" { // Internet Draft specifies that the SRV record is ignored if a port is given - _, addrs, err := net.LookupSRV("http", "tcp", c.addr.Hostname()) + _, addrs, err := net.LookupSRV("http", "tcp", addr.Hostname()) if err == nil && len(addrs) > 0 { host = fmt.Sprintf("%s:%d", addrs[0].Target, addrs[0].Port) } @@ -383,12 +568,12 @@ func (c *Client) NewRequest(method, requestPath string) *Request { req := &Request{ Method: method, URL: &url.URL{ - User: c.addr.User, - Scheme: c.addr.Scheme, + User: addr.User, + Scheme: addr.Scheme, Host: host, - Path: path.Join(c.addr.Path, requestPath), + Path: path.Join(addr.Path, requestPath), }, - ClientToken: c.token, + ClientToken: token, Params: make(map[string][]string), } @@ -401,18 +586,21 @@ func (c *Client) NewRequest(method, requestPath string) *Request { default: lookupPath = requestPath } - if c.wrappingLookupFunc != nil { - req.WrapTTL = c.wrappingLookupFunc(method, lookupPath) + + req.MFAHeaderVals = mfaCreds + + if wrappingLookupFunc != nil { + req.WrapTTL = wrappingLookupFunc(method, lookupPath) } else { req.WrapTTL = DefaultWrappingLookupFunc(method, lookupPath) } - if c.config.Timeout != 0 { - c.config.HttpClient.Timeout = c.config.Timeout - } - if c.headers != nil { - req.Headers = c.headers + + if headers != nil { + req.Headers = headers } + req.PolicyOverride = policyOverride + return req } @@ -420,26 +608,75 @@ func (c *Client) NewRequest(method, requestPath string) *Request { // a Vault server not configured with this client. This is an advanced operation // that generally won't need to be called externally. func (c *Client) RawRequest(r *Request) (*Response, error) { + c.modifyLock.RLock() + token := c.token + + c.config.modifyLock.RLock() + limiter := c.config.Limiter + maxRetries := c.config.MaxRetries + backoff := c.config.Backoff + httpClient := c.config.HttpClient + timeout := c.config.Timeout + c.config.modifyLock.RUnlock() + + c.modifyLock.RUnlock() + + if limiter != nil { + limiter.Wait(context.Background()) + } + + // Sanity check the token before potentially erroring from the API + idx := strings.IndexFunc(token, func(c rune) bool { + return !unicode.IsPrint(c) + }) + if idx != -1 { + return nil, fmt.Errorf("configured Vault token contains non-printable characters and cannot be used") + } + redirectCount := 0 START: - req, err := r.ToHTTP() + req, err := r.toRetryableHTTP() if err != nil { return nil, err } + if req == nil { + return nil, fmt.Errorf("nil request created") + } - client := pester.NewExtendedClient(c.config.HttpClient) - client.Backoff = pester.LinearJitterBackoff - client.MaxRetries = c.config.MaxRetries + // Set the timeout, if any + var cancelFunc context.CancelFunc + if timeout != 0 { + var ctx context.Context + ctx, cancelFunc = context.WithTimeout(context.Background(), timeout) + req.Request = req.Request.WithContext(ctx) + } + + if backoff == nil { + backoff = retryablehttp.LinearJitterBackoff + } + + client := &retryablehttp.Client{ + HTTPClient: httpClient, + RetryWaitMin: 1000 * time.Millisecond, + RetryWaitMax: 1500 * time.Millisecond, + RetryMax: maxRetries, + CheckRetry: retryablehttp.DefaultRetryPolicy, + Backoff: backoff, + ErrorHandler: retryablehttp.PassthroughErrorHandler, + } var result *Response resp, err := client.Do(req) + if cancelFunc != nil { + cancelFunc() + } if resp != nil { result = &Response{Response: resp} } if err != nil { if strings.Contains(err.Error(), "tls: oversized") { - err = fmt.Errorf( - "%s\n\n"+ + err = errwrap.Wrapf( + "{{err}}\n\n"+ "This error usually means that the server is running with TLS disabled\n"+ "but the client is configured to use TLS. Please either enable TLS\n"+ "on the server or run the client with -address set to an address\n"+ diff --git a/vendor/github.com/hashicorp/vault/api/logical.go b/vendor/github.com/hashicorp/vault/api/logical.go index 0d5e7d4..346a711 100644 --- a/vendor/github.com/hashicorp/vault/api/logical.go +++ b/vendor/github.com/hashicorp/vault/api/logical.go @@ -3,9 +3,10 @@ package api import ( "bytes" "fmt" - "net/http" + "io" "os" + "github.com/hashicorp/errwrap" "github.com/hashicorp/vault/helper/jsonutil" ) @@ -50,6 +51,17 @@ func (c *Logical) Read(path string) (*Secret, error) { defer resp.Body.Close() } if resp != nil && resp.StatusCode == 404 { + secret, parseErr := ParseSecret(resp.Body) + switch parseErr { + case nil: + case io.EOF: + return nil, nil + default: + return nil, err + } + if secret != nil && (len(secret.Warnings) > 0 || len(secret.Data) > 0) { + return secret, nil + } return nil, nil } if err != nil { @@ -70,6 +82,17 @@ func (c *Logical) List(path string) (*Secret, error) { defer resp.Body.Close() } if resp != nil && resp.StatusCode == 404 { + secret, parseErr := ParseSecret(resp.Body) + switch parseErr { + case nil: + case io.EOF: + return nil, nil + default: + return nil, err + } + if secret != nil && (len(secret.Warnings) > 0 || len(secret.Data) > 0) { + return secret, nil + } return nil, nil } if err != nil { @@ -89,6 +112,19 @@ func (c *Logical) Write(path string, data map[string]interface{}) (*Secret, erro if resp != nil { defer resp.Body.Close() } + if resp != nil && resp.StatusCode == 404 { + secret, parseErr := ParseSecret(resp.Body) + switch parseErr { + case nil: + case io.EOF: + return nil, nil + default: + return nil, err + } + if secret != nil && (len(secret.Warnings) > 0 || len(secret.Data) > 0) { + return secret, err + } + } if err != nil { return nil, err } @@ -106,6 +142,19 @@ func (c *Logical) Delete(path string) (*Secret, error) { if resp != nil { defer resp.Body.Close() } + if resp != nil && resp.StatusCode == 404 { + secret, parseErr := ParseSecret(resp.Body) + switch parseErr { + case nil: + case io.EOF: + return nil, nil + default: + return nil, err + } + if secret != nil && (len(secret.Warnings) > 0 || len(secret.Data) > 0) { + return secret, err + } + } if err != nil { return nil, err } @@ -138,35 +187,43 @@ func (c *Logical) Unwrap(wrappingToken string) (*Secret, error) { if resp != nil { defer resp.Body.Close() } - if err != nil { - if resp != nil && resp.StatusCode != 404 { + if resp == nil || resp.StatusCode != 404 { + if err != nil { return nil, err } - } - if resp == nil { - return nil, nil + if resp == nil { + return nil, nil + } + return ParseSecret(resp.Body) } - switch resp.StatusCode { - case http.StatusOK: // New method is supported - return ParseSecret(resp.Body) - case http.StatusNotFound: // Fall back to old method - default: + // In the 404 case this may actually be a wrapped 404 error + secret, parseErr := ParseSecret(resp.Body) + switch parseErr { + case nil: + case io.EOF: return nil, nil + default: + return nil, err + } + if secret != nil && (len(secret.Warnings) > 0 || len(secret.Data) > 0) { + return secret, nil } + // Otherwise this might be an old-style wrapping token so attempt the old + // method if wrappingToken != "" { origToken := c.c.Token() defer c.c.SetToken(origToken) c.c.SetToken(wrappingToken) } - secret, err := c.Read(wrappedResponseLocation) + secret, err = c.Read(wrappedResponseLocation) if err != nil { - return nil, fmt.Errorf("error reading %s: %s", wrappedResponseLocation, err) + return nil, errwrap.Wrapf(fmt.Sprintf("error reading %q: {{err}}", wrappedResponseLocation), err) } if secret == nil { - return nil, fmt.Errorf("no value found at %s", wrappedResponseLocation) + return nil, fmt.Errorf("no value found at %q", wrappedResponseLocation) } if secret.Data == nil { return nil, fmt.Errorf("\"data\" not found in wrapping response") @@ -178,7 +235,7 @@ func (c *Logical) Unwrap(wrappingToken string) (*Secret, error) { wrappedSecret := new(Secret) buf := bytes.NewBufferString(secret.Data["response"].(string)) if err := jsonutil.DecodeJSONFromReader(buf, wrappedSecret); err != nil { - return nil, fmt.Errorf("error unmarshaling wrapped secret: %s", err) + return nil, errwrap.Wrapf("error unmarshalling wrapped secret: {{err}}", err) } return wrappedSecret, nil diff --git a/vendor/github.com/hashicorp/vault/api/renewer.go b/vendor/github.com/hashicorp/vault/api/renewer.go index a2a4b66..1d37a19 100644 --- a/vendor/github.com/hashicorp/vault/api/renewer.go +++ b/vendor/github.com/hashicorp/vault/api/renewer.go @@ -13,9 +13,6 @@ var ( ErrRenewerNotRenewable = errors.New("secret is not renewable") ErrRenewerNoSecretData = errors.New("returned empty secret data") - // DefaultRenewerGrace is the default grace period - DefaultRenewerGrace = 15 * time.Second - // DefaultRenewerRenewBuffer is the default size of the buffer for renew // messages on the channel. DefaultRenewerRenewBuffer = 5 @@ -50,12 +47,13 @@ var ( type Renewer struct { l sync.Mutex - client *Client - secret *Secret - grace time.Duration - random *rand.Rand - doneCh chan error - renewCh chan *RenewOutput + client *Client + secret *Secret + grace time.Duration + random *rand.Rand + increment int + doneCh chan error + renewCh chan *RenewOutput stopped bool stopCh chan struct{} @@ -66,9 +64,7 @@ type RenewerInput struct { // Secret is the secret to renew Secret *Secret - // Grace is a minimum renewal before returning so the upstream client - // can do a re-read. This can be used to prevent clients from waiting - // too long to read a new credential and incur downtime. + // DEPRECATED: this does not do anything. Grace time.Duration // Rand is the randomizer to use for underlying randomization. If not @@ -79,6 +75,11 @@ type RenewerInput struct { // RenewBuffer is the size of the buffered channel where renew messages are // dispatched. RenewBuffer int + + // The new TTL, in seconds, that should be set on the lease. The TTL set + // here may or may not be honored by the vault server, based on Vault + // configuration or any associated max TTL values. + Increment int } // RenewOutput is the metadata returned to the client (if it's listening) to @@ -104,11 +105,6 @@ func (c *Client) NewRenewer(i *RenewerInput) (*Renewer, error) { return nil, ErrRenewerMissingSecret } - grace := i.Grace - if grace == 0 { - grace = DefaultRenewerGrace - } - random := i.Rand if random == nil { random = rand.New(rand.NewSource(int64(time.Now().Nanosecond()))) @@ -120,12 +116,12 @@ func (c *Client) NewRenewer(i *RenewerInput) (*Renewer, error) { } return &Renewer{ - client: c, - secret: secret, - grace: grace, - random: random, - doneCh: make(chan error, 1), - renewCh: make(chan *RenewOutput, renewBuffer), + client: c, + secret: secret, + increment: i.Increment, + random: random, + doneCh: make(chan error, 1), + renewCh: make(chan *RenewOutput, renewBuffer), stopped: false, stopCh: make(chan struct{}), @@ -155,8 +151,8 @@ func (r *Renewer) Stop() { } // Renew starts a background process for renewing this secret. When the secret -// is has auth data, this attempts to renew the auth (token). When the secret -// has a lease, this attempts to renew the lease. +// has auth data, this attempts to renew the auth (token). When the secret has +// a lease, this attempts to renew the lease. func (r *Renewer) Renew() { var result error if r.secret.Auth != nil { @@ -165,10 +161,7 @@ func (r *Renewer) Renew() { result = r.renewLease() } - select { - case r.doneCh <- result: - case <-r.stopCh: - } + r.doneCh <- result } // renewAuth is a helper for renewing authentication. @@ -177,6 +170,9 @@ func (r *Renewer) renewAuth() error { return ErrRenewerNotRenewable } + priorDuration := time.Duration(r.secret.Auth.LeaseDuration) * time.Second + r.calculateGrace(priorDuration) + client, token := r.client, r.secret.Auth.ClientToken for { @@ -188,7 +184,7 @@ func (r *Renewer) renewAuth() error { } // Renew the auth. - renewal, err := client.Auth().Token().RenewTokenAsSelf(token, 0) + renewal, err := client.Auth().Token().RenewTokenAsSelf(token, r.increment) if err != nil { return err } @@ -209,13 +205,28 @@ func (r *Renewer) renewAuth() error { return ErrRenewerNotRenewable } - // Grab the lease duration and sleep duration - note that we grab the auth - // lease duration, not the secret lease duration. + // Grab the lease duration leaseDuration := time.Duration(renewal.Auth.LeaseDuration) * time.Second - sleepDuration := r.sleepDuration(leaseDuration) - // If we are within grace, return now. - if leaseDuration <= r.grace || sleepDuration <= r.grace { + // We keep evaluating a new grace period so long as the lease is + // extending. Once it stops extending, we've hit the max and need to + // rely on the grace duration. + if leaseDuration > priorDuration { + r.calculateGrace(leaseDuration) + } + priorDuration = leaseDuration + + // The sleep duration is set to 2/3 of the current lease duration plus + // 1/3 of the current grace period, which adds jitter. + sleepDuration := time.Duration(float64(leaseDuration.Nanoseconds())*2/3 + float64(r.grace.Nanoseconds())/3) + + // If we are within grace, return now; or, if the amount of time we + // would sleep would land us in the grace period. This helps with short + // tokens; for example, you don't want a current lease duration of 4 + // seconds, a grace period of 3 seconds, and end up sleeping for more + // than three of those seconds and having a very small budget of time + // to renew. + if leaseDuration <= r.grace || leaseDuration-sleepDuration <= r.grace { return nil } @@ -234,6 +245,9 @@ func (r *Renewer) renewLease() error { return ErrRenewerNotRenewable } + priorDuration := time.Duration(r.secret.LeaseDuration) * time.Second + r.calculateGrace(priorDuration) + client, leaseID := r.client, r.secret.LeaseID for { @@ -245,7 +259,7 @@ func (r *Renewer) renewLease() error { } // Renew the lease. - renewal, err := client.Sys().Renew(leaseID, 0) + renewal, err := client.Sys().Renew(leaseID, r.increment) if err != nil { return err } @@ -266,12 +280,28 @@ func (r *Renewer) renewLease() error { return ErrRenewerNotRenewable } - // Grab the lease duration and sleep duration + // Grab the lease duration leaseDuration := time.Duration(renewal.LeaseDuration) * time.Second - sleepDuration := r.sleepDuration(leaseDuration) - // If we are within grace, return now. - if leaseDuration <= r.grace || sleepDuration <= r.grace { + // We keep evaluating a new grace period so long as the lease is + // extending. Once it stops extending, we've hit the max and need to + // rely on the grace duration. + if leaseDuration > priorDuration { + r.calculateGrace(leaseDuration) + } + priorDuration = leaseDuration + + // The sleep duration is set to 2/3 of the current lease duration plus + // 1/3 of the current grace period, which adds jitter. + sleepDuration := time.Duration(float64(leaseDuration.Nanoseconds())*2/3 + float64(r.grace.Nanoseconds())/3) + + // If we are within grace, return now; or, if the amount of time we + // would sleep would land us in the grace period. This helps with short + // tokens; for example, you don't want a current lease duration of 4 + // seconds, a grace period of 3 seconds, and end up sleeping for more + // than three of those seconds and having a very small budget of time + // to renew. + if leaseDuration <= r.grace || leaseDuration-sleepDuration <= r.grace { return nil } @@ -300,3 +330,20 @@ func (r *Renewer) sleepDuration(base time.Duration) time.Duration { return time.Duration(sleep) } + +// calculateGrace calculates the grace period based on a reasonable set of +// assumptions given the total lease time; it also adds some jitter to not have +// clients be in sync. +func (r *Renewer) calculateGrace(leaseDuration time.Duration) { + if leaseDuration == 0 { + r.grace = 0 + return + } + + leaseNanos := float64(leaseDuration.Nanoseconds()) + jitterMax := 0.1 * leaseNanos + + // For a given lease duration, we want to allow 80-90% of that to elapse, + // so the remaining amount is the grace period + r.grace = time.Duration(jitterMax) + time.Duration(uint64(r.random.Int63())%uint64(jitterMax)) +} diff --git a/vendor/github.com/hashicorp/vault/api/request.go b/vendor/github.com/hashicorp/vault/api/request.go index 83a28bd..5bcff8c 100644 --- a/vendor/github.com/hashicorp/vault/api/request.go +++ b/vendor/github.com/hashicorp/vault/api/request.go @@ -4,54 +4,108 @@ import ( "bytes" "encoding/json" "io" + "io/ioutil" "net/http" "net/url" + + retryablehttp "github.com/hashicorp/go-retryablehttp" ) // Request is a raw request configuration structure used to initiate // API requests to the Vault server. type Request struct { - Method string - URL *url.URL - Params url.Values - Headers http.Header - ClientToken string - WrapTTL string - Obj interface{} - Body io.Reader - BodySize int64 + Method string + URL *url.URL + Params url.Values + Headers http.Header + ClientToken string + MFAHeaderVals []string + WrapTTL string + Obj interface{} + + // When possible, use BodyBytes as it is more efficient due to how the + // retry logic works + BodyBytes []byte + + // Fallback + Body io.Reader + BodySize int64 + + // Whether to request overriding soft-mandatory Sentinel policies (RGPs and + // EGPs). If set, the override flag will take effect for all policies + // evaluated during the request. + PolicyOverride bool } // SetJSONBody is used to set a request body that is a JSON-encoded value. func (r *Request) SetJSONBody(val interface{}) error { - buf := bytes.NewBuffer(nil) - enc := json.NewEncoder(buf) - if err := enc.Encode(val); err != nil { + buf, err := json.Marshal(val) + if err != nil { return err } r.Obj = val - r.Body = buf - r.BodySize = int64(buf.Len()) + r.BodyBytes = buf return nil } // ResetJSONBody is used to reset the body for a redirect func (r *Request) ResetJSONBody() error { - if r.Body == nil { + if r.BodyBytes == nil { return nil } return r.SetJSONBody(r.Obj) } -// ToHTTP turns this request into a valid *http.Request for use with the -// net/http package. +// DEPRECATED: ToHTTP turns this request into a valid *http.Request for use +// with the net/http package. func (r *Request) ToHTTP() (*http.Request, error) { + req, err := r.toRetryableHTTP() + if err != nil { + return nil, err + } + + switch { + case r.BodyBytes == nil && r.Body == nil: + // No body + + case r.BodyBytes != nil: + req.Request.Body = ioutil.NopCloser(bytes.NewReader(r.BodyBytes)) + + default: + if c, ok := r.Body.(io.ReadCloser); ok { + req.Request.Body = c + } else { + req.Request.Body = ioutil.NopCloser(r.Body) + } + } + + return req.Request, nil +} + +func (r *Request) toRetryableHTTP() (*retryablehttp.Request, error) { // Encode the query parameters r.URL.RawQuery = r.Params.Encode() - // Create the HTTP request - req, err := http.NewRequest(r.Method, r.URL.RequestURI(), r.Body) + // Create the HTTP request, defaulting to retryable + var req *retryablehttp.Request + + var err error + var body interface{} + + switch { + case r.BodyBytes == nil && r.Body == nil: + // No body + + case r.BodyBytes != nil: + // Use bytes, it's more efficient + body = r.BodyBytes + + default: + body = r.Body + } + + req, err = retryablehttp.NewRequest(r.Method, r.URL.RequestURI(), body) if err != nil { return nil, err } @@ -77,5 +131,15 @@ func (r *Request) ToHTTP() (*http.Request, error) { req.Header.Set("X-Vault-Wrap-TTL", r.WrapTTL) } + if len(r.MFAHeaderVals) != 0 { + for _, mfaHeaderVal := range r.MFAHeaderVals { + req.Header.Add("X-Vault-MFA", mfaHeaderVal) + } + } + + if r.PolicyOverride { + req.Header.Set("X-Vault-Policy-Override", "true") + } + return req, nil } diff --git a/vendor/github.com/hashicorp/vault/api/response.go b/vendor/github.com/hashicorp/vault/api/response.go index 05502e1..053a277 100644 --- a/vendor/github.com/hashicorp/vault/api/response.go +++ b/vendor/github.com/hashicorp/vault/api/response.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "io" + "io/ioutil" "net/http" "github.com/hashicorp/vault/helper/jsonutil" @@ -33,11 +34,14 @@ func (r *Response) Error() error { // We have an error. Let's copy the body into our own buffer first, // so that if we can't decode JSON, we can at least copy it raw. - var bodyBuf bytes.Buffer - if _, err := io.Copy(&bodyBuf, r.Body); err != nil { + bodyBuf := &bytes.Buffer{} + if _, err := io.Copy(bodyBuf, r.Body); err != nil { return err } + r.Body.Close() + r.Body = ioutil.NopCloser(bodyBuf) + // Decode the error response if we can. Note that we wrap the bodyBuf // in a bytes.Reader here so that the JSON decoder doesn't move the // read pointer for the original buffer. diff --git a/vendor/github.com/hashicorp/vault/api/secret.go b/vendor/github.com/hashicorp/vault/api/secret.go index 7478a0c..b6517c4 100644 --- a/vendor/github.com/hashicorp/vault/api/secret.go +++ b/vendor/github.com/hashicorp/vault/api/secret.go @@ -1,10 +1,13 @@ package api import ( + "fmt" "io" "time" + "github.com/hashicorp/errwrap" "github.com/hashicorp/vault/helper/jsonutil" + "github.com/hashicorp/vault/helper/parseutil" ) // Secret is the structure returned for every secret within Vault. @@ -35,11 +38,245 @@ type Secret struct { WrapInfo *SecretWrapInfo `json:"wrap_info,omitempty"` } +// TokenID returns the standardized token ID (token) for the given secret. +func (s *Secret) TokenID() (string, error) { + if s == nil { + return "", nil + } + + if s.Auth != nil && len(s.Auth.ClientToken) > 0 { + return s.Auth.ClientToken, nil + } + + if s.Data == nil || s.Data["id"] == nil { + return "", nil + } + + id, ok := s.Data["id"].(string) + if !ok { + return "", fmt.Errorf("token found but in the wrong format") + } + + return id, nil +} + +// TokenAccessor returns the standardized token accessor for the given secret. +// If the secret is nil or does not contain an accessor, this returns the empty +// string. +func (s *Secret) TokenAccessor() (string, error) { + if s == nil { + return "", nil + } + + if s.Auth != nil && len(s.Auth.Accessor) > 0 { + return s.Auth.Accessor, nil + } + + if s.Data == nil || s.Data["accessor"] == nil { + return "", nil + } + + accessor, ok := s.Data["accessor"].(string) + if !ok { + return "", fmt.Errorf("token found but in the wrong format") + } + + return accessor, nil +} + +// TokenRemainingUses returns the standardized remaining uses for the given +// secret. If the secret is nil or does not contain the "num_uses", this +// returns -1. On error, this will return -1 and a non-nil error. +func (s *Secret) TokenRemainingUses() (int, error) { + if s == nil || s.Data == nil || s.Data["num_uses"] == nil { + return -1, nil + } + + uses, err := parseutil.ParseInt(s.Data["num_uses"]) + if err != nil { + return 0, err + } + + return int(uses), nil +} + +// TokenPolicies returns the standardized list of policies for the given secret. +// If the secret is nil or does not contain any policies, this returns nil. It +// also populates the secret's Auth info with identity/token policy info. +func (s *Secret) TokenPolicies() ([]string, error) { + if s == nil { + return nil, nil + } + + if s.Auth != nil && len(s.Auth.Policies) > 0 { + return s.Auth.Policies, nil + } + + if s.Data == nil || s.Data["policies"] == nil { + return nil, nil + } + + var tokenPolicies []string + + // Token policies + { + _, ok := s.Data["policies"] + if !ok { + goto TOKEN_DONE + } + + sList, ok := s.Data["policies"].([]string) + if ok { + tokenPolicies = sList + goto TOKEN_DONE + } + + list, ok := s.Data["policies"].([]interface{}) + if !ok { + return nil, fmt.Errorf("unable to convert token policies to expected format") + } + for _, v := range list { + p, ok := v.(string) + if !ok { + return nil, fmt.Errorf("unable to convert policy %v to string", v) + } + tokenPolicies = append(tokenPolicies, p) + } + } + +TOKEN_DONE: + var identityPolicies []string + + // Identity policies + { + _, ok := s.Data["identity_policies"] + if !ok { + goto DONE + } + + sList, ok := s.Data["identity_policies"].([]string) + if ok { + identityPolicies = sList + goto DONE + } + + list, ok := s.Data["identity_policies"].([]interface{}) + if !ok { + return nil, fmt.Errorf("unable to convert identity policies to expected format") + } + for _, v := range list { + p, ok := v.(string) + if !ok { + return nil, fmt.Errorf("unable to convert policy %v to string", v) + } + identityPolicies = append(identityPolicies, p) + } + } + +DONE: + + if s.Auth == nil { + s.Auth = &SecretAuth{} + } + + policies := append(tokenPolicies, identityPolicies...) + + s.Auth.TokenPolicies = tokenPolicies + s.Auth.IdentityPolicies = identityPolicies + s.Auth.Policies = policies + + return policies, nil +} + +// TokenMetadata returns the map of metadata associated with this token, if any +// exists. If the secret is nil or does not contain the "metadata" key, this +// returns nil. +func (s *Secret) TokenMetadata() (map[string]string, error) { + if s == nil { + return nil, nil + } + + if s.Auth != nil && len(s.Auth.Metadata) > 0 { + return s.Auth.Metadata, nil + } + + if s.Data == nil || (s.Data["metadata"] == nil && s.Data["meta"] == nil) { + return nil, nil + } + + data, ok := s.Data["metadata"].(map[string]interface{}) + if !ok { + data, ok = s.Data["meta"].(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("unable to convert metadata field to expected format") + } + } + + metadata := make(map[string]string, len(data)) + for k, v := range data { + typed, ok := v.(string) + if !ok { + return nil, fmt.Errorf("unable to convert metadata value %v to string", v) + } + metadata[k] = typed + } + + return metadata, nil +} + +// TokenIsRenewable returns the standardized token renewability for the given +// secret. If the secret is nil or does not contain the "renewable" key, this +// returns false. +func (s *Secret) TokenIsRenewable() (bool, error) { + if s == nil { + return false, nil + } + + if s.Auth != nil && s.Auth.Renewable { + return s.Auth.Renewable, nil + } + + if s.Data == nil || s.Data["renewable"] == nil { + return false, nil + } + + renewable, err := parseutil.ParseBool(s.Data["renewable"]) + if err != nil { + return false, errwrap.Wrapf("could not convert renewable value to a boolean: {{err}}", err) + } + + return renewable, nil +} + +// TokenTTL returns the standardized remaining token TTL for the given secret. +// If the secret is nil or does not contain a TTL, this returns 0. +func (s *Secret) TokenTTL() (time.Duration, error) { + if s == nil { + return 0, nil + } + + if s.Auth != nil && s.Auth.LeaseDuration > 0 { + return time.Duration(s.Auth.LeaseDuration) * time.Second, nil + } + + if s.Data == nil || s.Data["ttl"] == nil { + return 0, nil + } + + ttl, err := parseutil.ParseDurationSecond(s.Data["ttl"]) + if err != nil { + return 0, err + } + + return ttl, nil +} + // SecretWrapInfo contains wrapping information if we have it. If what is // contained is an authentication token, the accessor for the token will be // available in WrappedAccessor. type SecretWrapInfo struct { Token string `json:"token"` + Accessor string `json:"accessor"` TTL int `json:"ttl"` CreationTime time.Time `json:"creation_time"` CreationPath string `json:"creation_path"` @@ -48,10 +285,12 @@ type SecretWrapInfo struct { // SecretAuth is the structure containing auth information if we have it. type SecretAuth struct { - ClientToken string `json:"client_token"` - Accessor string `json:"accessor"` - Policies []string `json:"policies"` - Metadata map[string]string `json:"metadata"` + ClientToken string `json:"client_token"` + Accessor string `json:"accessor"` + Policies []string `json:"policies"` + TokenPolicies []string `json:"token_policies"` + IdentityPolicies []string `json:"identity_policies"` + Metadata map[string]string `json:"metadata"` LeaseDuration int `json:"lease_duration"` Renewable bool `json:"renewable"` diff --git a/vendor/github.com/hashicorp/vault/api/ssh_agent.go b/vendor/github.com/hashicorp/vault/api/ssh_agent.go index 729fd99..032fb43 100644 --- a/vendor/github.com/hashicorp/vault/api/ssh_agent.go +++ b/vendor/github.com/hashicorp/vault/api/ssh_agent.go @@ -7,11 +7,13 @@ import ( "io/ioutil" "os" + "github.com/hashicorp/errwrap" "github.com/hashicorp/go-cleanhttp" "github.com/hashicorp/go-multierror" "github.com/hashicorp/go-rootcerts" "github.com/hashicorp/hcl" "github.com/hashicorp/hcl/hcl/ast" + "github.com/hashicorp/vault/helper/hclutil" "github.com/mitchellh/mapstructure" ) @@ -41,16 +43,16 @@ type SSHHelper struct { type SSHVerifyResponse struct { // Usually empty. If the request OTP is echo request message, this will // be set to the corresponding echo response message. - Message string `json:"message" structs:"message" mapstructure:"message"` + Message string `json:"message" mapstructure:"message"` // Username associated with the OTP - Username string `json:"username" structs:"username" mapstructure:"username"` + Username string `json:"username" mapstructure:"username"` // IP associated with the OTP - IP string `json:"ip" structs:"ip" mapstructure:"ip"` + IP string `json:"ip" mapstructure:"ip"` // Name of the role against which the OTP was issued - RoleName string `json:"role_name" structs:"role_name" mapstructure:"role_name"` + RoleName string `json:"role_name" mapstructure:"role_name"` } // SSHHelperConfig is a structure which represents the entries from the vault-ssh-helper's configuration file. @@ -141,12 +143,12 @@ func LoadSSHHelperConfig(path string) (*SSHHelperConfig, error) { func ParseSSHHelperConfig(contents string) (*SSHHelperConfig, error) { root, err := hcl.Parse(string(contents)) if err != nil { - return nil, fmt.Errorf("ssh_helper: error parsing config: %s", err) + return nil, errwrap.Wrapf("error parsing config: {{err}}", err) } list, ok := root.Node.(*ast.ObjectList) if !ok { - return nil, fmt.Errorf("ssh_helper: error parsing config: file doesn't contain a root object") + return nil, fmt.Errorf("error parsing config: file doesn't contain a root object") } valid := []string{ @@ -159,7 +161,7 @@ func ParseSSHHelperConfig(contents string) (*SSHHelperConfig, error) { "tls_skip_verify", "tls_server_name", } - if err := checkHCLKeys(list, valid); err != nil { + if err := hclutil.CheckHCLKeys(list, valid); err != nil { return nil, multierror.Prefix(err, "ssh_helper:") } @@ -170,7 +172,7 @@ func ParseSSHHelperConfig(contents string) (*SSHHelperConfig, error) { } if c.VaultAddr == "" { - return nil, fmt.Errorf("ssh_helper: missing config 'vault_addr'") + return nil, fmt.Errorf(`missing config "vault_addr"`) } return &c, nil } @@ -227,31 +229,3 @@ func (c *SSHHelper) Verify(otp string) (*SSHVerifyResponse, error) { } return &verifyResp, nil } - -func checkHCLKeys(node ast.Node, valid []string) error { - var list *ast.ObjectList - switch n := node.(type) { - case *ast.ObjectList: - list = n - case *ast.ObjectType: - list = n.List - default: - return fmt.Errorf("cannot check HCL keys of type %T", n) - } - - validMap := make(map[string]struct{}, len(valid)) - for _, v := range valid { - validMap[v] = struct{}{} - } - - var result error - for _, item := range list.Items { - key := item.Keys[0].Token.Value().(string) - if _, ok := validMap[key]; !ok { - result = multierror.Append(result, fmt.Errorf( - "invalid key '%s' on line %d", key, item.Assign.Line)) - } - } - - return result -} diff --git a/vendor/github.com/hashicorp/vault/api/sys_audit.go b/vendor/github.com/hashicorp/vault/api/sys_audit.go index 89f2141..05cd756 100644 --- a/vendor/github.com/hashicorp/vault/api/sys_audit.go +++ b/vendor/github.com/hashicorp/vault/api/sys_audit.go @@ -3,7 +3,6 @@ package api import ( "fmt" - "github.com/fatih/structs" "github.com/mitchellh/mapstructure" ) @@ -83,10 +82,8 @@ func (c *Sys) EnableAudit( } func (c *Sys) EnableAuditWithOptions(path string, options *EnableAuditOptions) error { - body := structs.Map(options) - r := c.c.NewRequest("PUT", fmt.Sprintf("/v1/sys/audit/%s", path)) - if err := r.SetJSONBody(body); err != nil { + if err := r.SetJSONBody(options); err != nil { return err } @@ -113,10 +110,10 @@ func (c *Sys) DisableAudit(path string) error { // documentation. Please refer to that documentation for more details. type EnableAuditOptions struct { - Type string `json:"type" structs:"type"` - Description string `json:"description" structs:"description"` - Options map[string]string `json:"options" structs:"options"` - Local bool `json:"local" structs:"local"` + Type string `json:"type"` + Description string `json:"description"` + Options map[string]string `json:"options"` + Local bool `json:"local"` } type Audit struct { diff --git a/vendor/github.com/hashicorp/vault/api/sys_auth.go b/vendor/github.com/hashicorp/vault/api/sys_auth.go index 32f4bbd..0b1a319 100644 --- a/vendor/github.com/hashicorp/vault/api/sys_auth.go +++ b/vendor/github.com/hashicorp/vault/api/sys_auth.go @@ -3,7 +3,6 @@ package api import ( "fmt" - "github.com/fatih/structs" "github.com/mitchellh/mapstructure" ) @@ -52,10 +51,8 @@ func (c *Sys) EnableAuth(path, authType, desc string) error { } func (c *Sys) EnableAuthWithOptions(path string, options *EnableAuthOptions) error { - body := structs.Map(options) - r := c.c.NewRequest("POST", fmt.Sprintf("/v1/sys/auth/%s", path)) - if err := r.SetJSONBody(body); err != nil { + if err := r.SetJSONBody(options); err != nil { return err } @@ -78,31 +75,45 @@ func (c *Sys) DisableAuth(path string) error { } // Structures for the requests/resposne are all down here. They aren't -// individually documentd because the map almost directly to the raw HTTP API +// individually documented because the map almost directly to the raw HTTP API // documentation. Please refer to that documentation for more details. type EnableAuthOptions struct { - Type string `json:"type" structs:"type"` - Description string `json:"description" structs:"description"` - Config AuthConfigInput `json:"config" structs:"config"` - Local bool `json:"local" structs:"local"` - PluginName string `json:"plugin_name,omitempty" structs:"plugin_name,omitempty"` + Type string `json:"type"` + Description string `json:"description"` + Config AuthConfigInput `json:"config"` + Local bool `json:"local"` + PluginName string `json:"plugin_name,omitempty"` + SealWrap bool `json:"seal_wrap" mapstructure:"seal_wrap"` + Options map[string]string `json:"options" mapstructure:"options"` } type AuthConfigInput struct { - PluginName string `json:"plugin_name,omitempty" structs:"plugin_name,omitempty" mapstructure:"plugin_name"` + DefaultLeaseTTL string `json:"default_lease_ttl" mapstructure:"default_lease_ttl"` + MaxLeaseTTL string `json:"max_lease_ttl" mapstructure:"max_lease_ttl"` + PluginName string `json:"plugin_name,omitempty" mapstructure:"plugin_name"` + AuditNonHMACRequestKeys []string `json:"audit_non_hmac_request_keys,omitempty" mapstructure:"audit_non_hmac_request_keys"` + AuditNonHMACResponseKeys []string `json:"audit_non_hmac_response_keys,omitempty" mapstructure:"audit_non_hmac_response_keys"` + ListingVisibility string `json:"listing_visibility,omitempty" mapstructure:"listing_visibility"` + PassthroughRequestHeaders []string `json:"passthrough_request_headers,omitempty" mapstructure:"passthrough_request_headers"` } type AuthMount struct { - Type string `json:"type" structs:"type" mapstructure:"type"` - Description string `json:"description" structs:"description" mapstructure:"description"` - Accessor string `json:"accessor" structs:"accessor" mapstructure:"accessor"` - Config AuthConfigOutput `json:"config" structs:"config" mapstructure:"config"` - Local bool `json:"local" structs:"local" mapstructure:"local"` + Type string `json:"type" mapstructure:"type"` + Description string `json:"description" mapstructure:"description"` + Accessor string `json:"accessor" mapstructure:"accessor"` + Config AuthConfigOutput `json:"config" mapstructure:"config"` + Local bool `json:"local" mapstructure:"local"` + SealWrap bool `json:"seal_wrap" mapstructure:"seal_wrap"` + Options map[string]string `json:"options" mapstructure:"options"` } type AuthConfigOutput struct { - DefaultLeaseTTL int `json:"default_lease_ttl" structs:"default_lease_ttl" mapstructure:"default_lease_ttl"` - MaxLeaseTTL int `json:"max_lease_ttl" structs:"max_lease_ttl" mapstructure:"max_lease_ttl"` - PluginName string `json:"plugin_name,omitempty" structs:"plugin_name,omitempty" mapstructure:"plugin_name"` + DefaultLeaseTTL int `json:"default_lease_ttl" mapstructure:"default_lease_ttl"` + MaxLeaseTTL int `json:"max_lease_ttl" mapstructure:"max_lease_ttl"` + PluginName string `json:"plugin_name,omitempty" mapstructure:"plugin_name"` + AuditNonHMACRequestKeys []string `json:"audit_non_hmac_request_keys,omitempty" mapstructure:"audit_non_hmac_request_keys"` + AuditNonHMACResponseKeys []string `json:"audit_non_hmac_response_keys,omitempty" mapstructure:"audit_non_hmac_response_keys"` + ListingVisibility string `json:"listing_visibility,omitempty" mapstructure:"listing_visibility"` + PassthroughRequestHeaders []string `json:"passthrough_request_headers,omitempty" mapstructure:"passthrough_request_headers"` } diff --git a/vendor/github.com/hashicorp/vault/api/sys_capabilities.go b/vendor/github.com/hashicorp/vault/api/sys_capabilities.go index 80f6218..cbb3a72 100644 --- a/vendor/github.com/hashicorp/vault/api/sys_capabilities.go +++ b/vendor/github.com/hashicorp/vault/api/sys_capabilities.go @@ -34,8 +34,14 @@ func (c *Sys) Capabilities(token, path string) ([]string, error) { return nil, err } + if result["capabilities"] == nil { + return nil, nil + } var capabilities []string - capabilitiesRaw := result["capabilities"].([]interface{}) + capabilitiesRaw, ok := result["capabilities"].([]interface{}) + if !ok { + return nil, fmt.Errorf("error interpreting returned capabilities") + } for _, capability := range capabilitiesRaw { capabilities = append(capabilities, capability.(string)) } diff --git a/vendor/github.com/hashicorp/vault/api/sys_generate_root.go b/vendor/github.com/hashicorp/vault/api/sys_generate_root.go index 8dc2095..adb5496 100644 --- a/vendor/github.com/hashicorp/vault/api/sys_generate_root.go +++ b/vendor/github.com/hashicorp/vault/api/sys_generate_root.go @@ -1,7 +1,15 @@ package api func (c *Sys) GenerateRootStatus() (*GenerateRootStatusResponse, error) { - r := c.c.NewRequest("GET", "/v1/sys/generate-root/attempt") + return c.generateRootStatusCommon("/v1/sys/generate-root/attempt") +} + +func (c *Sys) GenerateDROperationTokenStatus() (*GenerateRootStatusResponse, error) { + return c.generateRootStatusCommon("/v1/sys/replication/dr/secondary/generate-operation-token/attempt") +} + +func (c *Sys) generateRootStatusCommon(path string) (*GenerateRootStatusResponse, error) { + r := c.c.NewRequest("GET", path) resp, err := c.c.RawRequest(r) if err != nil { return nil, err @@ -14,12 +22,20 @@ func (c *Sys) GenerateRootStatus() (*GenerateRootStatusResponse, error) { } func (c *Sys) GenerateRootInit(otp, pgpKey string) (*GenerateRootStatusResponse, error) { + return c.generateRootInitCommon("/v1/sys/generate-root/attempt", otp, pgpKey) +} + +func (c *Sys) GenerateDROperationTokenInit(otp, pgpKey string) (*GenerateRootStatusResponse, error) { + return c.generateRootInitCommon("/v1/sys/replication/dr/secondary/generate-operation-token/attempt", otp, pgpKey) +} + +func (c *Sys) generateRootInitCommon(path, otp, pgpKey string) (*GenerateRootStatusResponse, error) { body := map[string]interface{}{ "otp": otp, "pgp_key": pgpKey, } - r := c.c.NewRequest("PUT", "/v1/sys/generate-root/attempt") + r := c.c.NewRequest("PUT", path) if err := r.SetJSONBody(body); err != nil { return nil, err } @@ -36,7 +52,15 @@ func (c *Sys) GenerateRootInit(otp, pgpKey string) (*GenerateRootStatusResponse, } func (c *Sys) GenerateRootCancel() error { - r := c.c.NewRequest("DELETE", "/v1/sys/generate-root/attempt") + return c.generateRootCancelCommon("/v1/sys/generate-root/attempt") +} + +func (c *Sys) GenerateDROperationTokenCancel() error { + return c.generateRootCancelCommon("/v1/sys/replication/dr/secondary/generate-operation-token/attempt") +} + +func (c *Sys) generateRootCancelCommon(path string) error { + r := c.c.NewRequest("DELETE", path) resp, err := c.c.RawRequest(r) if err == nil { defer resp.Body.Close() @@ -45,12 +69,20 @@ func (c *Sys) GenerateRootCancel() error { } func (c *Sys) GenerateRootUpdate(shard, nonce string) (*GenerateRootStatusResponse, error) { + return c.generateRootUpdateCommon("/v1/sys/generate-root/update", shard, nonce) +} + +func (c *Sys) GenerateDROperationTokenUpdate(shard, nonce string) (*GenerateRootStatusResponse, error) { + return c.generateRootUpdateCommon("/v1/sys/replication/dr/secondary/generate-operation-token/update", shard, nonce) +} + +func (c *Sys) generateRootUpdateCommon(path, shard, nonce string) (*GenerateRootStatusResponse, error) { body := map[string]interface{}{ "key": shard, "nonce": nonce, } - r := c.c.NewRequest("PUT", "/v1/sys/generate-root/update") + r := c.c.NewRequest("PUT", path) if err := r.SetJSONBody(body); err != nil { return nil, err } @@ -67,11 +99,12 @@ func (c *Sys) GenerateRootUpdate(shard, nonce string) (*GenerateRootStatusRespon } type GenerateRootStatusResponse struct { - Nonce string - Started bool - Progress int - Required int - Complete bool + Nonce string `json:"nonce"` + Started bool `json:"started"` + Progress int `json:"progress"` + Required int `json:"required"` + Complete bool `json:"complete"` + EncodedToken string `json:"encoded_token"` EncodedRootToken string `json:"encoded_root_token"` PGPFingerprint string `json:"pgp_fingerprint"` } diff --git a/vendor/github.com/hashicorp/vault/api/sys_health.go b/vendor/github.com/hashicorp/vault/api/sys_health.go index 822354c..82fd1f6 100644 --- a/vendor/github.com/hashicorp/vault/api/sys_health.go +++ b/vendor/github.com/hashicorp/vault/api/sys_health.go @@ -5,8 +5,10 @@ func (c *Sys) Health() (*HealthResponse, error) { // If the code is 400 or above it will automatically turn into an error, // but the sys/health API defaults to returning 5xx when not sealed or // inited, so we force this code to be something else so we parse correctly - r.Params.Add("sealedcode", "299") r.Params.Add("uninitcode", "299") + r.Params.Add("sealedcode", "299") + r.Params.Add("standbycode", "299") + r.Params.Add("drsecondarycode", "299") resp, err := c.c.RawRequest(r) if err != nil { return nil, err @@ -19,11 +21,13 @@ func (c *Sys) Health() (*HealthResponse, error) { } type HealthResponse struct { - Initialized bool `json:"initialized"` - Sealed bool `json:"sealed"` - Standby bool `json:"standby"` - ServerTimeUTC int64 `json:"server_time_utc"` - Version string `json:"version"` - ClusterName string `json:"cluster_name,omitempty"` - ClusterID string `json:"cluster_id,omitempty"` + Initialized bool `json:"initialized"` + Sealed bool `json:"sealed"` + Standby bool `json:"standby"` + ReplicationPerformanceMode string `json:"replication_performance_mode"` + ReplicationDRMode string `json:"replication_dr_mode"` + ServerTimeUTC int64 `json:"server_time_utc"` + Version string `json:"version"` + ClusterName string `json:"cluster_name,omitempty"` + ClusterID string `json:"cluster_id,omitempty"` } diff --git a/vendor/github.com/hashicorp/vault/api/sys_mounts.go b/vendor/github.com/hashicorp/vault/api/sys_mounts.go index 091a8f6..8ac5b45 100644 --- a/vendor/github.com/hashicorp/vault/api/sys_mounts.go +++ b/vendor/github.com/hashicorp/vault/api/sys_mounts.go @@ -3,7 +3,6 @@ package api import ( "fmt" - "github.com/fatih/structs" "github.com/mitchellh/mapstructure" ) @@ -44,10 +43,8 @@ func (c *Sys) ListMounts() (map[string]*MountOutput, error) { } func (c *Sys) Mount(path string, mountInfo *MountInput) error { - body := structs.Map(mountInfo) - r := c.c.NewRequest("POST", fmt.Sprintf("/v1/sys/mounts/%s", path)) - if err := r.SetJSONBody(body); err != nil { + if err := r.SetJSONBody(mountInfo); err != nil { return err } @@ -88,9 +85,8 @@ func (c *Sys) Remount(from, to string) error { } func (c *Sys) TuneMount(path string, config MountConfigInput) error { - body := structs.Map(config) r := c.c.NewRequest("POST", fmt.Sprintf("/v1/sys/mounts/%s/tune", path)) - if err := r.SetJSONBody(body); err != nil { + if err := r.SetJSONBody(config); err != nil { return err } @@ -120,31 +116,44 @@ func (c *Sys) MountConfig(path string) (*MountConfigOutput, error) { } type MountInput struct { - Type string `json:"type" structs:"type"` - Description string `json:"description" structs:"description"` - Config MountConfigInput `json:"config" structs:"config"` - Local bool `json:"local" structs:"local"` - PluginName string `json:"plugin_name,omitempty" structs:"plugin_name"` + Type string `json:"type"` + Description string `json:"description"` + Config MountConfigInput `json:"config"` + Options map[string]string `json:"options"` + Local bool `json:"local"` + PluginName string `json:"plugin_name,omitempty"` + SealWrap bool `json:"seal_wrap" mapstructure:"seal_wrap"` } type MountConfigInput struct { - DefaultLeaseTTL string `json:"default_lease_ttl" structs:"default_lease_ttl" mapstructure:"default_lease_ttl"` - MaxLeaseTTL string `json:"max_lease_ttl" structs:"max_lease_ttl" mapstructure:"max_lease_ttl"` - ForceNoCache bool `json:"force_no_cache" structs:"force_no_cache" mapstructure:"force_no_cache"` - PluginName string `json:"plugin_name,omitempty" structs:"plugin_name,omitempty" mapstructure:"plugin_name"` + Options map[string]string `json:"options" mapstructure:"options"` + DefaultLeaseTTL string `json:"default_lease_ttl" mapstructure:"default_lease_ttl"` + MaxLeaseTTL string `json:"max_lease_ttl" mapstructure:"max_lease_ttl"` + ForceNoCache bool `json:"force_no_cache" mapstructure:"force_no_cache"` + PluginName string `json:"plugin_name,omitempty" mapstructure:"plugin_name"` + AuditNonHMACRequestKeys []string `json:"audit_non_hmac_request_keys,omitempty" mapstructure:"audit_non_hmac_request_keys"` + AuditNonHMACResponseKeys []string `json:"audit_non_hmac_response_keys,omitempty" mapstructure:"audit_non_hmac_response_keys"` + ListingVisibility string `json:"listing_visibility,omitempty" mapstructure:"listing_visibility"` + PassthroughRequestHeaders []string `json:"passthrough_request_headers,omitempty" mapstructure:"passthrough_request_headers"` } type MountOutput struct { - Type string `json:"type" structs:"type"` - Description string `json:"description" structs:"description"` - Accessor string `json:"accessor" structs:"accessor"` - Config MountConfigOutput `json:"config" structs:"config"` - Local bool `json:"local" structs:"local"` + Type string `json:"type"` + Description string `json:"description"` + Accessor string `json:"accessor"` + Config MountConfigOutput `json:"config"` + Options map[string]string `json:"options"` + Local bool `json:"local"` + SealWrap bool `json:"seal_wrap" mapstructure:"seal_wrap"` } type MountConfigOutput struct { - DefaultLeaseTTL int `json:"default_lease_ttl" structs:"default_lease_ttl" mapstructure:"default_lease_ttl"` - MaxLeaseTTL int `json:"max_lease_ttl" structs:"max_lease_ttl" mapstructure:"max_lease_ttl"` - ForceNoCache bool `json:"force_no_cache" structs:"force_no_cache" mapstructure:"force_no_cache"` - PluginName string `json:"plugin_name,omitempty" structs:"plugin_name,omitempty" mapstructure:"plugin_name"` + DefaultLeaseTTL int `json:"default_lease_ttl" mapstructure:"default_lease_ttl"` + MaxLeaseTTL int `json:"max_lease_ttl" mapstructure:"max_lease_ttl"` + ForceNoCache bool `json:"force_no_cache" mapstructure:"force_no_cache"` + PluginName string `json:"plugin_name,omitempty" mapstructure:"plugin_name"` + AuditNonHMACRequestKeys []string `json:"audit_non_hmac_request_keys,omitempty" mapstructure:"audit_non_hmac_request_keys"` + AuditNonHMACResponseKeys []string `json:"audit_non_hmac_response_keys,omitempty" mapstructure:"audit_non_hmac_response_keys"` + ListingVisibility string `json:"listing_visibility,omitempty" mapstructure:"listing_visibility"` + PassthroughRequestHeaders []string `json:"passthrough_request_headers,omitempty" mapstructure:"passthrough_request_headers"` } diff --git a/vendor/github.com/hashicorp/vault/api/sys_plugins.go b/vendor/github.com/hashicorp/vault/api/sys_plugins.go new file mode 100644 index 0000000..8183b10 --- /dev/null +++ b/vendor/github.com/hashicorp/vault/api/sys_plugins.go @@ -0,0 +1,117 @@ +package api + +import ( + "fmt" + "net/http" +) + +// ListPluginsInput is used as input to the ListPlugins function. +type ListPluginsInput struct{} + +// ListPluginsResponse is the response from the ListPlugins call. +type ListPluginsResponse struct { + // Names is the list of names of the plugins. + Names []string +} + +// ListPlugins lists all plugins in the catalog and returns their names as a +// list of strings. +func (c *Sys) ListPlugins(i *ListPluginsInput) (*ListPluginsResponse, error) { + path := "/v1/sys/plugins/catalog" + req := c.c.NewRequest("LIST", path) + resp, err := c.c.RawRequest(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var result struct { + Data struct { + Keys []string `json:"keys"` + } `json:"data"` + } + if err := resp.DecodeJSON(&result); err != nil { + return nil, err + } + + return &ListPluginsResponse{Names: result.Data.Keys}, nil +} + +// GetPluginInput is used as input to the GetPlugin function. +type GetPluginInput struct { + Name string `json:"-"` +} + +// GetPluginResponse is the response from the GetPlugin call. +type GetPluginResponse struct { + Args []string `json:"args"` + Builtin bool `json:"builtin"` + Command string `json:"command"` + Name string `json:"name"` + SHA256 string `json:"sha256"` +} + +func (c *Sys) GetPlugin(i *GetPluginInput) (*GetPluginResponse, error) { + path := fmt.Sprintf("/v1/sys/plugins/catalog/%s", i.Name) + req := c.c.NewRequest(http.MethodGet, path) + resp, err := c.c.RawRequest(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var result GetPluginResponse + err = resp.DecodeJSON(&result) + if err != nil { + return nil, err + } + return &result, err +} + +// RegisterPluginInput is used as input to the RegisterPlugin function. +type RegisterPluginInput struct { + // Name is the name of the plugin. Required. + Name string `json:"-"` + + // Args is the list of args to spawn the process with. + Args []string `json:"args,omitempty"` + + // Command is the command to run. + Command string `json:"command,omitempty"` + + // SHA256 is the shasum of the plugin. + SHA256 string `json:"sha256,omitempty"` +} + +// RegisterPlugin registers the plugin with the given information. +func (c *Sys) RegisterPlugin(i *RegisterPluginInput) error { + path := fmt.Sprintf("/v1/sys/plugins/catalog/%s", i.Name) + req := c.c.NewRequest(http.MethodPut, path) + if err := req.SetJSONBody(i); err != nil { + return err + } + + resp, err := c.c.RawRequest(req) + if err == nil { + defer resp.Body.Close() + } + return err +} + +// DeregisterPluginInput is used as input to the DeregisterPlugin function. +type DeregisterPluginInput struct { + // Name is the name of the plugin. Required. + Name string `json:"-"` +} + +// DeregisterPlugin removes the plugin with the given name from the plugin +// catalog. +func (c *Sys) DeregisterPlugin(i *DeregisterPluginInput) error { + path := fmt.Sprintf("/v1/sys/plugins/catalog/%s", i.Name) + req := c.c.NewRequest(http.MethodDelete, path) + resp, err := c.c.RawRequest(req) + if err == nil { + defer resp.Body.Close() + } + return err +} diff --git a/vendor/github.com/hashicorp/vault/api/sys_policy.go b/vendor/github.com/hashicorp/vault/api/sys_policy.go index ba0e17f..9c9d9c0 100644 --- a/vendor/github.com/hashicorp/vault/api/sys_policy.go +++ b/vendor/github.com/hashicorp/vault/api/sys_policy.go @@ -50,12 +50,14 @@ func (c *Sys) GetPolicy(name string) (string, error) { return "", err } - var ok bool - if _, ok = result["rules"]; !ok { - return "", fmt.Errorf("rules not found in response") + if rulesRaw, ok := result["rules"]; ok { + return rulesRaw.(string), nil + } + if policyRaw, ok := result["policy"]; ok { + return policyRaw.(string), nil } - return result["rules"].(string), nil + return "", fmt.Errorf("no policy found in response") } func (c *Sys) PutPolicy(name, rules string) error { diff --git a/vendor/github.com/hashicorp/vault/api/sys_rekey.go b/vendor/github.com/hashicorp/vault/api/sys_rekey.go index e6d039e..ddeac01 100644 --- a/vendor/github.com/hashicorp/vault/api/sys_rekey.go +++ b/vendor/github.com/hashicorp/vault/api/sys_rekey.go @@ -26,6 +26,32 @@ func (c *Sys) RekeyRecoveryKeyStatus() (*RekeyStatusResponse, error) { return &result, err } +func (c *Sys) RekeyVerificationStatus() (*RekeyVerificationStatusResponse, error) { + r := c.c.NewRequest("GET", "/v1/sys/rekey/verify") + resp, err := c.c.RawRequest(r) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var result RekeyVerificationStatusResponse + err = resp.DecodeJSON(&result) + return &result, err +} + +func (c *Sys) RekeyRecoveryKeyVerificationStatus() (*RekeyVerificationStatusResponse, error) { + r := c.c.NewRequest("GET", "/v1/sys/rekey-recovery-key/verify") + resp, err := c.c.RawRequest(r) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var result RekeyVerificationStatusResponse + err = resp.DecodeJSON(&result) + return &result, err +} + func (c *Sys) RekeyInit(config *RekeyInitRequest) (*RekeyStatusResponse, error) { r := c.c.NewRequest("PUT", "/v1/sys/rekey/init") if err := r.SetJSONBody(config); err != nil { @@ -78,6 +104,24 @@ func (c *Sys) RekeyRecoveryKeyCancel() error { return err } +func (c *Sys) RekeyVerificationCancel() error { + r := c.c.NewRequest("DELETE", "/v1/sys/rekey/verify") + resp, err := c.c.RawRequest(r) + if err == nil { + defer resp.Body.Close() + } + return err +} + +func (c *Sys) RekeyRecoveryKeyVerificationCancel() error { + r := c.c.NewRequest("DELETE", "/v1/sys/rekey-recovery-key/verify") + resp, err := c.c.RawRequest(r) + if err == nil { + defer resp.Body.Close() + } + return err +} + func (c *Sys) RekeyUpdate(shard, nonce string) (*RekeyUpdateResponse, error) { body := map[string]interface{}{ "key": shard, @@ -168,35 +212,98 @@ func (c *Sys) RekeyDeleteRecoveryBackup() error { return err } +func (c *Sys) RekeyVerificationUpdate(shard, nonce string) (*RekeyVerificationUpdateResponse, error) { + body := map[string]interface{}{ + "key": shard, + "nonce": nonce, + } + + r := c.c.NewRequest("PUT", "/v1/sys/rekey/verify") + if err := r.SetJSONBody(body); err != nil { + return nil, err + } + + resp, err := c.c.RawRequest(r) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var result RekeyVerificationUpdateResponse + err = resp.DecodeJSON(&result) + return &result, err +} + +func (c *Sys) RekeyRecoveryKeyVerificationUpdate(shard, nonce string) (*RekeyVerificationUpdateResponse, error) { + body := map[string]interface{}{ + "key": shard, + "nonce": nonce, + } + + r := c.c.NewRequest("PUT", "/v1/sys/rekey-recovery-key/verify") + if err := r.SetJSONBody(body); err != nil { + return nil, err + } + + resp, err := c.c.RawRequest(r) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var result RekeyVerificationUpdateResponse + err = resp.DecodeJSON(&result) + return &result, err +} + type RekeyInitRequest struct { - SecretShares int `json:"secret_shares"` - SecretThreshold int `json:"secret_threshold"` - PGPKeys []string `json:"pgp_keys"` - Backup bool + SecretShares int `json:"secret_shares"` + SecretThreshold int `json:"secret_threshold"` + StoredShares int `json:"stored_shares"` + PGPKeys []string `json:"pgp_keys"` + Backup bool + RequireVerification bool `json:"require_verification"` } type RekeyStatusResponse struct { - Nonce string - Started bool - T int - N int - Progress int - Required int - PGPFingerprints []string `json:"pgp_fingerprints"` - Backup bool + Nonce string `json:"nonce"` + Started bool `json:"started"` + T int `json:"t"` + N int `json:"n"` + Progress int `json:"progress"` + Required int `json:"required"` + PGPFingerprints []string `json:"pgp_fingerprints"` + Backup bool `json:"backup"` + VerificationRequired bool `json:"verification_required"` + VerificationNonce string `json:"verification_nonce"` } type RekeyUpdateResponse struct { - Nonce string - Complete bool - Keys []string - KeysB64 []string `json:"keys_base64"` - PGPFingerprints []string `json:"pgp_fingerprints"` - Backup bool + Nonce string `json:"nonce"` + Complete bool `json:"complete"` + Keys []string `json:"keys"` + KeysB64 []string `json:"keys_base64"` + PGPFingerprints []string `json:"pgp_fingerprints"` + Backup bool `json:"backup"` + VerificationRequired bool `json:"verification_required"` + VerificationNonce string `json:"verification_nonce,omitempty"` } type RekeyRetrieveResponse struct { - Nonce string - Keys map[string][]string + Nonce string `json:"nonce"` + Keys map[string][]string `json:"keys"` KeysB64 map[string][]string `json:"keys_base64"` } + +type RekeyVerificationStatusResponse struct { + Nonce string `json:"nonce"` + Started bool `json:"started"` + T int `json:"t"` + N int `json:"n"` + Progress int `json:"progress"` +} + +type RekeyVerificationUpdateResponse struct { + Nonce string `json:"nonce"` + Complete bool `json:"complete"` +} diff --git a/vendor/github.com/hashicorp/vault/api/sys_seal.go b/vendor/github.com/hashicorp/vault/api/sys_seal.go index 97a49ae..3d594ba 100644 --- a/vendor/github.com/hashicorp/vault/api/sys_seal.go +++ b/vendor/github.com/hashicorp/vault/api/sys_seal.go @@ -49,12 +49,14 @@ func sealStatusRequest(c *Sys, r *Request) (*SealStatusResponse, error) { } type SealStatusResponse struct { - Sealed bool `json:"sealed"` - T int `json:"t"` - N int `json:"n"` - Progress int `json:"progress"` - Nonce string `json:"nonce"` - Version string `json:"version"` - ClusterName string `json:"cluster_name,omitempty"` - ClusterID string `json:"cluster_id,omitempty"` + Type string `json:"type"` + Sealed bool `json:"sealed"` + T int `json:"t"` + N int `json:"n"` + Progress int `json:"progress"` + Nonce string `json:"nonce"` + Version string `json:"version"` + ClusterName string `json:"cluster_name,omitempty"` + ClusterID string `json:"cluster_id,omitempty"` + RecoverySeal bool `json:"recovery_seal"` } diff --git a/vendor/github.com/hashicorp/vault/helper/compressutil/compress.go b/vendor/github.com/hashicorp/vault/helper/compressutil/compress.go index 31a2dcd..a7fb87b 100644 --- a/vendor/github.com/hashicorp/vault/helper/compressutil/compress.go +++ b/vendor/github.com/hashicorp/vault/helper/compressutil/compress.go @@ -8,6 +8,7 @@ import ( "io" "github.com/golang/snappy" + "github.com/hashicorp/errwrap" ) const ( @@ -33,7 +34,7 @@ const ( ) // SnappyReadCloser embeds the snappy reader which implements the io.Reader -// interface. The decompress procedure in this utility expectes an +// interface. The decompress procedure in this utility expects an // io.ReadCloser. This type implements the io.Closer interface to retain the // generic way of decompression. type SnappyReadCloser struct { @@ -107,7 +108,7 @@ func Compress(data []byte, config *CompressionConfig) ([]byte, error) { } if err != nil { - return nil, fmt.Errorf("failed to create a compression writer; err: %v", err) + return nil, errwrap.Wrapf("failed to create a compression writer: {{err}}", err) } if writer == nil { @@ -117,7 +118,7 @@ func Compress(data []byte, config *CompressionConfig) ([]byte, error) { // Compress the input and place it in the same buffer containing the // canary byte. if _, err = writer.Write(data); err != nil { - return nil, fmt.Errorf("failed to compress input data; err: %v", err) + return nil, errwrap.Wrapf("failed to compress input data: err: {{err}}", err) } // Close the io.WriteCloser @@ -172,7 +173,7 @@ func Decompress(data []byte) ([]byte, bool, error) { return nil, true, nil } if err != nil { - return nil, false, fmt.Errorf("failed to create a compression reader; err: %v", err) + return nil, false, errwrap.Wrapf("failed to create a compression reader: {{err}}", err) } if reader == nil { return nil, false, fmt.Errorf("failed to create a compression reader") diff --git a/vendor/github.com/hashicorp/vault/helper/hclutil/hcl.go b/vendor/github.com/hashicorp/vault/helper/hclutil/hcl.go new file mode 100644 index 0000000..0b12036 --- /dev/null +++ b/vendor/github.com/hashicorp/vault/helper/hclutil/hcl.go @@ -0,0 +1,36 @@ +package hclutil + +import ( + "fmt" + + multierror "github.com/hashicorp/go-multierror" + "github.com/hashicorp/hcl/hcl/ast" +) + +// CheckHCLKeys checks whether the keys in the AST list contains any of the valid keys provided. +func CheckHCLKeys(node ast.Node, valid []string) error { + var list *ast.ObjectList + switch n := node.(type) { + case *ast.ObjectList: + list = n + case *ast.ObjectType: + list = n.List + default: + return fmt.Errorf("cannot check HCL keys of type %T", n) + } + + validMap := make(map[string]struct{}, len(valid)) + for _, v := range valid { + validMap[v] = struct{}{} + } + + var result error + for _, item := range list.Items { + key := item.Keys[0].Token.Value().(string) + if _, ok := validMap[key]; !ok { + result = multierror.Append(result, fmt.Errorf("invalid key %q on line %d", key, item.Assign.Line)) + } + } + + return result +} diff --git a/vendor/github.com/hashicorp/vault/helper/jsonutil/json.go b/vendor/github.com/hashicorp/vault/helper/jsonutil/json.go index a96745b..d03ddef 100644 --- a/vendor/github.com/hashicorp/vault/helper/jsonutil/json.go +++ b/vendor/github.com/hashicorp/vault/helper/jsonutil/json.go @@ -7,6 +7,7 @@ import ( "fmt" "io" + "github.com/hashicorp/errwrap" "github.com/hashicorp/vault/helper/compressutil" ) @@ -64,7 +65,7 @@ func DecodeJSON(data []byte, out interface{}) error { // Decompress the data if it was compressed in the first place decompressedBytes, uncompressed, err := compressutil.Decompress(data) if err != nil { - return fmt.Errorf("failed to decompress JSON: err: %v", err) + return errwrap.Wrapf("failed to decompress JSON: {{err}}", err) } if !uncompressed && (decompressedBytes == nil || len(decompressedBytes) == 0) { return fmt.Errorf("decompressed data being decoded is invalid") @@ -91,7 +92,7 @@ func DecodeJSONFromReader(r io.Reader, out interface{}) error { dec := json.NewDecoder(r) - // While decoding JSON values, intepret the integer values as `json.Number`s instead of `float64`. + // While decoding JSON values, interpret the integer values as `json.Number`s instead of `float64`. dec.UseNumber() // Since 'out' is an interface representing a pointer, pass it to the decoder without an '&' diff --git a/vendor/github.com/hashicorp/vault/helper/parseutil/parseutil.go b/vendor/github.com/hashicorp/vault/helper/parseutil/parseutil.go index 957d533..ae8c58b 100644 --- a/vendor/github.com/hashicorp/vault/helper/parseutil/parseutil.go +++ b/vendor/github.com/hashicorp/vault/helper/parseutil/parseutil.go @@ -3,10 +3,14 @@ package parseutil import ( "encoding/json" "errors" + "fmt" "strconv" "strings" "time" + "github.com/hashicorp/errwrap" + sockaddr "github.com/hashicorp/go-sockaddr" + "github.com/hashicorp/vault/helper/strutil" "github.com/mitchellh/mapstructure" ) @@ -56,6 +60,43 @@ func ParseDurationSecond(in interface{}) (time.Duration, error) { return dur, nil } +func ParseInt(in interface{}) (int64, error) { + var ret int64 + jsonIn, ok := in.(json.Number) + if ok { + in = jsonIn.String() + } + switch in.(type) { + case string: + inp := in.(string) + if inp == "" { + return 0, nil + } + var err error + left, err := strconv.ParseInt(inp, 10, 64) + if err != nil { + return ret, err + } + ret = left + case int: + ret = int64(in.(int)) + case int32: + ret = int64(in.(int32)) + case int64: + ret = in.(int64) + case uint: + ret = int64(in.(uint)) + case uint32: + ret = int64(in.(uint32)) + case uint64: + ret = int64(in.(uint64)) + default: + return 0, errors.New("could not parse value from input") + } + + return ret, nil +} + func ParseBool(in interface{}) (bool, error) { var result bool if err := mapstructure.WeakDecode(in, &result); err != nil { @@ -63,3 +104,60 @@ func ParseBool(in interface{}) (bool, error) { } return result, nil } + +func ParseCommaStringSlice(in interface{}) ([]string, error) { + var result []string + config := &mapstructure.DecoderConfig{ + Result: &result, + WeaklyTypedInput: true, + DecodeHook: mapstructure.StringToSliceHookFunc(","), + } + decoder, err := mapstructure.NewDecoder(config) + if err != nil { + return nil, err + } + if err := decoder.Decode(in); err != nil { + return nil, err + } + return strutil.TrimStrings(result), nil +} + +func ParseAddrs(addrs interface{}) ([]*sockaddr.SockAddrMarshaler, error) { + out := make([]*sockaddr.SockAddrMarshaler, 0) + stringAddrs := make([]string, 0) + + switch addrs.(type) { + case string: + stringAddrs = strutil.ParseArbitraryStringSlice(addrs.(string), ",") + if len(stringAddrs) == 0 { + return nil, fmt.Errorf("unable to parse addresses from %v", addrs) + } + + case []string: + stringAddrs = addrs.([]string) + + case []interface{}: + for _, v := range addrs.([]interface{}) { + stringAddr, ok := v.(string) + if !ok { + return nil, fmt.Errorf("error parsing %v as string", v) + } + stringAddrs = append(stringAddrs, stringAddr) + } + + default: + return nil, fmt.Errorf("unknown address input type %T", addrs) + } + + for _, addr := range stringAddrs { + sa, err := sockaddr.NewSockAddr(addr) + if err != nil { + return nil, errwrap.Wrapf(fmt.Sprintf("error parsing address %q: {{err}}", addr), err) + } + out = append(out, &sockaddr.SockAddrMarshaler{ + SockAddr: sa, + }) + } + + return out, nil +} diff --git a/vendor/github.com/hashicorp/vault/helper/strutil/strutil.go b/vendor/github.com/hashicorp/vault/helper/strutil/strutil.go new file mode 100644 index 0000000..a77e60d --- /dev/null +++ b/vendor/github.com/hashicorp/vault/helper/strutil/strutil.go @@ -0,0 +1,327 @@ +package strutil + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "sort" + "strings" + + "github.com/hashicorp/errwrap" + glob "github.com/ryanuber/go-glob" +) + +// StrListContainsGlob looks for a string in a list of strings and allows +// globs. +func StrListContainsGlob(haystack []string, needle string) bool { + for _, item := range haystack { + if glob.Glob(item, needle) { + return true + } + } + return false +} + +// StrListContains looks for a string in a list of strings. +func StrListContains(haystack []string, needle string) bool { + for _, item := range haystack { + if item == needle { + return true + } + } + return false +} + +// StrListSubset checks if a given list is a subset +// of another set +func StrListSubset(super, sub []string) bool { + for _, item := range sub { + if !StrListContains(super, item) { + return false + } + } + return true +} + +// Parses a comma separated list of strings into a slice of strings. +// The return slice will be sorted and will not contain duplicate or +// empty items. +func ParseDedupAndSortStrings(input string, sep string) []string { + input = strings.TrimSpace(input) + parsed := []string{} + if input == "" { + // Don't return nil + return parsed + } + return RemoveDuplicates(strings.Split(input, sep), false) +} + +// Parses a comma separated list of strings into a slice of strings. +// The return slice will be sorted and will not contain duplicate or +// empty items. The values will be converted to lower case. +func ParseDedupLowercaseAndSortStrings(input string, sep string) []string { + input = strings.TrimSpace(input) + parsed := []string{} + if input == "" { + // Don't return nil + return parsed + } + return RemoveDuplicates(strings.Split(input, sep), true) +} + +// Parses a comma separated list of `=` tuples into a +// map[string]string. +func ParseKeyValues(input string, out map[string]string, sep string) error { + if out == nil { + return fmt.Errorf("'out is nil") + } + + keyValues := ParseDedupLowercaseAndSortStrings(input, sep) + if len(keyValues) == 0 { + return nil + } + + for _, keyValue := range keyValues { + shards := strings.Split(keyValue, "=") + if len(shards) != 2 { + return fmt.Errorf("invalid format") + } + + key := strings.TrimSpace(shards[0]) + value := strings.TrimSpace(shards[1]) + if key == "" || value == "" { + return fmt.Errorf("invalid pair: key: %q value: %q", key, value) + } + out[key] = value + } + return nil +} + +// Parses arbitrary tuples. The input can be one of +// the following: +// * JSON string +// * Base64 encoded JSON string +// * Comma separated list of `=` pairs +// * Base64 encoded string containing comma separated list of +// `=` pairs +// +// Input will be parsed into the output parameter, which should +// be a non-nil map[string]string. +func ParseArbitraryKeyValues(input string, out map[string]string, sep string) error { + input = strings.TrimSpace(input) + if input == "" { + return nil + } + if out == nil { + return fmt.Errorf("'out' is nil") + } + + // Try to base64 decode the input. If successful, consider the decoded + // value as input. + inputBytes, err := base64.StdEncoding.DecodeString(input) + if err == nil { + input = string(inputBytes) + } + + // Try to JSON unmarshal the input. If successful, consider that the + // metadata was supplied as JSON input. + err = json.Unmarshal([]byte(input), &out) + if err != nil { + // If JSON unmarshalling fails, consider that the input was + // supplied as a comma separated string of 'key=value' pairs. + if err = ParseKeyValues(input, out, sep); err != nil { + return errwrap.Wrapf("failed to parse the input: {{err}}", err) + } + } + + // Validate the parsed input + for key, value := range out { + if key != "" && value == "" { + return fmt.Errorf("invalid value for key %q", key) + } + } + + return nil +} + +// Parses a `sep`-separated list of strings into a +// []string. +// +// The output will always be a valid slice but may be of length zero. +func ParseStringSlice(input string, sep string) []string { + input = strings.TrimSpace(input) + if input == "" { + return []string{} + } + + splitStr := strings.Split(input, sep) + ret := make([]string, len(splitStr)) + for i, val := range splitStr { + ret[i] = val + } + + return ret +} + +// Parses arbitrary string slice. The input can be one of +// the following: +// * JSON string +// * Base64 encoded JSON string +// * `sep` separated list of values +// * Base64-encoded string containing a `sep` separated list of values +// +// Note that the separator is ignored if the input is found to already be in a +// structured format (e.g., JSON) +// +// The output will always be a valid slice but may be of length zero. +func ParseArbitraryStringSlice(input string, sep string) []string { + input = strings.TrimSpace(input) + if input == "" { + return []string{} + } + + // Try to base64 decode the input. If successful, consider the decoded + // value as input. + inputBytes, err := base64.StdEncoding.DecodeString(input) + if err == nil { + input = string(inputBytes) + } + + ret := []string{} + + // Try to JSON unmarshal the input. If successful, consider that the + // metadata was supplied as JSON input. + err = json.Unmarshal([]byte(input), &ret) + if err != nil { + // If JSON unmarshalling fails, consider that the input was + // supplied as a separated string of values. + return ParseStringSlice(input, sep) + } + + if ret == nil { + return []string{} + } + + return ret +} + +// TrimStrings takes a slice of strings and returns a slice of strings +// with trimmed spaces +func TrimStrings(items []string) []string { + ret := make([]string, len(items)) + for i, item := range items { + ret[i] = strings.TrimSpace(item) + } + return ret +} + +// Removes duplicate and empty elements from a slice of strings. This also may +// convert the items in the slice to lower case and returns a sorted slice. +func RemoveDuplicates(items []string, lowercase bool) []string { + itemsMap := map[string]bool{} + for _, item := range items { + item = strings.TrimSpace(item) + if lowercase { + item = strings.ToLower(item) + } + if item == "" { + continue + } + itemsMap[item] = true + } + items = make([]string, 0, len(itemsMap)) + for item, _ := range itemsMap { + items = append(items, item) + } + sort.Strings(items) + return items +} + +// EquivalentSlices checks whether the given string sets are equivalent, as in, +// they contain the same values. +func EquivalentSlices(a, b []string) bool { + if a == nil && b == nil { + return true + } + + if a == nil || b == nil { + return false + } + + // First we'll build maps to ensure unique values + mapA := map[string]bool{} + mapB := map[string]bool{} + for _, keyA := range a { + mapA[keyA] = true + } + for _, keyB := range b { + mapB[keyB] = true + } + + // Now we'll build our checking slices + var sortedA, sortedB []string + for keyA, _ := range mapA { + sortedA = append(sortedA, keyA) + } + for keyB, _ := range mapB { + sortedB = append(sortedB, keyB) + } + sort.Strings(sortedA) + sort.Strings(sortedB) + + // Finally, compare + if len(sortedA) != len(sortedB) { + return false + } + + for i := range sortedA { + if sortedA[i] != sortedB[i] { + return false + } + } + + return true +} + +// StrListDelete removes the first occurrence of the given item from the slice +// of strings if the item exists. +func StrListDelete(s []string, d string) []string { + if s == nil { + return s + } + + for index, element := range s { + if element == d { + return append(s[:index], s[index+1:]...) + } + } + + return s +} + +func GlobbedStringsMatch(item, val string) bool { + if len(item) < 2 { + return val == item + } + + hasPrefix := strings.HasPrefix(item, "*") + hasSuffix := strings.HasSuffix(item, "*") + + if hasPrefix && hasSuffix { + return strings.Contains(val, item[1:len(item)-1]) + } else if hasPrefix { + return strings.HasSuffix(val, item[1:]) + } else if hasSuffix { + return strings.HasPrefix(val, item[:len(item)-1]) + } + + return val == item +} + +// AppendIfMissing adds a string to a slice if the given string is not present +func AppendIfMissing(slice []string, i string) []string { + if StrListContains(slice, i) { + return slice + } + return append(slice, i) +} -- cgit v1.2.3