Merge branch '21666-provision-test-improvement'
[arvados.git] / lib / controller / handler.go
index 9054dd633023309cc117c21bdb38d4740e5b0c7e..7c4bb0912fb3feae8871d9a5e2f920bb777738c4 100644 (file)
@@ -140,6 +140,8 @@ func (h *Handler) setup() {
        mux.Handle("/arvados/v1/groups/", rtr)
        mux.Handle("/arvados/v1/links", rtr)
        mux.Handle("/arvados/v1/links/", rtr)
+       mux.Handle("/arvados/v1/authorized_keys", rtr)
+       mux.Handle("/arvados/v1/authorized_keys/", rtr)
        mux.Handle("/login", rtr)
        mux.Handle("/logout", rtr)
        mux.Handle("/arvados/v1/api_client_authorizations", rtr)
@@ -147,6 +149,7 @@ func (h *Handler) setup() {
 
        hs := http.NotFoundHandler()
        hs = prepend(hs, h.proxyRailsAPI)
+       hs = prepend(hs, h.routeContainerEndpoints(rtr))
        hs = prepend(hs, h.limitLogCreateRequests)
        hs = h.setupProxyRemoteCluster(hs)
        hs = prepend(hs, oidcAuthorizer.Middleware)
@@ -202,9 +205,32 @@ func (h *Handler) localClusterRequest(req *http.Request) (*http.Response, error)
        if insecure {
                client = h.insecureClient
        }
+       // Clearing the Host field here causes the Go http client to
+       // use the host part of urlOut as the Host header in the
+       // outgoing request, instead of the Host value from the
+       // original request we received.
+       req.Host = ""
        return h.proxy.Do(req, urlOut, client)
 }
 
+// Route /arvados/v1/containers/{uuid}/log*, .../ssh, and
+// .../gateway_tunnel to rtr, pass everything else to next.
+//
+// (http.ServeMux doesn't let us route these without also routing
+// everything under /containers/, which we don't want yet.)
+func (h *Handler) routeContainerEndpoints(rtr http.Handler) middlewareFunc {
+       return func(w http.ResponseWriter, req *http.Request, next http.Handler) {
+               trim := strings.TrimPrefix(req.URL.Path, "/arvados/v1/containers/")
+               if trim != req.URL.Path && (strings.Index(trim, "/log") == 27 ||
+                       strings.Index(trim, "/ssh") == 27 ||
+                       strings.Index(trim, "/gateway_tunnel") == 27) {
+                       rtr.ServeHTTP(w, req)
+               } else {
+                       next.ServeHTTP(w, req)
+               }
+       }
+}
+
 func (h *Handler) limitLogCreateRequests(w http.ResponseWriter, req *http.Request, next http.Handler) {
        if cap(h.limitLogCreate) > 0 && req.Method == http.MethodPost && strings.HasPrefix(req.URL.Path, "/arvados/v1/logs") {
                select {
@@ -226,11 +252,15 @@ type cacheEnt struct {
        mtx          sync.Mutex
        header       http.Header
        body         []byte
+       expireAfter  time.Time
        refreshAfter time.Time
        refreshLock  sync.Mutex
 }
 
-const cacheTTL = 5 * time.Minute
+const (
+       cacheTTL    = 5 * time.Minute
+       cacheExpire = 24 * time.Hour
+)
 
 func (ent *cacheEnt) refresh(path string, do func(*http.Request) (*http.Response, error)) (http.Header, []byte, error) {
        ent.refreshLock.Lock()
@@ -254,13 +284,15 @@ func (ent *cacheEnt) refresh(path string, do func(*http.Request) (*http.Response
 
        ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Minute))
        defer cancel()
-       // 0.0.0.0:0 is just a placeholder here -- do(), which is
+       // "http://localhost" is just a placeholder here -- we'll fill
+       // in req.URL.Path below, and then do(), which is
        // localClusterRequest(), will replace the scheme and host
        // parts with the real proxy destination.
-       req, err := http.NewRequestWithContext(ctx, http.MethodGet, "http://0.0.0.0:0/"+path, nil)
+       req, err := http.NewRequestWithContext(ctx, http.MethodGet, "http://localhost", nil)
        if err != nil {
                return nil, nil, err
        }
+       req.URL.Path = path
        resp, err := do(req)
        if err != nil {
                return nil, nil, err
@@ -292,12 +324,16 @@ func (ent *cacheEnt) refresh(path string, do func(*http.Request) (*http.Response
        ent.header = header
        ent.body = body
        ent.refreshAfter = time.Now().Add(cacheTTL)
+       ent.expireAfter = time.Now().Add(cacheExpire)
        return ent.header, ent.body, nil
 }
 
 func (ent *cacheEnt) response() (http.Header, []byte, bool) {
        ent.mtx.Lock()
        defer ent.mtx.Unlock()
+       if ent.expireAfter.Before(time.Now()) {
+               ent.header, ent.body, ent.refreshAfter = nil, nil, time.Time{}
+       }
        return ent.header, ent.body, ent.refreshAfter.Before(time.Now())
 }