Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use UNSIGNED-PAYLOAD if sha256 is not calculated #668

Merged

Conversation

krishnasrinivas
Copy link
Contributor

@krishnasrinivas krishnasrinivas commented Apr 25, 2017

The issue was found when testing S3 gateway. In gateway setup, if client does
presignedPUT the sha256 value is not valid in which case we should use
UNSIGNED-PAYLOAD instead of calculating sha256sum() on empty byte array

Reference: #661

Tested api_functional_v4_test.go on localhost:9000 minio
Tested with mc --debug on aws-s3

@@ -27,6 +27,8 @@ const (
SignatureV4Streaming
)

var emptySHA256 = sum256([]byte{})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is []byte{} used in favour of nil? The default value for a slice is nil so we can avoid setting metadata.contentSHA256Bytes like we are doing in this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I copy/pasted it from the previous code:
shaHeader = hex.EncodeToString(sum256([]byte{}))

let me fix that.

@krisis
Copy link
Member

krisis commented Apr 26, 2017

@krishnasrinivas Could you add a functional test case capturing this?

@krishnasrinivas
Copy link
Contributor Author

@krishnasrinivas Could you add a functional test case capturing this?

@krisis actually I see that we don't have functional tests for the Core functions. I could bring it as a separate commit as it will take time to write all the tests.

@krisis
Copy link
Member

krisis commented Apr 26, 2017

@krishnasrinivas see core_test.go. @harshavardhana added one yesterday.

} else {
shaHeader = hex.EncodeToString(metadata.contentSHA256Bytes)
}
if len(metadata.contentSHA256Bytes) > 0 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can't we check for nil like before? I.e,

if metadata.contentSHA256Bytes != nil {
     // ....
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So that we do the right thing if the caller passes []byte{} like how remco's s3 gateway patch does.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To quote you from #661 (comment)

It can be fixed in a cleaner way if we want to fix it. i.e metadata.contentSHA256Bytes being nil should not indicate whether the body itself is empty. If the body is empty the caller should set metadata.contentSHA256Bytes to Hex(SHA256Hash("")) and If metadata.contentSHA256Bytes is set to nil then we simple use UNSIGNED-PAYLOAD

So, I thought metadata.contentSHA256Bytes == nil meant the client wants to send unsigned payload and signed payload otherwise.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was coming from the point that nil is a sentinel value for []T and makes it easier to recognise as a pattern while reading code. if len(contentSHA256Bytes) > 0 allows users to send []byte{} and nil interchangeably, which is convenient, so this check is fine by me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had it as nil, but changed it after it failed with remco's patch

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The story of

slice := nil 

or 

var slice []byte
slice := []byte{}

is that if slice != nil would work for the former but not the latter but functionally both are of same len and cap

Quite surprisingly this happens with encoding JSON objects (a nil slice encodes to null, while []string{} encodes to the JSON array []).

core_test.go Outdated
t.Fatal("Error:", err)
}
if size != minPartSize {
t.Fatalf("Error: number of bytes does not match, want %v, got %v\n", minPartSize*4, size)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the error message as expected? Shouldn't it be as below?

t.Fatalf(..., minPartSize, size)

st, err := r.Stat()
if err != nil {
t.Fatal("Error:", err, bucketName, objectName)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for uniformity sake, shall we have one newline between any two code-blocks. Could you fix that in this function?

api.go Outdated
// Add signature version '2' authorization header.
req = s3signer.SignV2(*req, c.accessKeyID, c.secretAccessKey)
} else if c.signature.isV4() || c.signature.isStreamingV4() &&
method != "PUT" {
case c.signature.isV4():
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this change break something ?
I think it should be:

case c.signature.isV4():
   fallthrough
case c.signature.isStreamingV4() && method != "PUT":
   ..

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vadmeste the previous code was not looking correct.

	} else if c.signature.isV4() || c.signature.isStreamingV4() &&
		method != "PUT" {

I reviewed the code and saw that StreamingSignature is enabled only for PUT request. Hence this code was not necessary. Let me know if I am reading anything incorrectly.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed the code and saw that StreamingSignature is enabled only for PUT request. Hence this code was not necessary. Let me know if I am reading anything incorrectly.

yes you are right, a request is stream v4 only when we are going to upload an object or a part, thanks.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@krishnasrinivas the check is still required since the client's signature type is set before we know if the upload is going to be a single PUT object or a multipart upload. Streaming signature applies only to http.MethodPUT. API calls corresponding to get incomplete uploads, initiate new multipart, etc should use signature v4. Having said that, you could see if it can be refactored to be obvious to the reader and maintainable in the long run.

The current PR fails in go test -v -run=TestPutObjectStreaming when we make the following change. I have made the size of object to be uploaded to 64MB + 1 bytes.

diff --git a/api_functional_v4_test.go b/api_functional_v4_test.go
index dacbba8..c7c4a00 100644
--- a/api_functional_v4_test.go
+++ b/api_functional_v4_test.go
@@ -392,7 +392,7 @@ func TestPutObjectStreaming(t *testing.T) {
        }
 
        // Upload an object.
-       sizes := []int64{0, 64*1024 - 1, 64 * 1024}
+       sizes := []int64{0, 64*1024 - 1, 64*1024*1024 + 1}
        objectName := "test-object"
        for i, size := range sizes {
                data := bytes.Repeat([]byte("a"), int(size))

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@krisis you are right, let me fix that

@krishnasrinivas krishnasrinivas force-pushed the refactor-unsigned-payload branch from 38af1a1 to 1791d4b Compare April 27, 2017 21:29
Copy link
Member

@krisis krisis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Locally tested with object size 64MB + 1 bytes.

@harshavardhana harshavardhana merged commit e988fc9 into minio:master Apr 28, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants