1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
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 "github.com/prometheus/client_golang/prometheus"
24 check "gopkg.in/check.v1"
27 var _ = check.Suite(&ProxyRemoteSuite{})
29 type ProxyRemoteSuite struct {
30 cluster *arvados.Cluster
34 remoteClusterID string
35 remoteBlobSigningKey []byte
36 remoteKeepLocator string
38 remoteKeepproxy *httptest.Server
39 remoteKeepRequests int64
40 remoteAPI *httptest.Server
43 func (s *ProxyRemoteSuite) remoteKeepproxyHandler(w http.ResponseWriter, r *http.Request) {
44 expectToken, err := auth.SaltToken(arvadostest.ActiveTokenV2, s.remoteClusterID)
48 atomic.AddInt64(&s.remoteKeepRequests, 1)
50 if auth := strings.Split(r.Header.Get("Authorization"), " "); len(auth) == 2 && (auth[0] == "OAuth2" || auth[0] == "Bearer") {
53 if r.Method == "GET" && r.URL.Path == "/"+s.remoteKeepLocator && token == expectToken {
54 w.Write(s.remoteKeepData)
57 http.Error(w, "404", 404)
60 func (s *ProxyRemoteSuite) remoteAPIHandler(w http.ResponseWriter, r *http.Request) {
61 host, port, _ := net.SplitHostPort(strings.Split(s.remoteKeepproxy.URL, "//")[1])
62 portnum, _ := strconv.Atoi(port)
63 if r.URL.Path == "/arvados/v1/discovery/v1/rest" {
64 json.NewEncoder(w).Encode(arvados.DiscoveryDocument{})
67 if r.URL.Path == "/arvados/v1/keep_services/accessible" {
68 json.NewEncoder(w).Encode(arvados.KeepServiceList{
69 Items: []arvados.KeepService{
71 UUID: s.remoteClusterID + "-bi6l4-proxyproxyproxy",
75 ServiceSSLFlag: false,
81 http.Error(w, "404", 404)
84 func (s *ProxyRemoteSuite) SetUpTest(c *check.C) {
85 s.remoteClusterID = "z0000"
86 s.remoteBlobSigningKey = []byte("3b6df6fb6518afe12922a5bc8e67bf180a358bc8")
87 s.remoteKeepproxy = httptest.NewServer(http.HandlerFunc(s.remoteKeepproxyHandler))
88 s.remoteAPI = httptest.NewUnstartedServer(http.HandlerFunc(s.remoteAPIHandler))
89 s.remoteAPI.StartTLS()
90 s.cluster = arvados.IntegrationTestCluster()
91 s.cluster.RemoteClusters = map[string]arvados.RemoteCluster{
92 s.remoteClusterID: arvados.RemoteCluster{
93 Host: strings.Split(s.remoteAPI.URL, "//")[1],
99 s.vm = MakeTestVolumeManager(2)
101 theConfig = DefaultConfig()
102 theConfig.systemAuthToken = arvadostest.DataManagerToken
103 theConfig.blobSigningKey = []byte(knownKey)
104 r := prometheus.NewRegistry()
106 s.rtr = MakeRESTRouter(s.cluster, r)
109 func (s *ProxyRemoteSuite) TearDownTest(c *check.C) {
112 theConfig = DefaultConfig()
113 theConfig.Start(prometheus.NewRegistry())
115 s.remoteKeepproxy.Close()
118 func (s *ProxyRemoteSuite) TestProxyRemote(c *check.C) {
119 data := []byte("foo bar")
120 s.remoteKeepData = data
121 locator := fmt.Sprintf("%x+%d", md5.Sum(data), len(data))
122 s.remoteKeepLocator = keepclient.SignLocator(locator, arvadostest.ActiveTokenV2, time.Now().Add(time.Minute), time.Minute, s.remoteBlobSigningKey)
124 path := "/" + strings.Replace(s.remoteKeepLocator, "+A", "+R"+s.remoteClusterID+"-", 1)
126 for _, trial := range []struct {
130 xKeepSignature string
131 expectRemoteReqs int64
138 token: arvadostest.ActiveTokenV2,
140 expectCode: http.StatusOK,
143 label: "obsolete token",
145 token: arvadostest.ActiveToken,
147 expectCode: http.StatusBadRequest,
152 token: arvadostest.ActiveTokenV2[:len(arvadostest.ActiveTokenV2)-3] + "xxx",
154 expectCode: http.StatusNotFound,
159 token: arvadostest.ActiveTokenV2,
161 expectCode: http.StatusOK,
164 label: "HEAD with local signature",
166 xKeepSignature: "local, time=" + time.Now().Format(time.RFC3339),
167 token: arvadostest.ActiveTokenV2,
169 expectCode: http.StatusOK,
170 expectSignature: true,
173 label: "GET with local signature",
175 xKeepSignature: "local, time=" + time.Now().Format(time.RFC3339),
176 token: arvadostest.ActiveTokenV2,
178 expectCode: http.StatusOK,
179 expectSignature: true,
182 c.Logf("trial: %s", trial.label)
184 s.remoteKeepRequests = 0
186 var req *http.Request
187 var resp *httptest.ResponseRecorder
188 req = httptest.NewRequest(trial.method, path, nil)
189 req.Header.Set("Authorization", "Bearer "+trial.token)
190 if trial.xKeepSignature != "" {
191 req.Header.Set("X-Keep-Signature", trial.xKeepSignature)
193 resp = httptest.NewRecorder()
194 s.rtr.ServeHTTP(resp, req)
195 c.Check(s.remoteKeepRequests, check.Equals, trial.expectRemoteReqs)
196 c.Check(resp.Code, check.Equals, trial.expectCode)
197 if resp.Code == http.StatusOK {
198 c.Check(resp.Body.String(), check.Equals, string(data))
200 c.Check(resp.Body.String(), check.Not(check.Equals), string(data))
203 c.Check(resp.Header().Get("Vary"), check.Matches, `(.*, )?X-Keep-Signature(, .*)?`)
205 locHdr := resp.Header().Get("X-Keep-Locator")
206 if !trial.expectSignature {
207 c.Check(locHdr, check.Equals, "")
211 c.Check(locHdr, check.Not(check.Equals), "")
212 c.Check(locHdr, check.Not(check.Matches), `.*\+R.*`)
213 c.Check(VerifySignature(locHdr, trial.token), check.IsNil)
215 // Ensure block can be requested using new signature
216 req = httptest.NewRequest("GET", "/"+locHdr, nil)
217 req.Header.Set("Authorization", "Bearer "+trial.token)
218 resp = httptest.NewRecorder()
219 s.rtr.ServeHTTP(resp, req)
220 c.Check(resp.Code, check.Equals, http.StatusOK)
221 c.Check(s.remoteKeepRequests, check.Equals, trial.expectRemoteReqs)