github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/command/agent/http.go (about) 1 package agent 2 3 import ( 4 "bytes" 5 "crypto/tls" 6 "encoding/json" 7 "errors" 8 "fmt" 9 "net" 10 "net/http" 11 "net/http/pprof" 12 "os" 13 "strconv" 14 "strings" 15 "time" 16 17 "github.com/armon/go-metrics" 18 assetfs "github.com/elazarl/go-bindata-assetfs" 19 "github.com/gorilla/handlers" 20 "github.com/gorilla/websocket" 21 "github.com/hashicorp/go-connlimit" 22 log "github.com/hashicorp/go-hclog" 23 "github.com/hashicorp/go-msgpack/codec" 24 multierror "github.com/hashicorp/go-multierror" 25 "github.com/rs/cors" 26 "golang.org/x/time/rate" 27 28 "github.com/hashicorp/nomad/acl" 29 "github.com/hashicorp/nomad/helper/noxssrw" 30 "github.com/hashicorp/nomad/helper/tlsutil" 31 "github.com/hashicorp/nomad/nomad/structs" 32 ) 33 34 const ( 35 // ErrInvalidMethod is used if the HTTP method is not supported 36 ErrInvalidMethod = "Invalid method" 37 38 // ErrEntOnly is the error returned if accessing an enterprise only 39 // endpoint 40 ErrEntOnly = "Nomad Enterprise only endpoint" 41 42 // ErrServerOnly is the error text returned if accessing a server only 43 // endpoint 44 ErrServerOnly = "Server only endpoint" 45 46 // ContextKeyReqID is a unique ID for a given request 47 ContextKeyReqID = "requestID" 48 49 // MissingRequestID is a placeholder if we cannot retrieve a request 50 // UUID from context 51 MissingRequestID = "<missing request id>" 52 ) 53 54 var ( 55 // Set to false by stub_asset if the ui build tag isn't enabled 56 uiEnabled = true 57 58 // Displayed when ui is disabled, but overridden if the ui build 59 // tag isn't enabled 60 stubHTML = "<html><p>Nomad UI is disabled</p></html>" 61 62 // allowCORSWithMethods sets permissive CORS headers for a handler, used by 63 // wrapCORS and wrapCORSWithMethods 64 allowCORSWithMethods = func(methods ...string) *cors.Cors { 65 return cors.New(cors.Options{ 66 AllowedOrigins: []string{"*"}, 67 AllowedMethods: methods, 68 AllowedHeaders: []string{"*"}, 69 AllowCredentials: true, 70 }) 71 } 72 ) 73 74 type handlerFn func(resp http.ResponseWriter, req *http.Request) (interface{}, error) 75 type handlerByteFn func(resp http.ResponseWriter, req *http.Request) ([]byte, error) 76 77 // HTTPServer is used to wrap an Agent and expose it over an HTTP interface 78 type HTTPServer struct { 79 agent *Agent 80 mux *http.ServeMux 81 listener net.Listener 82 listenerCh chan struct{} 83 logger log.Logger 84 Addr string 85 86 wsUpgrader *websocket.Upgrader 87 } 88 89 // NewHTTPServers starts an HTTP server for every address.http configured in 90 // the agent. 91 func NewHTTPServers(agent *Agent, config *Config) ([]*HTTPServer, error) { 92 var srvs []*HTTPServer 93 var serverInitializationErrors error 94 95 // Get connection handshake timeout limit 96 handshakeTimeout, err := time.ParseDuration(config.Limits.HTTPSHandshakeTimeout) 97 if err != nil { 98 return srvs, fmt.Errorf("error parsing https_handshake_timeout: %v", err) 99 } else if handshakeTimeout < 0 { 100 return srvs, fmt.Errorf("https_handshake_timeout must be >= 0") 101 } 102 103 // Get max connection limit 104 maxConns := 0 105 if mc := config.Limits.HTTPMaxConnsPerClient; mc != nil { 106 maxConns = *mc 107 } 108 if maxConns < 0 { 109 return srvs, fmt.Errorf("http_max_conns_per_client must be >= 0") 110 } 111 112 tlsConf, err := tlsutil.NewTLSConfiguration(config.TLSConfig, config.TLSConfig.VerifyHTTPSClient, true) 113 if err != nil && config.TLSConfig.EnableHTTP { 114 return srvs, fmt.Errorf("failed to initialize HTTP server TLS configuration: %s", err) 115 } 116 117 wsUpgrader := &websocket.Upgrader{ 118 ReadBufferSize: 2048, 119 WriteBufferSize: 2048, 120 } 121 122 // Start the listener 123 for _, addr := range config.normalizedAddrs.HTTP { 124 lnAddr, err := net.ResolveTCPAddr("tcp", addr) 125 if err != nil { 126 serverInitializationErrors = multierror.Append(serverInitializationErrors, err) 127 continue 128 } 129 ln, err := config.Listener("tcp", lnAddr.IP.String(), lnAddr.Port) 130 if err != nil { 131 serverInitializationErrors = multierror.Append(serverInitializationErrors, fmt.Errorf("failed to start HTTP listener: %v", err)) 132 continue 133 } 134 135 // If TLS is enabled, wrap the listener with a TLS listener 136 if config.TLSConfig.EnableHTTP { 137 tlsConfig, err := tlsConf.IncomingTLSConfig() 138 if err != nil { 139 serverInitializationErrors = multierror.Append(serverInitializationErrors, err) 140 continue 141 } 142 ln = tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, tlsConfig) 143 } 144 145 // Create the server 146 srv := &HTTPServer{ 147 agent: agent, 148 mux: http.NewServeMux(), 149 listener: ln, 150 listenerCh: make(chan struct{}), 151 logger: agent.httpLogger, 152 Addr: ln.Addr().String(), 153 wsUpgrader: wsUpgrader, 154 } 155 srv.registerHandlers(config.EnableDebug) 156 157 // Create HTTP server with timeouts 158 httpServer := http.Server{ 159 Addr: srv.Addr, 160 Handler: handlers.CompressHandler(srv.mux), 161 ConnState: makeConnState(config.TLSConfig.EnableHTTP, handshakeTimeout, maxConns, srv.logger), 162 ErrorLog: newHTTPServerLogger(srv.logger), 163 } 164 165 go func() { 166 defer close(srv.listenerCh) 167 httpServer.Serve(ln) 168 }() 169 170 srvs = append(srvs, srv) 171 } 172 173 // This HTTP server is only create when running in client mode, otherwise 174 // the builtinDialer and builtinListener will be nil. 175 if agent.builtinDialer != nil && agent.builtinListener != nil { 176 srv := &HTTPServer{ 177 agent: agent, 178 mux: http.NewServeMux(), 179 listener: agent.builtinListener, 180 listenerCh: make(chan struct{}), 181 logger: agent.httpLogger, 182 Addr: "builtin", 183 wsUpgrader: wsUpgrader, 184 } 185 186 srv.registerHandlers(config.EnableDebug) 187 188 httpServer := http.Server{ 189 Addr: srv.Addr, 190 Handler: srv.mux, 191 ErrorLog: newHTTPServerLogger(srv.logger), 192 } 193 194 go func() { 195 defer close(srv.listenerCh) 196 httpServer.Serve(agent.builtinListener) 197 }() 198 199 srvs = append(srvs, srv) 200 } 201 202 if serverInitializationErrors != nil { 203 for _, srv := range srvs { 204 srv.Shutdown() 205 } 206 } 207 208 return srvs, serverInitializationErrors 209 } 210 211 // makeConnState returns a ConnState func for use in an http.Server. If 212 // isTLS=true and handshakeTimeout>0 then the handshakeTimeout will be applied 213 // as a connection deadline to new connections and removed when the connection 214 // is active (meaning it has successfully completed the TLS handshake). 215 // 216 // If limit > 0, a per-address connection limit will be enabled regardless of 217 // TLS. If connLimit == 0 there is no connection limit. 218 func makeConnState(isTLS bool, handshakeTimeout time.Duration, connLimit int, logger log.Logger) func(conn net.Conn, state http.ConnState) { 219 connLimiter := connLimiter(connLimit, logger) 220 if !isTLS || handshakeTimeout == 0 { 221 if connLimit > 0 { 222 // Still return the connection limiter 223 return connLimiter 224 } 225 return nil 226 } 227 228 if connLimit > 0 { 229 // Return conn state callback with connection limiting and a 230 // handshake timeout. 231 232 return func(conn net.Conn, state http.ConnState) { 233 switch state { 234 case http.StateNew: 235 // Set deadline to prevent slow send before TLS handshake or first 236 // byte of request. 237 conn.SetDeadline(time.Now().Add(handshakeTimeout)) 238 case http.StateActive: 239 // Clear read deadline. We should maybe set read timeouts more 240 // generally but that's a bigger task as some HTTP endpoints may 241 // stream large requests and responses (e.g. snapshot) so we can't 242 // set sensible blanket timeouts here. 243 conn.SetDeadline(time.Time{}) 244 } 245 246 // Call connection limiter 247 connLimiter(conn, state) 248 } 249 } 250 251 // Return conn state callback with just a handshake timeout 252 // (connection limiting disabled). 253 return func(conn net.Conn, state http.ConnState) { 254 switch state { 255 case http.StateNew: 256 // Set deadline to prevent slow send before TLS handshake or first 257 // byte of request. 258 conn.SetDeadline(time.Now().Add(handshakeTimeout)) 259 case http.StateActive: 260 // Clear read deadline. We should maybe set read timeouts more 261 // generally but that's a bigger task as some HTTP endpoints may 262 // stream large requests and responses (e.g. snapshot) so we can't 263 // set sensible blanket timeouts here. 264 conn.SetDeadline(time.Time{}) 265 } 266 } 267 } 268 269 // connLimiter returns a connection-limiter function with a rate-limited 429-response error handler. 270 // The rate-limit prevents the TLS handshake necessary to write the HTTP response 271 // from consuming too many server resources. 272 func connLimiter(connLimit int, logger log.Logger) func(conn net.Conn, state http.ConnState) { 273 // Global rate-limit of 10 responses per second with a 100-response burst. 274 limiter := rate.NewLimiter(10, 100) 275 276 tooManyConnsMsg := "Your IP is issuing too many concurrent connections, please rate limit your calls\n" 277 tooManyRequestsResponse := []byte(fmt.Sprintf("HTTP/1.1 429 Too Many Requests\r\n"+ 278 "Content-Type: text/plain\r\n"+ 279 "Content-Length: %d\r\n"+ 280 "Connection: close\r\n\r\n%s", len(tooManyConnsMsg), tooManyConnsMsg)) 281 return connlimit.NewLimiter(connlimit.Config{ 282 MaxConnsPerClientIP: connLimit, 283 }).HTTPConnStateFuncWithErrorHandler(func(err error, conn net.Conn) { 284 if err == connlimit.ErrPerClientIPLimitReached { 285 metrics.IncrCounter([]string{"nomad", "agent", "http", "exceeded"}, 1) 286 if n := limiter.Reserve(); n.Delay() == 0 { 287 logger.Warn("Too many concurrent connections", "address", conn.RemoteAddr().String(), "limit", connLimit) 288 conn.SetDeadline(time.Now().Add(10 * time.Millisecond)) 289 conn.Write(tooManyRequestsResponse) 290 } else { 291 n.Cancel() 292 } 293 } 294 conn.Close() 295 }) 296 } 297 298 // tcpKeepAliveListener sets TCP keep-alive timeouts on accepted 299 // connections. It's used by NewHttpServer so 300 // dead TCP connections eventually go away. 301 type tcpKeepAliveListener struct { 302 *net.TCPListener 303 } 304 305 func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) { 306 tc, err := ln.AcceptTCP() 307 if err != nil { 308 return 309 } 310 tc.SetKeepAlive(true) 311 tc.SetKeepAlivePeriod(30 * time.Second) 312 return tc, nil 313 } 314 315 // Shutdown is used to shutdown the HTTP server 316 func (s *HTTPServer) Shutdown() { 317 if s != nil { 318 s.logger.Debug("shutting down http server") 319 s.listener.Close() 320 <-s.listenerCh // block until http.Serve has returned. 321 } 322 } 323 324 // ResolveToken extracts the ACL token secret ID from the request and 325 // translates it into an ACL object. Returns nil if ACLs are disabled. 326 func (s *HTTPServer) ResolveToken(req *http.Request) (*acl.ACL, error) { 327 var secret string 328 s.parseToken(req, &secret) 329 330 var aclObj *acl.ACL 331 var err error 332 333 if srv := s.agent.Server(); srv != nil { 334 aclObj, err = srv.ResolveToken(secret) 335 } else { 336 // Not a Server, so use the Client for token resolution. Note 337 // this gets forwarded to a server with AllowStale = true if 338 // the local ACL cache TTL has expired (30s by default) 339 aclObj, err = s.agent.Client().ResolveToken(secret) 340 } 341 342 if err != nil { 343 return nil, fmt.Errorf("failed to resolve ACL token: %v", err) 344 } 345 346 return aclObj, nil 347 } 348 349 // registerHandlers is used to attach our handlers to the mux 350 func (s *HTTPServer) registerHandlers(enableDebug bool) { 351 s.mux.HandleFunc("/v1/jobs", s.wrap(s.JobsRequest)) 352 s.mux.HandleFunc("/v1/jobs/parse", s.wrap(s.JobsParseRequest)) 353 s.mux.HandleFunc("/v1/job/", s.wrap(s.JobSpecificRequest)) 354 355 s.mux.HandleFunc("/v1/nodes", s.wrap(s.NodesRequest)) 356 s.mux.HandleFunc("/v1/node/", s.wrap(s.NodeSpecificRequest)) 357 358 s.mux.HandleFunc("/v1/allocations", s.wrap(s.AllocsRequest)) 359 s.mux.HandleFunc("/v1/allocation/", s.wrap(s.AllocSpecificRequest)) 360 361 s.mux.HandleFunc("/v1/evaluations", s.wrap(s.EvalsRequest)) 362 s.mux.HandleFunc("/v1/evaluations/count", s.wrap(s.EvalsCountRequest)) 363 s.mux.HandleFunc("/v1/evaluation/", s.wrap(s.EvalSpecificRequest)) 364 365 s.mux.HandleFunc("/v1/deployments", s.wrap(s.DeploymentsRequest)) 366 s.mux.HandleFunc("/v1/deployment/", s.wrap(s.DeploymentSpecificRequest)) 367 368 s.mux.HandleFunc("/v1/volumes", s.wrap(s.CSIVolumesRequest)) 369 s.mux.HandleFunc("/v1/volumes/external", s.wrap(s.CSIExternalVolumesRequest)) 370 s.mux.HandleFunc("/v1/volumes/snapshot", s.wrap(s.CSISnapshotsRequest)) 371 s.mux.HandleFunc("/v1/volume/csi/", s.wrap(s.CSIVolumeSpecificRequest)) 372 s.mux.HandleFunc("/v1/plugins", s.wrap(s.CSIPluginsRequest)) 373 s.mux.HandleFunc("/v1/plugin/csi/", s.wrap(s.CSIPluginSpecificRequest)) 374 375 s.mux.HandleFunc("/v1/acl/policies", s.wrap(s.ACLPoliciesRequest)) 376 s.mux.HandleFunc("/v1/acl/policy/", s.wrap(s.ACLPolicySpecificRequest)) 377 378 s.mux.HandleFunc("/v1/acl/token/onetime", s.wrap(s.UpsertOneTimeToken)) 379 s.mux.HandleFunc("/v1/acl/token/onetime/exchange", s.wrap(s.ExchangeOneTimeToken)) 380 s.mux.HandleFunc("/v1/acl/bootstrap", s.wrap(s.ACLTokenBootstrap)) 381 s.mux.HandleFunc("/v1/acl/tokens", s.wrap(s.ACLTokensRequest)) 382 s.mux.HandleFunc("/v1/acl/token", s.wrap(s.ACLTokenSpecificRequest)) 383 s.mux.HandleFunc("/v1/acl/token/", s.wrap(s.ACLTokenSpecificRequest)) 384 385 // Register our ACL role handlers. 386 s.mux.HandleFunc("/v1/acl/roles", s.wrap(s.ACLRoleListRequest)) 387 s.mux.HandleFunc("/v1/acl/role", s.wrap(s.ACLRoleRequest)) 388 s.mux.HandleFunc("/v1/acl/role/", s.wrap(s.ACLRoleSpecificRequest)) 389 390 // Register our ACL auth-method handlers. 391 s.mux.HandleFunc("/v1/acl/auth-methods", s.wrap(s.ACLAuthMethodListRequest)) 392 s.mux.HandleFunc("/v1/acl/auth-method", s.wrap(s.ACLAuthMethodRequest)) 393 s.mux.HandleFunc("/v1/acl/auth-method/", s.wrap(s.ACLAuthMethodSpecificRequest)) 394 395 s.mux.Handle("/v1/client/fs/", wrapCORS(s.wrap(s.FsRequest))) 396 s.mux.HandleFunc("/v1/client/gc", s.wrap(s.ClientGCRequest)) 397 s.mux.Handle("/v1/client/stats", wrapCORS(s.wrap(s.ClientStatsRequest))) 398 s.mux.Handle("/v1/client/allocation/", wrapCORS(s.wrap(s.ClientAllocRequest))) 399 400 s.mux.HandleFunc("/v1/agent/self", s.wrap(s.AgentSelfRequest)) 401 s.mux.HandleFunc("/v1/agent/join", s.wrap(s.AgentJoinRequest)) 402 s.mux.HandleFunc("/v1/agent/members", s.wrap(s.AgentMembersRequest)) 403 s.mux.HandleFunc("/v1/agent/force-leave", s.wrap(s.AgentForceLeaveRequest)) 404 s.mux.HandleFunc("/v1/agent/servers", s.wrap(s.AgentServersRequest)) 405 s.mux.HandleFunc("/v1/agent/schedulers", s.wrap(s.AgentSchedulerWorkerInfoRequest)) 406 s.mux.HandleFunc("/v1/agent/schedulers/config", s.wrap(s.AgentSchedulerWorkerConfigRequest)) 407 s.mux.HandleFunc("/v1/agent/keyring/", s.wrap(s.KeyringOperationRequest)) 408 s.mux.HandleFunc("/v1/agent/health", s.wrap(s.HealthRequest)) 409 s.mux.HandleFunc("/v1/agent/host", s.wrap(s.AgentHostRequest)) 410 411 // Register our service registration handlers. 412 s.mux.HandleFunc("/v1/services", s.wrap(s.ServiceRegistrationListRequest)) 413 s.mux.HandleFunc("/v1/service/", s.wrap(s.ServiceRegistrationRequest)) 414 415 // Monitor is *not* an untrusted endpoint despite the log contents 416 // potentially containing unsanitized user input. Monitor, like 417 // "/v1/client/fs/logs", explicitly sets a "text/plain" or 418 // "application/json" Content-Type depending on the ?plain= query 419 // parameter. 420 s.mux.HandleFunc("/v1/agent/monitor", s.wrap(s.AgentMonitor)) 421 422 s.mux.HandleFunc("/v1/agent/pprof/", s.wrapNonJSON(s.AgentPprofRequest)) 423 424 s.mux.HandleFunc("/v1/metrics", s.wrap(s.MetricsRequest)) 425 426 s.mux.HandleFunc("/v1/validate/job", s.wrap(s.ValidateJobRequest)) 427 428 s.mux.HandleFunc("/v1/regions", s.wrap(s.RegionListRequest)) 429 430 s.mux.HandleFunc("/v1/scaling/policies", s.wrap(s.ScalingPoliciesRequest)) 431 s.mux.HandleFunc("/v1/scaling/policy/", s.wrap(s.ScalingPolicySpecificRequest)) 432 433 s.mux.HandleFunc("/v1/status/leader", s.wrap(s.StatusLeaderRequest)) 434 s.mux.HandleFunc("/v1/status/peers", s.wrap(s.StatusPeersRequest)) 435 436 s.mux.HandleFunc("/v1/search/fuzzy", s.wrap(s.FuzzySearchRequest)) 437 s.mux.HandleFunc("/v1/search", s.wrap(s.SearchRequest)) 438 s.mux.HandleFunc("/v1/operator/license", s.wrap(s.LicenseRequest)) 439 s.mux.HandleFunc("/v1/operator/raft/", s.wrap(s.OperatorRequest)) 440 s.mux.HandleFunc("/v1/operator/keyring/", s.wrap(s.KeyringRequest)) 441 s.mux.HandleFunc("/v1/operator/autopilot/configuration", s.wrap(s.OperatorAutopilotConfiguration)) 442 s.mux.HandleFunc("/v1/operator/autopilot/health", s.wrap(s.OperatorServerHealth)) 443 s.mux.HandleFunc("/v1/operator/snapshot", s.wrap(s.SnapshotRequest)) 444 445 s.mux.HandleFunc("/v1/system/gc", s.wrap(s.GarbageCollectRequest)) 446 s.mux.HandleFunc("/v1/system/reconcile/summaries", s.wrap(s.ReconcileJobSummaries)) 447 448 s.mux.HandleFunc("/v1/operator/scheduler/configuration", s.wrap(s.OperatorSchedulerConfiguration)) 449 450 s.mux.HandleFunc("/v1/event/stream", s.wrap(s.EventStream)) 451 452 s.mux.HandleFunc("/v1/namespaces", s.wrap(s.NamespacesRequest)) 453 s.mux.HandleFunc("/v1/namespace", s.wrap(s.NamespaceCreateRequest)) 454 s.mux.HandleFunc("/v1/namespace/", s.wrap(s.NamespaceSpecificRequest)) 455 456 s.mux.Handle("/v1/vars", wrapCORS(s.wrap(s.VariablesListRequest))) 457 s.mux.Handle("/v1/var/", wrapCORSWithAllowedMethods(s.wrap(s.VariableSpecificRequest), "HEAD", "GET", "PUT", "DELETE")) 458 459 uiConfigEnabled := s.agent.config.UI != nil && s.agent.config.UI.Enabled 460 461 if uiEnabled && uiConfigEnabled { 462 s.mux.Handle("/ui/", http.StripPrefix("/ui/", s.handleUI(http.FileServer(&UIAssetWrapper{FileSystem: assetFS()})))) 463 s.logger.Debug("UI is enabled") 464 } else { 465 // Write the stubHTML 466 s.mux.HandleFunc("/ui/", func(w http.ResponseWriter, r *http.Request) { 467 w.Write([]byte(stubHTML)) 468 }) 469 if uiEnabled && !uiConfigEnabled { 470 s.logger.Warn("UI is disabled") 471 } else { 472 s.logger.Debug("UI is disabled in this build") 473 } 474 } 475 s.mux.Handle("/", s.handleRootFallthrough()) 476 477 if enableDebug { 478 if !s.agent.config.DevMode { 479 s.logger.Warn("enable_debug is set to true. This is insecure and should not be enabled in production") 480 } 481 s.mux.HandleFunc("/debug/pprof/", pprof.Index) 482 s.mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) 483 s.mux.HandleFunc("/debug/pprof/profile", pprof.Profile) 484 s.mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) 485 s.mux.HandleFunc("/debug/pprof/trace", pprof.Trace) 486 } 487 488 // Register enterprise endpoints. 489 s.registerEnterpriseHandlers() 490 } 491 492 // HTTPCodedError is used to provide the HTTP error code 493 type HTTPCodedError interface { 494 error 495 Code() int 496 } 497 498 type UIAssetWrapper struct { 499 FileSystem *assetfs.AssetFS 500 } 501 502 func (fs *UIAssetWrapper) Open(name string) (http.File, error) { 503 if file, err := fs.FileSystem.Open(name); err == nil { 504 return file, nil 505 } else { 506 // serve index.html instead of 404ing 507 if err == os.ErrNotExist { 508 return fs.FileSystem.Open("index.html") 509 } 510 return nil, err 511 } 512 } 513 514 func CodedError(c int, s string) HTTPCodedError { 515 return &codedError{s, c} 516 } 517 518 type codedError struct { 519 s string 520 code int 521 } 522 523 func (e *codedError) Error() string { 524 return e.s 525 } 526 527 func (e *codedError) Code() int { 528 return e.code 529 } 530 531 func (s *HTTPServer) handleUI(h http.Handler) http.Handler { 532 return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 533 header := w.Header() 534 header.Add("Content-Security-Policy", "default-src 'none'; connect-src *; img-src 'self' data:; script-src 'self'; style-src 'self' 'unsafe-inline'; form-action 'none'; frame-ancestors 'none'") 535 h.ServeHTTP(w, req) 536 }) 537 } 538 539 func (s *HTTPServer) handleRootFallthrough() http.Handler { 540 return s.auditHTTPHandler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 541 if req.URL.Path == "/" { 542 url := "/ui/" 543 if req.URL.RawQuery != "" { 544 url = url + "?" + req.URL.RawQuery 545 } 546 http.Redirect(w, req, url, 307) 547 } else { 548 w.WriteHeader(http.StatusNotFound) 549 } 550 })) 551 } 552 553 func errCodeFromHandler(err error) (int, string) { 554 if err == nil { 555 return 0, "" 556 } 557 558 code := 500 559 errMsg := err.Error() 560 if http, ok := err.(HTTPCodedError); ok { 561 code = http.Code() 562 } else if ecode, emsg, ok := structs.CodeFromRPCCodedErr(err); ok { 563 code = ecode 564 errMsg = emsg 565 } else { 566 // RPC errors get wrapped, so manually unwrap by only looking at their suffix 567 if strings.HasSuffix(errMsg, structs.ErrPermissionDenied.Error()) { 568 errMsg = structs.ErrPermissionDenied.Error() 569 code = 403 570 } else if strings.HasSuffix(errMsg, structs.ErrTokenNotFound.Error()) { 571 errMsg = structs.ErrTokenNotFound.Error() 572 code = 403 573 } else if strings.HasSuffix(errMsg, structs.ErrJobRegistrationDisabled.Error()) { 574 errMsg = structs.ErrJobRegistrationDisabled.Error() 575 code = 403 576 } 577 } 578 579 return code, errMsg 580 } 581 582 // wrap is used to wrap functions to make them more convenient 583 func (s *HTTPServer) wrap(handler func(resp http.ResponseWriter, req *http.Request) (interface{}, error)) func(resp http.ResponseWriter, req *http.Request) { 584 f := func(resp http.ResponseWriter, req *http.Request) { 585 setHeaders(resp, s.agent.config.HTTPAPIResponseHeaders) 586 // Invoke the handler 587 reqURL := req.URL.String() 588 start := time.Now() 589 defer func() { 590 s.logger.Debug("request complete", "method", req.Method, "path", reqURL, "duration", time.Since(start)) 591 }() 592 obj, err := s.auditHandler(handler)(resp, req) 593 594 // Check for an error 595 HAS_ERR: 596 if err != nil { 597 code := 500 598 errMsg := err.Error() 599 if http, ok := err.(HTTPCodedError); ok { 600 code = http.Code() 601 } else if ecode, emsg, ok := structs.CodeFromRPCCodedErr(err); ok { 602 code = ecode 603 errMsg = emsg 604 } else { 605 // RPC errors get wrapped, so manually unwrap by only looking at their suffix 606 if strings.HasSuffix(errMsg, structs.ErrPermissionDenied.Error()) { 607 errMsg = structs.ErrPermissionDenied.Error() 608 code = 403 609 } else if strings.HasSuffix(errMsg, structs.ErrTokenNotFound.Error()) { 610 errMsg = structs.ErrTokenNotFound.Error() 611 code = 403 612 } else if strings.HasSuffix(errMsg, structs.ErrJobRegistrationDisabled.Error()) { 613 errMsg = structs.ErrJobRegistrationDisabled.Error() 614 code = 403 615 } else if strings.HasSuffix(errMsg, structs.ErrIncompatibleFiltering.Error()) { 616 errMsg = structs.ErrIncompatibleFiltering.Error() 617 code = 400 618 } 619 } 620 621 resp.WriteHeader(code) 622 resp.Write([]byte(errMsg)) 623 if isAPIClientError(code) { 624 s.logger.Debug("request failed", "method", req.Method, "path", reqURL, "error", err, "code", code) 625 } else { 626 s.logger.Error("request failed", "method", req.Method, "path", reqURL, "error", err, "code", code) 627 } 628 return 629 } 630 631 prettyPrint := false 632 if v, ok := req.URL.Query()["pretty"]; ok { 633 if len(v) > 0 && (len(v[0]) == 0 || v[0] != "0") { 634 prettyPrint = true 635 } 636 } 637 638 // Write out the JSON object 639 if obj != nil { 640 var buf bytes.Buffer 641 if prettyPrint { 642 enc := codec.NewEncoder(&buf, structs.JsonHandlePretty) 643 err = enc.Encode(obj) 644 if err == nil { 645 buf.Write([]byte("\n")) 646 } 647 } else { 648 enc := codec.NewEncoder(&buf, structs.JsonHandleWithExtensions) 649 err = enc.Encode(obj) 650 } 651 if err != nil { 652 goto HAS_ERR 653 } 654 resp.Header().Set("Content-Type", "application/json") 655 resp.Write(buf.Bytes()) 656 } 657 } 658 return f 659 } 660 661 // wrapNonJSON is used to wrap functions returning non JSON 662 // serializeable data to make them more convenient. It is primarily 663 // responsible for setting nomad headers and logging. 664 // Handler functions are responsible for setting Content-Type Header 665 func (s *HTTPServer) wrapNonJSON(handler func(resp http.ResponseWriter, req *http.Request) ([]byte, error)) func(resp http.ResponseWriter, req *http.Request) { 666 f := func(resp http.ResponseWriter, req *http.Request) { 667 setHeaders(resp, s.agent.config.HTTPAPIResponseHeaders) 668 // Invoke the handler 669 reqURL := req.URL.String() 670 start := time.Now() 671 defer func() { 672 s.logger.Debug("request complete", "method", req.Method, "path", reqURL, "duration", time.Since(start)) 673 }() 674 obj, err := s.auditNonJSONHandler(handler)(resp, req) 675 676 // Check for an error 677 if err != nil { 678 code, errMsg := errCodeFromHandler(err) 679 resp.WriteHeader(code) 680 resp.Write([]byte(errMsg)) 681 if isAPIClientError(code) { 682 s.logger.Debug("request failed", "method", req.Method, "path", reqURL, "error", err, "code", code) 683 } else { 684 s.logger.Error("request failed", "method", req.Method, "path", reqURL, "error", err, "code", code) 685 } 686 return 687 } 688 689 // write response 690 if obj != nil { 691 resp.Write(obj) 692 } 693 } 694 return f 695 } 696 697 // isAPIClientError returns true if the passed http code represents a client error 698 func isAPIClientError(code int) bool { 699 return 400 <= code && code <= 499 700 } 701 702 // decodeBody is used to decode a JSON request body 703 func decodeBody(req *http.Request, out interface{}) error { 704 705 if req.Body == http.NoBody { 706 return errors.New("Request body is empty") 707 } 708 709 dec := json.NewDecoder(req.Body) 710 return dec.Decode(&out) 711 } 712 713 // setIndex is used to set the index response header 714 func setIndex(resp http.ResponseWriter, index uint64) { 715 resp.Header().Set("X-Nomad-Index", strconv.FormatUint(index, 10)) 716 } 717 718 // setKnownLeader is used to set the known leader header 719 func setKnownLeader(resp http.ResponseWriter, known bool) { 720 s := "true" 721 if !known { 722 s = "false" 723 } 724 resp.Header().Set("X-Nomad-KnownLeader", s) 725 } 726 727 // setLastContact is used to set the last contact header 728 func setLastContact(resp http.ResponseWriter, last time.Duration) { 729 lastMsec := uint64(last / time.Millisecond) 730 resp.Header().Set("X-Nomad-LastContact", strconv.FormatUint(lastMsec, 10)) 731 } 732 733 // setNextToken is used to set the next token header for pagination 734 func setNextToken(resp http.ResponseWriter, nextToken string) { 735 if nextToken != "" { 736 resp.Header().Set("X-Nomad-NextToken", nextToken) 737 } 738 } 739 740 // setMeta is used to set the query response meta data 741 func setMeta(resp http.ResponseWriter, m *structs.QueryMeta) { 742 setIndex(resp, m.Index) 743 setLastContact(resp, m.LastContact) 744 setKnownLeader(resp, m.KnownLeader) 745 setNextToken(resp, m.NextToken) 746 } 747 748 // setHeaders is used to set canonical response header fields 749 func setHeaders(resp http.ResponseWriter, headers map[string]string) { 750 for field, value := range headers { 751 resp.Header().Set(field, value) 752 } 753 } 754 755 // parseWait is used to parse the ?wait and ?index query params 756 // Returns true on error 757 func parseWait(resp http.ResponseWriter, req *http.Request, b *structs.QueryOptions) 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(400) 763 resp.Write([]byte("Invalid wait time")) 764 return true 765 } 766 b.MaxQueryTime = dur 767 } 768 if idx := query.Get("index"); idx != "" { 769 index, err := strconv.ParseUint(idx, 10, 64) 770 if err != nil { 771 resp.WriteHeader(400) 772 resp.Write([]byte("Invalid index")) 773 return true 774 } 775 b.MinQueryIndex = index 776 } 777 return false 778 } 779 780 // parseConsistency is used to parse the ?stale query params. 781 func parseConsistency(req *http.Request, b *structs.QueryOptions) { 782 query := req.URL.Query() 783 if _, ok := query["stale"]; ok { 784 b.AllowStale = true 785 } 786 } 787 788 // parsePrefix is used to parse the ?prefix query param 789 func parsePrefix(req *http.Request, b *structs.QueryOptions) { 790 query := req.URL.Query() 791 if prefix := query.Get("prefix"); prefix != "" { 792 b.Prefix = prefix 793 } 794 } 795 796 // parseRegion is used to parse the ?region query param 797 func (s *HTTPServer) parseRegion(req *http.Request, r *string) { 798 if other := req.URL.Query().Get("region"); other != "" { 799 *r = other 800 } else if *r == "" { 801 *r = s.agent.config.Region 802 } 803 } 804 805 // parseNamespace is used to parse the ?namespace parameter 806 func parseNamespace(req *http.Request, n *string) { 807 if other := req.URL.Query().Get("namespace"); other != "" { 808 *n = other 809 } else if *n == "" { 810 *n = structs.DefaultNamespace 811 } 812 } 813 814 // parseIdempotencyToken is used to parse the ?idempotency_token parameter 815 func parseIdempotencyToken(req *http.Request, n *string) { 816 if idempotencyToken := req.URL.Query().Get("idempotency_token"); idempotencyToken != "" { 817 *n = idempotencyToken 818 } 819 } 820 821 // parseBool parses a query parameter to a boolean or returns (nil, nil) if the 822 // parameter is not present. 823 func parseBool(req *http.Request, field string) (*bool, error) { 824 if str := req.URL.Query().Get(field); str != "" { 825 param, err := strconv.ParseBool(str) 826 if err != nil { 827 return nil, fmt.Errorf("Failed to parse value of %q (%v) as a bool: %v", field, str, err) 828 } 829 return ¶m, nil 830 } 831 832 return nil, nil 833 } 834 835 // parseInt parses a query parameter to a int or returns (nil, nil) if the 836 // parameter is not present. 837 func parseInt(req *http.Request, field string) (*int, error) { 838 if str := req.URL.Query().Get(field); str != "" { 839 param, err := strconv.Atoi(str) 840 if err != nil { 841 return nil, fmt.Errorf("Failed to parse value of %q (%v) as a int: %v", field, str, err) 842 } 843 return ¶m, nil 844 } 845 return nil, nil 846 } 847 848 // parseToken is used to parse the X-Nomad-Token param 849 func (s *HTTPServer) parseToken(req *http.Request, token *string) { 850 if other := req.Header.Get("X-Nomad-Token"); other != "" { 851 *token = other 852 return 853 } 854 855 if other := req.Header.Get("Authorization"); other != "" { 856 // HTTP Authorization headers are in the format: <Scheme>[SPACE]<Value> 857 // Ref. https://tools.ietf.org/html/rfc7236#section-3 858 parts := strings.Split(other, " ") 859 860 // Authorization Header is invalid if containing 1 or 0 parts, e.g.: 861 // "" || "<Scheme><Value>" || "<Scheme>" || "<Value>" 862 if len(parts) > 1 { 863 scheme := parts[0] 864 // Everything after "<Scheme>" is "<Value>", trimmed 865 value := strings.TrimSpace(strings.Join(parts[1:], " ")) 866 867 // <Scheme> must be "Bearer" 868 if strings.ToLower(scheme) == "bearer" { 869 // Since Bearer tokens shouldn't contain spaces (rfc6750#section-2.1) 870 // "value" is tokenized, only the first item is used 871 *token = strings.TrimSpace(strings.Split(value, " ")[0]) 872 } 873 } 874 } 875 } 876 877 // parse is a convenience method for endpoints that need to parse multiple flags 878 // It sets r to the region and b to the QueryOptions in req 879 func (s *HTTPServer) parse(resp http.ResponseWriter, req *http.Request, r *string, b *structs.QueryOptions) bool { 880 s.parseRegion(req, r) 881 s.parseToken(req, &b.AuthToken) 882 parseConsistency(req, b) 883 parsePrefix(req, b) 884 parseNamespace(req, &b.Namespace) 885 parsePagination(req, b) 886 parseFilter(req, b) 887 parseReverse(req, b) 888 return parseWait(resp, req, b) 889 } 890 891 // parsePagination parses the pagination fields for QueryOptions 892 func parsePagination(req *http.Request, b *structs.QueryOptions) { 893 query := req.URL.Query() 894 rawPerPage := query.Get("per_page") 895 if rawPerPage != "" { 896 perPage, err := strconv.ParseInt(rawPerPage, 10, 32) 897 if err == nil { 898 b.PerPage = int32(perPage) 899 } 900 } 901 902 b.NextToken = query.Get("next_token") 903 } 904 905 // parseFilter parses the filter query parameter for QueryOptions 906 func parseFilter(req *http.Request, b *structs.QueryOptions) { 907 query := req.URL.Query() 908 if filter := query.Get("filter"); filter != "" { 909 b.Filter = filter 910 } 911 } 912 913 // parseReverse parses the reverse query parameter for QueryOptions 914 func parseReverse(req *http.Request, b *structs.QueryOptions) { 915 query := req.URL.Query() 916 b.Reverse = query.Get("reverse") == "true" 917 } 918 919 // parseWriteRequest is a convenience method for endpoints that need to parse a 920 // write request. 921 func (s *HTTPServer) parseWriteRequest(req *http.Request, w *structs.WriteRequest) { 922 parseNamespace(req, &w.Namespace) 923 s.parseToken(req, &w.AuthToken) 924 s.parseRegion(req, &w.Region) 925 parseIdempotencyToken(req, &w.IdempotencyToken) 926 } 927 928 // wrapUntrustedContent wraps handlers in a http.ResponseWriter that prevents 929 // setting Content-Types that a browser may render (eg text/html). Any API that 930 // returns service-generated content (eg /v1/client/fs/cat) must be wrapped. 931 func (s *HTTPServer) wrapUntrustedContent(handler handlerFn) handlerFn { 932 return func(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 933 resp, closeWriter := noxssrw.NewResponseWriter(resp) 934 defer func() { 935 if _, err := closeWriter(); err != nil { 936 // Can't write an error response at this point so just 937 // log. s.wrap does not even log when resp.Write fails, 938 // so log at low level. 939 s.logger.Debug("error writing HTTP response", "error", err, 940 "method", req.Method, "path", req.URL.String()) 941 } 942 }() 943 944 // Call the wrapped handler 945 return handler(resp, req) 946 } 947 } 948 949 // wrapCORS wraps a HandlerFunc in allowCORS with read ("HEAD", "GET") methods 950 // and returns a http.Handler 951 func wrapCORS(f func(http.ResponseWriter, *http.Request)) http.Handler { 952 return wrapCORSWithAllowedMethods(f, "HEAD", "GET") 953 } 954 955 // wrapCORSWithAllowedMethods wraps a HandlerFunc in an allowCORS with the given 956 // method list and returns a http.Handler 957 func wrapCORSWithAllowedMethods(f func(http.ResponseWriter, *http.Request), methods ...string) http.Handler { 958 return allowCORSWithMethods(methods...).Handler(http.HandlerFunc(f)) 959 }