Skip to content

Commit 93c787b

Browse files
authored
HADOOP-18516: [ABFS][Authentication] Support Fixed SAS Token for ABFS Authentication (#6552)
Contributed by Anuj Modi
1 parent 59b2980 commit 93c787b

File tree

14 files changed

+611
-72
lines changed

14 files changed

+611
-72
lines changed

hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/AbfsConfiguration.java

Lines changed: 53 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.lang.reflect.Field;
2323

2424
import org.apache.hadoop.classification.VisibleForTesting;
25+
import org.apache.hadoop.fs.azurebfs.services.FixedSASTokenProvider;
2526
import org.apache.hadoop.util.Preconditions;
2627

2728
import org.apache.commons.lang3.StringUtils;
@@ -980,33 +981,63 @@ public AccessTokenProvider getTokenProvider() throws TokenAccessProviderExceptio
980981
}
981982
}
982983

984+
/**
985+
* Returns the SASTokenProvider implementation to be used to generate SAS token.<br>
986+
* Users can choose between a custom implementation of {@link SASTokenProvider}
987+
* or an in house implementation {@link FixedSASTokenProvider}.<br>
988+
* For Custom implementation "fs.azure.sas.token.provider.type" needs to be provided.<br>
989+
* For Fixed SAS Token use "fs.azure.sas.fixed.token" needs to be provided.<br>
990+
* In case both are provided, Preference will be given to Custom implementation.<br>
991+
* Avoid using a custom tokenProvider implementation just to read the configured
992+
* fixed token, as this could create confusion. Also,implementing the SASTokenProvider
993+
* requires relying on the raw configurations. It is more stable to depend on
994+
* the AbfsConfiguration with which a filesystem is initialized, and eliminate
995+
* chances of dynamic modifications and spurious situations.<br>
996+
* @return sasTokenProvider object based on configurations provided
997+
* @throws AzureBlobFileSystemException
998+
*/
983999
public SASTokenProvider getSASTokenProvider() throws AzureBlobFileSystemException {
9841000
AuthType authType = getEnum(FS_AZURE_ACCOUNT_AUTH_TYPE_PROPERTY_NAME, AuthType.SharedKey);
9851001
if (authType != AuthType.SAS) {
9861002
throw new SASTokenProviderException(String.format(
987-
"Invalid auth type: %s is being used, expecting SAS", authType));
1003+
"Invalid auth type: %s is being used, expecting SAS.", authType));
9881004
}
9891005

9901006
try {
991-
String configKey = FS_AZURE_SAS_TOKEN_PROVIDER_TYPE;
992-
Class<? extends SASTokenProvider> sasTokenProviderClass =
993-
getTokenProviderClass(authType, configKey, null,
994-
SASTokenProvider.class);
995-
996-
Preconditions.checkArgument(sasTokenProviderClass != null,
997-
String.format("The configuration value for \"%s\" is invalid.", configKey));
998-
999-
SASTokenProvider sasTokenProvider = ReflectionUtils
1000-
.newInstance(sasTokenProviderClass, rawConfig);
1001-
Preconditions.checkArgument(sasTokenProvider != null,
1002-
String.format("Failed to initialize %s", sasTokenProviderClass));
1003-
1004-
LOG.trace("Initializing {}", sasTokenProviderClass.getName());
1005-
sasTokenProvider.initialize(rawConfig, accountName);
1006-
LOG.trace("{} init complete", sasTokenProviderClass.getName());
1007-
return sasTokenProvider;
1007+
Class<? extends SASTokenProvider> customSasTokenProviderImplementation =
1008+
getTokenProviderClass(authType, FS_AZURE_SAS_TOKEN_PROVIDER_TYPE,
1009+
null, SASTokenProvider.class);
1010+
String configuredFixedToken = this.getTrimmedPasswordString(FS_AZURE_SAS_FIXED_TOKEN, EMPTY_STRING);
1011+
1012+
if (customSasTokenProviderImplementation == null && configuredFixedToken.isEmpty()) {
1013+
throw new SASTokenProviderException(String.format(
1014+
"At least one of the \"%s\" and \"%s\" must be set.",
1015+
FS_AZURE_SAS_TOKEN_PROVIDER_TYPE, FS_AZURE_SAS_FIXED_TOKEN));
1016+
}
1017+
1018+
// Prefer Custom SASTokenProvider Implementation if configured.
1019+
if (customSasTokenProviderImplementation != null) {
1020+
LOG.trace("Using Custom SASTokenProvider implementation because it is given precedence when it is set.");
1021+
SASTokenProvider sasTokenProvider = ReflectionUtils.newInstance(
1022+
customSasTokenProviderImplementation, rawConfig);
1023+
if (sasTokenProvider == null) {
1024+
throw new SASTokenProviderException(String.format(
1025+
"Failed to initialize %s", customSasTokenProviderImplementation));
1026+
}
1027+
LOG.trace("Initializing {}", customSasTokenProviderImplementation.getName());
1028+
sasTokenProvider.initialize(rawConfig, accountName);
1029+
LOG.trace("{} init complete", customSasTokenProviderImplementation.getName());
1030+
return sasTokenProvider;
1031+
} else {
1032+
LOG.trace("Using FixedSASTokenProvider implementation");
1033+
FixedSASTokenProvider fixedSASTokenProvider = new FixedSASTokenProvider(configuredFixedToken);
1034+
return fixedSASTokenProvider;
1035+
}
1036+
} catch (SASTokenProviderException e) {
1037+
throw e;
10081038
} catch (Exception e) {
1009-
throw new TokenAccessProviderException("Unable to load SAS token provider class: " + e, e);
1039+
throw new SASTokenProviderException(
1040+
"Unable to load SAS token provider class: " + e, e);
10101041
}
10111042
}
10121043

