mirror of
https://github.com/minio/minio.git
synced 2024-12-24 06:05:55 -05:00
Fix presigned URL for access key with special characters (#6012)
Fixes #6011
This commit is contained in:
parent
6fb0604502
commit
eafc15cd47
@ -18,12 +18,9 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"encoding/hex"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode/utf8"
|
|
||||||
|
|
||||||
"github.com/minio/sha256-simd"
|
"github.com/minio/sha256-simd"
|
||||||
)
|
)
|
||||||
@ -104,46 +101,6 @@ func sumHMAC(key []byte, data []byte) []byte {
|
|||||||
return hash.Sum(nil)
|
return hash.Sum(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reserved string regexp.
|
|
||||||
var reservedNames = regexp.MustCompile("^[a-zA-Z0-9-_.~/]+$")
|
|
||||||
|
|
||||||
// getURLEncodedName encode the strings from UTF-8 byte representations to HTML hex escape sequences
|
|
||||||
//
|
|
||||||
// This is necessary since regular url.Parse() and url.Encode() functions do not support UTF-8
|
|
||||||
// non english characters cannot be parsed due to the nature in which url.Encode() is written
|
|
||||||
//
|
|
||||||
// This function on the other hand is a direct replacement for url.Encode() technique to support
|
|
||||||
// pretty much every UTF-8 character.
|
|
||||||
func getURLEncodedName(name string) string {
|
|
||||||
// if object matches reserved string, no need to encode them
|
|
||||||
if reservedNames.MatchString(name) {
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
var encodedName string
|
|
||||||
for _, s := range name {
|
|
||||||
if 'A' <= s && s <= 'Z' || 'a' <= s && s <= 'z' || '0' <= s && s <= '9' { // §2.3 Unreserved characters (mark)
|
|
||||||
encodedName = encodedName + string(s)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch s {
|
|
||||||
case '-', '_', '.', '~', '/': // §2.3 Unreserved characters (mark)
|
|
||||||
encodedName = encodedName + string(s)
|
|
||||||
continue
|
|
||||||
default:
|
|
||||||
len := utf8.RuneLen(s)
|
|
||||||
if len > 0 {
|
|
||||||
u := make([]byte, len)
|
|
||||||
utf8.EncodeRune(u, s)
|
|
||||||
for _, r := range u {
|
|
||||||
hex := hex.EncodeToString([]byte{r})
|
|
||||||
encodedName = encodedName + "%" + strings.ToUpper(hex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return encodedName
|
|
||||||
}
|
|
||||||
|
|
||||||
// extractSignedHeaders extract signed headers from Authorization header
|
// extractSignedHeaders extract signed headers from Authorization header
|
||||||
func extractSignedHeaders(signedHeaders []string, r *http.Request) (http.Header, APIErrorCode) {
|
func extractSignedHeaders(signedHeaders []string, r *http.Request) (http.Header, APIErrorCode) {
|
||||||
reqHeaders := r.Header
|
reqHeaders := r.Header
|
||||||
|
@ -120,39 +120,6 @@ func TestIsValidRegion(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests validate the URL path encoder.
|
|
||||||
func TestGetURLEncodedName(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
// Input.
|
|
||||||
inputStr string
|
|
||||||
// Expected result.
|
|
||||||
result string
|
|
||||||
}{
|
|
||||||
// % should be encoded as %25
|
|
||||||
{"thisisthe%url", "thisisthe%25url"},
|
|
||||||
// UTF-8 encoding.
|
|
||||||
{"本語", "%E6%9C%AC%E8%AA%9E"},
|
|
||||||
// UTF-8 encoding with ASCII.
|
|
||||||
{"本語.1", "%E6%9C%AC%E8%AA%9E.1"},
|
|
||||||
// Unusual ASCII characters.
|
|
||||||
{">123", "%3E123"},
|
|
||||||
// Fragment path characters.
|
|
||||||
{"myurl#link", "myurl%23link"},
|
|
||||||
// Space should be set to %20 not '+'.
|
|
||||||
{"space in url", "space%20in%20url"},
|
|
||||||
// '+' shouldn't be treated as space.
|
|
||||||
{"url+path", "url%2Bpath"},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests generated values from url encoded name.
|
|
||||||
for i, testCase := range testCases {
|
|
||||||
result := getURLEncodedName(testCase.inputStr)
|
|
||||||
if testCase.result != result {
|
|
||||||
t.Errorf("Test %d: Expected URLEncoded result to be \"%s\", but found it to be \"%s\" instead", i+1, testCase.result, result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestExtractSignedHeaders - Tests validate extraction of signed headers using list of signed header keys.
|
// TestExtractSignedHeaders - Tests validate extraction of signed headers using list of signed header keys.
|
||||||
func TestExtractSignedHeaders(t *testing.T) {
|
func TestExtractSignedHeaders(t *testing.T) {
|
||||||
signedHeaders := []string{"host", "x-amz-content-sha256", "x-amz-date", "transfer-encoding"}
|
signedHeaders := []string{"host", "x-amz-content-sha256", "x-amz-date", "transfer-encoding"}
|
||||||
|
@ -35,6 +35,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/minio/minio-go/pkg/s3utils"
|
||||||
sha256 "github.com/minio/sha256-simd"
|
sha256 "github.com/minio/sha256-simd"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -92,7 +93,7 @@ func getSignedHeaders(signedHeaders http.Header) string {
|
|||||||
//
|
//
|
||||||
func getCanonicalRequest(extractedSignedHeaders http.Header, payload, queryStr, urlPath, method string) string {
|
func getCanonicalRequest(extractedSignedHeaders http.Header, payload, queryStr, urlPath, method string) string {
|
||||||
rawQuery := strings.Replace(queryStr, "+", "%20", -1)
|
rawQuery := strings.Replace(queryStr, "+", "%20", -1)
|
||||||
encodedPath := getURLEncodedName(urlPath)
|
encodedPath := s3utils.EncodePath(urlPath)
|
||||||
canonicalRequest := strings.Join([]string{
|
canonicalRequest := strings.Join([]string{
|
||||||
method,
|
method,
|
||||||
encodedPath,
|
encodedPath,
|
||||||
|
@ -54,6 +54,7 @@ import (
|
|||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/minio/minio-go/pkg/s3signer"
|
"github.com/minio/minio-go/pkg/s3signer"
|
||||||
|
"github.com/minio/minio-go/pkg/s3utils"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
"github.com/minio/minio/pkg/auth"
|
"github.com/minio/minio/pkg/auth"
|
||||||
"github.com/minio/minio/pkg/bpool"
|
"github.com/minio/minio/pkg/bpool"
|
||||||
@ -749,7 +750,7 @@ func signStreamingRequest(req *http.Request, accessKey, secretKey string, currTi
|
|||||||
req.URL.RawQuery = strings.Replace(req.URL.Query().Encode(), "+", "%20", -1)
|
req.URL.RawQuery = strings.Replace(req.URL.Query().Encode(), "+", "%20", -1)
|
||||||
|
|
||||||
// Get canonical URI.
|
// Get canonical URI.
|
||||||
canonicalURI := getURLEncodedName(req.URL.Path)
|
canonicalURI := s3utils.EncodePath(req.URL.Path)
|
||||||
|
|
||||||
// Get canonical request.
|
// Get canonical request.
|
||||||
// canonicalRequest =
|
// canonicalRequest =
|
||||||
@ -1104,7 +1105,7 @@ func signRequestV4(req *http.Request, accessKey, secretKey string) error {
|
|||||||
req.URL.RawQuery = strings.Replace(req.URL.Query().Encode(), "+", "%20", -1)
|
req.URL.RawQuery = strings.Replace(req.URL.Query().Encode(), "+", "%20", -1)
|
||||||
|
|
||||||
// Get canonical URI.
|
// Get canonical URI.
|
||||||
canonicalURI := getURLEncodedName(req.URL.Path)
|
canonicalURI := s3utils.EncodePath(req.URL.Path)
|
||||||
|
|
||||||
// Get canonical request.
|
// Get canonical request.
|
||||||
// canonicalRequest =
|
// canonicalRequest =
|
||||||
@ -1455,7 +1456,7 @@ func makeTestTargetURL(endPoint, bucketName, objectName string, queryValues url.
|
|||||||
urlStr = urlStr + bucketName + "/"
|
urlStr = urlStr + bucketName + "/"
|
||||||
}
|
}
|
||||||
if objectName != "" {
|
if objectName != "" {
|
||||||
urlStr = urlStr + getURLEncodedName(objectName)
|
urlStr = urlStr + s3utils.EncodePath(objectName)
|
||||||
}
|
}
|
||||||
if len(queryValues) > 0 {
|
if len(queryValues) > 0 {
|
||||||
urlStr = urlStr + "?" + queryValues.Encode()
|
urlStr = urlStr + "?" + queryValues.Encode()
|
||||||
|
@ -23,6 +23,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"runtime"
|
"runtime"
|
||||||
@ -34,6 +35,7 @@ import (
|
|||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/gorilla/rpc/v2/json2"
|
"github.com/gorilla/rpc/v2/json2"
|
||||||
miniogopolicy "github.com/minio/minio-go/pkg/policy"
|
miniogopolicy "github.com/minio/minio-go/pkg/policy"
|
||||||
|
"github.com/minio/minio-go/pkg/s3utils"
|
||||||
"github.com/minio/minio/browser"
|
"github.com/minio/minio/browser"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
"github.com/minio/minio/pkg/auth"
|
"github.com/minio/minio/pkg/auth"
|
||||||
@ -1005,26 +1007,27 @@ func presignedGet(host, bucket, object string, expiry int64) string {
|
|||||||
if expiry < 604800 && expiry > 0 {
|
if expiry < 604800 && expiry > 0 {
|
||||||
expiryStr = strconv.FormatInt(expiry, 10)
|
expiryStr = strconv.FormatInt(expiry, 10)
|
||||||
}
|
}
|
||||||
query := strings.Join([]string{
|
|
||||||
"X-Amz-Algorithm=" + signV4Algorithm,
|
query := url.Values{}
|
||||||
"X-Amz-Credential=" + strings.Replace(credential, "/", "%2F", -1),
|
query.Set("X-Amz-Algorithm", signV4Algorithm)
|
||||||
"X-Amz-Date=" + dateStr,
|
query.Set("X-Amz-Credential", credential)
|
||||||
"X-Amz-Expires=" + expiryStr,
|
query.Set("X-Amz-Date", dateStr)
|
||||||
"X-Amz-SignedHeaders=host",
|
query.Set("X-Amz-Expires", expiryStr)
|
||||||
}, "&")
|
query.Set("X-Amz-SignedHeaders", "host")
|
||||||
|
queryStr := s3utils.QueryEncode(query)
|
||||||
|
|
||||||
path := "/" + path.Join(bucket, object)
|
path := "/" + path.Join(bucket, object)
|
||||||
|
|
||||||
// "host" is the only header required to be signed for Presigned URLs.
|
// "host" is the only header required to be signed for Presigned URLs.
|
||||||
extractedSignedHeaders := make(http.Header)
|
extractedSignedHeaders := make(http.Header)
|
||||||
extractedSignedHeaders.Set("host", host)
|
extractedSignedHeaders.Set("host", host)
|
||||||
canonicalRequest := getCanonicalRequest(extractedSignedHeaders, unsignedPayload, query, path, "GET")
|
canonicalRequest := getCanonicalRequest(extractedSignedHeaders, unsignedPayload, queryStr, path, "GET")
|
||||||
stringToSign := getStringToSign(canonicalRequest, date, getScope(date, region))
|
stringToSign := getStringToSign(canonicalRequest, date, getScope(date, region))
|
||||||
signingKey := getSigningKey(secretKey, date, region)
|
signingKey := getSigningKey(secretKey, date, region)
|
||||||
signature := getSignature(signingKey, stringToSign)
|
signature := getSignature(signingKey, stringToSign)
|
||||||
|
|
||||||
// Construct the final presigned URL.
|
// Construct the final presigned URL.
|
||||||
return host + getURLEncodedName(path) + "?" + query + "&" + "X-Amz-Signature=" + signature
|
return host + s3utils.EncodePath(path) + "?" + queryStr + "&" + "X-Amz-Signature=" + signature
|
||||||
}
|
}
|
||||||
|
|
||||||
// toJSONError converts regular errors into more user friendly
|
// toJSONError converts regular errors into more user friendly
|
||||||
|
Loading…
Reference in New Issue
Block a user