1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
17 "git.curoverse.com/arvados.git/sdk/go/arvados"
18 "git.curoverse.com/arvados.git/sdk/go/arvadosclient"
19 "git.curoverse.com/arvados.git/sdk/go/auth"
20 "git.curoverse.com/arvados.git/sdk/go/keepclient"
23 type remoteProxy struct {
24 clients map[string]*keepclient.KeepClient
28 func (rp *remoteProxy) Get(ctx context.Context, w http.ResponseWriter, r *http.Request, cluster *arvados.Cluster) {
29 token := GetAPIToken(r)
31 http.Error(w, "no token provided in Authorization header", http.StatusUnauthorized)
34 if sign := r.Header.Get("X-Keep-Signature"); sign != "" {
35 buf, err := getBufferWithContext(ctx, bufs, BlockSize)
37 http.Error(w, err.Error(), http.StatusServiceUnavailable)
41 rrc := &remoteResponseCacher{
42 Locator: r.URL.Path[1:],
50 var remoteClient *keepclient.KeepClient
52 for i, part := range strings.Split(r.URL.Path[1:], "+") {
55 // don't try to parse hash part as hint
56 case strings.HasPrefix(part, "A"):
57 // drop local permission hint
59 case len(part) > 7 && part[0] == 'R' && part[6] == '-':
61 remote, ok := cluster.RemoteClusters[remoteID]
63 http.Error(w, "remote cluster not configured", http.StatusBadGateway)
66 kc, err := rp.remoteClient(remoteID, remote, token)
67 if err == auth.ErrObsoleteToken {
68 http.Error(w, err.Error(), http.StatusBadRequest)
70 } else if err != nil {
71 http.Error(w, err.Error(), http.StatusInternalServerError)
77 parts = append(parts, part)
79 if remoteClient == nil {
80 http.Error(w, "bad request", http.StatusBadRequest)
83 locator := strings.Join(parts, "+")
84 rdr, _, _, err := remoteClient.Get(locator)
89 case *keepclient.ErrNotFound:
90 http.Error(w, err.Error(), http.StatusNotFound)
92 http.Error(w, err.Error(), http.StatusBadGateway)
96 func (rp *remoteProxy) remoteClient(remoteID string, remoteCluster arvados.RemoteCluster, token string) (*keepclient.KeepClient, error) {
98 kc, ok := rp.clients[remoteID]
101 c := &arvados.Client{
102 APIHost: remoteCluster.Host,
104 Insecure: remoteCluster.Insecure,
106 ac, err := arvadosclient.New(c)
110 kc, err = keepclient.MakeKeepClient(ac)
116 if rp.clients == nil {
117 rp.clients = map[string]*keepclient.KeepClient{remoteID: kc}
119 rp.clients[remoteID] = kc
123 accopy := *kc.Arvados
124 accopy.ApiToken = token
126 kccopy.Arvados = &accopy
127 token, err := auth.SaltToken(token, remoteID)
131 kccopy.Arvados.ApiToken = token
135 var localOrRemoteSignature = regexp.MustCompile(`\+[AR][^\+]*`)
137 // remoteResponseCacher wraps http.ResponseWriter. It buffers the
138 // response data in the provided buffer, writes/touches a copy on a
139 // local volume, adds a response header with a locally-signed locator,
140 // and finally writes the data through.
141 type remoteResponseCacher struct {
149 func (rrc *remoteResponseCacher) Write(p []byte) (int, error) {
150 if len(rrc.Buffer)+len(p) > cap(rrc.Buffer) {
151 return 0, errors.New("buffer full")
153 rrc.Buffer = append(rrc.Buffer, p...)
157 func (rrc *remoteResponseCacher) WriteHeader(statusCode int) {
158 rrc.statusCode = statusCode
161 func (rrc *remoteResponseCacher) Flush(ctx context.Context) {
162 if rrc.statusCode == 0 {
163 rrc.statusCode = http.StatusOK
164 } else if rrc.statusCode != http.StatusOK {
165 rrc.ResponseWriter.WriteHeader(rrc.statusCode)
166 rrc.ResponseWriter.Write(rrc.Buffer)
169 _, err := PutBlock(ctx, rrc.Buffer, rrc.Locator[:32])
170 if err == RequestHashError {
171 http.Error(rrc.ResponseWriter, "checksum mismatch in remote response", http.StatusBadGateway)
174 if err, ok := err.(*KeepError); ok {
175 http.Error(rrc.ResponseWriter, err.Error(), err.HTTPCode)
179 http.Error(rrc.ResponseWriter, err.Error(), http.StatusBadGateway)
183 unsigned := localOrRemoteSignature.ReplaceAllLiteralString(rrc.Locator, "")
184 signed := SignLocator(unsigned, rrc.Token, time.Now().Add(theConfig.BlobSignatureTTL.Duration()))
185 if signed == unsigned {
186 http.Error(rrc.ResponseWriter, "could not sign locator", http.StatusInternalServerError)
189 rrc.Header().Set("X-Keep-Locator", signed)
190 rrc.ResponseWriter.WriteHeader(rrc.statusCode)
191 rrc.ResponseWriter.Write(rrc.Buffer)