@@ -1019,14 +1050,14 @@ public EncryptionContextProvider createEncryptionContextProvider() {
10191050
Class<? extends EncryptionContextProvider> encryptionContextClass =
10201051
getAccountSpecificClass(configKey, null,
10211052
EncryptionContextProvider.class);
1022-
Preconditions.checkArgument(encryptionContextClass != null, String.format(
1053+
Preconditions.checkArgument(encryptionContextClass != null,
10231054
"The configuration value for %s is invalid, or config key is not account-specific",
1024-
configKey));
1055+
configKey);
10251056

10261057
EncryptionContextProvider encryptionContextProvider =
10271058
ReflectionUtils.newInstance(encryptionContextClass, rawConfig);
10281059
Preconditions.checkArgument(encryptionContextProvider != null,
1029-
String.format("Failed to initialize %s", encryptionContextClass));
1060+
"Failed to initialize %s", encryptionContextClass);
10301061

10311062
LOG.trace("{} init complete", encryptionContextClass.getName());
10321063
return encryptionContextProvider;

hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/AzureBlobFileSystem.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1292,10 +1292,9 @@ public void access(final Path path, final FsAction mode) throws IOException {
12921292

12931293
/**
12941294
* Incrementing exists() calls from superclass for statistic collection.
1295-
*
12961295
* @param f source path.
12971296
* @return true if the path exists.
1298-
* @throws IOException
1297+
* @throws IOException if some issue in checking path.
12991298
*/
13001299
@Override
13011300
public boolean exists(Path f) throws IOException {

hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/AzureBlobFileSystemStore.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1729,7 +1729,7 @@ private void initializeClient(URI uri, String fileSystemName,
17291729
creds = new SharedKeyCredentials(accountName.substring(0, dotIndex),
17301730
abfsConfiguration.getStorageAccountKey());
17311731
} else if (authType == AuthType.SAS) {
1732-
LOG.trace("Fetching SAS token provider");
1732+
LOG.trace("Fetching SAS Token Provider");
17331733
sasTokenProvider = abfsConfiguration.getSASTokenProvider();
17341734
} else {
17351735
LOG.trace("Fetching token provider");

hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/constants/ConfigurationKeys.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,10 @@ public static String accountProperty(String property, String account) {
290290
public static final String FS_AZURE_ENABLE_DELEGATION_TOKEN = "fs.azure.enable.delegation.token";
291291
public static final String FS_AZURE_DELEGATION_TOKEN_PROVIDER_TYPE = "fs.azure.delegation.token.provider.type";
292292

293-
/** Key for SAS token provider **/
293+
/** Key for fixed SAS token: {@value}. **/
294+
public static final String FS_AZURE_SAS_FIXED_TOKEN = "fs.azure.sas.fixed.token";
295+
296+
/** Key for SAS token provider: {@value}. **/
294297
public static final String FS_AZURE_SAS_TOKEN_PROVIDER_TYPE = "fs.azure.sas.token.provider.type";
295298

296299
/** For performance, AbfsInputStream/AbfsOutputStream re-use SAS tokens until the expiry is within this number of seconds. **/

hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/services/AbfsClient.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1011,6 +1011,7 @@ public AbfsRestOperation flush(final String path, final long position,
10111011
abfsUriQueryBuilder.addQuery(QUERY_PARAM_POSITION, Long.toString(position));
10121012
abfsUriQueryBuilder.addQuery(QUERY_PARAM_RETAIN_UNCOMMITTED_DATA, String.valueOf(retainUncommittedData));
10131013
abfsUriQueryBuilder.addQuery(QUERY_PARAM_CLOSE, String.valueOf(isClose));
1014+
10141015
// AbfsInputStream/AbfsOutputStream reuse SAS tokens for better performance
10151016
String sasTokenForReuse = appendSASTokenToQuery(path, SASTokenProvider.WRITE_OPERATION,
10161017
abfsUriQueryBuilder, cachedSasToken);
@@ -1107,6 +1108,7 @@ public AbfsRestOperation read(final String path,
11071108
}
11081109

11091110
final AbfsUriQueryBuilder abfsUriQueryBuilder = createDefaultUriQueryBuilder();
1111+
11101112
// AbfsInputStream/AbfsOutputStream reuse SAS tokens for better performance
11111113
String sasTokenForReuse = appendSASTokenToQuery(path, SASTokenProvider.READ_OPERATION,
11121114
abfsUriQueryBuilder, cachedSasToken);
@@ -1419,16 +1421,17 @@ private String appendSASTokenToQuery(String path,
14191421
sasToken = cachedSasToken;
14201422
LOG.trace("Using cached SAS token.");
14211423
}
1424+
14221425
// if SAS Token contains a prefix of ?, it should be removed
14231426
if (sasToken.charAt(0) == '?') {
14241427
sasToken = sasToken.substring(1);
14251428
}
1429+
14261430
queryBuilder.setSASToken(sasToken);
14271431
LOG.trace("SAS token fetch complete for {} on {}", operation, path);
14281432
} catch (Exception ex) {
1429-
throw new SASTokenProviderException(String.format("Failed to acquire a SAS token for %s on %s due to %s",
1430-
operation,
1431-
path,
1433+
throw new SASTokenProviderException(String.format(
1434+
"Failed to acquire a SAS token for %s on %s due to %s", operation, path,
14321435
ex.toString()));
14331436
}
14341437
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
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+
19+
package org.apache.hadoop.fs.azurebfs.services;
20+
21+
import java.io.IOException;
22+
23+
import org.apache.hadoop.conf.Configuration;
24+
import org.apache.hadoop.fs.azurebfs.contracts.exceptions.SASTokenProviderException;
25+
import org.apache.hadoop.fs.azurebfs.extensions.SASTokenProvider;
26+
27+
/**
28+
* In house implementation of {@link SASTokenProvider} to use a fixed SAS token with ABFS.
29+
* Use this to avoid implementing a Custom Token Provider just to return fixed SAS.
30+
* Fixed SAS Token to be provided using the config "fs.azure.sas.fixed.token".
31+
*/
32+
public class FixedSASTokenProvider implements SASTokenProvider {
33+
private String fixedSASToken;
34+
35+
public FixedSASTokenProvider(final String fixedSASToken) throws SASTokenProviderException {
36+
this.fixedSASToken = fixedSASToken;
37+
if (fixedSASToken == null || fixedSASToken.isEmpty()) {
38+
throw new SASTokenProviderException(
39+
String.format("Configured Fixed SAS Token is Invalid: %s", fixedSASToken));
40+
}
41+
}
42+
43+
@Override
44+
public void initialize(final Configuration configuration,
45+
final String accountName)
46+
throws IOException {
47+
}
48+
49+
/**
50+
* Returns the fixed SAS Token configured.
51+
* @param account the name of the storage account.
52+
* @param fileSystem the name of the fileSystem.
53+
* @param path the file or directory path.
54+
* @param operation the operation to be performed on the path.
55+
* @return Fixed SAS Token
56+
* @throws IOException never
57+
*/
58+
@Override
59+
public String getSASToken(final String account,
60+
final String fileSystem,
61+
final String path,
62+
final String operation) throws IOException {
63+
return fixedSASToken;
64+
}
65+
}

0 commit comments

Comments
 (0)