Skip to content

Commit bcb4f35

Browse files
dpaanipani
authored andcommitted
AWS: Support S3 Credentials provider with DefaultAwsClientFactory #7063 (#7066)
Allows dynamically loading a Credential Provider with the DefaultAwsClientFactory. Co-authored-by: pani <[email protected]>
1 parent 214bfc9 commit bcb4f35

File tree

3 files changed

+292
-8
lines changed

3 files changed

+292
-8
lines changed

aws/src/main/java/org/apache/iceberg/aws/AwsClientFactories.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ static class DefaultAwsClientFactory implements AwsClientFactory {
9898
@Override
9999
public S3Client s3() {
100100
return S3Client.builder()
101+
.applyMutation(awsProperties::applyClientRegionConfiguration)
101102
.applyMutation(awsProperties::applyHttpClientConfigurations)
102103
.applyMutation(awsProperties::applyS3EndpointConfigurations)
103104
.applyMutation(awsProperties::applyS3ServiceConfigurations)
@@ -109,22 +110,28 @@ public S3Client s3() {
109110
@Override
110111
public GlueClient glue() {
111112
return GlueClient.builder()
113+
.applyMutation(awsProperties::applyClientRegionConfiguration)
112114
.applyMutation(awsProperties::applyHttpClientConfigurations)
113115
.applyMutation(awsProperties::applyGlueEndpointConfigurations)
116+
.applyMutation(awsProperties::applyClientCredentialConfigurations)
114117
.build();
115118
}
116119

117120
@Override
118121
public KmsClient kms() {
119122
return KmsClient.builder()
123+
.applyMutation(awsProperties::applyClientRegionConfiguration)
120124
.applyMutation(awsProperties::applyHttpClientConfigurations)
125+
.applyMutation(awsProperties::applyClientCredentialConfigurations)
121126
.build();
122127
}
123128

124129
@Override
125130
public DynamoDbClient dynamo() {
126131
return DynamoDbClient.builder()
132+
.applyMutation(awsProperties::applyClientRegionConfiguration)
127133
.applyMutation(awsProperties::applyHttpClientConfigurations)
134+
.applyMutation(awsProperties::applyClientCredentialConfigurations)
128135
.applyMutation(awsProperties::applyDynamoDbEndpointConfigurations)
129136
.build();
130137
}

aws/src/main/java/org/apache/iceberg/aws/AwsProperties.java

Lines changed: 132 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.apache.iceberg.aws.lakeformation.LakeFormationAwsClientFactory;
3030
import org.apache.iceberg.aws.s3.S3FileIO;
3131
import org.apache.iceberg.aws.s3.signer.S3V4RestSignerClient;
32+
import org.apache.iceberg.common.DynClasses;
3233
import org.apache.iceberg.common.DynMethods;
3334
import org.apache.iceberg.exceptions.ValidationException;
3435
import org.apache.iceberg.relocated.com.google.common.base.Preconditions;
@@ -45,6 +46,7 @@
4546
import software.amazon.awssdk.auth.credentials.AwsSessionCredentials;
4647
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
4748
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
49+
import software.amazon.awssdk.awscore.client.builder.AwsClientBuilder;
4850
import software.amazon.awssdk.awscore.client.builder.AwsSyncClientBuilder;
4951
import software.amazon.awssdk.core.client.builder.SdkClientBuilder;
5052
import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption;
@@ -351,6 +353,38 @@ public class AwsProperties implements Serializable {
351353
*/
352354
public static final String CLIENT_ASSUME_ROLE_SESSION_NAME = "client.assume-role.session-name";
353355

356+
/**
357+
* Configure the AWS credentials provider used to create AWS clients. A fully qualified concrete
358+
* class with package that implements the {@link AwsCredentialsProvider} interface is required.
359+
*
360+
* <p>Additionally, the implementation class must also have a create() or create(Map) method
361+
* implemented, which returns an instance of the class that provides aws credentials provider.
362+
*
363+
* <p>Example:
364+
* client.credentials-provider=software.amazon.awssdk.auth.credentials.SystemPropertyCredentialsProvider
365+
*
366+
* <p>When set, the default client factory {@link
367+
* org.apache.iceberg.aws.AwsClientFactories.DefaultAwsClientFactory} and also other client
368+
* factory classes will use this provider to get AWS credentials provided instead of reading the
369+
* default credential chain to get AWS access credentials.
370+
*/
371+
public static final String CLIENT_CREDENTIALS_PROVIDER = "client.credentials-provider";
372+
373+
/**
374+
* Used by the client.credentials-provider configured value that will be used by {@link
375+
* org.apache.iceberg.aws.AwsClientFactories.DefaultAwsClientFactory} and also other client
376+
* factory classes to pass provider-specific properties. Each property consists of a key name and
377+
* an associated value.
378+
*/
379+
private static final String CLIENT_CREDENTIAL_PROVIDER_PREFIX = "client.credentials-provider.";
380+
381+
/**
382+
* Used by {@link org.apache.iceberg.aws.AwsClientFactories.DefaultAwsClientFactory} and also
383+
* other client factory classes. If set, all AWS clients except STS client will use the given
384+
* region instead of the default region chain.
385+
*/
386+
public static final String CLIENT_REGION = "client.region";
387+
354388
/**
355389
* The type of {@link software.amazon.awssdk.http.SdkHttpClient} implementation used by {@link
356390
* AwsClientFactory} If set, all AWS clients will use this specified HTTP client. If not set,
@@ -677,6 +711,9 @@ public class AwsProperties implements Serializable {
677711
private int clientAssumeRoleTimeoutSec;
678712
private String clientAssumeRoleRegion;
679713
private String clientAssumeRoleSessionName;
714+
private String clientRegion;
715+
private String clientCredentialsProvider;
716+
private final Map<String, String> clientCredentialsProviderProperties;
680717

681718
private String s3FileIoSseType;
682719
private String s3FileIoSseKey;
@@ -733,6 +770,9 @@ public AwsProperties() {
733770
this.clientAssumeRoleExternalId = null;
734771
this.clientAssumeRoleRegion = null;
735772
this.clientAssumeRoleSessionName = null;
773+
this.clientRegion = null;
774+
this.clientCredentialsProvider = null;
775+
this.clientCredentialsProviderProperties = null;
736776

737777
this.s3FileIoSseType = S3FILEIO_SSE_TYPE_NONE;
738778
this.s3FileIoSseKey = null;
@@ -794,6 +834,10 @@ public AwsProperties(Map<String, String> properties) {
794834
this.clientAssumeRoleExternalId = properties.get(CLIENT_ASSUME_ROLE_EXTERNAL_ID);
795835
this.clientAssumeRoleRegion = properties.get(CLIENT_ASSUME_ROLE_REGION);
796836
this.clientAssumeRoleSessionName = properties.get(CLIENT_ASSUME_ROLE_SESSION_NAME);
837+
this.clientRegion = properties.get(CLIENT_REGION);
838+
this.clientCredentialsProvider = properties.get(CLIENT_CREDENTIALS_PROVIDER);
839+
this.clientCredentialsProviderProperties =
840+
PropertyUtil.propertiesWithPrefix(properties, CLIENT_CREDENTIAL_PROVIDER_PREFIX);
797841

798842
this.s3FileIoSseType = properties.getOrDefault(S3FILEIO_SSE_TYPE, S3FILEIO_SSE_TYPE_NONE);
799843
this.s3FileIoSseKey = properties.get(S3FILEIO_SSE_KEY);
@@ -1116,6 +1160,14 @@ public Map<String, String> httpClientProperties() {
11161160
return httpClientProperties;
11171161
}
11181162

1163+
public String clientRegion() {
1164+
return clientRegion;
1165+
}
1166+
1167+
public void setClientRegion(String clientRegion) {
1168+
this.clientRegion = clientRegion;
1169+
}
1170+
11191171
/**
11201172
* Configure the credentials for an S3 client.
11211173
*
@@ -1132,6 +1184,36 @@ public <T extends S3ClientBuilder> void applyS3CredentialConfigurations(T builde
11321184
: credentialsProvider(s3AccessKeyId, s3SecretAccessKey, s3SessionToken));
11331185
}
11341186

1187+
/**
1188+
* Configure a client AWS region.
1189+
*
1190+
* <p>Sample usage:
1191+
*
1192+
* <pre>
1193+
* S3Client.builder().applyMutation(awsProperties::applyClientRegionConfiguration)
1194+
* </pre>
1195+
*/
1196+
public <T extends AwsClientBuilder> void applyClientRegionConfiguration(T builder) {
1197+
if (clientRegion != null) {
1198+
builder.region(Region.of(clientRegion));
1199+
}
1200+
}
1201+
1202+
/**
1203+
* Configure the credential provider for AWS clients.
1204+
*
1205+
* <p>Sample usage:
1206+
*
1207+
* <pre>
1208+
* DynamoDbClient.builder().applyMutation(awsProperties::applyClientCredentialConfigurations)
1209+
* </pre>
1210+
*/
1211+
public <T extends AwsClientBuilder> void applyClientCredentialConfigurations(T builder) {
1212+
if (!Strings.isNullOrEmpty(this.clientCredentialsProvider)) {
1213+
builder.credentialsProvider(credentialsProvider(this.clientCredentialsProvider));
1214+
}
1215+
}
1216+
11351217
/**
11361218
* Configure services settings for an S3 client. The settings include: s3DualStack,
11371219
* s3UseArnRegion, s3PathStyleAccess, and s3Acceleration
@@ -1188,14 +1270,12 @@ public <T extends AwsSyncClientBuilder> void applyHttpClientConfigurations(T bui
11881270
switch (httpClientType) {
11891271
case HTTP_CLIENT_TYPE_URLCONNECTION:
11901272
UrlConnectionHttpClientConfigurations urlConnectionHttpClientConfigurations =
1191-
(UrlConnectionHttpClientConfigurations)
1192-
loadHttpClientConfigurations(UrlConnectionHttpClientConfigurations.class.getName());
1273+
loadHttpClientConfigurations(UrlConnectionHttpClientConfigurations.class.getName());
11931274
urlConnectionHttpClientConfigurations.configureHttpClientBuilder(builder);
11941275
break;
11951276
case HTTP_CLIENT_TYPE_APACHE:
11961277
ApacheHttpClientConfigurations apacheHttpClientConfigurations =
1197-
(ApacheHttpClientConfigurations)
1198-
loadHttpClientConfigurations(ApacheHttpClientConfigurations.class.getName());
1278+
loadHttpClientConfigurations(ApacheHttpClientConfigurations.class.getName());
11991279
apacheHttpClientConfigurations.configureHttpClientBuilder(builder);
12001280
break;
12011281
default:
@@ -1291,8 +1371,52 @@ private AwsCredentialsProvider credentialsProvider(
12911371
return StaticCredentialsProvider.create(
12921372
AwsSessionCredentials.create(accessKeyId, secretAccessKey, sessionToken));
12931373
}
1294-
} else {
1295-
return DefaultCredentialsProvider.create();
1374+
}
1375+
1376+
if (!Strings.isNullOrEmpty(this.clientCredentialsProvider)) {
1377+
return credentialsProvider(this.clientCredentialsProvider);
1378+
}
1379+
1380+
return DefaultCredentialsProvider.create();
1381+
}
1382+
1383+
private AwsCredentialsProvider credentialsProvider(String credentialsProviderClass) {
1384+
Class<?> providerClass;
1385+
try {
1386+
providerClass = DynClasses.builder().impl(credentialsProviderClass).buildChecked();
1387+
} catch (ClassNotFoundException e) {
1388+
throw new IllegalArgumentException(
1389+
String.format(
1390+
"Cannot load class %s, it does not exist in the classpath", credentialsProviderClass),
1391+
e);
1392+
}
1393+
1394+
Preconditions.checkArgument(
1395+
AwsCredentialsProvider.class.isAssignableFrom(providerClass),
1396+
String.format(
1397+
"Cannot initialize %s, it does not implement %s.",
1398+
credentialsProviderClass, AwsCredentialsProvider.class.getName()));
1399+
1400+
AwsCredentialsProvider provider;
1401+
try {
1402+
try {
1403+
provider =
1404+
DynMethods.builder("create")
1405+
.hiddenImpl(providerClass, Map.class)
1406+
.buildStaticChecked()
1407+
.invoke(clientCredentialsProviderProperties);
1408+
} catch (NoSuchMethodException e) {
1409+
provider =
1410+
DynMethods.builder("create").hiddenImpl(providerClass).buildStaticChecked().invoke();
1411+
}
1412+
1413+
return provider;
1414+
} catch (NoSuchMethodException e) {
1415+
throw new IllegalArgumentException(
1416+
String.format(
1417+
"Cannot create an instance of %s, it does not contain a static 'create' or 'create(Map<String, String>)' method",
1418+
credentialsProviderClass),
1419+
e);
12961420
}
12971421
}
12981422

@@ -1308,15 +1432,15 @@ private <T extends SdkClientBuilder> void configureEndpoint(T builder, String en
13081432
* software.amazon.awssdk.http.apache.ApacheHttpClient}, since including both will cause error
13091433
* described in <a href="https://github.com/apache/iceberg/issues/6715">issue#6715</a>
13101434
*/
1311-
private Object loadHttpClientConfigurations(String impl) {
1435+
private <T> T loadHttpClientConfigurations(String impl) {
13121436
Object httpClientConfigurations;
13131437
try {
13141438
httpClientConfigurations =
13151439
DynMethods.builder("create")
13161440
.hiddenImpl(impl, Map.class)
13171441
.buildStaticChecked()
13181442
.invoke(httpClientProperties);
1319-
return httpClientConfigurations;
1443+
return (T) httpClientConfigurations;
13201444
} catch (NoSuchMethodException e) {
13211445
throw new IllegalArgumentException(
13221446
String.format("Cannot create %s to generate and configure the http client builder", impl),

0 commit comments

Comments
 (0)