• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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