1 /* 2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"). 5 * You may not use this file except in compliance with the License. 6 * A copy of the License is located at 7 * 8 * http://aws.amazon.com/apache2.0 9 * 10 * or in the "license" file accompanying this file. This file is distributed 11 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 * express or implied. See the License for the specific language governing 13 * permissions and limitations under the License. 14 */ 15 16 package software.amazon.awssdk.services.s3.internal.handlers; 17 18 import static software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME; 19 20 import java.util.List; 21 import java.util.Map; 22 import java.util.Objects; 23 import java.util.Optional; 24 import software.amazon.awssdk.annotations.SdkInternalApi; 25 import software.amazon.awssdk.checksums.DefaultChecksumAlgorithm; 26 import software.amazon.awssdk.checksums.spi.ChecksumAlgorithm; 27 import software.amazon.awssdk.core.SdkRequest; 28 import software.amazon.awssdk.core.SelectedAuthScheme; 29 import software.amazon.awssdk.core.checksums.Algorithm; 30 import software.amazon.awssdk.core.checksums.ChecksumSpecs; 31 import software.amazon.awssdk.core.interceptor.Context; 32 import software.amazon.awssdk.core.interceptor.ExecutionAttributes; 33 import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; 34 import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute; 35 import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute; 36 import software.amazon.awssdk.core.interceptor.trait.HttpChecksum; 37 import software.amazon.awssdk.http.SdkHttpRequest; 38 import software.amazon.awssdk.http.auth.aws.signer.AwsV4FamilyHttpSigner; 39 import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; 40 import software.amazon.awssdk.identity.spi.Identity; 41 import software.amazon.awssdk.services.s3.internal.s3express.S3ExpressUtils; 42 import software.amazon.awssdk.services.s3.model.PutObjectRequest; 43 44 /** 45 * S3Express has different checksum requirements compared to standard S3 calls. This interceptor modifies checksums only 46 * for S3Express calls. 47 * <p> 48 * Checksums can be configured through model traits on operations as follows 49 * <ol> 50 * <li><i>httpChecksumRequired</i> - older setting used in S3Control -> not allowed</li> 51 * <li><i>httpChecksum</i> is set and required -> always add CRC32 checksum even if algorithm is not specified.</li> 52 * <li><i>httpChecksum</i> is set but not required -> if algorithm is not specified, behavior differs</li> 53 * </ol> 54 * <p>Note that, if <i>httpChecksum</i> is not present, no checksum may be calculated. PutBucketPolicy, DeleteObjects are examples 55 * of operations that require checksums. PutObject, UploadPart are examples of operations that do not require checksums. 56 * <p> 57 * Special cases 58 * <ul> 59 * <li>PutObject -> always calculate CRC32</li> 60 * <li>UploadPart -> do not calculate CRC32 if algorithm is missing, unless TM is used</li> 61 * </ul> 62 */ 63 @SdkInternalApi 64 public final class S3ExpressChecksumInterceptor implements ExecutionInterceptor { 65 66 @Override modifyRequest(Context.ModifyRequest context, ExecutionAttributes executionAttributes)67 public SdkRequest modifyRequest(Context.ModifyRequest context, ExecutionAttributes executionAttributes) { 68 SdkRequest request = context.request(); 69 if (!S3ExpressUtils.useS3Express(executionAttributes)) { 70 return request; 71 } 72 Optional<ChecksumSpecs> resolvedChecksumSpecs = 73 executionAttributes.getOptionalAttribute(SdkExecutionAttribute.RESOLVED_CHECKSUM_SPECS); 74 HttpChecksum httpChecksumTraitInOperation = executionAttributes.getAttribute(SdkInternalExecutionAttribute.HTTP_CHECKSUM); 75 if (!resolvedChecksumSpecs.isPresent()) { 76 if (httpChecksumTraitInOperation != null) { 77 throw new IllegalStateException("S3Express: illegal checksum parameter combination"); 78 } 79 return request; 80 } 81 ChecksumSpecs checksumSpecs = resolvedChecksumSpecs.get(); 82 if (checksumSpecs.algorithm() != null || requestContainsUserCalculatedChecksum(request)) { 83 return request; 84 } 85 if (shouldAlwaysAddChecksum(checksumSpecs, request)) { 86 SelectedAuthScheme<Identity> authScheme = 87 (SelectedAuthScheme<Identity>) getAuthScheme(executionAttributes); 88 AuthSchemeOption authSchemeOption = 89 authScheme.authSchemeOption().copy(o -> o.putSignerProperty(AwsV4FamilyHttpSigner.CHECKSUM_ALGORITHM, 90 DefaultChecksumAlgorithm.CRC32)); 91 SelectedAuthScheme<Identity> authSchemeWithCrc32 = new SelectedAuthScheme<>(authScheme.identity(), 92 authScheme.signer(), 93 authSchemeOption); 94 executionAttributes.putAttribute(SELECTED_AUTH_SCHEME, authSchemeWithCrc32); 95 } 96 return request; 97 } 98 requestContainsUserCalculatedChecksum(SdkRequest request)99 private boolean requestContainsUserCalculatedChecksum(SdkRequest request) { 100 return request.getValueForField("ChecksumCRC32", String.class).isPresent() 101 || request.getValueForField("ChecksumCRC32C", String.class).isPresent() 102 || request.getValueForField("ChecksumSHA1", String.class).isPresent() 103 || request.getValueForField("ChecksumSHA256", String.class).isPresent(); 104 } 105 shouldAlwaysAddChecksum(ChecksumSpecs checksumSpecs, SdkRequest request)106 private boolean shouldAlwaysAddChecksum(ChecksumSpecs checksumSpecs, SdkRequest request) { 107 return checksumSpecs.isRequestChecksumRequired() || request instanceof PutObjectRequest; 108 } 109 110 @Override modifyHttpRequest(Context.ModifyHttpRequest context, ExecutionAttributes executionAttributes)111 public SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context, ExecutionAttributes executionAttributes) { 112 // TODO (s3express) - possibly migrate elsewhere 113 // if algorithm is set in modifyRequest(), the marshaller won't add this header, since it checks in the request object 114 SdkHttpRequest sdkHttpRequest = context.httpRequest(); 115 SelectedAuthScheme<?> selectedAuthScheme = getAuthScheme(executionAttributes); 116 ChecksumAlgorithm algorithm = 117 selectedAuthScheme.authSchemeOption().signerProperty(AwsV4FamilyHttpSigner.CHECKSUM_ALGORITHM); 118 if (Objects.equals(algorithm, DefaultChecksumAlgorithm.CRC32)) { 119 Optional<String> headerValue = getFirstNestedValue(sdkHttpRequest.headers(), "x-amz-sdk-checksum-algorithm"); 120 if (!headerValue.isPresent()) { 121 return sdkHttpRequest.toBuilder().appendHeader("x-amz-sdk-checksum-algorithm", Algorithm.CRC32.name()).build(); 122 } 123 } 124 return sdkHttpRequest; 125 } 126 getAuthScheme(ExecutionAttributes executionAttributes)127 private SelectedAuthScheme<?> getAuthScheme(ExecutionAttributes executionAttributes) { 128 SelectedAuthScheme<?> selectedAuthScheme = executionAttributes.getAttribute(SELECTED_AUTH_SCHEME); 129 if (selectedAuthScheme == null) { 130 throw new IllegalStateException("Auth scheme should not be null"); 131 } 132 return selectedAuthScheme; 133 } 134 getFirstNestedValue(Map<String, List<String>> map, String key)135 private Optional<String> getFirstNestedValue(Map<String, List<String>> map, String key) { 136 List<String> value = map.get(key); 137 if (value == null) { 138 return Optional.empty(); 139 } 140 String firstValue = value.get(0); 141 return firstValue.isEmpty() ? Optional.empty() : Optional.of(firstValue); 142 } 143 } 144