Merge branch '14262-remote-container' refs #14262
[arvados.git] / lib / controller / fed_containers.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 package controller
6
7 import (
8         "bytes"
9         "encoding/json"
10         "fmt"
11         "io/ioutil"
12         "net/http"
13         "strings"
14
15         "git.curoverse.com/arvados.git/sdk/go/auth"
16         "git.curoverse.com/arvados.git/sdk/go/httpserver"
17 )
18
19 func remoteContainerRequestCreate(
20         h *genericFederatedRequestHandler,
21         effectiveMethod string,
22         clusterId *string,
23         uuid string,
24         remainder string,
25         w http.ResponseWriter,
26         req *http.Request) bool {
27
28         if effectiveMethod != "POST" || uuid != "" || remainder != "" ||
29                 *clusterId == "" || *clusterId == h.handler.Cluster.ClusterID {
30                 return false
31         }
32
33         if req.Header.Get("Content-Type") != "application/json" {
34                 httpserver.Error(w, "Expected Content-Type: application/json, got "+req.Header.Get("Content-Type"), http.StatusBadRequest)
35                 return true
36         }
37
38         originalBody := req.Body
39         defer originalBody.Close()
40         var request map[string]interface{}
41         err := json.NewDecoder(req.Body).Decode(&request)
42         if err != nil {
43                 httpserver.Error(w, err.Error(), http.StatusBadRequest)
44                 return true
45         }
46
47         crString, ok := request["container_request"].(string)
48         if ok {
49                 var crJson map[string]interface{}
50                 err := json.Unmarshal([]byte(crString), &crJson)
51                 if err != nil {
52                         httpserver.Error(w, err.Error(), http.StatusBadRequest)
53                         return true
54                 }
55
56                 request["container_request"] = crJson
57         }
58
59         containerRequest, ok := request["container_request"].(map[string]interface{})
60         if !ok {
61                 // Use toplevel object as the container_request object
62                 containerRequest = request
63         }
64
65         // If runtime_token is not set, create a new token
66         if _, ok := containerRequest["runtime_token"]; !ok {
67                 // First make sure supplied token is valid.
68                 creds := auth.NewCredentials()
69                 creds.LoadTokensFromHTTPRequest(req)
70
71                 currentUser, err := h.handler.validateAPItoken(req, creds.Tokens[0])
72                 if err != nil {
73                         httpserver.Error(w, err.Error(), http.StatusForbidden)
74                         return true
75                 }
76
77                 if len(currentUser.Authorization.Scopes) != 1 || currentUser.Authorization.Scopes[0] != "all" {
78                         httpserver.Error(w, "Token scope is not [all]", http.StatusForbidden)
79                         return true
80                 }
81
82                 // Must be home cluster for this authorization
83                 if strings.HasPrefix(currentUser.Authorization.UUID, h.handler.Cluster.ClusterID) {
84                         newtok, err := h.handler.createAPItoken(req, currentUser.UUID, nil)
85                         if err != nil {
86                                 httpserver.Error(w, err.Error(), http.StatusForbidden)
87                                 return true
88                         }
89                         containerRequest["runtime_token"] = newtok.TokenV2()
90                 }
91         }
92
93         newbody, err := json.Marshal(request)
94         buf := bytes.NewBuffer(newbody)
95         req.Body = ioutil.NopCloser(buf)
96         req.ContentLength = int64(buf.Len())
97         req.Header.Set("Content-Length", fmt.Sprintf("%v", buf.Len()))
98
99         resp, err := h.handler.remoteClusterRequest(*clusterId, req)
100         h.handler.proxy.ForwardResponse(w, resp, err)
101         return true
102 }