X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/5132075320db7a19e12a5454a70f894c30e917e8..dad333f819ba1e4c7634527130634da40585f3aa:/services/keep-web/s3.go diff --git a/services/keep-web/s3.go b/services/keep-web/s3.go index 629f3c1ab1..49fb2456f5 100644 --- a/services/keep-web/s3.go +++ b/services/keep-web/s3.go @@ -10,6 +10,7 @@ import ( "encoding/xml" "errors" "fmt" + "hash" "io" "net/http" "net/url" @@ -37,6 +38,11 @@ func hmacstring(msg string, key []byte) []byte { return h.Sum(nil) } +func hashdigest(h hash.Hash, payload string) string { + io.WriteString(h, payload) + return fmt.Sprintf("%x", h.Sum(nil)) +} + // Signing key for given secret key and request attrs. func s3signatureKey(key, datestamp, regionName, serviceName string) []byte { return hmacstring("aws4_request", @@ -68,7 +74,7 @@ func s3querystring(u *url.URL) string { return strings.Join(keys, "&") } -func s3signature(alg, secretKey, scope, signedHeaders string, r *http.Request) (string, error) { +func s3stringToSign(alg, scope, signedHeaders string, r *http.Request) (string, error) { timefmt, timestr := "20060102T150405Z", r.Header.Get("X-Amz-Date") if timestr == "" { timefmt, timestr = time.RFC1123, r.Header.Get("Date") @@ -84,28 +90,25 @@ func s3signature(alg, secretKey, scope, signedHeaders string, r *http.Request) ( var canonicalHeaders string for _, h := range strings.Split(signedHeaders, ";") { if h == "host" { - canonicalHeaders += h + ":" + r.URL.Host + "\n" + canonicalHeaders += h + ":" + r.Host + "\n" } else { canonicalHeaders += h + ":" + r.Header.Get(h) + "\n" } } - crhash := sha256.New() - fmt.Fprintf(crhash, "%s\n%s\n%s\n%s\n%s\n%s", r.Method, r.URL.EscapedPath(), s3querystring(r.URL), canonicalHeaders, signedHeaders, r.Header.Get("X-Amz-Content-Sha256")) - crdigest := fmt.Sprintf("%x", crhash.Sum(nil)) - - payload := fmt.Sprintf("%s\n%s\n%s\n%s", alg, r.Header.Get("X-Amz-Date"), scope, crdigest) + canonicalRequest := fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s", r.Method, r.URL.EscapedPath(), s3querystring(r.URL), canonicalHeaders, signedHeaders, r.Header.Get("X-Amz-Content-Sha256")) + ctxlog.FromContext(r.Context()).Debugf("s3stringToSign: canonicalRequest %s", canonicalRequest) + return fmt.Sprintf("%s\n%s\n%s\n%s", alg, r.Header.Get("X-Amz-Date"), scope, hashdigest(sha256.New(), canonicalRequest)), nil +} +func s3signature(secretKey, scope, signedHeaders, stringToSign string) (string, error) { // scope is {datestamp}/{region}/{service}/aws4_request drs := strings.Split(scope, "/") if len(drs) != 4 { return "", fmt.Errorf("invalid scope %q", scope) } - key := s3signatureKey(secretKey, drs[0], drs[1], drs[2]) - h := hmac.New(sha256.New, key) - h.Write([]byte(payload)) - return fmt.Sprintf("%x", h.Sum(nil)), nil + return hashdigest(hmac.New(sha256.New, key), stringToSign), nil } // checks3signature verifies the given S3 V4 signature and returns the @@ -138,19 +141,36 @@ func (h *handler) checks3signature(r *http.Request) (string, error) { Insecure: h.Config.cluster.TLS.Insecure, }).WithRequestID(r.Header.Get("X-Request-Id")) var aca arvados.APIClientAuthorization - ctx := arvados.ContextWithAuthorization(r.Context(), "Bearer "+h.Config.cluster.SystemRootToken) - err := client.RequestAndDecodeContext(ctx, &aca, "GET", "arvados/v1/api_client_authorizations/"+key, nil, nil) + var secret string + var err error + if len(key) == 27 && key[5:12] == "-gj3su-" { + // Access key is the UUID of an Arvados token, secret + // key is the secret part. + ctx := arvados.ContextWithAuthorization(r.Context(), "Bearer "+h.Config.cluster.SystemRootToken) + err = client.RequestAndDecodeContext(ctx, &aca, "GET", "arvados/v1/api_client_authorizations/"+key, nil, nil) + secret = aca.APIToken + } else { + // Access key and secret key are both an entire + // Arvados token or OIDC access token. + ctx := arvados.ContextWithAuthorization(r.Context(), "Bearer "+key) + err = client.RequestAndDecodeContext(ctx, &aca, "GET", "arvados/v1/api_client_authorizations/current", nil, nil) + secret = key + } if err != nil { - ctxlog.FromContext(ctx).WithError(err).WithField("UUID", key).Info("token lookup failed") + ctxlog.FromContext(r.Context()).WithError(err).WithField("UUID", key).Info("token lookup failed") return "", errors.New("invalid access key") } - expect, err := s3signature(s3SignAlgorithm, aca.APIToken, scope, signedHeaders, r) + stringToSign, err := s3stringToSign(s3SignAlgorithm, scope, signedHeaders, r) + if err != nil { + return "", err + } + expect, err := s3signature(secret, scope, signedHeaders, stringToSign) if err != nil { return "", err } else if expect != signature { - return "", errors.New("signature does not match") + return "", fmt.Errorf("signature does not match (scope %q signedHeaders %q stringToSign %q)", scope, signedHeaders, stringToSign) } - return aca.TokenV2(), nil + return secret, nil } // serveS3 handles r and returns true if r is a request from an S3 @@ -483,6 +503,8 @@ func (h *handler) s3list(w http.ResponseWriter, r *http.Request, fs arvados.Cust // github.com/aws/aws-sdk-net never terminates its // paging loop). NextMarker string `xml:"NextMarker,omitempty"` + // ListObjectsV2 has a KeyCount response field. + KeyCount int } resp := listResp{ ListResp: s3.ListResp{ @@ -579,6 +601,7 @@ func (h *handler) s3list(w http.ResponseWriter, r *http.Request, fs arvados.Cust } sort.Slice(resp.CommonPrefixes, func(i, j int) bool { return resp.CommonPrefixes[i].Prefix < resp.CommonPrefixes[j].Prefix }) } + resp.KeyCount = len(resp.Contents) w.Header().Set("Content-Type", "application/xml") io.WriteString(w, xml.Header) if err := xml.NewEncoder(w).Encode(resp); err != nil {