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