UrlConnectionHttpClient returns null messages when S3.putObject returns 403 in some cases
- Dominant language
- Java
- Stars
- 2.6k
- Forks
- 1k
- Avg merge
- 2d 9h
- Merged PRs (30d)
- 51
Description
### Describe the bug
When an v2 S3 client using the `UrlConnectionHttpClient` makes a `putObject` request with `kms:sse` that fails due to the caller not having `kms:GenerateDataKey` on the kms key being used to encrypt the file, the response is 403. However, the error message is `null`, when it should be something like `User: XYZ is not authorized to perform: kms:GenerateDataKey on this resource because the resource does not exist in this Region, no resource-based policies allow access, or a resource-based policy explicitly denies access`.
I have verified that a v2 S3 client using the `ApacheHttpClient` or `AwsCrtHttpClient`, or the v1 S3 client results in a non null error message with the expected message.
### Expected Behavior
When calling `putObject` using `kms:sse` and specifying a kms key in the request with a key policy that does not give the caller permission to `kms:GenerateDataKey` then the request should fail with the error message:
`User: {REDACTED PRINCIPAL} is not authorized to perform: kms:GenerateDataKey on this resource because the resource does not exist in this Region, no resource-based policies allow access, or a resource-based policy explicitly denies access (Service: S3, Status Code: 403, Request ID: 1DB63EJ6C0XM1Z1M, Extended Request ID: UrS2WQqsumheP9K8KKLCWgVlsnnSklK0MM9Idz2zaR/HeOw6WeWIlnpdCm+AjBv0y0h0umH9P/94Y4i5R+LW9Q==)`
### Current Behavior
When calling `putObject` using `kms:sse` and specifying a kms key in the request with a key policy that does not give the caller permission to `kms:GenerateDataKey` then the request fails with the following error message:
`null (Service: S3, Status Code: 403, Request ID: 1NHWS3N3B2Q8KHFG, Extended Request ID: qNCefm0Z1GuLMVTUr2ew8OiKQeMyToU/Z8nBGe+zjjORC7JPauIE3tOg7MdxWPTPPxAGUQrPvJidHH8EHu+uqQ==)`
### Reproduction Steps
Below is a main method from a mvn project that will run with three different http clients, and the v1 s3 client.
```
package com.example;
import com.amazonaws.auth.profile.ProfileCredentialsProvider;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectRequest;
import com.amazonaws.services.s3.model.SSEAwsKeyManagementParams;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.http.SdkHttpClient;
import software.amazon.awssdk.http.crt.AwsCrtHttpClient;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.ServerSideEncryption;
import software.amazon.awssdk.services.sts.StsClient;
import software.amazon.awssdk.services.sts.auth.StsAssumeRoleCredentialsProvider;
import software.amazon.awssdk.services.sts.model.AssumeRoleRequest;
import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient;
import software.amazon.awssdk.http.apache.ApacheHttpClient;
public class App {
private static final String bucketName = "insert your own bucket name";
private static final String kmsKeyId = "insert your own kms key";
private static final String role = "insert your role arn";
private static final String key = "random/path.txt";
public static void main(String[] args) throws IOException {
v1Client();
putObjectWithClient(getS3ClientWithHttpClient(ApacheHttpClient.create()));
putObjectWithClient(getS3ClientWithHttpClient(UrlConnectionHttpClient.create()));
putObjectWithClient(getS3ClientWithHttpClient(AwsCrtHttpClient.create()));
System.exit(0);
}
private static StsClient getStsClient() {
return StsClient.builder()
.httpClientBuilder(UrlConnectionHttpClient.builder())
.region(Region.AP_SOUTHEAST_2)
.credentialsProvider(software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider.create())
.build();
}
private static void v1Client() throws IOException {
InputStream inputStream = generateFileContent();
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentLength(inputStream.available());
AmazonS3 s3clientv1 = AmazonS3ClientBuilder.standard()
.withRegion("ap-southeast-2")
.withCredentials(new ProfileCredentialsProvider())
.build();
try {
SSEAwsKeyManagementParams kmsParams = new SSEAwsKeyManagementParams(kmsKeyId);
PutObjectRequest request = new PutObjectRequest(bucketName, key, inputStream, metadata)
.withSSEAwsKeyManagementParams(kmsParams);
s3clientv1.putObject(request);
System.out.println("Object uploaded to S3 successfully, this means you made your key policy allow kms:GenerateDataKey!");
System.exit(1);
} catch (Exception e) {
System.out.printf("v1 errorMsg: %s", e.getMessage());
}
}
private static void putObjectWithClient(final S3Client s3Client) {
final InputStream inputStream = generateFileContent();
try {
// Upload the object to S3 with SSE-KMS encryption
var putObjectRequest = software.amazon.awssdk.services.s3.model.PutObjectRequest.builder()
.bucket(bucketName)
.key(key)
.serverSideEncryption(ServerSideEncryption.AWS_KMS)
.ssekmsKeyId(kmsKeyId)
.build();
s3Client.putObject(putObjectRequest, RequestBody.fromInputStream(inputStream, inputStream.available()));
System.out.println("Object uploaded to S3 successfully, this means you made your key policy allow kms:GenerateDataKey!");
System.exit(1);
} catch (Exception e) {
System.out.println("ErrorMessage: \n" + e.getMessage());
}
}
private static S3Client getS3ClientWithHttpClient(final SdkHttpClient sdkHttpClient) {
final var assumeRoleRequest = AssumeRoleRequest.builder()
.roleArn()
.roleSessionName("whatever")
.build();
return S3Client.builder()
.httpClient(sdkHttpClient)
.credentialsProvider(
StsAssumeRoleCredentialsProvider.builder()
.stsClient(getStsClient())
.refreshRequest(assumeRoleRequest)
.build()
)
.region(Region.AP_SOUTHEAST_2)
.build();
}
private static InputStream generateFileContent() {
String content = "random file content for demo purposes 123432423";
byte[] contentAsBytes = content.getBytes(StandardCharsets.UTF_8);
return new ByteArrayInputStream(contentAsBytes);
}
}
```
you will need the following dependencies:
```
com.amazonaws
aws-java-sdk-s3
1.12.539
software.amazon.awssdk
s3
2.25.61
software.amazon.awssdk
sts
2.25.61
software.amazon.awssdk
url-connection-client
2.25.61
software.amazon.awssdk
aws-crt-client
2.25.61
software.amazon.awssdk
apache-client
2.25.61
```
### Possible Solution
_No response_
### Additional Information/Context
_No response_
### AWS Java SDK version used
2.25.61
### JDK version used
OpenJDK Runtime Environment Corretto-11.0.18.10.1 (build 11.0.18+10-LTS) OpenJDK 64-Bit Server VM Corretto-11.0.18.10.1 (build 11.0.18+10-LTS, mixed mode)
### Operating System and version
Amazon Linux 2 x86_64, but I see this in multiple places
Contributor guide
Assessment
This issue has not been assessed yet.