Merge branch '12690-12748-crunchstat-summary'
[arvados.git] / services / keepstore / proxy_remote.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 package main
6
7 import (
8         "io"
9         "net/http"
10         "strings"
11         "sync"
12
13         "git.curoverse.com/arvados.git/sdk/go/arvados"
14         "git.curoverse.com/arvados.git/sdk/go/arvadosclient"
15         "git.curoverse.com/arvados.git/sdk/go/auth"
16         "git.curoverse.com/arvados.git/sdk/go/keepclient"
17 )
18
19 type remoteProxy struct {
20         clients map[string]*keepclient.KeepClient
21         mtx     sync.Mutex
22 }
23
24 func (rp *remoteProxy) Get(w http.ResponseWriter, r *http.Request, cluster *arvados.Cluster) {
25         var remoteClient *keepclient.KeepClient
26         var parts []string
27         for i, part := range strings.Split(r.URL.Path[1:], "+") {
28                 switch {
29                 case i == 0:
30                         // don't try to parse hash part as hint
31                 case strings.HasPrefix(part, "A"):
32                         // drop local permission hint
33                         continue
34                 case len(part) > 7 && part[0] == 'R' && part[6] == '-':
35                         remoteID := part[1:6]
36                         remote, ok := cluster.RemoteClusters[remoteID]
37                         if !ok {
38                                 http.Error(w, "remote cluster not configured", http.StatusBadGateway)
39                                 return
40                         }
41                         token := GetAPIToken(r)
42                         if token == "" {
43                                 http.Error(w, "no token provided in Authorization header", http.StatusUnauthorized)
44                                 return
45                         }
46                         kc, err := rp.remoteClient(remoteID, remote, token)
47                         if err == auth.ErrObsoleteToken {
48                                 http.Error(w, err.Error(), http.StatusBadRequest)
49                                 return
50                         } else if err != nil {
51                                 http.Error(w, err.Error(), http.StatusInternalServerError)
52                                 return
53                         }
54                         remoteClient = kc
55                         part = "A" + part[7:]
56                 }
57                 parts = append(parts, part)
58         }
59         if remoteClient == nil {
60                 http.Error(w, "bad request", http.StatusBadRequest)
61                 return
62         }
63         locator := strings.Join(parts, "+")
64         rdr, _, _, err := remoteClient.Get(locator)
65         switch err.(type) {
66         case nil:
67                 defer rdr.Close()
68                 io.Copy(w, rdr)
69         case *keepclient.ErrNotFound:
70                 http.Error(w, err.Error(), http.StatusNotFound)
71         default:
72                 http.Error(w, err.Error(), http.StatusBadGateway)
73         }
74 }
75
76 func (rp *remoteProxy) remoteClient(remoteID string, remoteCluster arvados.RemoteCluster, token string) (*keepclient.KeepClient, error) {
77         rp.mtx.Lock()
78         kc, ok := rp.clients[remoteID]
79         rp.mtx.Unlock()
80         if !ok {
81                 c := &arvados.Client{
82                         APIHost:   remoteCluster.Host,
83                         AuthToken: "xxx",
84                         Insecure:  remoteCluster.Insecure,
85                 }
86                 ac, err := arvadosclient.New(c)
87                 if err != nil {
88                         return nil, err
89                 }
90                 kc, err = keepclient.MakeKeepClient(ac)
91                 if err != nil {
92                         return nil, err
93                 }
94
95                 rp.mtx.Lock()
96                 if rp.clients == nil {
97                         rp.clients = map[string]*keepclient.KeepClient{remoteID: kc}
98                 } else {
99                         rp.clients[remoteID] = kc
100                 }
101                 rp.mtx.Unlock()
102         }
103         accopy := *kc.Arvados
104         accopy.ApiToken = token
105         kccopy := *kc
106         kccopy.Arvados = &accopy
107         token, err := auth.SaltToken(token, remoteID)
108         if err != nil {
109                 return nil, err
110         }
111         kccopy.Arvados.ApiToken = token
112         return &kccopy, nil
113 }