1package agent 2 3import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "net" 10 "net/http" 11 "net/http/pprof" 12 "net/url" 13 "os" 14 "reflect" 15 "regexp" 16 "strconv" 17 "strings" 18 "text/template" 19 "time" 20 21 "github.com/NYTimes/gziphandler" 22 "github.com/armon/go-metrics" 23 "github.com/hashicorp/consul/acl" 24 "github.com/hashicorp/consul/agent/cache" 25 "github.com/hashicorp/consul/agent/consul" 26 "github.com/hashicorp/consul/agent/structs" 27 "github.com/hashicorp/consul/api" 28 "github.com/hashicorp/consul/lib" 29 "github.com/hashicorp/consul/logging" 30 "github.com/hashicorp/go-cleanhttp" 31 "github.com/mitchellh/mapstructure" 32 "github.com/pkg/errors" 33) 34 35// MethodNotAllowedError should be returned by a handler when the HTTP method is not allowed. 36type MethodNotAllowedError struct { 37 Method string 38 Allow []string 39} 40 41func (e MethodNotAllowedError) Error() string { 42 return fmt.Sprintf("method %s not allowed", e.Method) 43} 44 45// BadRequestError should be returned by a handler when parameters or the payload are not valid 46type BadRequestError struct { 47 Reason string 48} 49 50func (e BadRequestError) Error() string { 51 return fmt.Sprintf("Bad request: %s", e.Reason) 52} 53 54// NotFoundError should be returned by a handler when a resource specified does not exist 55type NotFoundError struct { 56 Reason string 57} 58 59func (e NotFoundError) Error() string { 60 return e.Reason 61} 62 63// CodeWithPayloadError allow returning non HTTP 200 64// Error codes while not returning PlainText payload 65type CodeWithPayloadError struct { 66 Reason string 67 StatusCode int 68 ContentType string 69} 70 71func (e CodeWithPayloadError) Error() string { 72 return e.Reason 73} 74 75type ForbiddenError struct { 76} 77 78func (e ForbiddenError) Error() string { 79 return "Access is restricted" 80} 81 82// HTTPServer provides an HTTP api for an agent. 83type HTTPServer struct { 84 *http.Server 85 ln net.Listener 86 agent *Agent 87 blacklist *Blacklist 88 89 // proto is filled by the agent to "http" or "https". 90 proto string 91} 92type templatedFile struct { 93 templated *bytes.Reader 94 name string 95 mode os.FileMode 96 modTime time.Time 97} 98 99func newTemplatedFile(buf *bytes.Buffer, raw http.File) *templatedFile { 100 info, _ := raw.Stat() 101 return &templatedFile{ 102 templated: bytes.NewReader(buf.Bytes()), 103 name: info.Name(), 104 mode: info.Mode(), 105 modTime: info.ModTime(), 106 } 107} 108 109func (t *templatedFile) Read(p []byte) (n int, err error) { 110 return t.templated.Read(p) 111} 112 113func (t *templatedFile) Seek(offset int64, whence int) (int64, error) { 114 return t.templated.Seek(offset, whence) 115} 116 117func (t *templatedFile) Close() error { 118 return nil 119} 120 121func (t *templatedFile) Readdir(count int) ([]os.FileInfo, error) { 122 return nil, errors.New("not a directory") 123} 124 125func (t *templatedFile) Stat() (os.FileInfo, error) { 126 return t, nil 127} 128 129func (t *templatedFile) Name() string { 130 return t.name 131} 132 133func (t *templatedFile) Size() int64 { 134 return int64(t.templated.Len()) 135} 136 137func (t *templatedFile) Mode() os.FileMode { 138 return t.mode 139} 140 141func (t *templatedFile) ModTime() time.Time { 142 return t.modTime 143} 144 145func (t *templatedFile) IsDir() bool { 146 return false 147} 148 149func (t *templatedFile) Sys() interface{} { 150 return nil 151} 152 153type redirectFS struct { 154 fs http.FileSystem 155} 156 157func (fs *redirectFS) Open(name string) (http.File, error) { 158 file, err := fs.fs.Open(name) 159 if err != nil { 160 file, err = fs.fs.Open("/index.html") 161 } 162 return file, err 163} 164 165type templatedIndexFS struct { 166 fs http.FileSystem 167 templateVars func() map[string]interface{} 168} 169 170func (fs *templatedIndexFS) Open(name string) (http.File, error) { 171 file, err := fs.fs.Open(name) 172 if err == nil && name == "/index.html" { 173 content, _ := ioutil.ReadAll(file) 174 file.Seek(0, 0) 175 t, err := template.New("fmtedindex").Parse(string(content)) 176 if err != nil { 177 return nil, err 178 } 179 var out bytes.Buffer 180 err = t.Execute(&out, fs.templateVars()) 181 182 file = newTemplatedFile(&out, file) 183 } 184 185 return file, err 186} 187 188// endpoint is a Consul-specific HTTP handler that takes the usual arguments in 189// but returns a response object and error, both of which are handled in a 190// common manner by Consul's HTTP server. 191type endpoint func(resp http.ResponseWriter, req *http.Request) (interface{}, error) 192 193// unboundEndpoint is an endpoint method on a server. 194type unboundEndpoint func(s *HTTPServer, resp http.ResponseWriter, req *http.Request) (interface{}, error) 195 196// endpoints is a map from URL pattern to unbound endpoint. 197var endpoints map[string]unboundEndpoint 198 199// allowedMethods is a map from endpoint prefix to supported HTTP methods. 200// An empty slice means an endpoint handles OPTIONS requests and MethodNotFound errors itself. 201var allowedMethods map[string][]string = make(map[string][]string) 202 203// registerEndpoint registers a new endpoint, which should be done at package 204// init() time. 205func registerEndpoint(pattern string, methods []string, fn unboundEndpoint) { 206 if endpoints == nil { 207 endpoints = make(map[string]unboundEndpoint) 208 } 209 if endpoints[pattern] != nil || allowedMethods[pattern] != nil { 210 panic(fmt.Errorf("Pattern %q is already registered", pattern)) 211 } 212 213 endpoints[pattern] = fn 214 allowedMethods[pattern] = methods 215} 216 217// wrappedMux hangs on to the underlying mux for unit tests. 218type wrappedMux struct { 219 mux *http.ServeMux 220 handler http.Handler 221} 222 223// ServeHTTP implements the http.Handler interface. 224func (w *wrappedMux) ServeHTTP(resp http.ResponseWriter, req *http.Request) { 225 w.handler.ServeHTTP(resp, req) 226} 227 228// handler is used to attach our handlers to the mux 229func (s *HTTPServer) handler(enableDebug bool) http.Handler { 230 mux := http.NewServeMux() 231 232 // handleFuncMetrics takes the given pattern and handler and wraps to produce 233 // metrics based on the pattern and request. 234 handleFuncMetrics := func(pattern string, handler http.HandlerFunc) { 235 // Get the parts of the pattern. We omit any initial empty for the 236 // leading slash, and put an underscore as a "thing" placeholder if we 237 // see a trailing slash, which means the part after is parsed. This lets 238 // us distinguish from things like /v1/query and /v1/query/<query id>. 239 var parts []string 240 for i, part := range strings.Split(pattern, "/") { 241 if part == "" { 242 if i == 0 { 243 continue 244 } 245 part = "_" 246 } 247 parts = append(parts, part) 248 } 249 250 // Register the wrapper, which will close over the expensive-to-compute 251 // parts from above. 252 // TODO (kyhavlov): Convert this to utilize metric labels in a major release 253 wrapper := func(resp http.ResponseWriter, req *http.Request) { 254 start := time.Now() 255 handler(resp, req) 256 key := append([]string{"http", req.Method}, parts...) 257 metrics.MeasureSince(key, start) 258 } 259 260 gzipWrapper, _ := gziphandler.GzipHandlerWithOpts(gziphandler.MinSize(0)) 261 gzipHandler := gzipWrapper(http.HandlerFunc(wrapper)) 262 mux.Handle(pattern, gzipHandler) 263 } 264 265 // handlePProf takes the given pattern and pprof handler 266 // and wraps it to add authorization and metrics 267 handlePProf := func(pattern string, handler http.HandlerFunc) { 268 wrapper := func(resp http.ResponseWriter, req *http.Request) { 269 var token string 270 s.parseToken(req, &token) 271 272 rule, err := s.agent.resolveToken(token) 273 if err != nil { 274 resp.WriteHeader(http.StatusForbidden) 275 return 276 } 277 278 // If enableDebug is not set, and ACLs are disabled, write 279 // an unauthorized response 280 if !enableDebug { 281 if s.checkACLDisabled(resp, req) { 282 return 283 } 284 } 285 286 // If the token provided does not have the necessary permissions, 287 // write a forbidden response 288 if rule != nil && rule.OperatorRead(nil) != acl.Allow { 289 resp.WriteHeader(http.StatusForbidden) 290 return 291 } 292 293 // Call the pprof handler 294 handler(resp, req) 295 } 296 297 handleFuncMetrics(pattern, http.HandlerFunc(wrapper)) 298 } 299 mux.HandleFunc("/", s.Index) 300 for pattern, fn := range endpoints { 301 thisFn := fn 302 methods := allowedMethods[pattern] 303 bound := func(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 304 return thisFn(s, resp, req) 305 } 306 handleFuncMetrics(pattern, s.wrap(bound, methods)) 307 } 308 309 // Register wrapped pprof handlers 310 handlePProf("/debug/pprof/", pprof.Index) 311 handlePProf("/debug/pprof/cmdline", pprof.Cmdline) 312 handlePProf("/debug/pprof/profile", pprof.Profile) 313 handlePProf("/debug/pprof/symbol", pprof.Symbol) 314 handlePProf("/debug/pprof/trace", pprof.Trace) 315 316 if s.IsUIEnabled() { 317 var uifs http.FileSystem 318 // Use the custom UI dir if provided. 319 if s.agent.config.UIDir != "" { 320 uifs = http.Dir(s.agent.config.UIDir) 321 } else { 322 fs := assetFS() 323 uifs = fs 324 } 325 uifs = &redirectFS{fs: &templatedIndexFS{fs: uifs, templateVars: s.GenerateHTMLTemplateVars}} 326 mux.Handle("/robots.txt", http.FileServer(uifs)) 327 mux.Handle(s.agent.config.UIContentPath, http.StripPrefix(s.agent.config.UIContentPath, http.FileServer(uifs))) 328 } 329 330 // Wrap the whole mux with a handler that bans URLs with non-printable 331 // characters, unless disabled explicitly to deal with old keys that fail this 332 // check. 333 h := cleanhttp.PrintablePathCheckHandler(mux, nil) 334 if s.agent.config.DisableHTTPUnprintableCharFilter { 335 h = mux 336 } 337 return &wrappedMux{ 338 mux: mux, 339 handler: h, 340 } 341} 342 343func (s *HTTPServer) GenerateHTMLTemplateVars() map[string]interface{} { 344 vars := map[string]interface{}{ 345 "ContentPath": s.agent.config.UIContentPath, 346 "ACLsEnabled": s.agent.delegate.ACLsEnabled(), 347 } 348 349 s.addEnterpriseHTMLTemplateVars(vars) 350 351 return vars 352} 353 354// nodeName returns the node name of the agent 355func (s *HTTPServer) nodeName() string { 356 return s.agent.config.NodeName 357} 358 359// aclEndpointRE is used to find old ACL endpoints that take tokens in the URL 360// so that we can redact them. The ACL endpoints that take the token in the URL 361// are all of the form /v1/acl/<verb>/<token>, and can optionally include query 362// parameters which are indicated by a question mark. We capture the part before 363// the token, the token, and any query parameters after, and then reassemble as 364// $1<hidden>$3 (the token in $2 isn't used), which will give: 365// 366// /v1/acl/clone/foo -> /v1/acl/clone/<hidden> 367// /v1/acl/clone/foo?token=bar -> /v1/acl/clone/<hidden>?token=<hidden> 368// 369// The query parameter in the example above is obfuscated like any other, after 370// this regular expression is applied, so the regular expression substitution 371// results in: 372// 373// /v1/acl/clone/foo?token=bar -> /v1/acl/clone/<hidden>?token=bar 374// ^---- $1 ----^^- $2 -^^-- $3 --^ 375// 376// And then the loop that looks for parameters called "token" does the last 377// step to get to the final redacted form. 378var ( 379 aclEndpointRE = regexp.MustCompile("^(/v1/acl/(create|update|destroy|info|clone|list)/)([^?]+)([?]?.*)$") 380) 381 382// wrap is used to wrap functions to make them more convenient 383func (s *HTTPServer) wrap(handler endpoint, methods []string) http.HandlerFunc { 384 httpLogger := s.agent.logger.Named(logging.HTTP) 385 return func(resp http.ResponseWriter, req *http.Request) { 386 setHeaders(resp, s.agent.config.HTTPResponseHeaders) 387 setTranslateAddr(resp, s.agent.config.TranslateWANAddrs) 388 389 // Obfuscate any tokens from appearing in the logs 390 formVals, err := url.ParseQuery(req.URL.RawQuery) 391 if err != nil { 392 httpLogger.Error("Failed to decode query", 393 "from", req.RemoteAddr, 394 "error", err, 395 ) 396 resp.WriteHeader(http.StatusInternalServerError) 397 return 398 } 399 logURL := req.URL.String() 400 if tokens, ok := formVals["token"]; ok { 401 for _, token := range tokens { 402 if token == "" { 403 logURL += "<hidden>" 404 continue 405 } 406 logURL = strings.Replace(logURL, token, "<hidden>", -1) 407 } 408 } 409 logURL = aclEndpointRE.ReplaceAllString(logURL, "$1<hidden>$4") 410 411 if s.blacklist.Block(req.URL.Path) { 412 errMsg := "Endpoint is blocked by agent configuration" 413 httpLogger.Error("Request error", 414 "method", req.Method, 415 "url", logURL, 416 "from", req.RemoteAddr, 417 "error", errMsg, 418 ) 419 resp.WriteHeader(http.StatusForbidden) 420 fmt.Fprint(resp, errMsg) 421 return 422 } 423 424 isForbidden := func(err error) bool { 425 if acl.IsErrPermissionDenied(err) || acl.IsErrNotFound(err) { 426 return true 427 } 428 _, ok := err.(ForbiddenError) 429 return ok 430 } 431 432 isMethodNotAllowed := func(err error) bool { 433 _, ok := err.(MethodNotAllowedError) 434 return ok 435 } 436 437 isBadRequest := func(err error) bool { 438 _, ok := err.(BadRequestError) 439 return ok 440 } 441 442 isNotFound := func(err error) bool { 443 _, ok := err.(NotFoundError) 444 return ok 445 } 446 447 isTooManyRequests := func(err error) bool { 448 // Sadness net/rpc can't do nice typed errors so this is all we got 449 return err.Error() == consul.ErrRateLimited.Error() 450 } 451 452 addAllowHeader := func(methods []string) { 453 resp.Header().Add("Allow", strings.Join(methods, ",")) 454 } 455 456 handleErr := func(err error) { 457 httpLogger.Error("Request error", 458 "method", req.Method, 459 "url", logURL, 460 "from", req.RemoteAddr, 461 "error", err, 462 ) 463 switch { 464 case isForbidden(err): 465 resp.WriteHeader(http.StatusForbidden) 466 fmt.Fprint(resp, err.Error()) 467 case structs.IsErrRPCRateExceeded(err): 468 resp.WriteHeader(http.StatusTooManyRequests) 469 case isMethodNotAllowed(err): 470 // RFC2616 states that for 405 Method Not Allowed the response 471 // MUST include an Allow header containing the list of valid 472 // methods for the requested resource. 473 // https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html 474 addAllowHeader(err.(MethodNotAllowedError).Allow) 475 resp.WriteHeader(http.StatusMethodNotAllowed) // 405 476 fmt.Fprint(resp, err.Error()) 477 case isBadRequest(err): 478 resp.WriteHeader(http.StatusBadRequest) 479 fmt.Fprint(resp, err.Error()) 480 case isNotFound(err): 481 resp.WriteHeader(http.StatusNotFound) 482 fmt.Fprintf(resp, err.Error()) 483 case isTooManyRequests(err): 484 resp.WriteHeader(http.StatusTooManyRequests) 485 fmt.Fprint(resp, err.Error()) 486 default: 487 resp.WriteHeader(http.StatusInternalServerError) 488 fmt.Fprint(resp, err.Error()) 489 } 490 } 491 492 start := time.Now() 493 defer func() { 494 httpLogger.Debug("Request finished", 495 "method", req.Method, 496 "url", logURL, 497 "from", req.RemoteAddr, 498 "latency", time.Since(start).String(), 499 ) 500 }() 501 502 var obj interface{} 503 504 // if this endpoint has declared methods, respond appropriately to OPTIONS requests. Otherwise let the endpoint handle that. 505 if req.Method == "OPTIONS" && len(methods) > 0 { 506 addAllowHeader(append([]string{"OPTIONS"}, methods...)) 507 return 508 } 509 510 // if this endpoint has declared methods, check the request method. Otherwise let the endpoint handle that. 511 methodFound := len(methods) == 0 512 for _, method := range methods { 513 if method == req.Method { 514 methodFound = true 515 break 516 } 517 } 518 519 if !methodFound { 520 err = MethodNotAllowedError{req.Method, append([]string{"OPTIONS"}, methods...)} 521 } else { 522 err = s.checkWriteAccess(req) 523 524 if err == nil { 525 // Invoke the handler 526 obj, err = handler(resp, req) 527 } 528 } 529 contentType := "application/json" 530 httpCode := http.StatusOK 531 if err != nil { 532 if errPayload, ok := err.(CodeWithPayloadError); ok { 533 httpCode = errPayload.StatusCode 534 if errPayload.ContentType != "" { 535 contentType = errPayload.ContentType 536 } 537 if errPayload.Reason != "" { 538 resp.Header().Add("X-Consul-Reason", errPayload.Reason) 539 } 540 } else { 541 handleErr(err) 542 return 543 } 544 } 545 if obj == nil { 546 return 547 } 548 var buf []byte 549 if contentType == "application/json" { 550 buf, err = s.marshalJSON(req, obj) 551 if err != nil { 552 handleErr(err) 553 return 554 } 555 } else { 556 if strings.HasPrefix(contentType, "text/") { 557 if val, ok := obj.(string); ok { 558 buf = []byte(val) 559 } 560 } 561 } 562 resp.Header().Set("Content-Type", contentType) 563 resp.WriteHeader(httpCode) 564 resp.Write(buf) 565 } 566} 567 568// marshalJSON marshals the object into JSON, respecting the user's pretty-ness 569// configuration. 570func (s *HTTPServer) marshalJSON(req *http.Request, obj interface{}) ([]byte, error) { 571 if _, ok := req.URL.Query()["pretty"]; ok || s.agent.config.DevMode { 572 buf, err := json.MarshalIndent(obj, "", " ") 573 if err != nil { 574 return nil, err 575 } 576 buf = append(buf, "\n"...) 577 return buf, nil 578 } 579 580 buf, err := json.Marshal(obj) 581 if err != nil { 582 return nil, err 583 } 584 return buf, err 585} 586 587// Returns true if the UI is enabled. 588func (s *HTTPServer) IsUIEnabled() bool { 589 return s.agent.config.UIDir != "" || s.agent.config.EnableUI 590} 591 592// Renders a simple index page 593func (s *HTTPServer) Index(resp http.ResponseWriter, req *http.Request) { 594 // Check if this is a non-index path 595 if req.URL.Path != "/" { 596 resp.WriteHeader(http.StatusNotFound) 597 return 598 } 599 600 // Give them something helpful if there's no UI so they at least know 601 // what this server is. 602 if !s.IsUIEnabled() { 603 fmt.Fprint(resp, "Consul Agent") 604 return 605 } 606 607 // Redirect to the UI endpoint 608 http.Redirect(resp, req, s.agent.config.UIContentPath, http.StatusMovedPermanently) // 301 609} 610 611func decodeBody(body io.Reader, out interface{}) error { 612 return lib.DecodeJSON(body, out) 613} 614 615// decodeBodyDeprecated is deprecated, please ues decodeBody above. 616// decodeBodyDeprecated is used to decode a JSON request body 617func decodeBodyDeprecated(req *http.Request, out interface{}, cb func(interface{}) error) error { 618 // This generally only happens in tests since real HTTP requests set 619 // a non-nil body with no content. We guard against it anyways to prevent 620 // a panic. The EOF response is the same behavior as an empty reader. 621 if req.Body == nil { 622 return io.EOF 623 } 624 625 var raw interface{} 626 dec := json.NewDecoder(req.Body) 627 if err := dec.Decode(&raw); err != nil { 628 return err 629 } 630 631 // Invoke the callback prior to decode 632 if cb != nil { 633 if err := cb(raw); err != nil { 634 return err 635 } 636 } 637 638 decodeConf := &mapstructure.DecoderConfig{ 639 DecodeHook: mapstructure.ComposeDecodeHookFunc( 640 mapstructure.StringToTimeDurationHookFunc(), 641 stringToReadableDurationFunc(), 642 ), 643 Result: &out, 644 } 645 646 decoder, err := mapstructure.NewDecoder(decodeConf) 647 if err != nil { 648 return err 649 } 650 651 return decoder.Decode(raw) 652} 653 654// stringToReadableDurationFunc is a mapstructure hook for decoding a string 655// into an api.ReadableDuration for backwards compatibility. 656func stringToReadableDurationFunc() mapstructure.DecodeHookFunc { 657 return func( 658 f reflect.Type, 659 t reflect.Type, 660 data interface{}) (interface{}, error) { 661 var v api.ReadableDuration 662 if t != reflect.TypeOf(v) { 663 return data, nil 664 } 665 666 switch { 667 case f.Kind() == reflect.String: 668 if dur, err := time.ParseDuration(data.(string)); err != nil { 669 return nil, err 670 } else { 671 v = api.ReadableDuration(dur) 672 } 673 return v, nil 674 default: 675 return data, nil 676 } 677 } 678} 679 680// setTranslateAddr is used to set the address translation header. This is only 681// present if the feature is active. 682func setTranslateAddr(resp http.ResponseWriter, active bool) { 683 if active { 684 resp.Header().Set("X-Consul-Translate-Addresses", "true") 685 } 686} 687 688// setIndex is used to set the index response header 689func setIndex(resp http.ResponseWriter, index uint64) { 690 // If we ever return X-Consul-Index of 0 blocking clients will go into a busy 691 // loop and hammer us since ?index=0 will never block. It's always safe to 692 // return index=1 since the very first Raft write is always an internal one 693 // writing the raft config for the cluster so no user-facing blocking query 694 // will ever legitimately have an X-Consul-Index of 1. 695 if index == 0 { 696 index = 1 697 } 698 resp.Header().Set("X-Consul-Index", strconv.FormatUint(index, 10)) 699} 700 701// setKnownLeader is used to set the known leader header 702func setKnownLeader(resp http.ResponseWriter, known bool) { 703 s := "true" 704 if !known { 705 s = "false" 706 } 707 resp.Header().Set("X-Consul-KnownLeader", s) 708} 709 710func setConsistency(resp http.ResponseWriter, consistency string) { 711 if consistency != "" { 712 resp.Header().Set("X-Consul-Effective-Consistency", consistency) 713 } 714} 715 716// setLastContact is used to set the last contact header 717func setLastContact(resp http.ResponseWriter, last time.Duration) { 718 if last < 0 { 719 last = 0 720 } 721 lastMsec := uint64(last / time.Millisecond) 722 resp.Header().Set("X-Consul-LastContact", strconv.FormatUint(lastMsec, 10)) 723} 724 725// setMeta is used to set the query response meta data 726func setMeta(resp http.ResponseWriter, m structs.QueryMetaCompat) { 727 setIndex(resp, m.GetIndex()) 728 setLastContact(resp, m.GetLastContact()) 729 setKnownLeader(resp, m.GetKnownLeader()) 730 setConsistency(resp, m.GetConsistencyLevel()) 731} 732 733// setCacheMeta sets http response headers to indicate cache status. 734func setCacheMeta(resp http.ResponseWriter, m *cache.ResultMeta) { 735 if m == nil { 736 return 737 } 738 str := "MISS" 739 if m.Hit { 740 str = "HIT" 741 } 742 resp.Header().Set("X-Cache", str) 743 if m.Hit { 744 resp.Header().Set("Age", fmt.Sprintf("%.0f", m.Age.Seconds())) 745 } 746} 747 748// setHeaders is used to set canonical response header fields 749func setHeaders(resp http.ResponseWriter, headers map[string]string) { 750 for field, value := range headers { 751 resp.Header().Set(http.CanonicalHeaderKey(field), value) 752 } 753} 754 755// parseWait is used to parse the ?wait and ?index query params 756// Returns true on error 757func parseWait(resp http.ResponseWriter, req *http.Request, b structs.QueryOptionsCompat) bool { 758 query := req.URL.Query() 759 if wait := query.Get("wait"); wait != "" { 760 dur, err := time.ParseDuration(wait) 761 if err != nil { 762 resp.WriteHeader(http.StatusBadRequest) 763 fmt.Fprint(resp, "Invalid wait time") 764 return true 765 } 766 b.SetMaxQueryTime(dur) 767 } 768 if idx := query.Get("index"); idx != "" { 769 index, err := strconv.ParseUint(idx, 10, 64) 770 if err != nil { 771 resp.WriteHeader(http.StatusBadRequest) 772 fmt.Fprint(resp, "Invalid index") 773 return true 774 } 775 b.SetMinQueryIndex(index) 776 } 777 return false 778} 779 780// parseCacheControl parses the CacheControl HTTP header value. So far we only 781// support maxage directive. 782func parseCacheControl(resp http.ResponseWriter, req *http.Request, b structs.QueryOptionsCompat) bool { 783 raw := strings.ToLower(req.Header.Get("Cache-Control")) 784 785 if raw == "" { 786 return false 787 } 788 789 // Didn't want to import a full parser for this. While quoted strings are 790 // allowed in some directives, max-age does not allow them per 791 // https://tools.ietf.org/html/rfc7234#section-5.2.2.8 so we assume all 792 // well-behaved clients use the exact token form of max-age=<delta-seconds> 793 // where delta-seconds is a non-negative decimal integer. 794 directives := strings.Split(raw, ",") 795 796 parseDurationOrFail := func(raw string) (time.Duration, bool) { 797 i, err := strconv.Atoi(raw) 798 if err != nil { 799 resp.WriteHeader(http.StatusBadRequest) 800 fmt.Fprint(resp, "Invalid Cache-Control header.") 801 return 0, true 802 } 803 return time.Duration(i) * time.Second, false 804 } 805 806 for _, d := range directives { 807 d = strings.ToLower(strings.TrimSpace(d)) 808 809 if d == "must-revalidate" { 810 b.SetMustRevalidate(true) 811 } 812 813 if strings.HasPrefix(d, "max-age=") { 814 d, failed := parseDurationOrFail(d[8:]) 815 if failed { 816 return true 817 } 818 b.SetMaxAge(d) 819 if d == 0 { 820 // max-age=0 specifically means that we need to consider the cache stale 821 // immediately however MaxAge = 0 is indistinguishable from the default 822 // where MaxAge is unset. 823 b.SetMustRevalidate(true) 824 } 825 } 826 if strings.HasPrefix(d, "stale-if-error=") { 827 d, failed := parseDurationOrFail(d[15:]) 828 if failed { 829 return true 830 } 831 b.SetStaleIfError(d) 832 } 833 } 834 835 return false 836} 837 838// parseConsistency is used to parse the ?stale and ?consistent query params. 839// Returns true on error 840func (s *HTTPServer) parseConsistency(resp http.ResponseWriter, req *http.Request, b structs.QueryOptionsCompat) bool { 841 query := req.URL.Query() 842 defaults := true 843 if _, ok := query["stale"]; ok { 844 b.SetAllowStale(true) 845 defaults = false 846 } 847 if _, ok := query["consistent"]; ok { 848 b.SetRequireConsistent(true) 849 defaults = false 850 } 851 if _, ok := query["leader"]; ok { 852 defaults = false 853 } 854 if _, ok := query["cached"]; ok { 855 b.SetUseCache(true) 856 defaults = false 857 } 858 if maxStale := query.Get("max_stale"); maxStale != "" { 859 dur, err := time.ParseDuration(maxStale) 860 if err != nil { 861 resp.WriteHeader(http.StatusBadRequest) 862 fmt.Fprintf(resp, "Invalid max_stale value %q", maxStale) 863 return true 864 } 865 b.SetMaxStaleDuration(dur) 866 if dur.Nanoseconds() > 0 { 867 b.SetAllowStale(true) 868 defaults = false 869 } 870 } 871 // No specific Consistency has been specified by caller 872 if defaults { 873 path := req.URL.Path 874 if strings.HasPrefix(path, "/v1/catalog") || strings.HasPrefix(path, "/v1/health") { 875 if s.agent.config.DiscoveryMaxStale.Nanoseconds() > 0 { 876 b.SetMaxStaleDuration(s.agent.config.DiscoveryMaxStale) 877 b.SetAllowStale(true) 878 } 879 } 880 } 881 if b.GetAllowStale() && b.GetRequireConsistent() { 882 resp.WriteHeader(http.StatusBadRequest) 883 fmt.Fprint(resp, "Cannot specify ?stale with ?consistent, conflicting semantics.") 884 return true 885 } 886 if b.GetUseCache() && b.GetRequireConsistent() { 887 resp.WriteHeader(http.StatusBadRequest) 888 fmt.Fprint(resp, "Cannot specify ?cached with ?consistent, conflicting semantics.") 889 return true 890 } 891 return false 892} 893 894// parseDC is used to parse the ?dc query param 895func (s *HTTPServer) parseDC(req *http.Request, dc *string) { 896 if other := req.URL.Query().Get("dc"); other != "" { 897 *dc = other 898 } else if *dc == "" { 899 *dc = s.agent.config.Datacenter 900 } 901} 902 903// parseTokenInternal is used to parse the ?token query param or the X-Consul-Token header or 904// Authorization Bearer token (RFC6750) and 905// optionally resolve proxy tokens to real ACL tokens. If the token is invalid or not specified it will populate 906// the token with the agents UserToken (acl_token in the consul configuration) 907// Parsing has the following priority: ?token, X-Consul-Token and last "Authorization: Bearer " 908func (s *HTTPServer) parseTokenInternal(req *http.Request, token *string) { 909 tok := "" 910 if other := req.URL.Query().Get("token"); other != "" { 911 tok = other 912 } else if other := req.Header.Get("X-Consul-Token"); other != "" { 913 tok = other 914 } else if other := req.Header.Get("Authorization"); other != "" { 915 // HTTP Authorization headers are in the format: <Scheme>[SPACE]<Value> 916 // Ref. https://tools.ietf.org/html/rfc7236#section-3 917 parts := strings.Split(other, " ") 918 919 // Authorization Header is invalid if containing 1 or 0 parts, e.g.: 920 // "" || "<Scheme><Value>" || "<Scheme>" || "<Value>" 921 if len(parts) > 1 { 922 scheme := parts[0] 923 // Everything after "<Scheme>" is "<Value>", trimmed 924 value := strings.TrimSpace(strings.Join(parts[1:], " ")) 925 926 // <Scheme> must be "Bearer" 927 if strings.ToLower(scheme) == "bearer" { 928 // Since Bearer tokens shouldnt contain spaces (rfc6750#section-2.1) 929 // "value" is tokenized, only the first item is used 930 tok = strings.TrimSpace(strings.Split(value, " ")[0]) 931 } 932 } 933 } 934 935 if tok != "" { 936 *token = tok 937 return 938 } 939 940 *token = s.agent.tokens.UserToken() 941} 942 943// parseToken is used to parse the ?token query param or the X-Consul-Token header or 944// Authorization Bearer token header (RFC6750) 945func (s *HTTPServer) parseToken(req *http.Request, token *string) { 946 s.parseTokenInternal(req, token) 947} 948 949func sourceAddrFromRequest(req *http.Request) string { 950 xff := req.Header.Get("X-Forwarded-For") 951 forwardHosts := strings.Split(xff, ",") 952 if len(forwardHosts) > 0 { 953 forwardIp := net.ParseIP(strings.TrimSpace(forwardHosts[0])) 954 if forwardIp != nil { 955 return forwardIp.String() 956 } 957 } 958 959 host, _, err := net.SplitHostPort(req.RemoteAddr) 960 if err != nil { 961 return "" 962 } 963 964 ip := net.ParseIP(host) 965 if ip != nil { 966 return ip.String() 967 } else { 968 return "" 969 } 970} 971 972// parseSource is used to parse the ?near=<node> query parameter, used for 973// sorting by RTT based on a source node. We set the source's DC to the target 974// DC in the request, if given, or else the agent's DC. 975func (s *HTTPServer) parseSource(req *http.Request, source *structs.QuerySource) { 976 s.parseDC(req, &source.Datacenter) 977 source.Ip = sourceAddrFromRequest(req) 978 if node := req.URL.Query().Get("near"); node != "" { 979 if node == "_agent" { 980 source.Node = s.agent.config.NodeName 981 } else { 982 source.Node = node 983 } 984 } 985} 986 987// parseMetaFilter is used to parse the ?node-meta=key:value query parameter, used for 988// filtering results to nodes with the given metadata key/value 989func (s *HTTPServer) parseMetaFilter(req *http.Request) map[string]string { 990 if filterList, ok := req.URL.Query()["node-meta"]; ok { 991 filters := make(map[string]string) 992 for _, filter := range filterList { 993 key, value := ParseMetaPair(filter) 994 filters[key] = value 995 } 996 return filters 997 } 998 return nil 999} 1000 1001// parseInternal is a convenience method for endpoints that need 1002// to use both parseWait and parseDC. 1003func (s *HTTPServer) parseInternal(resp http.ResponseWriter, req *http.Request, dc *string, b structs.QueryOptionsCompat) bool { 1004 s.parseDC(req, dc) 1005 var token string 1006 s.parseTokenInternal(req, &token) 1007 b.SetToken(token) 1008 var filter string 1009 s.parseFilter(req, &filter) 1010 b.SetFilter(filter) 1011 if s.parseConsistency(resp, req, b) { 1012 return true 1013 } 1014 if parseCacheControl(resp, req, b) { 1015 return true 1016 } 1017 return parseWait(resp, req, b) 1018} 1019 1020// parse is a convenience method for endpoints that need 1021// to use both parseWait and parseDC. 1022func (s *HTTPServer) parse(resp http.ResponseWriter, req *http.Request, dc *string, b structs.QueryOptionsCompat) bool { 1023 return s.parseInternal(resp, req, dc, b) 1024} 1025 1026func (s *HTTPServer) checkWriteAccess(req *http.Request) error { 1027 if req.Method == http.MethodGet || req.Method == http.MethodHead || req.Method == http.MethodOptions { 1028 return nil 1029 } 1030 1031 allowed := s.agent.config.AllowWriteHTTPFrom 1032 if len(allowed) == 0 { 1033 return nil 1034 } 1035 1036 ipStr, _, err := net.SplitHostPort(req.RemoteAddr) 1037 if err != nil { 1038 return errors.Wrap(err, "unable to parse remote addr") 1039 } 1040 1041 ip := net.ParseIP(ipStr) 1042 1043 for _, n := range allowed { 1044 if n.Contains(ip) { 1045 return nil 1046 } 1047 } 1048 1049 return ForbiddenError{} 1050} 1051 1052func (s *HTTPServer) parseFilter(req *http.Request, filter *string) { 1053 if other := req.URL.Query().Get("filter"); other != "" { 1054 *filter = other 1055 } 1056} 1057