Skip to content

Commit e02b102

Browse files
authored
HADOOP-16445. Allow separate custom signing algorithms for S3 and DDB (#1332)
1 parent dbdc612 commit e02b102

File tree

12 files changed

+402
-8
lines changed

12 files changed

+402
-8
lines changed

hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/Constants.java

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,9 +350,43 @@ private Constants() {
350350
public static final String SERVER_SIDE_ENCRYPTION_KEY =
351351
"fs.s3a.server-side-encryption.key";
352352

353-
//override signature algorithm used for signing requests
353+
/**
354+
* List of custom Signers. The signer class will be loaded, and the signer
355+
* name will be associated with this signer class in the S3 SDK. e.g. Single
356+
* CustomSigner -> 'CustomSigner:org.apache...CustomSignerClass Multiple
357+
* CustomSigners -> 'CSigner1:CustomSignerClass1,CSigner2:CustomerSignerClass2
358+
*/
359+
public static final String CUSTOM_SIGNERS = "fs.s3a.custom.signers";
360+
361+
/**
362+
* There's 3 parameters that can be used to specify a non-default signing
363+
* algorithm. fs.s3a.signing-algorithm - This property has existed for the
364+
* longest time. If specified, without either of the other 2 properties being
365+
* specified, this signing algorithm will be used for S3 and DDB (S3Guard).
366+
* The other 2 properties override this value for S3 or DDB.
367+
* fs.s3a.s3.signing-algorithm - Allows overriding the S3 Signing algorithm.
368+
* This does not affect DDB. Specifying this property without specifying
369+
* fs.s3a.signing-algorithm will only update the signing algorithm for S3
370+
* requests, and the default will be used for DDB fs.s3a.ddb.signing-algorithm
371+
* - Allows overriding the DDB Signing algorithm. This does not affect S3.
372+
* Specifying this property without specifying fs.s3a.signing-algorithm will
373+
* only update the signing algorithm for DDB requests, and the default will be
374+
* used for S3
375+
*/
354376
public static final String SIGNING_ALGORITHM = "fs.s3a.signing-algorithm";
355377

378+
public static final String SIGNING_ALGORITHM_S3 =
379+
"fs.s3a." + Constants.AWS_SERVICE_IDENTIFIER_S3.toLowerCase()
380+
+ ".signing-algorithm";
381+
382+
public static final String SIGNING_ALGORITHM_DDB =
383+
"fs.s3a." + Constants.AWS_SERVICE_IDENTIFIER_DDB.toLowerCase()
384+
+ "signing-algorithm";
385+
386+
public static final String SIGNING_ALGORITHM_STS =
387+
"fs.s3a." + Constants.AWS_SERVICE_IDENTIFIER_STS.toLowerCase()
388+
+ "signing-algorithm";
389+
356390
public static final String S3N_FOLDER_SUFFIX = "_$folder$";
357391
public static final String FS_S3A_BLOCK_SIZE = "fs.s3a.block.size";
358392
public static final String FS_S3A = "s3a";
@@ -796,4 +830,7 @@ private Constants() {
796830
public static final String S3GUARD_CONSISTENCY_RETRY_INTERVAL_DEFAULT =
797831
"2s";
798832

833+
public static final String AWS_SERVICE_IDENTIFIER_S3 = "S3";
834+
public static final String AWS_SERVICE_IDENTIFIER_DDB = "DDB";
835+
public static final String AWS_SERVICE_IDENTIFIER_STS = "STS";
799836
}

hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/DefaultS3ClientFactory.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ public AmazonS3 createS3Client(URI name,
5555
final AWSCredentialsProvider credentials,
5656
final String userAgentSuffix) throws IOException {
5757
Configuration conf = getConf();
58-
final ClientConfiguration awsConf = S3AUtils.createAwsConf(getConf(), bucket);
58+
final ClientConfiguration awsConf = S3AUtils
59+
.createAwsConf(getConf(), bucket, Constants.AWS_SERVICE_IDENTIFIER_S3);
5960
if (!StringUtils.isEmpty(userAgentSuffix)) {
6061
awsConf.setUserAgentSuffix(userAgentSuffix);
6162
}

hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/S3AFileSystem.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,7 @@ public class S3AFileSystem extends FileSystem implements StreamCapabilities,
259259
private MagicCommitIntegration committerIntegration;
260260

261261
private AWSCredentialProviderList credentials;
262+
private SignerManager signerManager;
262263

263264
private ITtlTimeProvider ttlTimeProvider;
264265

@@ -359,6 +360,9 @@ public void initialize(URI name, Configuration originalConf)
359360
}
360361
useListV1 = (listVersion == 1);
361362

363+
signerManager = new SignerManager();
364+
signerManager.initCustomSigners(conf);
365+
362366
// creates the AWS client, including overriding auth chain if
363367
// the FS came with a DT
364368
// this may do some patching of the configuration (e.g. setting
@@ -3053,6 +3057,8 @@ public void close() throws IOException {
30533057
instrumentation = null;
30543058
closeAutocloseables(LOG, credentials);
30553059
cleanupWithLogger(LOG, delegationTokens.orElse(null));
3060+
cleanupWithLogger(LOG, signerManager);
3061+
signerManager = null;
30563062
credentials = null;
30573063
}
30583064
}

hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/S3AUtils.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1203,14 +1203,59 @@ public static void deleteWithWarning(FileSystem fs,
12031203
* @param bucket Optional bucket to use to look up per-bucket proxy secrets
12041204
* @return new AWS client configuration
12051205
* @throws IOException problem creating AWS client configuration
1206+
*
1207+
* @deprecated use {@link #createAwsConf(Configuration, String, String)}
12061208
*/
1209+
@Deprecated
12071210
public static ClientConfiguration createAwsConf(Configuration conf,
12081211
String bucket)
12091212
throws IOException {
1213+
return createAwsConf(conf, bucket, null);
1214+
}
1215+
1216+
/**
1217+
* Create a new AWS {@code ClientConfiguration}. All clients to AWS services
1218+
* <i>MUST</i> use this or the equivalents for the specific service for
1219+
* consistent setup of connectivity, UA, proxy settings.
1220+
*
1221+
* @param conf The Hadoop configuration
1222+
* @param bucket Optional bucket to use to look up per-bucket proxy secrets
1223+
* @param awsServiceIdentifier a string representing the AWS service (S3,
1224+
* DDB, etc) for which the ClientConfiguration is being created.
1225+
* @return new AWS client configuration
1226+
* @throws IOException problem creating AWS client configuration
1227+
*/
1228+
public static ClientConfiguration createAwsConf(Configuration conf,
1229+
String bucket, String awsServiceIdentifier)
1230+
throws IOException {
12101231
final ClientConfiguration awsConf = new ClientConfiguration();
12111232
initConnectionSettings(conf, awsConf);
12121233
initProxySupport(conf, bucket, awsConf);
12131234
initUserAgent(conf, awsConf);
1235+
if (StringUtils.isNotEmpty(awsServiceIdentifier)) {
1236+
String configKey = null;
1237+
switch (awsServiceIdentifier) {
1238+
case AWS_SERVICE_IDENTIFIER_S3:
1239+
configKey = SIGNING_ALGORITHM_S3;
1240+
break;
1241+
case AWS_SERVICE_IDENTIFIER_DDB:
1242+
configKey = SIGNING_ALGORITHM_DDB;
1243+
break;
1244+
case AWS_SERVICE_IDENTIFIER_STS:
1245+
configKey = SIGNING_ALGORITHM_STS;
1246+
break;
1247+
default:
1248+
// Nothing to do. The original signer override is already setup
1249+
}
1250+
if (configKey != null) {
1251+
String signerOverride = conf.getTrimmed(configKey, "");
1252+
if (!signerOverride.isEmpty()) {
1253+
LOG.debug("Signer override for {}} = {}", awsServiceIdentifier,
1254+
signerOverride);
1255+
awsConf.setSignerOverride(signerOverride);
1256+
}
1257+
}
1258+
}
12141259
return awsConf;
12151260
}
12161261

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package org.apache.hadoop.fs.s3a;
19+
20+
import com.amazonaws.auth.Signer;
21+
import com.amazonaws.auth.SignerFactory;
22+
import java.io.Closeable;
23+
import java.io.IOException;
24+
import org.slf4j.Logger;
25+
import org.slf4j.LoggerFactory;
26+
27+
import org.apache.hadoop.conf.Configuration;
28+
29+
import static org.apache.hadoop.fs.s3a.Constants.CUSTOM_SIGNERS;
30+
31+
/**
32+
* Class to handle custom signers.
33+
*/
34+
public class SignerManager implements Closeable {
35+
36+
private static final Logger LOG = LoggerFactory
37+
.getLogger(SignerManager.class);
38+
39+
40+
public SignerManager() {
41+
}
42+
43+
/**
44+
* Initialize custom signers and register them with the AWS SDK.
45+
*
46+
* @param conf Hadoop configuration
47+
*/
48+
public void initCustomSigners(Configuration conf) {
49+
String[] customSigners = conf.getTrimmedStrings(CUSTOM_SIGNERS);
50+
if (customSigners == null || customSigners.length == 0) {
51+
// No custom signers specified, nothing to do.
52+
LOG.debug("No custom signers specified");
53+
return;
54+
}
55+
56+
for (String customSigner : customSigners) {
57+
String[] parts = customSigner.split(":");
58+
if (parts.length != 2) {
59+
String message =
60+
"Invalid format (Expected name:SignerClass) for CustomSigner: ["
61+
+ customSigner
62+
+ "]";
63+
LOG.error(message);
64+
throw new IllegalArgumentException(message);
65+
}
66+
maybeRegisterSigner(parts[0], parts[1], conf);
67+
}
68+
}
69+
70+
/*
71+
* Make sure the signer class is registered once with the AWS SDK
72+
*/
73+
private static void maybeRegisterSigner(String signerName,
74+
String signerClassName, Configuration conf) {
75+
try {
76+
SignerFactory.getSignerByTypeAndService(signerName, null);
77+
} catch (IllegalArgumentException e) {
78+
// Signer is not registered with the AWS SDK.
79+
// Load the class and register the signer.
80+
Class<? extends Signer> clazz = null;
81+
try {
82+
clazz = (Class<? extends Signer>) conf.getClassByName(signerClassName);
83+
} catch (ClassNotFoundException cnfe) {
84+
throw new RuntimeException(String
85+
.format("Signer class [%s] not found for signer [%s]",
86+
signerClassName, signerName), cnfe);
87+
}
88+
LOG.debug("Registering Custom Signer - [{}->{}]", signerName,
89+
clazz.getName());
90+
synchronized (SignerManager.class) {
91+
SignerFactory.registerSigner(signerName, clazz);
92+
}
93+
}
94+
}
95+
96+
@Override
97+
public void close() throws IOException {
98+
}
99+
}

hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/auth/STSClientFactory.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,14 @@
3131
import com.amazonaws.services.securitytoken.model.Credentials;
3232
import com.amazonaws.services.securitytoken.model.GetSessionTokenRequest;
3333
import com.google.common.base.Preconditions;
34+
3435
import org.slf4j.Logger;
3536
import org.slf4j.LoggerFactory;
3637

3738
import org.apache.hadoop.classification.InterfaceAudience;
3839
import org.apache.hadoop.classification.InterfaceStability;
3940
import org.apache.hadoop.conf.Configuration;
41+
import org.apache.hadoop.fs.s3a.Constants;
4042
import org.apache.hadoop.fs.s3a.Invoker;
4143
import org.apache.hadoop.fs.s3a.Retries;
4244
import org.apache.hadoop.fs.s3a.S3AUtils;
@@ -73,7 +75,8 @@ public static AWSSecurityTokenServiceClientBuilder builder(
7375
final Configuration conf,
7476
final String bucket,
7577
final AWSCredentialsProvider credentials) throws IOException {
76-
final ClientConfiguration awsConf = S3AUtils.createAwsConf(conf, bucket);
78+
final ClientConfiguration awsConf = S3AUtils.createAwsConf(conf, bucket,
79+
Constants.AWS_SERVICE_IDENTIFIER_STS);
7780
String endpoint = conf.getTrimmed(DELEGATION_TOKEN_ENDPOINT,
7881
DEFAULT_DELEGATION_TOKEN_ENDPOINT);
7982
String region = conf.getTrimmed(DELEGATION_TOKEN_REGION,
@@ -99,7 +102,8 @@ public static AWSSecurityTokenServiceClientBuilder builder(
99102
final AWSCredentialsProvider credentials,
100103
final String stsEndpoint,
101104
final String stsRegion) throws IOException {
102-
final ClientConfiguration awsConf = S3AUtils.createAwsConf(conf, bucket);
105+
final ClientConfiguration awsConf = S3AUtils.createAwsConf(conf, bucket,
106+
Constants.AWS_SERVICE_IDENTIFIER_STS);
103107
return builder(credentials, awsConf, stsEndpoint, stsRegion);
104108
}
105109

hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/auth/delegation/SessionTokenBinding.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636

3737
import org.apache.hadoop.conf.Configuration;
3838
import org.apache.hadoop.fs.s3a.AWSCredentialProviderList;
39+
import org.apache.hadoop.fs.s3a.Constants;
3940
import org.apache.hadoop.fs.s3a.Invoker;
4041
import org.apache.hadoop.fs.s3a.Retries;
4142
import org.apache.hadoop.fs.s3a.S3ARetryPolicy;
@@ -301,7 +302,8 @@ private synchronized Optional<STSClientFactory.STSClient> maybeInitSTS()
301302

302303
invoker = new Invoker(new S3ARetryPolicy(conf), LOG_EVENT);
303304
ClientConfiguration awsConf =
304-
S3AUtils.createAwsConf(conf, uri.getHost());
305+
S3AUtils.createAwsConf(conf, uri.getHost(),
306+
Constants.AWS_SERVICE_IDENTIFIER_STS);
305307
AWSSecurityTokenService tokenService =
306308
STSClientFactory.builder(parentAuthChain,
307309
awsConf,

hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/s3guard/DynamoDBClientFactory.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.apache.hadoop.conf.Configurable;
3535
import org.apache.hadoop.conf.Configuration;
3636
import org.apache.hadoop.conf.Configured;
37+
import org.apache.hadoop.fs.s3a.Constants;
3738
import org.apache.hadoop.fs.s3a.S3AUtils;
3839

3940
import static org.apache.hadoop.fs.s3a.Constants.S3GUARD_DDB_REGION_KEY;
@@ -80,7 +81,8 @@ public AmazonDynamoDB createDynamoDBClient(String defaultRegion,
8081
"Should have been configured before usage");
8182

8283
final Configuration conf = getConf();
83-
final ClientConfiguration awsConf = S3AUtils.createAwsConf(conf, bucket);
84+
final ClientConfiguration awsConf = S3AUtils
85+
.createAwsConf(conf, bucket, Constants.AWS_SERVICE_IDENTIFIER_DDB);
8486

8587
final String region = getRegion(conf, defaultRegion);
8688
LOG.debug("Creating DynamoDB client in region {}", region);

0 commit comments

Comments
 (0)