1package api
2
3import (
4	"context"
5	"crypto/tls"
6	"fmt"
7	"net"
8	"net/http"
9	"net/url"
10	"os"
11	"path"
12	"strconv"
13	"strings"
14	"sync"
15	"time"
16	"unicode"
17
18	"github.com/hashicorp/errwrap"
19	cleanhttp "github.com/hashicorp/go-cleanhttp"
20	retryablehttp "github.com/hashicorp/go-retryablehttp"
21	rootcerts "github.com/hashicorp/go-rootcerts"
22	"github.com/hashicorp/vault/sdk/helper/consts"
23	"github.com/hashicorp/vault/sdk/helper/parseutil"
24	"golang.org/x/net/http2"
25	"golang.org/x/time/rate"
26)
27
28const EnvVaultAddress = "VAULT_ADDR"
29const EnvVaultAgentAddr = "VAULT_AGENT_ADDR"
30const EnvVaultCACert = "VAULT_CACERT"
31const EnvVaultCAPath = "VAULT_CAPATH"
32const EnvVaultClientCert = "VAULT_CLIENT_CERT"
33const EnvVaultClientKey = "VAULT_CLIENT_KEY"
34const EnvVaultClientTimeout = "VAULT_CLIENT_TIMEOUT"
35const EnvVaultSkipVerify = "VAULT_SKIP_VERIFY"
36const EnvVaultNamespace = "VAULT_NAMESPACE"
37const EnvVaultTLSServerName = "VAULT_TLS_SERVER_NAME"
38const EnvVaultWrapTTL = "VAULT_WRAP_TTL"
39const EnvVaultMaxRetries = "VAULT_MAX_RETRIES"
40const EnvVaultToken = "VAULT_TOKEN"
41const EnvVaultMFA = "VAULT_MFA"
42const EnvRateLimit = "VAULT_RATE_LIMIT"
43
44// Deprecated values
45const EnvVaultAgentAddress = "VAULT_AGENT_ADDR"
46const EnvVaultInsecure = "VAULT_SKIP_VERIFY"
47
48// WrappingLookupFunc is a function that, given an HTTP verb and a path,
49// returns an optional string duration to be used for response wrapping (e.g.
50// "15s", or simply "15"). The path will not begin with "/v1/" or "v1/" or "/",
51// however, end-of-path forward slashes are not trimmed, so must match your
52// called path precisely.
53type WrappingLookupFunc func(operation, path string) string
54
55// Config is used to configure the creation of the client.
56type Config struct {
57	modifyLock sync.RWMutex
58
59	// Address is the address of the Vault server. This should be a complete
60	// URL such as "http://vault.example.com". If you need a custom SSL
61	// cert or want to enable insecure mode, you need to specify a custom
62	// HttpClient.
63	Address string
64
65	// AgentAddress is the address of the local Vault agent. This should be a
66	// complete URL such as "http://vault.example.com".
67	AgentAddress string
68
69	// HttpClient is the HTTP client to use. Vault sets sane defaults for the
70	// http.Client and its associated http.Transport created in DefaultConfig.
71	// If you must modify Vault's defaults, it is suggested that you start with
72	// that client and modify as needed rather than start with an empty client
73	// (or http.DefaultClient).
74	HttpClient *http.Client
75
76	// MaxRetries controls the maximum number of times to retry when a 5xx
77	// error occurs. Set to 0 to disable retrying. Defaults to 2 (for a total
78	// of three tries).
79	MaxRetries int
80
81	// Timeout is for setting custom timeout parameter in the HttpClient
82	Timeout time.Duration
83
84	// If there is an error when creating the configuration, this will be the
85	// error
86	Error error
87
88	// The Backoff function to use; a default is used if not provided
89	Backoff retryablehttp.Backoff
90
91	// The CheckRetry function to use; a default is used if not provided
92	CheckRetry retryablehttp.CheckRetry
93
94	// Limiter is the rate limiter used by the client.
95	// If this pointer is nil, then there will be no limit set.
96	// In contrast, if this pointer is set, even to an empty struct,
97	// then that limiter will be used. Note that an empty Limiter
98	// is equivalent blocking all events.
99	Limiter *rate.Limiter
100
101	// OutputCurlString causes the actual request to return an error of type
102	// *OutputStringError. Type asserting the error message will allow
103	// fetching a cURL-compatible string for the operation.
104	//
105	// Note: It is not thread-safe to set this and make concurrent requests
106	// with the same client. Cloning a client will not clone this value.
107	OutputCurlString bool
108}
109
110// TLSConfig contains the parameters needed to configure TLS on the HTTP client
111// used to communicate with Vault.
112type TLSConfig struct {
113	// CACert is the path to a PEM-encoded CA cert file to use to verify the
114	// Vault server SSL certificate.
115	CACert string
116
117	// CAPath is the path to a directory of PEM-encoded CA cert files to verify
118	// the Vault server SSL certificate.
119	CAPath string
120
121	// ClientCert is the path to the certificate for Vault communication
122	ClientCert string
123
124	// ClientKey is the path to the private key for Vault communication
125	ClientKey string
126
127	// TLSServerName, if set, is used to set the SNI host when connecting via
128	// TLS.
129	TLSServerName string
130
131	// Insecure enables or disables SSL verification
132	Insecure bool
133}
134
135// DefaultConfig returns a default configuration for the client. It is
136// safe to modify the return value of this function.
137//
138// The default Address is https://127.0.0.1:8200, but this can be overridden by
139// setting the `VAULT_ADDR` environment variable.
140//
141// If an error is encountered, this will return nil.
142func DefaultConfig() *Config {
143	config := &Config{
144		Address:    "https://127.0.0.1:8200",
145		HttpClient: cleanhttp.DefaultPooledClient(),
146		Timeout:    time.Second * 60,
147	}
148
149	transport := config.HttpClient.Transport.(*http.Transport)
150	transport.TLSHandshakeTimeout = 10 * time.Second
151	transport.TLSClientConfig = &tls.Config{
152		MinVersion: tls.VersionTLS12,
153	}
154	if err := http2.ConfigureTransport(transport); err != nil {
155		config.Error = err
156		return config
157	}
158
159	if err := config.ReadEnvironment(); err != nil {
160		config.Error = err
161		return config
162	}
163
164	// Ensure redirects are not automatically followed
165	// Note that this is sane for the API client as it has its own
166	// redirect handling logic (and thus also for command/meta),
167	// but in e.g. http_test actual redirect handling is necessary
168	config.HttpClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
169		// Returning this value causes the Go net library to not close the
170		// response body and to nil out the error. Otherwise retry clients may
171		// try three times on every redirect because it sees an error from this
172		// function (to prevent redirects) passing through to it.
173		return http.ErrUseLastResponse
174	}
175
176	config.Backoff = retryablehttp.LinearJitterBackoff
177	config.MaxRetries = 2
178
179	return config
180}
181
182// ConfigureTLS takes a set of TLS configurations and applies those to the the
183// HTTP client.
184func (c *Config) ConfigureTLS(t *TLSConfig) error {
185	if c.HttpClient == nil {
186		c.HttpClient = DefaultConfig().HttpClient
187	}
188	clientTLSConfig := c.HttpClient.Transport.(*http.Transport).TLSClientConfig
189
190	var clientCert tls.Certificate
191	foundClientCert := false
192
193	switch {
194	case t.ClientCert != "" && t.ClientKey != "":
195		var err error
196		clientCert, err = tls.LoadX509KeyPair(t.ClientCert, t.ClientKey)
197		if err != nil {
198			return err
199		}
200		foundClientCert = true
201	case t.ClientCert != "" || t.ClientKey != "":
202		return fmt.Errorf("both client cert and client key must be provided")
203	}
204
205	if t.CACert != "" || t.CAPath != "" {
206		rootConfig := &rootcerts.Config{
207			CAFile: t.CACert,
208			CAPath: t.CAPath,
209		}
210		if err := rootcerts.ConfigureTLS(clientTLSConfig, rootConfig); err != nil {
211			return err
212		}
213	}
214
215	if t.Insecure {
216		clientTLSConfig.InsecureSkipVerify = true
217	}
218
219	if foundClientCert {
220		// We use this function to ignore the server's preferential list of
221		// CAs, otherwise any CA used for the cert auth backend must be in the
222		// server's CA pool
223		clientTLSConfig.GetClientCertificate = func(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
224			return &clientCert, nil
225		}
226	}
227
228	if t.TLSServerName != "" {
229		clientTLSConfig.ServerName = t.TLSServerName
230	}
231
232	return nil
233}
234
235// ReadEnvironment reads configuration information from the environment. If
236// there is an error, no configuration value is updated.
237func (c *Config) ReadEnvironment() error {
238	var envAddress string
239	var envAgentAddress string
240	var envCACert string
241	var envCAPath string
242	var envClientCert string
243	var envClientKey string
244	var envClientTimeout time.Duration
245	var envInsecure bool
246	var envTLSServerName string
247	var envMaxRetries *uint64
248	var limit *rate.Limiter
249
250	// Parse the environment variables
251	if v := os.Getenv(EnvVaultAddress); v != "" {
252		envAddress = v
253	}
254	if v := os.Getenv(EnvVaultAgentAddr); v != "" {
255		envAgentAddress = v
256	} else if v := os.Getenv(EnvVaultAgentAddress); v != "" {
257		envAgentAddress = v
258	}
259	if v := os.Getenv(EnvVaultMaxRetries); v != "" {
260		maxRetries, err := strconv.ParseUint(v, 10, 32)
261		if err != nil {
262			return err
263		}
264		envMaxRetries = &maxRetries
265	}
266	if v := os.Getenv(EnvVaultCACert); v != "" {
267		envCACert = v
268	}
269	if v := os.Getenv(EnvVaultCAPath); v != "" {
270		envCAPath = v
271	}
272	if v := os.Getenv(EnvVaultClientCert); v != "" {
273		envClientCert = v
274	}
275	if v := os.Getenv(EnvVaultClientKey); v != "" {
276		envClientKey = v
277	}
278	if v := os.Getenv(EnvRateLimit); v != "" {
279		rateLimit, burstLimit, err := parseRateLimit(v)
280		if err != nil {
281			return err
282		}
283		limit = rate.NewLimiter(rate.Limit(rateLimit), burstLimit)
284	}
285	if t := os.Getenv(EnvVaultClientTimeout); t != "" {
286		clientTimeout, err := parseutil.ParseDurationSecond(t)
287		if err != nil {
288			return fmt.Errorf("could not parse %q", EnvVaultClientTimeout)
289		}
290		envClientTimeout = clientTimeout
291	}
292	if v := os.Getenv(EnvVaultSkipVerify); v != "" {
293		var err error
294		envInsecure, err = strconv.ParseBool(v)
295		if err != nil {
296			return fmt.Errorf("could not parse VAULT_SKIP_VERIFY")
297		}
298	} else if v := os.Getenv(EnvVaultInsecure); v != "" {
299		var err error
300		envInsecure, err = strconv.ParseBool(v)
301		if err != nil {
302			return fmt.Errorf("could not parse VAULT_INSECURE")
303		}
304	}
305
306	if v := os.Getenv(EnvVaultTLSServerName); v != "" {
307		envTLSServerName = v
308	}
309
310	// Configure the HTTP clients TLS configuration.
311	t := &TLSConfig{
312		CACert:        envCACert,
313		CAPath:        envCAPath,
314		ClientCert:    envClientCert,
315		ClientKey:     envClientKey,
316		TLSServerName: envTLSServerName,
317		Insecure:      envInsecure,
318	}
319
320	c.modifyLock.Lock()
321	defer c.modifyLock.Unlock()
322
323	c.Limiter = limit
324
325	if err := c.ConfigureTLS(t); err != nil {
326		return err
327	}
328
329	if envAddress != "" {
330		c.Address = envAddress
331	}
332
333	if envAgentAddress != "" {
334		c.AgentAddress = envAgentAddress
335	}
336
337	if envMaxRetries != nil {
338		c.MaxRetries = int(*envMaxRetries)
339	}
340
341	if envClientTimeout != 0 {
342		c.Timeout = envClientTimeout
343	}
344
345	return nil
346}
347
348func parseRateLimit(val string) (rate float64, burst int, err error) {
349
350	_, err = fmt.Sscanf(val, "%f:%d", &rate, &burst)
351	if err != nil {
352		rate, err = strconv.ParseFloat(val, 64)
353		if err != nil {
354			err = fmt.Errorf("%v was provided but incorrectly formatted", EnvRateLimit)
355		}
356		burst = int(rate)
357	}
358
359	return rate, burst, err
360
361}
362
363// Client is the client to the Vault API. Create a client with NewClient.
364type Client struct {
365	modifyLock         sync.RWMutex
366	addr               *url.URL
367	config             *Config
368	token              string
369	headers            http.Header
370	wrappingLookupFunc WrappingLookupFunc
371	mfaCreds           []string
372	policyOverride     bool
373}
374
375// NewClient returns a new client for the given configuration.
376//
377// If the configuration is nil, Vault will use configuration from
378// DefaultConfig(), which is the recommended starting configuration.
379//
380// If the environment variable `VAULT_TOKEN` is present, the token will be
381// automatically added to the client. Otherwise, you must manually call
382// `SetToken()`.
383func NewClient(c *Config) (*Client, error) {
384	def := DefaultConfig()
385	if def == nil {
386		return nil, fmt.Errorf("could not create/read default configuration")
387	}
388	if def.Error != nil {
389		return nil, errwrap.Wrapf("error encountered setting up default configuration: {{err}}", def.Error)
390	}
391
392	if c == nil {
393		c = def
394	}
395
396	c.modifyLock.Lock()
397	defer c.modifyLock.Unlock()
398
399	if c.HttpClient == nil {
400		c.HttpClient = def.HttpClient
401	}
402	if c.HttpClient.Transport == nil {
403		c.HttpClient.Transport = def.HttpClient.Transport
404	}
405
406	address := c.Address
407	if c.AgentAddress != "" {
408		address = c.AgentAddress
409	}
410
411	u, err := url.Parse(address)
412	if err != nil {
413		return nil, err
414	}
415
416	if strings.HasPrefix(address, "unix://") {
417		socket := strings.TrimPrefix(address, "unix://")
418		transport := c.HttpClient.Transport.(*http.Transport)
419		transport.DialContext = func(context.Context, string, string) (net.Conn, error) {
420			return net.Dial("unix", socket)
421		}
422
423		// Since the address points to a unix domain socket, the scheme in the
424		// *URL would be set to `unix`. The *URL in the client is expected to
425		// be pointing to the protocol used in the application layer and not to
426		// the transport layer. Hence, setting the fields accordingly.
427		u.Scheme = "http"
428		u.Host = socket
429		u.Path = ""
430	}
431
432	client := &Client{
433		addr:    u,
434		config:  c,
435		headers: make(http.Header),
436	}
437
438	// Add the VaultRequest SSRF protection header
439	client.headers[consts.RequestHeaderName] = []string{"true"}
440
441	if token := os.Getenv(EnvVaultToken); token != "" {
442		client.token = token
443	}
444
445	if namespace := os.Getenv(EnvVaultNamespace); namespace != "" {
446		client.setNamespace(namespace)
447	}
448
449	return client, nil
450}
451
452// Sets the address of Vault in the client. The format of address should be
453// "<Scheme>://<Host>:<Port>". Setting this on a client will override the
454// value of VAULT_ADDR environment variable.
455func (c *Client) SetAddress(addr string) error {
456	c.modifyLock.Lock()
457	defer c.modifyLock.Unlock()
458
459	parsedAddr, err := url.Parse(addr)
460	if err != nil {
461		return errwrap.Wrapf("failed to set address: {{err}}", err)
462	}
463
464	c.addr = parsedAddr
465	return nil
466}
467
468// Address returns the Vault URL the client is configured to connect to
469func (c *Client) Address() string {
470	c.modifyLock.RLock()
471	defer c.modifyLock.RUnlock()
472
473	return c.addr.String()
474}
475
476// SetLimiter will set the rate limiter for this client.
477// This method is thread-safe.
478// rateLimit and burst are specified according to https://godoc.org/golang.org/x/time/rate#NewLimiter
479func (c *Client) SetLimiter(rateLimit float64, burst int) {
480	c.modifyLock.RLock()
481	c.config.modifyLock.Lock()
482	defer c.config.modifyLock.Unlock()
483	c.modifyLock.RUnlock()
484
485	c.config.Limiter = rate.NewLimiter(rate.Limit(rateLimit), burst)
486}
487
488// SetMaxRetries sets the number of retries that will be used in the case of certain errors
489func (c *Client) SetMaxRetries(retries int) {
490	c.modifyLock.RLock()
491	c.config.modifyLock.Lock()
492	defer c.config.modifyLock.Unlock()
493	c.modifyLock.RUnlock()
494
495	c.config.MaxRetries = retries
496}
497
498// SetCheckRetry sets the CheckRetry function to be used for future requests.
499func (c *Client) SetCheckRetry(checkRetry retryablehttp.CheckRetry) {
500	c.modifyLock.RLock()
501	c.config.modifyLock.Lock()
502	defer c.config.modifyLock.Unlock()
503	c.modifyLock.RUnlock()
504
505	c.config.CheckRetry = checkRetry
506}
507
508// SetClientTimeout sets the client request timeout
509func (c *Client) SetClientTimeout(timeout time.Duration) {
510	c.modifyLock.RLock()
511	c.config.modifyLock.Lock()
512	defer c.config.modifyLock.Unlock()
513	c.modifyLock.RUnlock()
514
515	c.config.Timeout = timeout
516}
517
518func (c *Client) OutputCurlString() bool {
519	c.modifyLock.RLock()
520	c.config.modifyLock.RLock()
521	defer c.config.modifyLock.RUnlock()
522	c.modifyLock.RUnlock()
523
524	return c.config.OutputCurlString
525}
526
527func (c *Client) SetOutputCurlString(curl bool) {
528	c.modifyLock.RLock()
529	c.config.modifyLock.Lock()
530	defer c.config.modifyLock.Unlock()
531	c.modifyLock.RUnlock()
532
533	c.config.OutputCurlString = curl
534}
535
536// CurrentWrappingLookupFunc sets a lookup function that returns desired wrap TTLs
537// for a given operation and path
538func (c *Client) CurrentWrappingLookupFunc() WrappingLookupFunc {
539	c.modifyLock.RLock()
540	defer c.modifyLock.RUnlock()
541
542	return c.wrappingLookupFunc
543}
544
545// SetWrappingLookupFunc sets a lookup function that returns desired wrap TTLs
546// for a given operation and path
547func (c *Client) SetWrappingLookupFunc(lookupFunc WrappingLookupFunc) {
548	c.modifyLock.Lock()
549	defer c.modifyLock.Unlock()
550
551	c.wrappingLookupFunc = lookupFunc
552}
553
554// SetMFACreds sets the MFA credentials supplied either via the environment
555// variable or via the command line.
556func (c *Client) SetMFACreds(creds []string) {
557	c.modifyLock.Lock()
558	defer c.modifyLock.Unlock()
559
560	c.mfaCreds = creds
561}
562
563// SetNamespace sets the namespace supplied either via the environment
564// variable or via the command line.
565func (c *Client) SetNamespace(namespace string) {
566	c.modifyLock.Lock()
567	defer c.modifyLock.Unlock()
568	c.setNamespace(namespace)
569}
570
571func (c *Client) setNamespace(namespace string) {
572	if c.headers == nil {
573		c.headers = make(http.Header)
574	}
575
576	c.headers.Set(consts.NamespaceHeaderName, namespace)
577}
578
579// Token returns the access token being used by this client. It will
580// return the empty string if there is no token set.
581func (c *Client) Token() string {
582	c.modifyLock.RLock()
583	defer c.modifyLock.RUnlock()
584
585	return c.token
586}
587
588// SetToken sets the token directly. This won't perform any auth
589// verification, it simply sets the token properly for future requests.
590func (c *Client) SetToken(v string) {
591	c.modifyLock.Lock()
592	defer c.modifyLock.Unlock()
593
594	c.token = v
595}
596
597// ClearToken deletes the token if it is set or does nothing otherwise.
598func (c *Client) ClearToken() {
599	c.modifyLock.Lock()
600	defer c.modifyLock.Unlock()
601
602	c.token = ""
603}
604
605// Headers gets the current set of headers used for requests. This returns a
606// copy; to modify it call AddHeader or SetHeaders.
607func (c *Client) Headers() http.Header {
608	c.modifyLock.RLock()
609	defer c.modifyLock.RUnlock()
610
611	if c.headers == nil {
612		return nil
613	}
614
615	ret := make(http.Header)
616	for k, v := range c.headers {
617		for _, val := range v {
618			ret[k] = append(ret[k], val)
619		}
620	}
621
622	return ret
623}
624
625// AddHeader allows a single header key/value pair to be added
626// in a race-safe fashion.
627func (c *Client) AddHeader(key, value string) {
628	c.modifyLock.Lock()
629	defer c.modifyLock.Unlock()
630	c.headers.Add(key, value)
631}
632
633// SetHeaders clears all previous headers and uses only the given
634// ones going forward.
635func (c *Client) SetHeaders(headers http.Header) {
636	c.modifyLock.Lock()
637	defer c.modifyLock.Unlock()
638	c.headers = headers
639}
640
641// SetBackoff sets the backoff function to be used for future requests.
642func (c *Client) SetBackoff(backoff retryablehttp.Backoff) {
643	c.modifyLock.RLock()
644	c.config.modifyLock.Lock()
645	defer c.config.modifyLock.Unlock()
646	c.modifyLock.RUnlock()
647
648	c.config.Backoff = backoff
649}
650
651// Clone creates a new client with the same configuration. Note that the same
652// underlying http.Client is used; modifying the client from more than one
653// goroutine at once may not be safe, so modify the client as needed and then
654// clone.
655//
656// Also, only the client's config is currently copied; this means items not in
657// the api.Config struct, such as policy override and wrapping function
658// behavior, must currently then be set as desired on the new client.
659func (c *Client) Clone() (*Client, error) {
660	c.modifyLock.RLock()
661	c.config.modifyLock.RLock()
662	config := c.config
663	c.modifyLock.RUnlock()
664
665	newConfig := &Config{
666		Address:    config.Address,
667		HttpClient: config.HttpClient,
668		MaxRetries: config.MaxRetries,
669		Timeout:    config.Timeout,
670		Backoff:    config.Backoff,
671		CheckRetry: config.CheckRetry,
672		Limiter:    config.Limiter,
673	}
674	config.modifyLock.RUnlock()
675
676	return NewClient(newConfig)
677}
678
679// SetPolicyOverride sets whether requests should be sent with the policy
680// override flag to request overriding soft-mandatory Sentinel policies (both
681// RGPs and EGPs)
682func (c *Client) SetPolicyOverride(override bool) {
683	c.modifyLock.Lock()
684	defer c.modifyLock.Unlock()
685
686	c.policyOverride = override
687}
688
689// portMap defines the standard port map
690var portMap = map[string]string{
691	"http":  "80",
692	"https": "443",
693}
694
695// NewRequest creates a new raw request object to query the Vault server
696// configured for this client. This is an advanced method and generally
697// doesn't need to be called externally.
698func (c *Client) NewRequest(method, requestPath string) *Request {
699	c.modifyLock.RLock()
700	addr := c.addr
701	token := c.token
702	mfaCreds := c.mfaCreds
703	wrappingLookupFunc := c.wrappingLookupFunc
704	policyOverride := c.policyOverride
705	c.modifyLock.RUnlock()
706
707	// if SRV records exist (see https://tools.ietf.org/html/draft-andrews-http-srv-02), lookup the SRV
708	// record and take the highest match; this is not designed for high-availability, just discovery
709	var host string = addr.Host
710	if addr.Port() == "" {
711		// Avoid lookup of SRV record if scheme is known
712		port, ok := portMap[addr.Scheme]
713		if ok {
714			host = net.JoinHostPort(host, port)
715		} else {
716			// Internet Draft specifies that the SRV record is ignored if a port is given
717			_, addrs, err := net.LookupSRV("http", "tcp", addr.Hostname())
718			if err == nil && len(addrs) > 0 {
719				host = fmt.Sprintf("%s:%d", addrs[0].Target, addrs[0].Port)
720			}
721		}
722	}
723
724	req := &Request{
725		Method: method,
726		URL: &url.URL{
727			User:   addr.User,
728			Scheme: addr.Scheme,
729			Host:   host,
730			Path:   path.Join(addr.Path, requestPath),
731		},
732		ClientToken: token,
733		Params:      make(map[string][]string),
734	}
735
736	var lookupPath string
737	switch {
738	case strings.HasPrefix(requestPath, "/v1/"):
739		lookupPath = strings.TrimPrefix(requestPath, "/v1/")
740	case strings.HasPrefix(requestPath, "v1/"):
741		lookupPath = strings.TrimPrefix(requestPath, "v1/")
742	default:
743		lookupPath = requestPath
744	}
745
746	req.MFAHeaderVals = mfaCreds
747
748	if wrappingLookupFunc != nil {
749		req.WrapTTL = wrappingLookupFunc(method, lookupPath)
750	} else {
751		req.WrapTTL = DefaultWrappingLookupFunc(method, lookupPath)
752	}
753
754	req.Headers = c.Headers()
755	req.PolicyOverride = policyOverride
756
757	return req
758}
759
760// RawRequest performs the raw request given. This request may be against
761// a Vault server not configured with this client. This is an advanced operation
762// that generally won't need to be called externally.
763func (c *Client) RawRequest(r *Request) (*Response, error) {
764	return c.RawRequestWithContext(context.Background(), r)
765}
766
767// RawRequestWithContext performs the raw request given. This request may be against
768// a Vault server not configured with this client. This is an advanced operation
769// that generally won't need to be called externally.
770func (c *Client) RawRequestWithContext(ctx context.Context, r *Request) (*Response, error) {
771	c.modifyLock.RLock()
772	token := c.token
773
774	c.config.modifyLock.RLock()
775	limiter := c.config.Limiter
776	maxRetries := c.config.MaxRetries
777	checkRetry := c.config.CheckRetry
778	backoff := c.config.Backoff
779	httpClient := c.config.HttpClient
780	timeout := c.config.Timeout
781	outputCurlString := c.config.OutputCurlString
782	c.config.modifyLock.RUnlock()
783
784	c.modifyLock.RUnlock()
785
786	if limiter != nil {
787		limiter.Wait(ctx)
788	}
789
790	// Sanity check the token before potentially erroring from the API
791	idx := strings.IndexFunc(token, func(c rune) bool {
792		return !unicode.IsPrint(c)
793	})
794	if idx != -1 {
795		return nil, fmt.Errorf("configured Vault token contains non-printable characters and cannot be used")
796	}
797
798	redirectCount := 0
799START:
800	req, err := r.toRetryableHTTP()
801	if err != nil {
802		return nil, err
803	}
804	if req == nil {
805		return nil, fmt.Errorf("nil request created")
806	}
807
808	if outputCurlString {
809		LastOutputStringError = &OutputStringError{Request: req}
810		return nil, LastOutputStringError
811	}
812
813	if timeout != 0 {
814		// NOTE: this leaks a timer. But when we defer a cancel call here for
815		// the returned function we see errors in tests with contxt canceled.
816		// Although the request is done by the time we exit this function it is
817		// still causing something else to go wrong. Maybe it ends up being
818		// tied to the response somehow and reading the response body ends up
819		// checking it, or something. I don't know, but until we can chase this
820		// down, keep it not-canceled even though vet complains.
821		ctx, _ = context.WithTimeout(ctx, timeout)
822	}
823	req.Request = req.Request.WithContext(ctx)
824
825	if backoff == nil {
826		backoff = retryablehttp.LinearJitterBackoff
827	}
828
829	if checkRetry == nil {
830		checkRetry = retryablehttp.DefaultRetryPolicy
831	}
832
833	client := &retryablehttp.Client{
834		HTTPClient:   httpClient,
835		RetryWaitMin: 1000 * time.Millisecond,
836		RetryWaitMax: 1500 * time.Millisecond,
837		RetryMax:     maxRetries,
838		Backoff:      backoff,
839		CheckRetry:   checkRetry,
840		ErrorHandler: retryablehttp.PassthroughErrorHandler,
841	}
842
843	var result *Response
844	resp, err := client.Do(req)
845	if resp != nil {
846		result = &Response{Response: resp}
847	}
848	if err != nil {
849		if strings.Contains(err.Error(), "tls: oversized") {
850			err = errwrap.Wrapf(
851				"{{err}}\n\n"+
852					"This error usually means that the server is running with TLS disabled\n"+
853					"but the client is configured to use TLS. Please either enable TLS\n"+
854					"on the server or run the client with -address set to an address\n"+
855					"that uses the http protocol:\n\n"+
856					"    vault <command> -address http://<address>\n\n"+
857					"You can also set the VAULT_ADDR environment variable:\n\n\n"+
858					"    VAULT_ADDR=http://<address> vault <command>\n\n"+
859					"where <address> is replaced by the actual address to the server.",
860				err)
861		}
862		return result, err
863	}
864
865	// Check for a redirect, only allowing for a single redirect
866	if (resp.StatusCode == 301 || resp.StatusCode == 302 || resp.StatusCode == 307) && redirectCount == 0 {
867		// Parse the updated location
868		respLoc, err := resp.Location()
869		if err != nil {
870			return result, err
871		}
872
873		// Ensure a protocol downgrade doesn't happen
874		if req.URL.Scheme == "https" && respLoc.Scheme != "https" {
875			return result, fmt.Errorf("redirect would cause protocol downgrade")
876		}
877
878		// Update the request
879		r.URL = respLoc
880
881		// Reset the request body if any
882		if err := r.ResetJSONBody(); err != nil {
883			return result, err
884		}
885
886		// Retry the request
887		redirectCount++
888		goto START
889	}
890
891	if err := result.Error(); err != nil {
892		return result, err
893	}
894
895	return result, nil
896}
897