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

Do not send Md5 checksum header if ChecksumMode is enabled #4731

Merged
merged 5 commits into from
Dec 4, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changes/next-release/bugfix-AmazonS3-16d0bb1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"category": "Amazon S3",
"contributor": "",
"type": "bugfix",
"description": "Fixes double checksum validation for GetObject. Now when ChecksumMode is enabled, x-amz-te:append-md5 will not be sent, and only the flexible checksum will be validated. If ChecksumMode is enabled and no ChecksumAlgorithm was returned, no validation will be performed."
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,15 @@
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import software.amazon.awssdk.auth.signer.S3SignerExecutionAttribute;
import software.amazon.awssdk.authcrt.signer.internal.DefaultAwsCrtS3V4aSigner;
Expand All @@ -42,6 +45,7 @@
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
import software.amazon.awssdk.core.internal.async.FileAsyncRequestBody;
import software.amazon.awssdk.services.s3.S3AsyncClient;
import software.amazon.awssdk.services.s3.S3Configuration;
import software.amazon.awssdk.services.s3.S3IntegrationTestBase;
import software.amazon.awssdk.services.s3.model.ChecksumAlgorithm;
import software.amazon.awssdk.services.s3.model.ChecksumMode;
Expand Down Expand Up @@ -291,4 +295,82 @@ public void putObject_with_bufferCreatedFromZeroCapacityByteBuffer() {
assertThat(interceptor.responseValidation()).isEqualTo(ChecksumValidation.VALIDATED);
assertThat(response).isEqualTo("");
}

private static Stream<Arguments> getObjectChecksumValidationParams() {
return Stream.of(Arguments.of(true, ChecksumAlgorithm.CRC32, ChecksumMode.ENABLED),
Arguments.of(true, null, ChecksumMode.ENABLED),
Arguments.of(true, ChecksumAlgorithm.CRC32, null),
Arguments.of(true, null, null),
Arguments.of(false, ChecksumAlgorithm.CRC32, ChecksumMode.ENABLED),
Arguments.of(false, null, ChecksumMode.ENABLED),
Arguments.of(false, ChecksumAlgorithm.CRC32, null),
Arguments.of(false, null, null));
}

@ParameterizedTest
@MethodSource("getObjectChecksumValidationParams")
public void testGetObjectChecksumValidation(boolean checksumValidationEnabled, ChecksumAlgorithm checksumAlgorithm,
ChecksumMode checksumMode) {
S3AsyncClient s3Async = s3AsyncClientBuilder().overrideConfiguration(o -> o.addExecutionInterceptor(interceptor))
.serviceConfiguration(S3Configuration.builder()
.checksumValidationEnabled(checksumValidationEnabled)
.build())
.build();

s3Async.putObject(PutObjectRequest.builder()
.bucket(BUCKET)
.key(KEY)
.checksumAlgorithm(checksumAlgorithm)
.build(), AsyncRequestBody.fromString("Hello world")).join();

s3Async.getObject(GetObjectRequest.builder()
.bucket(BUCKET)
.key(KEY)
.checksumMode(checksumMode)
.build(), AsyncResponseTransformer.toBytes()).join();

validateChecksumValidation(checksumValidationEnabled, checksumAlgorithm, checksumMode);
interceptor.reset();
}

private void validateChecksumValidation(boolean checksumValidationEnabled, ChecksumAlgorithm checksumAlgorithm,
ChecksumMode checksumMode) {
if (checksumValidationEnabled) {
davidh44 marked this conversation as resolved.
Show resolved Hide resolved
if (checksumMode == ChecksumMode.ENABLED) {
if (checksumAlgorithm == null) {
assertRequestAndResponseDoNotContainMd5Header();
assertThat(interceptor.responseFlexibleChecksumHeader()).isNull();
} else {
assertRequestAndResponseDoNotContainMd5Header();
assertThat(interceptor.responseFlexibleChecksumHeader()).isNotNull();
}
} else {
assertRequestAndResponseContainMd5Header();
assertThat(interceptor.responseFlexibleChecksumHeader()).isNull();
}
} else {
if (checksumMode == ChecksumMode.ENABLED) {
if (checksumAlgorithm == null) {
assertRequestAndResponseDoNotContainMd5Header();
assertThat(interceptor.responseFlexibleChecksumHeader()).isNull();
} else {
assertRequestAndResponseDoNotContainMd5Header();
assertThat(interceptor.responseFlexibleChecksumHeader()).isNotNull();
}
} else {
assertRequestAndResponseDoNotContainMd5Header();
assertThat(interceptor.responseFlexibleChecksumHeader()).isNull();
}
}
}

