mirror of
https://github.com/minio/minio.git
synced 2024-12-24 06:05:55 -05:00
refactor: extractSignedHeaders() handles headers removed by Go http server (#4054)
* refactor: extractSignedHeaders() handles headers removed by Go http server. * Cleanup extractSignedHeaders() TestExtractSignedHeaders()
This commit is contained in:
parent
af82d27018
commit
1d99a560e3
@ -21,6 +21,7 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
@ -119,7 +120,14 @@ func extractSignedHeaders(signedHeaders []string, r *http.Request) (http.Header,
|
|||||||
// `host` will not be found in the headers, can be found in r.Host.
|
// `host` will not be found in the headers, can be found in r.Host.
|
||||||
// but its alway necessary that the list of signed headers containing host in it.
|
// but its alway necessary that the list of signed headers containing host in it.
|
||||||
val, ok := reqHeaders[http.CanonicalHeaderKey(header)]
|
val, ok := reqHeaders[http.CanonicalHeaderKey(header)]
|
||||||
if !ok {
|
if ok {
|
||||||
|
for _, enc := range val {
|
||||||
|
extractedSignedHeaders.Add(header, enc)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch header {
|
||||||
|
case "expect":
|
||||||
// Golang http server strips off 'Expect' header, if the
|
// Golang http server strips off 'Expect' header, if the
|
||||||
// client sent this as part of signed headers we need to
|
// client sent this as part of signed headers we need to
|
||||||
// handle otherwise we would see a signature mismatch.
|
// handle otherwise we would see a signature mismatch.
|
||||||
@ -136,24 +144,23 @@ func extractSignedHeaders(signedHeaders []string, r *http.Request) (http.Header,
|
|||||||
// be sent, for the time being keep this work around.
|
// be sent, for the time being keep this work around.
|
||||||
// Adding a *TODO* to remove this later when Golang server
|
// Adding a *TODO* to remove this later when Golang server
|
||||||
// doesn't filter out the 'Expect' header.
|
// doesn't filter out the 'Expect' header.
|
||||||
if header == "expect" {
|
extractedSignedHeaders.Set(header, "100-continue")
|
||||||
extractedSignedHeaders[header] = []string{"100-continue"}
|
case "host":
|
||||||
continue
|
// Go http server removes "host" from Request.Header
|
||||||
|
extractedSignedHeaders.Set(header, r.Host)
|
||||||
|
case "transfer-encoding":
|
||||||
|
// Go http server removes "host" from Request.Header
|
||||||
|
for _, enc := range r.TransferEncoding {
|
||||||
|
extractedSignedHeaders.Add(header, enc)
|
||||||
}
|
}
|
||||||
// the "host" field will not be found in the header map, it can be found in req.Host.
|
case "content-length":
|
||||||
// but its necessary to make sure that the "host" field exists in the list of signed parameters,
|
// Signature-V4 spec excludes Content-Length from signed headers list for signature calculation.
|
||||||
// the check is done above.
|
// But some clients deviate from this rule. Hence we consider Content-Length for signature
|
||||||
if header == "host" {
|
// calculation to be compatible with such clients.
|
||||||
continue
|
extractedSignedHeaders.Set(header, strconv.FormatInt(r.ContentLength, 10))
|
||||||
}
|
default:
|
||||||
if header == "transfer-encoding" {
|
|
||||||
extractedSignedHeaders[header] = r.TransferEncoding
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// If not found continue, we will stop here.
|
|
||||||
return nil, ErrUnsignedHeaders
|
return nil, ErrUnsignedHeaders
|
||||||
}
|
}
|
||||||
extractedSignedHeaders[header] = val
|
|
||||||
}
|
}
|
||||||
return extractedSignedHeaders, ErrNone
|
return extractedSignedHeaders, ErrNone
|
||||||
}
|
}
|
||||||
|
@ -141,8 +141,9 @@ func TestExtractSignedHeaders(t *testing.T) {
|
|||||||
expectedContentSha256 := "1234abcd"
|
expectedContentSha256 := "1234abcd"
|
||||||
expectedTime := UTCNow().Format(iso8601Format)
|
expectedTime := UTCNow().Format(iso8601Format)
|
||||||
expectedTransferEncoding := "gzip"
|
expectedTransferEncoding := "gzip"
|
||||||
|
expectedExpect := "100-continue"
|
||||||
|
|
||||||
r, err := http.NewRequest("GET", "http://localhost", nil)
|
r, err := http.NewRequest("GET", "http://play.minio.io:9000", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Unable to create http.Request :", err)
|
t.Fatal("Unable to create http.Request :", err)
|
||||||
}
|
}
|
||||||
@ -150,9 +151,8 @@ func TestExtractSignedHeaders(t *testing.T) {
|
|||||||
|
|
||||||
// Creating input http header.
|
// Creating input http header.
|
||||||
inputHeader := r.Header
|
inputHeader := r.Header
|
||||||
inputHeader.Set(signedHeaders[0], expectedHost)
|
inputHeader.Set("x-amz-content-sha256", expectedContentSha256)
|
||||||
inputHeader.Set(signedHeaders[1], expectedContentSha256)
|
inputHeader.Set("x-amz-date", expectedTime)
|
||||||
inputHeader.Set(signedHeaders[2], expectedTime)
|
|
||||||
// calling the function being tested.
|
// calling the function being tested.
|
||||||
extractedSignedHeaders, errCode := extractSignedHeaders(signedHeaders, r)
|
extractedSignedHeaders, errCode := extractSignedHeaders(signedHeaders, r)
|
||||||
if errCode != ErrNone {
|
if errCode != ErrNone {
|
||||||
@ -160,24 +160,24 @@ func TestExtractSignedHeaders(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// "x-amz-content-sha256" header value from the extracted result.
|
// "x-amz-content-sha256" header value from the extracted result.
|
||||||
extractedContentSha256 := extractedSignedHeaders[signedHeaders[1]]
|
extractedContentSha256 := extractedSignedHeaders.Get("x-amz-content-sha256")
|
||||||
// "host" header value from the extracted result.
|
// "host" header value from the extracted result.
|
||||||
extractedHost := extractedSignedHeaders[signedHeaders[0]]
|
extractedHost := extractedSignedHeaders.Get("host")
|
||||||
// "x-amz-date" header from the extracted result.
|
// "x-amz-date" header from the extracted result.
|
||||||
extractedDate := extractedSignedHeaders[signedHeaders[2]]
|
extractedDate := extractedSignedHeaders.Get("x-amz-date")
|
||||||
// extracted `expect` header.
|
// extracted `expect` header.
|
||||||
extractedExpect := extractedSignedHeaders["expect"][0]
|
extractedExpect := extractedSignedHeaders.Get("expect")
|
||||||
|
|
||||||
extractedTransferEncoding := extractedSignedHeaders["transfer-encoding"][0]
|
extractedTransferEncoding := extractedSignedHeaders.Get("transfer-encoding")
|
||||||
|
|
||||||
if expectedHost != extractedHost[0] {
|
if expectedHost != extractedHost {
|
||||||
t.Errorf("host header mismatch: expected `%s`, got `%s`", expectedHost, extractedHost)
|
t.Errorf("host header mismatch: expected `%s`, got `%s`", expectedHost, extractedHost)
|
||||||
}
|
}
|
||||||
// assert the result with the expected value.
|
// assert the result with the expected value.
|
||||||
if expectedContentSha256 != extractedContentSha256[0] {
|
if expectedContentSha256 != extractedContentSha256 {
|
||||||
t.Errorf("x-amz-content-sha256 header mismatch: expected `%s`, got `%s`", expectedContentSha256, extractedContentSha256)
|
t.Errorf("x-amz-content-sha256 header mismatch: expected `%s`, got `%s`", expectedContentSha256, extractedContentSha256)
|
||||||
}
|
}
|
||||||
if expectedTime != extractedDate[0] {
|
if expectedTime != extractedDate {
|
||||||
t.Errorf("x-amz-date header mismatch: expected `%s`, got `%s`", expectedTime, extractedDate)
|
t.Errorf("x-amz-date header mismatch: expected `%s`, got `%s`", expectedTime, extractedDate)
|
||||||
}
|
}
|
||||||
if extractedTransferEncoding != expectedTransferEncoding {
|
if extractedTransferEncoding != expectedTransferEncoding {
|
||||||
@ -185,12 +185,12 @@ func TestExtractSignedHeaders(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Since the list of signed headers value contained `expect`, the default value of `100-continue` will be added to extracted signed headers.
|
// Since the list of signed headers value contained `expect`, the default value of `100-continue` will be added to extracted signed headers.
|
||||||
if extractedExpect != "100-continue" {
|
if extractedExpect != expectedExpect {
|
||||||
t.Errorf("expect header incorrect value: expected `%s`, got `%s`", "100-continue", extractedExpect)
|
t.Errorf("expect header incorrect value: expected `%s`, got `%s`", expectedExpect, extractedExpect)
|
||||||
}
|
}
|
||||||
|
|
||||||
// case where the headers doesn't contain the one of the signed header in the signed headers list.
|
// case where the headers don't contain the one of the signed header in the signed headers list.
|
||||||
signedHeaders = append(signedHeaders, " X-Amz-Credential")
|
signedHeaders = append(signedHeaders, "X-Amz-Credential")
|
||||||
// expected to fail with `ErrUnsignedHeaders`.
|
// expected to fail with `ErrUnsignedHeaders`.
|
||||||
_, errCode = extractSignedHeaders(signedHeaders, r)
|
_, errCode = extractSignedHeaders(signedHeaders, r)
|
||||||
if errCode != ErrUnsignedHeaders {
|
if errCode != ErrUnsignedHeaders {
|
||||||
@ -198,7 +198,7 @@ func TestExtractSignedHeaders(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// case where the list of signed headers doesn't contain the host field.
|
// case where the list of signed headers doesn't contain the host field.
|
||||||
signedHeaders = signedHeaders[1:]
|
signedHeaders = signedHeaders[2:5]
|
||||||
// expected to fail with `ErrUnsignedHeaders`.
|
// expected to fail with `ErrUnsignedHeaders`.
|
||||||
_, errCode = extractSignedHeaders(signedHeaders, r)
|
_, errCode = extractSignedHeaders(signedHeaders, r)
|
||||||
if errCode != ErrUnsignedHeaders {
|
if errCode != ErrUnsignedHeaders {
|
||||||
|
@ -46,33 +46,26 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// getCanonicalHeaders generate a list of request headers with their values
|
// getCanonicalHeaders generate a list of request headers with their values
|
||||||
func getCanonicalHeaders(signedHeaders http.Header, host string) string {
|
func getCanonicalHeaders(signedHeaders http.Header) string {
|
||||||
var headers []string
|
var headers []string
|
||||||
vals := make(http.Header)
|
vals := make(http.Header)
|
||||||
for k, vv := range signedHeaders {
|
for k, vv := range signedHeaders {
|
||||||
headers = append(headers, strings.ToLower(k))
|
headers = append(headers, strings.ToLower(k))
|
||||||
vals[strings.ToLower(k)] = vv
|
vals[strings.ToLower(k)] = vv
|
||||||
}
|
}
|
||||||
headers = append(headers, presignedHostHeader)
|
|
||||||
sort.Strings(headers)
|
sort.Strings(headers)
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
for _, k := range headers {
|
for _, k := range headers {
|
||||||
buf.WriteString(k)
|
buf.WriteString(k)
|
||||||
buf.WriteByte(':')
|
buf.WriteByte(':')
|
||||||
switch {
|
for idx, v := range vals[k] {
|
||||||
case k == presignedHostHeader:
|
if idx > 0 {
|
||||||
buf.WriteString(host)
|
buf.WriteByte(',')
|
||||||
fallthrough
|
|
||||||
default:
|
|
||||||
for idx, v := range vals[k] {
|
|
||||||
if idx > 0 {
|
|
||||||
buf.WriteByte(',')
|
|
||||||
}
|
|
||||||
buf.WriteString(signV4TrimAll(v))
|
|
||||||
}
|
}
|
||||||
buf.WriteByte('\n')
|
buf.WriteString(signV4TrimAll(v))
|
||||||
}
|
}
|
||||||
|
buf.WriteByte('\n')
|
||||||
}
|
}
|
||||||
return buf.String()
|
return buf.String()
|
||||||
}
|
}
|
||||||
@ -83,7 +76,6 @@ func getSignedHeaders(signedHeaders http.Header) string {
|
|||||||
for k := range signedHeaders {
|
for k := range signedHeaders {
|
||||||
headers = append(headers, strings.ToLower(k))
|
headers = append(headers, strings.ToLower(k))
|
||||||
}
|
}
|
||||||
headers = append(headers, presignedHostHeader)
|
|
||||||
sort.Strings(headers)
|
sort.Strings(headers)
|
||||||
return strings.Join(headers, ";")
|
return strings.Join(headers, ";")
|
||||||
}
|
}
|
||||||
@ -98,14 +90,14 @@ func getSignedHeaders(signedHeaders http.Header) string {
|
|||||||
// <SignedHeaders>\n
|
// <SignedHeaders>\n
|
||||||
// <HashedPayload>
|
// <HashedPayload>
|
||||||
//
|
//
|
||||||
func getCanonicalRequest(extractedSignedHeaders http.Header, payload, queryStr, urlPath, method, host 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 := getURLEncodedName(urlPath)
|
||||||
canonicalRequest := strings.Join([]string{
|
canonicalRequest := strings.Join([]string{
|
||||||
method,
|
method,
|
||||||
encodedPath,
|
encodedPath,
|
||||||
rawQuery,
|
rawQuery,
|
||||||
getCanonicalHeaders(extractedSignedHeaders, host),
|
getCanonicalHeaders(extractedSignedHeaders),
|
||||||
getSignedHeaders(extractedSignedHeaders),
|
getSignedHeaders(extractedSignedHeaders),
|
||||||
payload,
|
payload,
|
||||||
}, "\n")
|
}, "\n")
|
||||||
@ -304,7 +296,7 @@ func doesPresignedSignatureMatch(hashedPayload string, r *http.Request, region s
|
|||||||
/// Verify finally if signature is same.
|
/// Verify finally if signature is same.
|
||||||
|
|
||||||
// Get canonical request.
|
// Get canonical request.
|
||||||
presignedCanonicalReq := getCanonicalRequest(extractedSignedHeaders, hashedPayload, encodedQuery, req.URL.Path, req.Method, req.Host)
|
presignedCanonicalReq := getCanonicalRequest(extractedSignedHeaders, hashedPayload, encodedQuery, req.URL.Path, req.Method)
|
||||||
|
|
||||||
// Get string to sign from canonical request.
|
// Get string to sign from canonical request.
|
||||||
presignedStringToSign := getStringToSign(presignedCanonicalReq, t, pSignValues.Credential.getScope())
|
presignedStringToSign := getStringToSign(presignedCanonicalReq, t, pSignValues.Credential.getScope())
|
||||||
@ -346,19 +338,6 @@ func doesSignatureMatch(hashedPayload string, r *http.Request, region string) AP
|
|||||||
return ErrContentSHA256Mismatch
|
return ErrContentSHA256Mismatch
|
||||||
}
|
}
|
||||||
|
|
||||||
header := req.Header
|
|
||||||
|
|
||||||
// Signature-V4 spec excludes Content-Length from signed headers list for signature calculation.
|
|
||||||
// But some clients deviate from this rule. Hence we consider Content-Length for signature
|
|
||||||
// calculation to be compatible with such clients.
|
|
||||||
for _, h := range signV4Values.SignedHeaders {
|
|
||||||
if h == "content-length" {
|
|
||||||
header = cloneHeader(req.Header)
|
|
||||||
header.Set("content-length", strconv.FormatInt(r.ContentLength, 10))
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract all the signed headers along with its values.
|
// Extract all the signed headers along with its values.
|
||||||
extractedSignedHeaders, errCode := extractSignedHeaders(signV4Values.SignedHeaders, r)
|
extractedSignedHeaders, errCode := extractSignedHeaders(signV4Values.SignedHeaders, r)
|
||||||
if errCode != ErrNone {
|
if errCode != ErrNone {
|
||||||
@ -401,8 +380,7 @@ func doesSignatureMatch(hashedPayload string, r *http.Request, region string) AP
|
|||||||
queryStr := req.URL.Query().Encode()
|
queryStr := req.URL.Query().Encode()
|
||||||
|
|
||||||
// Get canonical request.
|
// Get canonical request.
|
||||||
canonicalRequest := getCanonicalRequest(extractedSignedHeaders, hashedPayload, queryStr, req.URL.Path, req.Method, req.Host)
|
canonicalRequest := getCanonicalRequest(extractedSignedHeaders, hashedPayload, queryStr, req.URL.Path, req.Method)
|
||||||
|
|
||||||
// Get string to sign from canonical request.
|
// Get string to sign from canonical request.
|
||||||
stringToSign := getStringToSign(canonicalRequest, t, signV4Values.Credential.getScope())
|
stringToSign := getStringToSign(canonicalRequest, t, signV4Values.Credential.getScope())
|
||||||
|
|
||||||
|
@ -133,7 +133,7 @@ func calculateSeedSignature(r *http.Request) (signature string, date time.Time,
|
|||||||
queryStr := req.URL.Query().Encode()
|
queryStr := req.URL.Query().Encode()
|
||||||
|
|
||||||
// Get canonical request.
|
// Get canonical request.
|
||||||
canonicalRequest := getCanonicalRequest(extractedSignedHeaders, payload, queryStr, req.URL.Path, req.Method, req.Host)
|
canonicalRequest := getCanonicalRequest(extractedSignedHeaders, payload, queryStr, req.URL.Path, req.Method)
|
||||||
|
|
||||||
// Get string to sign from canonical request.
|
// Get string to sign from canonical request.
|
||||||
stringToSign := getStringToSign(canonicalRequest, date, signV4Values.Credential.getScope())
|
stringToSign := getStringToSign(canonicalRequest, date, signV4Values.Credential.getScope())
|
||||||
|
@ -889,11 +889,12 @@ func preSignV4(req *http.Request, accessKeyID, secretAccessKey string, expires i
|
|||||||
query.Set("X-Amz-Credential", credential)
|
query.Set("X-Amz-Credential", credential)
|
||||||
query.Set("X-Amz-Content-Sha256", unsignedPayload)
|
query.Set("X-Amz-Content-Sha256", unsignedPayload)
|
||||||
|
|
||||||
// Headers are empty, since "host" is the only header required to be signed for Presigned URLs.
|
// "host" is the only header required to be signed for Presigned URLs.
|
||||||
var extractedSignedHeaders http.Header
|
extractedSignedHeaders := make(http.Header)
|
||||||
|
extractedSignedHeaders.Set("host", req.Host)
|
||||||
|
|
||||||
queryStr := strings.Replace(query.Encode(), "+", "%20", -1)
|
queryStr := strings.Replace(query.Encode(), "+", "%20", -1)
|
||||||
canonicalRequest := getCanonicalRequest(extractedSignedHeaders, unsignedPayload, queryStr, req.URL.Path, req.Method, req.Host)
|
canonicalRequest := getCanonicalRequest(extractedSignedHeaders, unsignedPayload, queryStr, req.URL.Path, req.Method)
|
||||||
stringToSign := getStringToSign(canonicalRequest, date, scope)
|
stringToSign := getStringToSign(canonicalRequest, date, scope)
|
||||||
signingKey := getSigningKey(secretAccessKey, date, region)
|
signingKey := getSigningKey(secretAccessKey, date, region)
|
||||||
signature := getSignature(signingKey, stringToSign)
|
signature := getSignature(signingKey, stringToSign)
|
||||||
|
@ -868,10 +868,10 @@ func presignedGet(host, bucket, object string, expiry int64) string {
|
|||||||
|
|
||||||
path := "/" + path.Join(bucket, object)
|
path := "/" + path.Join(bucket, object)
|
||||||
|
|
||||||
// Headers are empty, since "host" is the only header required to be signed for Presigned URLs.
|
// "host" is the only header required to be signed for Presigned URLs.
|
||||||
var extractedSignedHeaders http.Header
|
extractedSignedHeaders := make(http.Header)
|
||||||
|
extractedSignedHeaders.Set("host", host)
|
||||||
canonicalRequest := getCanonicalRequest(extractedSignedHeaders, unsignedPayload, query, path, "GET", host)
|
canonicalRequest := getCanonicalRequest(extractedSignedHeaders, unsignedPayload, query, 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)
|
||||||
|
Loading…
Reference in New Issue
Block a user