17755: Merge branch 'main' into 17755-add-singularity-to-compute-image
[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.arvados.org/arvados.git/sdk/go/auth"
16         "git.arvados.org/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                 return false
30         }
31
32         // First make sure supplied token is valid.
33         creds := auth.NewCredentials()
34         creds.LoadTokensFromHTTPRequest(req)
35
36         currentUser, ok, err := h.handler.validateAPItoken(req, creds.Tokens[0])
37         if err != nil {
38                 httpserver.Error(w, err.Error(), http.StatusInternalServerError)
39                 return true
40         } else if !ok {
41                 httpserver.Error(w, "invalid API token", http.StatusForbidden)
42                 return true
43         }
44
45         if *clusterID == "" || *clusterID == h.handler.Cluster.ClusterID {
46                 // Submitting container request to local cluster. No
47                 // need to set a runtime_token (rails api will create
48                 // one when the container runs) or do a remote cluster
49                 // request.
50                 return false
51         }
52
53         if req.Header.Get("Content-Type") != "application/json" {
54                 httpserver.Error(w, "Expected Content-Type: application/json, got "+req.Header.Get("Content-Type"), http.StatusBadRequest)
55                 return true
56         }
57
58         originalBody := req.Body
59         defer originalBody.Close()
60         var request map[string]interface{}
61         err = json.NewDecoder(req.Body).Decode(&request)
62         if err != nil {
63                 httpserver.Error(w, err.Error(), http.StatusBadRequest)
64                 return true
65         }
66
67         crString, ok := request["container_request"].(string)
68         if ok {
69                 var crJSON map[string]interface{}
70                 err := json.Unmarshal([]byte(crString), &crJSON)
71                 if err != nil {
72                         httpserver.Error(w, err.Error(), http.StatusBadRequest)
73                         return true
74                 }
75
76                 request["container_request"] = crJSON
77         }
78
79         containerRequest, ok := request["container_request"].(map[string]interface{})
80         if !ok {
81                 // Use toplevel object as the container_request object
82                 containerRequest = request
83         }
84
85         // If runtime_token is not set, create a new token
86         if _, ok := containerRequest["runtime_token"]; !ok {
87                 if len(currentUser.Authorization.Scopes) != 1 || currentUser.Authorization.Scopes[0] != "all" {
88                         httpserver.Error(w, "Token scope is not [all]", http.StatusForbidden)
89                         return true
90                 }
91
92                 if strings.HasPrefix(currentUser.Authorization.UUID, h.handler.Cluster.ClusterID) {
93                         // Local user, submitting to a remote cluster.
94                         // Create a new time-limited token.
95                         newtok, err := h.handler.createAPItoken(req, currentUser.UUID, nil)
96                         if err != nil {
97                                 httpserver.Error(w, err.Error(), http.StatusForbidden)
98                                 return true
99                         }
100                         containerRequest["runtime_token"] = newtok.TokenV2()
101                 } else {
102                         // Remote user. Container request will use the
103                         // current token, minus the trailing portion
104                         // (optional container uuid).
105                         sp := strings.Split(creds.Tokens[0], "/")
106                         if len(sp) >= 3 {
107                                 containerRequest["runtime_token"] = strings.Join(sp[0:3], "/")
108                         } else {
109                                 containerRequest["runtime_token"] = creds.Tokens[0]
110                         }
111                 }
112         }
113
114         newbody, err := json.Marshal(request)
115         buf := bytes.NewBuffer(newbody)
116         req.Body = ioutil.NopCloser(buf)
117         req.ContentLength = int64(buf.Len())
118         req.Header.Set("Content-Length", fmt.Sprintf("%v", buf.Len()))
119
120         resp, err := h.handler.remoteClusterRequest(*clusterID, req)
121         h.handler.proxy.ForwardResponse(w, resp, err)
122         return true
123 }