private void assertRequestAndResponseContainMd5Header() {
assertThat(interceptor.requestTransferEncodingHeader()).isEqualTo("append-md5");
assertThat(interceptor.responseTransferEncodingHeader()).isEqualTo("append-md5");
}

private void assertRequestAndResponseDoNotContainMd5Header() {
assertThat(interceptor.requestTransferEncodingHeader()).isNull();
assertThat(interceptor.responseTransferEncodingHeader()).isNull();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,14 @@
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import software.amazon.awssdk.authcrt.signer.internal.DefaultAwsCrtS3V4aSigner;
import software.amazon.awssdk.core.HttpChecksumConstant;
import software.amazon.awssdk.core.ResponseInputStream;
Expand All @@ -41,6 +45,7 @@
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.S3AsyncClient;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.S3Configuration;
import software.amazon.awssdk.services.s3.S3IntegrationTestBase;
import software.amazon.awssdk.services.s3.model.ChecksumAlgorithm;
import software.amazon.awssdk.services.s3.model.ChecksumMode;
Expand All @@ -50,7 +55,6 @@
import software.amazon.awssdk.services.s3.model.PutObjectResponse;
import software.amazon.awssdk.services.s3.model.S3Exception;
import software.amazon.awssdk.services.s3.utils.CaptureChecksumValidationInterceptor;
import software.amazon.awssdk.services.s3.utils.ChecksumUtils;
import software.amazon.awssdk.testutils.RandomTempFile;

public class HttpChecksumIntegrationTest extends S3IntegrationTestBase {
Expand Down Expand Up @@ -149,7 +153,7 @@ public void invalidHeaderChecksumCalculatedByUserNotOverWrittenBySdkClient() {
}

@Test
public void syncValidUnsignedTrailerChecksumCalculatedBySdkClient() throws InterruptedException {
public void syncValidUnsignedTrailerChecksumCalculatedBySdkClient() {
s3Https.putObject(PutObjectRequest.builder()
.bucket(BUCKET)
.key(KEY)
Expand All @@ -173,8 +177,6 @@ public void syncValidUnsignedTrailerChecksumCalculatedBySdkClient() throws Inter

@Test
public void syncValidSignedTrailerChecksumCalculatedBySdkClient() {


s3.putObject(PutObjectRequest.builder()
.bucket(BUCKET)
.key(KEY)
Expand All @@ -198,13 +200,10 @@ public void syncValidSignedTrailerChecksumCalculatedBySdkClient() {
assertThat(interceptor.validationAlgorithm()).isEqualTo(Algorithm.CRC32);
assertThat(interceptor.responseValidation()).isEqualTo(ChecksumValidation.VALIDATED);
assertThat(text).isEqualTo("Hello world");

}

@Test
public void syncValidSignedTrailerChecksumCalculatedBySdkClient_Empty_String() {


s3.putObject(PutObjectRequest.builder()
.bucket(BUCKET)
.key(KEY)
Expand All @@ -230,7 +229,6 @@ public void syncValidSignedTrailerChecksumCalculatedBySdkClient_Empty_String() {

@Test
public void syncValidSignedTrailerChecksumCalculatedBySdkClientWithSigv4a() {

s3.putObject(PutObjectRequest.builder()
.bucket(BUCKET)
.key(KEY)
Expand All @@ -254,7 +252,6 @@ public void syncValidSignedTrailerChecksumCalculatedBySdkClientWithSigv4a() {

@Test
public void syncValidSignedTrailerChecksumCalculatedBySdkClientWithSigv4a_withContentEncoding() {

s3.putObject(PutObjectRequest.builder()
.bucket(BUCKET)
.key(KEY)
Expand Down Expand Up @@ -334,7 +331,7 @@ public void syncSignedPayloadForHugeMessage() {
}

@Test
public void syncUnsignedPayloadMultiPartForHugeMessage() throws InterruptedException {
public void syncUnsignedPayloadMultiPartForHugeMessage() {
s3Https.putObject(PutObjectRequest.builder()
.bucket(BUCKET)
.key(KEY)
Expand All @@ -360,8 +357,7 @@ public void syncUnsignedPayloadMultiPartForHugeMessage() throws InterruptedExcep


@Test
public void syncValidUnsignedTrailerChecksumCalculatedBySdkClient_withSmallFileRequestBody() throws InterruptedException,
IOException {
public void syncValidUnsignedTrailerChecksumCalculatedBySdkClient_withSmallFileRequestBody() throws IOException {
File randomFileOfFixedLength = new RandomTempFile(10 * KB);

s3Https.putObject(PutObjectRequest.builder()
Expand Down Expand Up @@ -410,4 +406,81 @@ public void syncValidUnsignedTrailerChecksumCalculatedBySdkClient_withHugeFileRe
assertThat(text).isEqualTo(new String(bytes));
}

private static Stream<Arguments> getObjectChecksumValidationParams() {
return Stream.of(Arguments.of(true, ChecksumAlgorithm.CRC32, ChecksumMode.ENABLED),
Arguments.of(true, null, ChecksumMode.ENABLED),
Arguments.of(true, ChecksumAlgorithm.CRC32, null),
Arguments.of(true, null, null),
Arguments.of(false, ChecksumAlgorithm.CRC32, ChecksumMode.ENABLED),
Arguments.of(false, null, ChecksumMode.ENABLED),
Arguments.of(false, ChecksumAlgorithm.CRC32, null),
Arguments.of(false, null, null));
}

@ParameterizedTest
@MethodSource("getObjectChecksumValidationParams")
public void testGetObjectChecksumValidation(boolean checksumValidationEnabled, ChecksumAlgorithm checksumAlgorithm,
ChecksumMode checksumMode) {
S3Client s3 = s3ClientBuilder().overrideConfiguration(o -> o.addExecutionInterceptor(interceptor))
.serviceConfiguration(S3Configuration.builder()
.checksumValidationEnabled(checksumValidationEnabled)
.build())
.build();

s3.putObject(PutObjectRequest.builder()
.bucket(BUCKET)
.key(KEY)
.checksumAlgorithm(checksumAlgorithm)
.build(), RequestBody.fromString("Hello world"));

s3.getObject(GetObjectRequest.builder()
.bucket(BUCKET)
.key(KEY)
.checksumMode(checksumMode)
.build());

validateChecksumValidation(checksumValidationEnabled, checksumAlgorithm, checksumMode);
interceptor.reset();
}

private void validateChecksumValidation(boolean checksumValidationEnabled, ChecksumAlgorithm checksumAlgorithm,
ChecksumMode checksumMode) {
if (checksumValidationEnabled) {
if (checksumMode == ChecksumMode.ENABLED) {
davidh44 marked this conversation as resolved.
Show resolved Hide resolved
if (checksumAlgorithm == null) {
assertRequestAndResponseDoNotContainMd5Header();
assertThat(interceptor.responseFlexibleChecksumHeader()).isNull();
} else {
assertRequestAndResponseDoNotContainMd5Header();
assertThat(interceptor.responseFlexibleChecksumHeader()).isNotNull();
}
} else {
assertRequestAndResponseContainMd5Header();
assertThat(interceptor.responseFlexibleChecksumHeader()).isNull();
}
} else {
if (checksumMode == ChecksumMode.ENABLED) {
if (checksumAlgorithm == null) {
assertRequestAndResponseDoNotContainMd5Header();
assertThat(interceptor.responseFlexibleChecksumHeader()).isNull();
} else {
assertRequestAndResponseDoNotContainMd5Header();
assertThat(interceptor.responseFlexibleChecksumHeader()).isNotNull();
}
} else {
assertRequestAndResponseDoNotContainMd5Header();
assertThat(interceptor.responseFlexibleChecksumHeader()).isNull();
}
}
}

private void assertRequestAndResponseContainMd5Header() {
assertThat(interceptor.requestTransferEncodingHeader()).isEqualTo("append-md5");
assertThat(interceptor.responseTransferEncodingHeader()).isEqualTo("append-md5");
}

private void assertRequestAndResponseDoNotContainMd5Header() {
assertThat(interceptor.requestTransferEncodingHeader()).isNull();
assertThat(interceptor.responseTransferEncodingHeader()).isNull();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@
import software.amazon.awssdk.services.s3.internal.FieldWithDefault;
import software.amazon.awssdk.services.s3.internal.settingproviders.DisableMultiRegionProviderChain;
import software.amazon.awssdk.services.s3.internal.settingproviders.UseArnRegionProviderChain;
import software.amazon.awssdk.services.s3.model.ChecksumAlgorithm;
import software.amazon.awssdk.services.s3.model.ChecksumMode;
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
import software.amazon.awssdk.services.s3.model.PutBucketAccelerateConfigurationRequest;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.awssdk.utils.builder.CopyableBuilder;
import software.amazon.awssdk.utils.builder.ToCopyableBuilder;
Expand Down Expand Up @@ -167,6 +171,26 @@ public boolean dualstackEnabled() {
return dualstackEnabled.value();
}

/**
* Returns whether trailing checksum validation is enabled. This is enabled by default.
davidh44 marked this conversation as resolved.
Show resolved Hide resolved
*
* <p>
* For {@link PutObjectRequest}, trailing checksum validation will be performed if:
* <ul>
* <li>Checksum validation is not disabled</li>
* <li>Server-side encryption is not used</li>
* <li>Flexible checksum {@link ChecksumAlgorithm} is not specified</li>
* </ul>
*
* For {@link GetObjectRequest}, trailing checksum validation will be performed if:
* <ul>
* <li>Checksum validation is not disabled</li>
* <li>{@link ChecksumMode} is disabled (default)</li>
* <li>Regular S3 is used (non-S3Express)</li>
* </ul>
*
* @return True if trailing checksum validation is enabled
*/
public boolean checksumValidationEnabled() {
return checksumValidationEnabled.value();
}
Expand Down Expand Up @@ -273,11 +297,22 @@ public interface Builder extends CopyableBuilder<Builder, S3Configuration> {
Boolean checksumValidationEnabled();

/**
* Option to disable doing a validation of the checksum of an object stored in S3.
* Option to disable trailing checksum validation of an object stored in S3. This is enabled by default.
*
* <p>
* Checksum validation is enabled by default.
* </p>
* For {@link PutObjectRequest}, trailing checksum validation will be performed if:
* <ul>
* <li>Checksum validation is not disabled</li>
* <li>Server-side encryption is not used</li>
* <li>Flexible checksum algorithm is not specified</li>
* </ul>
*
* For {@link GetObjectRequest}, trailing checksum validation will be performed if:
* <ul>
* <li>Checksum validation is not disabled</li>
* <li>{@link ChecksumMode} is disabled (default)</li>
* <li>Regular S3 is used (non-S3Express)</li>
* </ul>
*
* @see S3Configuration#checksumValidationEnabled().
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* permissions and limitations under the License.
*/

package software.amazon.awssdk.services.s3.checksums;
package software.amazon.awssdk.services.s3.internal.checksums;

import java.nio.ByteBuffer;
import java.util.Optional;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* permissions and limitations under the License.
*/

package software.amazon.awssdk.services.s3.checksums;
package software.amazon.awssdk.services.s3.internal.checksums;

import java.io.FilterInputStream;
import java.io.IOException;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* permissions and limitations under the License.
*/

package software.amazon.awssdk.services.s3.checksums;
package software.amazon.awssdk.services.s3.internal.checksums;

import software.amazon.awssdk.annotations.SdkInternalApi;

Expand Down
Loading
Loading