Skip to content

Commit 8d353d5

Browse files
authored
HADOOP-19471. [ABFS] Support Fixed SAS Token at Container Level (apache#7461) (apache#7597)
Contributed by Manika Joshi Reviewed by Anuj Modi, Anmol Asrani, Manish Bhatt Signed off by Anuj Modi <anujmodi@apache.org>
1 parent a6b2a21 commit 8d353d5

File tree

5 files changed

+148
-9
lines changed

5 files changed

+148
-9
lines changed

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

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
import org.apache.hadoop.util.ReflectionUtils;
7676

7777
import static org.apache.hadoop.fs.FileSystem.FS_DEFAULT_NAME_KEY;
78+
import static org.apache.hadoop.fs.azurebfs.constants.AbfsHttpConstants.DOT;
7879
import static org.apache.hadoop.fs.azurebfs.constants.AbfsHttpConstants.EMPTY_STRING;
7980
import static org.apache.hadoop.fs.azurebfs.constants.ConfigurationKeys.*;
8081
import static org.apache.hadoop.fs.azurebfs.constants.FileSystemConfigurations.*;
@@ -89,6 +90,7 @@ public class AbfsConfiguration{
8990

9091
private final Configuration rawConfig;
9192
private final String accountName;
93+
private String fsName;
9294
// Service type identified from URL used to initialize FileSystem.
9395
private final AbfsServiceType fsConfiguredServiceType;
9496
private final boolean isSecure;
@@ -485,6 +487,24 @@ public AbfsConfiguration(final Configuration rawConfig,
485487
}
486488
}
487489

490+
/**
491+
* Constructor for AbfsConfiguration for retrieve the FsName.
492+
* @param rawConfig used to initialize the configuration.
493+
* @param accountName the name of the azure storage account.
494+
* @param fsName the name of the file system (container name).
495+
* @param fsConfiguredServiceType service type configured for the file system.
496+
* @throws IllegalAccessException if the field is not accessible.
497+
* @throws IOException if an I/O error occurs.
498+
*/
499+
public AbfsConfiguration(final Configuration rawConfig,
500+
String accountName,
501+
String fsName,
502+
AbfsServiceType fsConfiguredServiceType)
503+
throws IllegalAccessException, IOException {
504+
this(rawConfig, accountName, fsConfiguredServiceType);
505+
this.fsName = fsName;
506+
}
507+
488508
/**
489509
* Constructor for AbfsConfiguration for default service type i.e. DFS.
490510
* @param rawConfig used to initialize the configuration.
@@ -584,7 +604,17 @@ public String getClientCorrelationId() {
584604
* @return Account-specific configuration key
585605
*/
586606
public String accountConf(String key) {
587-
return key + "." + accountName;
607+
return key + DOT + accountName;
608+
}
609+
610+
/**
611+
* Appends the container, account name to a configuration key yielding the
612+
* container-specific form.
613+
* @param key Account-agnostic configuration key
614+
* @return Container-specific configuration key
615+
*/
616+
public String containerConf(String key) {
617+
return key + DOT + fsName + DOT + accountName;
588618
}
589619

590620
/**
@@ -642,17 +672,19 @@ public int getInt(String key, int defaultValue) {
642672
}
643673

644674
/**
645-
* Returns the account-specific password in string form if it exists, then
675+
* Returns the container-specific password if it exists,
676+
* else searches for the account-specific password, else finally
646677
* looks for an account-agnostic value.
647678
* @param key Account-agnostic configuration key
648679
* @return value in String form if one exists, else null
649680
* @throws IOException if parsing fails.
650681
*/
651682
public String getPasswordString(String key) throws IOException {
652-
char[] passchars = rawConfig.getPassword(accountConf(key));
653-
if (passchars == null) {
654-
passchars = rawConfig.getPassword(key);
655-
}
683+
char[] passchars = rawConfig.getPassword(containerConf(key)) != null
684+
? rawConfig.getPassword(containerConf(key))
685+
: rawConfig.getPassword(accountConf(key)) != null
686+
? rawConfig.getPassword(accountConf(key))
687+
: rawConfig.getPassword(key);
656688
if (passchars != null) {
657689
return new String(passchars);
658690
}

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
@@ -227,7 +227,7 @@ public AzureBlobFileSystemStore(
227227

228228
try {
229229
this.abfsConfiguration = new AbfsConfiguration(abfsStoreBuilder.configuration,
230-
accountName, getAbfsServiceTypeFromUrl());
230+
accountName, fileSystemName, getAbfsServiceTypeFromUrl());
231231
} catch (IllegalAccessException exception) {
232232
throw new FileSystemOperationUnhandledException(exception);
233233
}

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import org.apache.hadoop.classification.InterfaceStability;
2323
import org.apache.hadoop.fs.FileSystem;
2424

25+
import static org.apache.hadoop.fs.azurebfs.constants.AbfsHttpConstants.DOT;
26+
2527
/**
2628
* Responsible to keep all the Azure Blob File System configurations keys in Hadoop configuration file.
2729
*/
@@ -319,7 +321,11 @@ public final class ConfigurationKeys {
319321
public static final String FS_AZURE_ABFS_ENABLE_CHECKSUM_VALIDATION = "fs.azure.enable.checksum.validation";
320322

321323
public static String accountProperty(String property, String account) {
322-
return property + "." + account;
324+
return property + DOT + account;
325+
}
326+
327+
public static String containerProperty(String property, String fsName, String account) {
328+
return property + DOT + fsName + DOT + account;
323329
}
324330

325331
public static final String FS_AZURE_ENABLE_DELEGATION_TOKEN = "fs.azure.enable.delegation.token";

hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azurebfs/ITestAzureBlobFileSystemChooseSAS.java

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,14 @@
3232
import org.apache.hadoop.fs.azurebfs.services.AuthType;
3333
import org.apache.hadoop.fs.azurebfs.services.FixedSASTokenProvider;
3434
import org.apache.hadoop.fs.azurebfs.utils.AccountSASGenerator;
35+
import org.apache.hadoop.fs.azurebfs.utils.ServiceSASGenerator;
3536
import org.apache.hadoop.fs.azurebfs.utils.Base64;
3637

38+
import static org.apache.hadoop.fs.azurebfs.constants.AbfsHttpConstants.EMPTY_STRING;
3739
import static org.apache.hadoop.fs.azurebfs.constants.ConfigurationKeys.FS_AZURE_SAS_FIXED_TOKEN;
3840
import static org.apache.hadoop.fs.azurebfs.constants.ConfigurationKeys.FS_AZURE_SAS_TOKEN_PROVIDER_TYPE;
3941
import static org.apache.hadoop.fs.azurebfs.constants.ConfigurationKeys.accountProperty;
42+
import static org.apache.hadoop.fs.azurebfs.constants.ConfigurationKeys.containerProperty;
4043
import static org.apache.hadoop.fs.azurebfs.constants.TestConfigurationKeys.FS_AZURE_TEST_APP_ID;
4144
import static org.apache.hadoop.fs.azurebfs.constants.TestConfigurationKeys.FS_AZURE_TEST_APP_SECRET;
4245
import static org.apache.hadoop.fs.azurebfs.constants.TestConfigurationKeys.FS_AZURE_TEST_APP_SERVICE_PRINCIPAL_OBJECT_ID;
@@ -50,6 +53,8 @@
5053
public class ITestAzureBlobFileSystemChooseSAS extends AbstractAbfsIntegrationTest{
5154

5255
private String accountSAS = null;
56+
private String containerSAS = null;
57+
private String accountAgnosticSAS = null;
5358
private static final String TEST_PATH = "testPath";
5459

5560
/**
@@ -69,6 +74,8 @@ public void setup() throws Exception {
6974
super.setup();
7075
createFilesystemWithTestFileForSASTests(new Path(TEST_PATH));
7176
generateAccountSAS();
77+
generateAccountAgnosticSAS();
78+
generateContainerSAS();
7279
}
7380

7481
/**
@@ -85,6 +92,37 @@ private void generateAccountSAS() throws AzureBlobFileSystemException {
8592
accountSAS = configAccountSASGenerator.getAccountSAS(getAccountName());
8693
}
8794

95+
/**
96+
* Generates an Account SAS Token (for account-agnostic config) using the Account Shared Key to
97+
* be used as a fixed SAS Token.
98+
* Account SAS used here will only have write permissions to resources.
99+
* This will be used by individual tests to set in the configurations.
100+
* @throws AzureBlobFileSystemException
101+
*/
102+
private void generateAccountAgnosticSAS() throws AzureBlobFileSystemException {
103+
final String accountKey = getConfiguration().getStorageAccountKey();
104+
AccountSASGenerator configAccountSASGenerator = new AccountSASGenerator(Base64.decode(accountKey));
105+
// Setting only write permissions.
106+
configAccountSASGenerator.setPermissions("w");
107+
accountAgnosticSAS = configAccountSASGenerator.getAccountSAS(getAccountName());
108+
}
109+
110+
/**
111+
* Generates a Container SAS Token using the Account Shared Key to be used as a fixed SAS Token.
112+
* Container SAS used here will have only read permissions to resources.
113+
* This will be used by individual tests to set in the configurations.
114+
* @throws AzureBlobFileSystemException
115+
*/
116+
private void generateContainerSAS() throws AzureBlobFileSystemException {
117+
final byte[] accountKey = Base64.decode(
118+
getConfiguration().getStorageAccountKey());
119+
ServiceSASGenerator configServiceSASGenerator = new ServiceSASGenerator(
120+
accountKey);
121+
// Setting only read permissions.
122+
configServiceSASGenerator.setPermissions("r");
123+
containerSAS = configServiceSASGenerator.getContainerSASWithFullControl(
124+
getAccountName(), getFileSystemName());
125+
}
88126
/**
89127
* Tests the scenario where both the custom SASTokenProvider and a fixed SAS token are configured.
90128
* Custom implementation of SASTokenProvider class should be chosen and User Delegation SAS should be used.
@@ -126,6 +164,58 @@ public void testBothProviderFixedTokenConfigured() throws Exception {
126164
}
127165
}
128166

167+
/**
168+
* Helper method to get the Fixed SAS token value
169+
*/
170+
private String getFixedSASToken(AbfsConfiguration config) throws Exception {
171+
return config.getSASTokenProvider()
172+
.getSASToken(this.getAccountName(), this.getFileSystemName(),
173+
getMethodName(),
174+
EMPTY_STRING);
175+
}
176+
177+
/**
178+
* Tests the implementation sequence if all fixed SAS configs are set.
179+
* The expected sequence is Container Specific Fixed SAS, Account Specific Fixed SAS, Account Agnostic Fixed SAS.
180+
* @throws IOException
181+
*/
182+
@Test
183+
public void testFixedSASTokenProviderPreference() throws Exception {
184+
AbfsConfiguration testAbfsConfig = new AbfsConfiguration(
185+
getRawConfiguration(), this.getAccountName(), this.getFileSystemName(),
186+
getAbfsServiceType());
187+
188+
// setting all types of Fixed SAS configs (container-specific, account-specific, account-agnostic)
189+
removeAnyPresetConfiguration(testAbfsConfig);
190+
testAbfsConfig.set(
191+
containerProperty(FS_AZURE_SAS_FIXED_TOKEN, this.getFileSystemName(),
192+
this.getAccountName()), containerSAS);
193+
testAbfsConfig.set(
194+
accountProperty(FS_AZURE_SAS_FIXED_TOKEN, this.getAccountName()),
195+
accountSAS);
196+
testAbfsConfig.set(FS_AZURE_SAS_FIXED_TOKEN, accountAgnosticSAS);
197+
198+
// Assert that Container Specific Fixed SAS is used
199+
Assertions.assertThat(getFixedSASToken(testAbfsConfig))
200+
.describedAs("Container-specific fixed SAS should've been used.")
201+
.isEqualTo(containerSAS);
202+
203+
// Assert that Account Specific Fixed SAS is used if container SAS isn't set
204+
testAbfsConfig.unset(
205+
containerProperty(FS_AZURE_SAS_FIXED_TOKEN, this.getFileSystemName(),
206+
this.getAccountName()));
207+
Assertions.assertThat(getFixedSASToken(testAbfsConfig))
208+
.describedAs("Account-specific fixed SAS should've been used.")
209+
.isEqualTo(accountSAS);
210+
211+
//Assert that Account-Agnostic fixed SAS is used if no other fixed SAS configs are set.
212+
testAbfsConfig.unset(
213+
accountProperty(FS_AZURE_SAS_FIXED_TOKEN, this.getAccountName()));
214+
Assertions.assertThat(getFixedSASToken(testAbfsConfig))
215+
.describedAs("Account-agnostic fixed SAS should've been used.")
216+
.isEqualTo(accountAgnosticSAS);
217+
}
218+
129219
/**
130220
* Tests the scenario where only the fixed token is configured, and no token provider class is set.
131221
* Account SAS Token configured as fixed SAS should be used.
@@ -189,5 +279,6 @@ private void removeAnyPresetConfiguration(AbfsConfiguration testAbfsConfig) {
189279
testAbfsConfig.unset(FS_AZURE_SAS_FIXED_TOKEN);
190280
testAbfsConfig.unset(accountProperty(FS_AZURE_SAS_TOKEN_PROVIDER_TYPE, this.getAccountName()));
191281
testAbfsConfig.unset(accountProperty(FS_AZURE_SAS_FIXED_TOKEN, this.getAccountName()));
282+
testAbfsConfig.unset(containerProperty(FS_AZURE_SAS_FIXED_TOKEN, this.getFileSystemName(), this.getAccountName()));
192283
}
193284
}

hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azurebfs/utils/ServiceSASGenerator.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,11 @@ public ServiceSASGenerator(byte[] accountKey) {
3737
super(accountKey);
3838
}
3939

40+
private String permissions = "racwdl";
4041
public String getContainerSASWithFullControl(String accountName, String containerName) throws
4142
InvalidConfigurationValueException {
4243
accountName = getCanonicalAccountName(accountName);
43-
String sp = "rcwdl";
44+
String sp = permissions;
4445
String sv = AuthenticationVersion.Feb20.toString();
4546
String sr = "c";
4647
String st = ISO_8601_FORMATTER.format(Instant.now().minus(FIVE_MINUTES));
@@ -96,4 +97,13 @@ private String computeSignatureForSAS(String sp, String st, String se, String sv
9697
LOG.debug("Service SAS stringToSign: " + stringToSign.replace("\n", "."));
9798
return computeHmac256(stringToSign);
9899
}
100+
101+
/**
102+
* By default, Container SAS has all the available permissions. Use this to
103+
* override the default permissions and set as per the requirements.
104+
* @param permissions
105+
*/
106+
public void setPermissions(final String permissions) {
107+
this.permissions = permissions;
108+
}
99109
}

0 commit comments

Comments
 (0)