Merge branch '13561-collection-versions-api'
[arvados.git] / services / keepstore / proxy_remote_test.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         "crypto/md5"
9         "encoding/json"
10         "fmt"
11         "net"
12         "net/http"
13         "net/http/httptest"
14         "strconv"
15         "strings"
16         "sync/atomic"
17         "time"
18
19         "git.curoverse.com/arvados.git/sdk/go/arvados"
20         "git.curoverse.com/arvados.git/sdk/go/arvadostest"
21         "git.curoverse.com/arvados.git/sdk/go/auth"
22         "git.curoverse.com/arvados.git/sdk/go/keepclient"
23         check "gopkg.in/check.v1"
24 )
25
26 var _ = check.Suite(&ProxyRemoteSuite{})
27
28 type ProxyRemoteSuite struct {
29         cluster *arvados.Cluster
30         vm      VolumeManager
31         rtr     http.Handler
32
33         remoteClusterID      string
34         remoteBlobSigningKey []byte
35         remoteKeepLocator    string
36         remoteKeepData       []byte
37         remoteKeepproxy      *httptest.Server
38         remoteKeepRequests   int64
39         remoteAPI            *httptest.Server
40 }
41
42 func (s *ProxyRemoteSuite) remoteKeepproxyHandler(w http.ResponseWriter, r *http.Request) {
43         expectToken, err := auth.SaltToken(arvadostest.ActiveTokenV2, s.remoteClusterID)
44         if err != nil {
45                 panic(err)
46         }
47         atomic.AddInt64(&s.remoteKeepRequests, 1)
48         var token string
49         if auth := strings.Split(r.Header.Get("Authorization"), " "); len(auth) == 2 && (auth[0] == "OAuth2" || auth[0] == "Bearer") {
50                 token = auth[1]
51         }
52         if r.Method == "GET" && r.URL.Path == "/"+s.remoteKeepLocator && token == expectToken {
53                 w.Write(s.remoteKeepData)
54                 return
55         }
56         http.Error(w, "404", 404)
57 }
58
59 func (s *ProxyRemoteSuite) remoteAPIHandler(w http.ResponseWriter, r *http.Request) {
60         host, port, _ := net.SplitHostPort(strings.Split(s.remoteKeepproxy.URL, "//")[1])
61         portnum, _ := strconv.Atoi(port)
62         if r.URL.Path == "/arvados/v1/discovery/v1/rest" {
63                 json.NewEncoder(w).Encode(arvados.DiscoveryDocument{})
64                 return
65         }
66         if r.URL.Path == "/arvados/v1/keep_services/accessible" {
67                 json.NewEncoder(w).Encode(arvados.KeepServiceList{
68                         Items: []arvados.KeepService{
69                                 {
70                                         UUID:           s.remoteClusterID + "-bi6l4-proxyproxyproxy",
71                                         ServiceType:    "proxy",
72                                         ServiceHost:    host,
73                                         ServicePort:    portnum,
74                                         ServiceSSLFlag: false,
75                                 },
76                         },
77                 })
78                 return
79         }
80         http.Error(w, "404", 404)
81 }
82
83 func (s *ProxyRemoteSuite) SetUpTest(c *check.C) {
84         s.remoteClusterID = "z0000"
85         s.remoteBlobSigningKey = []byte("3b6df6fb6518afe12922a5bc8e67bf180a358bc8")
86         s.remoteKeepproxy = httptest.NewServer(http.HandlerFunc(s.remoteKeepproxyHandler))
87         s.remoteAPI = httptest.NewUnstartedServer(http.HandlerFunc(s.remoteAPIHandler))
88         s.remoteAPI.StartTLS()
89         s.cluster = arvados.IntegrationTestCluster()
90         s.cluster.RemoteClusters = map[string]arvados.RemoteCluster{
91                 s.remoteClusterID: arvados.RemoteCluster{
92                         Host:     strings.Split(s.remoteAPI.URL, "//")[1],
93                         Proxy:    true,
94                         Scheme:   "http",
95                         Insecure: true,
96                 },
97         }
98         s.vm = MakeTestVolumeManager(2)
99         KeepVM = s.vm
100         theConfig = DefaultConfig()
101         theConfig.systemAuthToken = arvadostest.DataManagerToken
102         theConfig.blobSigningKey = []byte(knownKey)
103         theConfig.Start()
104         s.rtr = MakeRESTRouter(s.cluster)
105 }
106
107 func (s *ProxyRemoteSuite) TearDownTest(c *check.C) {
108         s.vm.Close()
109         KeepVM = nil
110         theConfig = DefaultConfig()
111         theConfig.Start()
112         s.remoteAPI.Close()
113         s.remoteKeepproxy.Close()
114 }
115
116 func (s *ProxyRemoteSuite) TestProxyRemote(c *check.C) {
117         data := []byte("foo bar")
118         s.remoteKeepData = data
119         locator := fmt.Sprintf("%x+%d", md5.Sum(data), len(data))
120         s.remoteKeepLocator = keepclient.SignLocator(locator, arvadostest.ActiveTokenV2, time.Now().Add(time.Minute), time.Minute, s.remoteBlobSigningKey)
121
122         path := "/" + strings.Replace(s.remoteKeepLocator, "+A", "+R"+s.remoteClusterID+"-", 1)
123
124         for _, trial := range []struct {
125                 label            string
126                 method           string
127                 token            string
128                 xKeepSignature   string
129                 expectRemoteReqs int64
130                 expectCode       int
131                 expectSignature  bool
132         }{
133                 {
134                         label:            "GET only",
135                         method:           "GET",
136                         token:            arvadostest.ActiveTokenV2,
137                         expectRemoteReqs: 1,
138                         expectCode:       http.StatusOK,
139                 },
140                 {
141                         label:            "obsolete token",
142                         method:           "GET",
143                         token:            arvadostest.ActiveToken,
144                         expectRemoteReqs: 0,
145                         expectCode:       http.StatusBadRequest,
146                 },
147                 {
148                         label:            "bad token",
149                         method:           "GET",
150                         token:            arvadostest.ActiveTokenV2[:len(arvadostest.ActiveTokenV2)-3] + "xxx",
151                         expectRemoteReqs: 1,
152                         expectCode:       http.StatusNotFound,
153                 },
154                 {
155                         label:            "HEAD only",
156                         method:           "HEAD",
157                         token:            arvadostest.ActiveTokenV2,
158                         expectRemoteReqs: 1,
159                         expectCode:       http.StatusOK,
160                 },
161                 {
162                         label:            "HEAD with local signature",
163                         method:           "HEAD",
164                         xKeepSignature:   "local, time=" + time.Now().Format(time.RFC3339),
165                         token:            arvadostest.ActiveTokenV2,
166                         expectRemoteReqs: 1,
167                         expectCode:       http.StatusOK,
168                         expectSignature:  true,
169                 },
170                 {
171                         label:            "GET with local signature",
172                         method:           "GET",
173                         xKeepSignature:   "local, time=" + time.Now().Format(time.RFC3339),
174                         token:            arvadostest.ActiveTokenV2,
175                         expectRemoteReqs: 1,
176                         expectCode:       http.StatusOK,
177                         expectSignature:  true,
178                 },
179         } {
180                 c.Logf("trial: %s", trial.label)
181
182                 s.remoteKeepRequests = 0
183
184                 var req *http.Request
185                 var resp *httptest.ResponseRecorder
186                 req = httptest.NewRequest(trial.method, path, nil)
187                 req.Header.Set("Authorization", "Bearer "+trial.token)
188                 if trial.xKeepSignature != "" {
189                         req.Header.Set("X-Keep-Signature", trial.xKeepSignature)
190                 }
191                 resp = httptest.NewRecorder()
192                 s.rtr.ServeHTTP(resp, req)
193                 c.Check(s.remoteKeepRequests, check.Equals, trial.expectRemoteReqs)
194                 c.Check(resp.Code, check.Equals, trial.expectCode)
195                 if resp.Code == http.StatusOK {
196                         c.Check(resp.Body.String(), check.Equals, string(data))
197                 } else {
198                         c.Check(resp.Body.String(), check.Not(check.Equals), string(data))
199                 }
200
201                 c.Check(resp.Header().Get("Vary"), check.Matches, `(.*, )?X-Keep-Signature(, .*)?`)
202
203                 locHdr := resp.Header().Get("X-Keep-Locator")
204                 if !trial.expectSignature {
205                         c.Check(locHdr, check.Equals, "")
206                         continue
207                 }
208
209                 c.Check(locHdr, check.Not(check.Equals), "")
210                 c.Check(locHdr, check.Not(check.Matches), `.*\+R.*`)
211                 c.Check(VerifySignature(locHdr, trial.token), check.IsNil)
212
213                 // Ensure block can be requested using new signature
214                 req = httptest.NewRequest("GET", "/"+locHdr, nil)
215                 req.Header.Set("Authorization", "Bearer "+trial.token)
216                 resp = httptest.NewRecorder()
217                 s.rtr.ServeHTTP(resp, req)
218                 c.Check(resp.Code, check.Equals, http.StatusOK)
219                 c.Check(s.remoteKeepRequests, check.Equals, trial.expectRemoteReqs)
220         }
221 }