1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
19 "git.arvados.org/arvados.git/sdk/go/arvados"
20 "git.arvados.org/arvados.git/sdk/go/arvadostest"
21 "git.arvados.org/arvados.git/sdk/go/auth"
22 "git.arvados.org/arvados.git/sdk/go/ctxlog"
23 "git.arvados.org/arvados.git/sdk/go/httpserver"
24 "git.arvados.org/arvados.git/sdk/go/keepclient"
25 "github.com/prometheus/client_golang/prometheus"
26 check "gopkg.in/check.v1"
29 var _ = check.Suite(&proxyRemoteSuite{})
31 type proxyRemoteSuite struct {
32 cluster *arvados.Cluster
35 remoteClusterID string
36 remoteBlobSigningKey []byte
37 remoteKeepLocator string
39 remoteKeepproxy *httptest.Server
40 remoteKeepRequests int64
41 remoteAPI *httptest.Server
44 func (s *proxyRemoteSuite) remoteKeepproxyHandler(w http.ResponseWriter, r *http.Request) {
45 expectToken, err := auth.SaltToken(arvadostest.ActiveTokenV2, s.remoteClusterID)
49 atomic.AddInt64(&s.remoteKeepRequests, 1)
51 if auth := strings.Split(r.Header.Get("Authorization"), " "); len(auth) == 2 && (auth[0] == "OAuth2" || auth[0] == "Bearer") {
54 if r.Method == "GET" && r.URL.Path == "/"+s.remoteKeepLocator && token == expectToken {
55 w.Write(s.remoteKeepData)
58 http.Error(w, "404", 404)
61 func (s *proxyRemoteSuite) remoteAPIHandler(w http.ResponseWriter, r *http.Request) {
62 host, port, _ := net.SplitHostPort(strings.Split(s.remoteKeepproxy.URL, "//")[1])
63 portnum, _ := strconv.Atoi(port)
64 if r.URL.Path == "/arvados/v1/discovery/v1/rest" {
65 json.NewEncoder(w).Encode(arvados.DiscoveryDocument{})
68 if r.URL.Path == "/arvados/v1/keep_services/accessible" {
69 json.NewEncoder(w).Encode(arvados.KeepServiceList{
70 Items: []arvados.KeepService{
72 UUID: s.remoteClusterID + "-bi6l4-proxyproxyproxy",
76 ServiceSSLFlag: false,
82 http.Error(w, "404", 404)
85 func (s *proxyRemoteSuite) SetUpTest(c *check.C) {
86 s.remoteClusterID = "z0000"
87 s.remoteBlobSigningKey = []byte("3b6df6fb6518afe12922a5bc8e67bf180a358bc8")
88 s.remoteKeepproxy = httptest.NewServer(httpserver.LogRequests(http.HandlerFunc(s.remoteKeepproxyHandler)))
89 s.remoteAPI = httptest.NewUnstartedServer(http.HandlerFunc(s.remoteAPIHandler))
90 s.remoteAPI.StartTLS()
91 s.cluster = testCluster(c)
92 s.cluster.RemoteClusters = map[string]arvados.RemoteCluster{
94 Host: strings.Split(s.remoteAPI.URL, "//")[1],
100 s.cluster.Volumes = map[string]arvados.Volume{"zzzzz-nyw5e-000000000000000": {Driver: "stub"}}
103 func (s *proxyRemoteSuite) TearDownTest(c *check.C) {
105 s.remoteKeepproxy.Close()
108 func (s *proxyRemoteSuite) TestProxyRemote(c *check.C) {
109 reg := prometheus.NewRegistry()
110 router, cancel := testRouter(c, s.cluster, reg)
112 instrumented := httpserver.Instrument(reg, ctxlog.TestLogger(c), router)
113 handler := httpserver.LogRequests(instrumented.ServeAPI(s.cluster.ManagementToken, instrumented))
115 data := []byte("foo bar")
116 s.remoteKeepData = data
117 locator := fmt.Sprintf("%x+%d", md5.Sum(data), len(data))
118 s.remoteKeepLocator = keepclient.SignLocator(locator, arvadostest.ActiveTokenV2, time.Now().Add(time.Minute), time.Minute, s.remoteBlobSigningKey)
120 path := "/" + strings.Replace(s.remoteKeepLocator, "+A", "+R"+s.remoteClusterID+"-", 1)
122 for _, trial := range []struct {
126 xKeepSignature string
127 expectRemoteReqs int64
134 token: arvadostest.ActiveTokenV2,
136 expectCode: http.StatusOK,
139 label: "obsolete token",
141 token: arvadostest.ActiveToken,
143 expectCode: http.StatusBadRequest,
148 token: arvadostest.ActiveTokenV2[:len(arvadostest.ActiveTokenV2)-3] + "xxx",
150 expectCode: http.StatusNotFound,
155 token: arvadostest.ActiveTokenV2,
157 expectCode: http.StatusOK,
160 label: "HEAD with local signature",
162 xKeepSignature: "local, time=" + time.Now().Format(time.RFC3339),
163 token: arvadostest.ActiveTokenV2,
165 expectCode: http.StatusOK,
166 expectSignature: true,
169 label: "GET with local signature",
171 xKeepSignature: "local, time=" + time.Now().Format(time.RFC3339),
172 token: arvadostest.ActiveTokenV2,
174 expectCode: http.StatusOK,
175 expectSignature: true,
178 c.Logf("=== trial: %s", trial.label)
180 s.remoteKeepRequests = 0
182 var req *http.Request
183 var resp *httptest.ResponseRecorder
184 req = httptest.NewRequest(trial.method, path, nil)
185 req.Header.Set("Authorization", "Bearer "+trial.token)
186 if trial.xKeepSignature != "" {
187 req.Header.Set("X-Keep-Signature", trial.xKeepSignature)
189 resp = httptest.NewRecorder()
190 handler.ServeHTTP(resp, req)
191 c.Check(s.remoteKeepRequests, check.Equals, trial.expectRemoteReqs)
192 if !c.Check(resp.Code, check.Equals, trial.expectCode) {
193 c.Logf("resp.Code %d came with resp.Body %q", resp.Code, resp.Body.String())
195 if resp.Code == http.StatusOK {
196 if trial.method == "HEAD" {
197 c.Check(resp.Body.String(), check.Equals, "")
198 c.Check(resp.Result().ContentLength, check.Equals, int64(len(data)))
200 c.Check(resp.Body.String(), check.Equals, string(data))
203 c.Check(resp.Body.String(), check.Not(check.Equals), string(data))
206 c.Check(resp.Header().Get("Vary"), check.Matches, `(.*, )?X-Keep-Signature(, .*)?`)
208 locHdr := resp.Header().Get("X-Keep-Locator")
209 if !trial.expectSignature {
210 c.Check(locHdr, check.Equals, "")
214 c.Check(locHdr, check.Not(check.Equals), "")
215 c.Check(locHdr, check.Not(check.Matches), `.*\+R.*`)
216 c.Check(arvados.VerifySignature(locHdr, trial.token, s.cluster.Collections.BlobSigningTTL.Duration(), []byte(s.cluster.Collections.BlobSigningKey)), check.IsNil)
218 // Ensure block can be requested using new signature
219 req = httptest.NewRequest("GET", "/"+locHdr, nil)
220 req.Header.Set("Authorization", "Bearer "+trial.token)
221 resp = httptest.NewRecorder()
222 handler.ServeHTTP(resp, req)
223 c.Check(resp.Code, check.Equals, http.StatusOK)
224 c.Check(s.remoteKeepRequests, check.Equals, trial.expectRemoteReqs)