aws / aws/aws-sdk-java-v2

UrlConnectionHttpClient returns null messages when S3.putObject returns 403 in some cases

Open
#5,305 1 comment 0 reactions 0 assignees View on GitHub
bug p2
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.