//
// SPDX-License-Identifier: AGPL-3.0
-package main
+package keepstore
import (
"crypto/md5"
"sync/atomic"
"time"
- "git.curoverse.com/arvados.git/sdk/go/arvados"
- "git.curoverse.com/arvados.git/sdk/go/arvadostest"
- "git.curoverse.com/arvados.git/sdk/go/auth"
- "git.curoverse.com/arvados.git/sdk/go/keepclient"
+ "git.arvados.org/arvados.git/sdk/go/arvados"
+ "git.arvados.org/arvados.git/sdk/go/arvadostest"
+ "git.arvados.org/arvados.git/sdk/go/auth"
+ "git.arvados.org/arvados.git/sdk/go/ctxlog"
+ "git.arvados.org/arvados.git/sdk/go/httpserver"
+ "git.arvados.org/arvados.git/sdk/go/keepclient"
+ "github.com/prometheus/client_golang/prometheus"
check "gopkg.in/check.v1"
)
-var _ = check.Suite(&ProxyRemoteSuite{})
+var _ = check.Suite(&proxyRemoteSuite{})
-type ProxyRemoteSuite struct {
+type proxyRemoteSuite struct {
cluster *arvados.Cluster
- vm VolumeManager
- rtr http.Handler
+ handler *router
remoteClusterID string
remoteBlobSigningKey []byte
remoteAPI *httptest.Server
}
-func (s *ProxyRemoteSuite) remoteKeepproxyHandler(w http.ResponseWriter, r *http.Request) {
+func (s *proxyRemoteSuite) remoteKeepproxyHandler(w http.ResponseWriter, r *http.Request) {
expectToken, err := auth.SaltToken(arvadostest.ActiveTokenV2, s.remoteClusterID)
if err != nil {
panic(err)
http.Error(w, "404", 404)
}
-func (s *ProxyRemoteSuite) remoteAPIHandler(w http.ResponseWriter, r *http.Request) {
+func (s *proxyRemoteSuite) remoteAPIHandler(w http.ResponseWriter, r *http.Request) {
host, port, _ := net.SplitHostPort(strings.Split(s.remoteKeepproxy.URL, "//")[1])
portnum, _ := strconv.Atoi(port)
if r.URL.Path == "/arvados/v1/discovery/v1/rest" {
http.Error(w, "404", 404)
}
-func (s *ProxyRemoteSuite) SetUpTest(c *check.C) {
+func (s *proxyRemoteSuite) SetUpTest(c *check.C) {
s.remoteClusterID = "z0000"
s.remoteBlobSigningKey = []byte("3b6df6fb6518afe12922a5bc8e67bf180a358bc8")
- s.remoteKeepproxy = httptest.NewServer(http.HandlerFunc(s.remoteKeepproxyHandler))
+ s.remoteKeepproxy = httptest.NewServer(httpserver.LogRequests(http.HandlerFunc(s.remoteKeepproxyHandler)))
s.remoteAPI = httptest.NewUnstartedServer(http.HandlerFunc(s.remoteAPIHandler))
s.remoteAPI.StartTLS()
- s.cluster = arvados.IntegrationTestCluster()
+ s.cluster = testCluster(c)
s.cluster.RemoteClusters = map[string]arvados.RemoteCluster{
- s.remoteClusterID: arvados.RemoteCluster{
+ s.remoteClusterID: {
Host: strings.Split(s.remoteAPI.URL, "//")[1],
Proxy: true,
Scheme: "http",
Insecure: true,
},
}
- s.vm = MakeTestVolumeManager(2)
- KeepVM = s.vm
- theConfig = DefaultConfig()
- theConfig.systemAuthToken = arvadostest.DataManagerToken
- theConfig.Start()
- s.rtr = MakeRESTRouter(s.cluster)
+ s.cluster.Volumes = map[string]arvados.Volume{"zzzzz-nyw5e-000000000000000": {Driver: "stub"}}
}
-func (s *ProxyRemoteSuite) TearDownTest(c *check.C) {
- s.vm.Close()
- KeepVM = nil
- theConfig = DefaultConfig()
- theConfig.Start()
+func (s *proxyRemoteSuite) TearDownTest(c *check.C) {
s.remoteAPI.Close()
s.remoteKeepproxy.Close()
}
-func (s *ProxyRemoteSuite) TestProxyRemote(c *check.C) {
+func (s *proxyRemoteSuite) TestProxyRemote(c *check.C) {
+ reg := prometheus.NewRegistry()
+ router, cancel := testRouter(c, s.cluster, reg)
+ defer cancel()
+ instrumented := httpserver.Instrument(reg, ctxlog.TestLogger(c), router)
+ handler := httpserver.LogRequests(instrumented.ServeAPI(s.cluster.ManagementToken, instrumented))
+
data := []byte("foo bar")
s.remoteKeepData = data
locator := fmt.Sprintf("%x+%d", md5.Sum(data), len(data))
path := "/" + strings.Replace(s.remoteKeepLocator, "+A", "+R"+s.remoteClusterID+"-", 1)
- var req *http.Request
- var resp *httptest.ResponseRecorder
- tryWithToken := func(token string) {
- req = httptest.NewRequest("GET", path, nil)
- req.Header.Set("Authorization", "Bearer "+token)
+ for _, trial := range []struct {
+ label string
+ method string
+ token string
+ xKeepSignature string
+ expectRemoteReqs int64
+ expectCode int
+ expectSignature bool
+ }{
+ {
+ label: "GET only",
+ method: "GET",
+ token: arvadostest.ActiveTokenV2,
+ expectRemoteReqs: 1,
+ expectCode: http.StatusOK,
+ },
+ {
+ label: "obsolete token",
+ method: "GET",
+ token: arvadostest.ActiveToken,
+ expectRemoteReqs: 0,
+ expectCode: http.StatusBadRequest,
+ },
+ {
+ label: "bad token",
+ method: "GET",
+ token: arvadostest.ActiveTokenV2[:len(arvadostest.ActiveTokenV2)-3] + "xxx",
+ expectRemoteReqs: 1,
+ expectCode: http.StatusNotFound,
+ },
+ {
+ label: "HEAD only",
+ method: "HEAD",
+ token: arvadostest.ActiveTokenV2,
+ expectRemoteReqs: 1,
+ expectCode: http.StatusOK,
+ },
+ {
+ label: "HEAD with local signature",
+ method: "HEAD",
+ xKeepSignature: "local, time=" + time.Now().Format(time.RFC3339),
+ token: arvadostest.ActiveTokenV2,
+ expectRemoteReqs: 1,
+ expectCode: http.StatusOK,
+ expectSignature: true,
+ },
+ {
+ label: "GET with local signature",
+ method: "GET",
+ xKeepSignature: "local, time=" + time.Now().Format(time.RFC3339),
+ token: arvadostest.ActiveTokenV2,
+ expectRemoteReqs: 1,
+ expectCode: http.StatusOK,
+ expectSignature: true,
+ },
+ } {
+ c.Logf("=== trial: %s", trial.label)
+
+ s.remoteKeepRequests = 0
+
+ var req *http.Request
+ var resp *httptest.ResponseRecorder
+ req = httptest.NewRequest(trial.method, path, nil)
+ req.Header.Set("Authorization", "Bearer "+trial.token)
+ if trial.xKeepSignature != "" {
+ req.Header.Set("X-Keep-Signature", trial.xKeepSignature)
+ }
resp = httptest.NewRecorder()
- s.rtr.ServeHTTP(resp, req)
+ handler.ServeHTTP(resp, req)
+ c.Check(s.remoteKeepRequests, check.Equals, trial.expectRemoteReqs)
+ if !c.Check(resp.Code, check.Equals, trial.expectCode) {
+ c.Logf("resp.Code %d came with resp.Body %q", resp.Code, resp.Body.String())
+ }
+ if resp.Code == http.StatusOK {
+ if trial.method == "HEAD" {
+ c.Check(resp.Body.String(), check.Equals, "")
+ c.Check(resp.Result().ContentLength, check.Equals, int64(len(data)))
+ } else {
+ c.Check(resp.Body.String(), check.Equals, string(data))
+ }
+ } else {
+ c.Check(resp.Body.String(), check.Not(check.Equals), string(data))
+ }
+
+ c.Check(resp.Header().Get("Vary"), check.Matches, `(.*, )?X-Keep-Signature(, .*)?`)
+
+ locHdr := resp.Header().Get("X-Keep-Locator")
+ if !trial.expectSignature {
+ c.Check(locHdr, check.Equals, "")
+ continue
+ }
+
+ c.Check(locHdr, check.Not(check.Equals), "")
+ c.Check(locHdr, check.Not(check.Matches), `.*\+R.*`)
+ c.Check(arvados.VerifySignature(locHdr, trial.token, s.cluster.Collections.BlobSigningTTL.Duration(), []byte(s.cluster.Collections.BlobSigningKey)), check.IsNil)
+
+ // Ensure block can be requested using new signature
+ req = httptest.NewRequest("GET", "/"+locHdr, nil)
+ req.Header.Set("Authorization", "Bearer "+trial.token)
+ resp = httptest.NewRecorder()
+ handler.ServeHTTP(resp, req)
+ c.Check(resp.Code, check.Equals, http.StatusOK)
+ c.Check(s.remoteKeepRequests, check.Equals, trial.expectRemoteReqs)
}
-
- // Happy path
- tryWithToken(arvadostest.ActiveTokenV2)
- c.Check(s.remoteKeepRequests, check.Equals, int64(1))
- c.Check(resp.Code, check.Equals, http.StatusOK)
- c.Check(resp.Body.String(), check.Equals, string(data))
-
- // Obsolete token
- tryWithToken(arvadostest.ActiveToken)
- c.Check(s.remoteKeepRequests, check.Equals, int64(1))
- c.Check(resp.Code, check.Equals, http.StatusBadRequest)
- c.Check(resp.Body.String(), check.Not(check.Equals), string(data))
-
- // Bad token
- tryWithToken(arvadostest.ActiveTokenV2[:len(arvadostest.ActiveTokenV2)-3] + "xxx")
- c.Check(s.remoteKeepRequests, check.Equals, int64(2))
- c.Check(resp.Code, check.Equals, http.StatusNotFound)
- c.Check(resp.Body.String(), check.Not(check.Equals), string(data))
}