Skip to content

HADOOP-19406. ABFS: [FNSOverBlob] Support User Delegation SAS for FNS Blob #7523

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Apr 24, 2025
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

package org.apache.hadoop.fs.azurebfs;

import javax.annotation.Nullable;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
Expand All @@ -41,7 +42,6 @@
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import javax.annotation.Nullable;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -118,8 +118,24 @@
import static org.apache.hadoop.fs.CommonConfigurationKeys.IOSTATISTICS_LOGGING_LEVEL;
import static org.apache.hadoop.fs.CommonConfigurationKeys.IOSTATISTICS_LOGGING_LEVEL_DEFAULT;
import static org.apache.hadoop.fs.Options.OpenFileOptions.FS_OPTION_OPENFILE_STANDARD_OPTIONS;
import static org.apache.hadoop.fs.azurebfs.AbfsStatistic.*;
import static org.apache.hadoop.fs.azurebfs.AbfsStatistic.CALL_APPEND;
import static org.apache.hadoop.fs.azurebfs.AbfsStatistic.CALL_CREATE;
import static org.apache.hadoop.fs.azurebfs.AbfsStatistic.CALL_CREATE_NON_RECURSIVE;
import static org.apache.hadoop.fs.azurebfs.AbfsStatistic.CALL_DELETE;
import static org.apache.hadoop.fs.azurebfs.AbfsStatistic.CALL_EXIST;
import static org.apache.hadoop.fs.azurebfs.AbfsStatistic.CALL_GET_DELEGATION_TOKEN;
import static org.apache.hadoop.fs.azurebfs.AbfsStatistic.CALL_GET_FILE_STATUS;
import static org.apache.hadoop.fs.azurebfs.AbfsStatistic.CALL_LIST_STATUS;
import static org.apache.hadoop.fs.azurebfs.AbfsStatistic.CALL_MKDIRS;
import static org.apache.hadoop.fs.azurebfs.AbfsStatistic.CALL_OPEN;
import static org.apache.hadoop.fs.azurebfs.AbfsStatistic.CALL_RENAME;
import static org.apache.hadoop.fs.azurebfs.AbfsStatistic.DIRECTORIES_CREATED;
import static org.apache.hadoop.fs.azurebfs.AbfsStatistic.DIRECTORIES_DELETED;
import static org.apache.hadoop.fs.azurebfs.AbfsStatistic.ERROR_IGNORED;
import static org.apache.hadoop.fs.azurebfs.AbfsStatistic.FILES_CREATED;
import static org.apache.hadoop.fs.azurebfs.AbfsStatistic.FILES_DELETED;
import static org.apache.hadoop.fs.azurebfs.constants.AbfsHttpConstants.CPK_IN_NON_HNS_ACCOUNT_ERROR_MESSAGE;
import static org.apache.hadoop.fs.azurebfs.constants.AbfsServiceType.DFS;
import static org.apache.hadoop.fs.azurebfs.constants.ConfigurationKeys.DATA_BLOCKS_BUFFER;
import static org.apache.hadoop.fs.azurebfs.constants.ConfigurationKeys.FS_AZURE_ACCOUNT_IS_HNS_ENABLED;
import static org.apache.hadoop.fs.azurebfs.constants.ConfigurationKeys.FS_AZURE_BLOCK_UPLOAD_ACTIVE_BLOCKS;
Expand Down Expand Up @@ -240,16 +256,18 @@ public void initialize(URI uri, Configuration configuration)

/*
* Validates if the correct SAS Token provider is configured for non-HNS accounts.
* For non-HNS accounts, if the authentication type is set to SAS, only a fixed SAS Token is supported as of now.
* A custom SAS Token Provider should not be configured in such cases, as it will override the FixedSASTokenProvider and render it unused.
* If the namespace is not enabled and the FixedSASTokenProvider is not configured,
* For non-HNS accounts with Blob endpoint, both fixed SAS Token and custom SAS Token provider are supported.
* For non-HNS accounts with DFS endpoint, if the authentication type is set to SAS, only fixed SAS Token is supported as of now.
* A custom SAS Token Provider should not be configured in this case as it will override the FixedSASTokenProvider and render it unused.
* If the namespace is not enabled and the FixedSASTokenProvider is not configured for non-HNS accounts with DFS endpoint,
* an InvalidConfigurationValueException will be thrown.
*
* @throws InvalidConfigurationValueException if account is not namespace enabled and FixedSASTokenProvider is not configured.
*/
try {
if (abfsConfiguration.getAuthType(abfsConfiguration.getAccountName()) == AuthType.SAS && // Auth type is SAS
!tryGetIsNamespaceEnabled(new TracingContext(initFSTracingContext)) && // Account is FNS
abfsConfiguration.getFsConfiguredServiceType() == DFS && // Service type is DFS
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the constant for DFS

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is using the AbfsServiceType.DFS constant here

!abfsConfiguration.isFixedSASTokenProviderConfigured()) { // Fixed SAS Token Provider is not configured
throw new InvalidConfigurationValueException(FS_AZURE_SAS_FIXED_TOKEN, UNAUTHORIZED_SAS);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,18 @@
public interface SASTokenProvider {

String CHECK_ACCESS_OPERATION = "check-access";
String COPY_BLOB_DST_OPERATION = "copy-blob-dst";
String COPY_BLOB_SRC_OPERATION = "copy-blob-src";
String CREATE_DIRECTORY_OPERATION = "create-directory";
String CREATE_FILE_OPERATION = "create-file";
String DELETE_OPERATION = "delete";
String DELETE_RECURSIVE_OPERATION = "delete-recursive";
String GET_ACL_OPERATION = "get-acl";
String GET_STATUS_OPERATION = "get-status";
String GET_PROPERTIES_OPERATION = "get-properties";
String LEASE_BLOB_OPERATION = "lease-blob";
String LIST_OPERATION = "list";
String LIST_OPERATION_BLOB = "list-blob";
String READ_OPERATION = "read";
String RENAME_SOURCE_OPERATION = "rename-source";
String RENAME_DESTINATION_OPERATION = "rename-destination";
Expand All @@ -49,8 +53,6 @@ public interface SASTokenProvider {
String SET_PERMISSION_OPERATION = "set-permission";
String SET_PROPERTIES_OPERATION = "set-properties";
String WRITE_OPERATION = "write";
// Generic HTTP operation can be used with FixedSASTokenProvider.
String FIXED_SAS_STORE_OPERATION = "fixed-sas";

/**
* Initialize authorizer for Azure Blob File System.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ public ListResponseData listPath(final String relativePath, final boolean recurs
abfsUriQueryBuilder.addQuery(QUERY_PARAM_DELIMITER, FORWARD_SLASH);
}
abfsUriQueryBuilder.addQuery(QUERY_PARAM_MAX_RESULTS, String.valueOf(listMaxResults));
appendSASTokenToQuery(relativePath, SASTokenProvider.FIXED_SAS_STORE_OPERATION, abfsUriQueryBuilder);
appendSASTokenToQuery(relativePath, SASTokenProvider.LIST_OPERATION_BLOB, abfsUriQueryBuilder);

final URL url = createRequestUrl(abfsUriQueryBuilder.toString());
final AbfsRestOperation op = getAbfsRestOperation(
Expand Down Expand Up @@ -544,11 +544,14 @@ public AbfsRestOperation createPathRestOp(final String path,
final ContextEncryptionAdapter contextEncryptionAdapter,
final TracingContext tracingContext) throws AzureBlobFileSystemException {
final List<AbfsHttpHeader> requestHeaders = createDefaultHeaders();
final AbfsUriQueryBuilder abfsUriQueryBuilder = createDefaultUriQueryBuilder();
if (isFile) {
addEncryptionKeyRequestHeaders(path, requestHeaders, true,
contextEncryptionAdapter, tracingContext);
appendSASTokenToQuery(path, SASTokenProvider.CREATE_FILE_OPERATION, abfsUriQueryBuilder);
} else {
requestHeaders.add(new AbfsHttpHeader(X_MS_META_HDI_ISFOLDER, TRUE));
appendSASTokenToQuery(path, SASTokenProvider.CREATE_DIRECTORY_OPERATION, abfsUriQueryBuilder);
}
requestHeaders.add(new AbfsHttpHeader(CONTENT_LENGTH, ZERO));
if (isAppendBlob) {
Expand All @@ -563,9 +566,6 @@ public AbfsRestOperation createPathRestOp(final String path,
requestHeaders.add(new AbfsHttpHeader(HttpHeaderConfigurations.IF_MATCH, eTag));
}

final AbfsUriQueryBuilder abfsUriQueryBuilder = createDefaultUriQueryBuilder();
appendSASTokenToQuery(path, SASTokenProvider.FIXED_SAS_STORE_OPERATION, abfsUriQueryBuilder);

final URL url = createRequestUrl(path, abfsUriQueryBuilder.toString());
final AbfsRestOperation op = getAbfsRestOperation(
AbfsRestOperationType.PutBlob,
Expand Down Expand Up @@ -687,7 +687,7 @@ public AbfsRestOperation acquireLease(final String path,

final AbfsUriQueryBuilder abfsUriQueryBuilder = createDefaultUriQueryBuilder();
abfsUriQueryBuilder.addQuery(QUERY_PARAM_COMP, LEASE);
appendSASTokenToQuery(path, SASTokenProvider.FIXED_SAS_STORE_OPERATION, abfsUriQueryBuilder);
appendSASTokenToQuery(path, SASTokenProvider.LEASE_BLOB_OPERATION, abfsUriQueryBuilder);

final URL url = createRequestUrl(path, abfsUriQueryBuilder.toString());
final AbfsRestOperation op = getAbfsRestOperation(
Expand Down Expand Up @@ -715,7 +715,7 @@ public AbfsRestOperation renewLease(final String path, final String leaseId,

final AbfsUriQueryBuilder abfsUriQueryBuilder = createDefaultUriQueryBuilder();
abfsUriQueryBuilder.addQuery(QUERY_PARAM_COMP, LEASE);
appendSASTokenToQuery(path, SASTokenProvider.FIXED_SAS_STORE_OPERATION, abfsUriQueryBuilder);
appendSASTokenToQuery(path, SASTokenProvider.LEASE_BLOB_OPERATION, abfsUriQueryBuilder);

final URL url = createRequestUrl(path, abfsUriQueryBuilder.toString());
final AbfsRestOperation op = getAbfsRestOperation(
Expand Down Expand Up @@ -743,7 +743,7 @@ public AbfsRestOperation releaseLease(final String path, final String leaseId,

final AbfsUriQueryBuilder abfsUriQueryBuilder = createDefaultUriQueryBuilder();
abfsUriQueryBuilder.addQuery(QUERY_PARAM_COMP, LEASE);
appendSASTokenToQuery(path, SASTokenProvider.FIXED_SAS_STORE_OPERATION, abfsUriQueryBuilder);
appendSASTokenToQuery(path, SASTokenProvider.LEASE_BLOB_OPERATION, abfsUriQueryBuilder);

final URL url = createRequestUrl(path, abfsUriQueryBuilder.toString());
final AbfsRestOperation op = getAbfsRestOperation(
Expand All @@ -770,7 +770,7 @@ public AbfsRestOperation breakLease(final String path,

final AbfsUriQueryBuilder abfsUriQueryBuilder = createDefaultUriQueryBuilder();
abfsUriQueryBuilder.addQuery(QUERY_PARAM_COMP, LEASE);
appendSASTokenToQuery(path, SASTokenProvider.FIXED_SAS_STORE_OPERATION, abfsUriQueryBuilder);
appendSASTokenToQuery(path, SASTokenProvider.LEASE_BLOB_OPERATION, abfsUriQueryBuilder);

final URL url = createRequestUrl(path, abfsUriQueryBuilder.toString());
final AbfsRestOperation op = getAbfsRestOperation(
Expand Down Expand Up @@ -818,6 +818,8 @@ destination, sourceEtag, isAtomicRenameKey(source), tracingContext
if (blobRenameHandler.execute()) {
final AbfsUriQueryBuilder abfsUriQueryBuilder
= createDefaultUriQueryBuilder();
appendSASTokenToQuery(source, SASTokenProvider.RENAME_SOURCE_OPERATION,
abfsUriQueryBuilder);
final URL url = createRequestUrl(destination,
abfsUriQueryBuilder.toString());
final List<AbfsHttpHeader> requestHeaders = createDefaultHeaders();
Expand Down Expand Up @@ -891,7 +893,7 @@ public AbfsRestOperation append(final String path,
abfsUriQueryBuilder.addQuery(QUERY_PARAM_COMP, BLOCK);
abfsUriQueryBuilder.addQuery(QUERY_PARAM_BLOCKID, reqParams.getBlockId());

String sasTokenForReuse = appendSASTokenToQuery(path, SASTokenProvider.FIXED_SAS_STORE_OPERATION,
String sasTokenForReuse = appendSASTokenToQuery(path, SASTokenProvider.WRITE_OPERATION,
abfsUriQueryBuilder, cachedSasToken);

final URL url = createRequestUrl(path, abfsUriQueryBuilder.toString());
Expand Down Expand Up @@ -964,7 +966,7 @@ public AbfsRestOperation appendBlock(final String path,
}
final AbfsUriQueryBuilder abfsUriQueryBuilder = createDefaultUriQueryBuilder();
abfsUriQueryBuilder.addQuery(QUERY_PARAM_COMP, APPEND_BLOCK);
String sasTokenForReuse = appendSASTokenToQuery(path, SASTokenProvider.FIXED_SAS_STORE_OPERATION, abfsUriQueryBuilder);
String sasTokenForReuse = appendSASTokenToQuery(path, SASTokenProvider.WRITE_OPERATION, abfsUriQueryBuilder);

final URL url = createRequestUrl(path, abfsUriQueryBuilder.toString());
final AbfsRestOperation op = getAbfsRestOperation(
Expand Down Expand Up @@ -1056,7 +1058,7 @@ public AbfsRestOperation flush(byte[] buffer,
final AbfsUriQueryBuilder abfsUriQueryBuilder = createDefaultUriQueryBuilder();
abfsUriQueryBuilder.addQuery(QUERY_PARAM_COMP, BLOCKLIST);
abfsUriQueryBuilder.addQuery(QUERY_PARAM_CLOSE, String.valueOf(isClose));
String sasTokenForReuse = appendSASTokenToQuery(path, SASTokenProvider.FIXED_SAS_STORE_OPERATION,
String sasTokenForReuse = appendSASTokenToQuery(path, SASTokenProvider.WRITE_OPERATION,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is using WRITE_OPERATION for the flush call a good idea? Should we create another operation type for this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have the same operation type for DFS flush call as well. We can keep it as it is I think

abfsUriQueryBuilder, cachedSasToken);

final URL url = createRequestUrl(path, abfsUriQueryBuilder.toString());
Expand Down Expand Up @@ -1118,7 +1120,7 @@ public AbfsRestOperation setPathProperties(final String path,

AbfsUriQueryBuilder abfsUriQueryBuilder = createDefaultUriQueryBuilder();
abfsUriQueryBuilder.addQuery(QUERY_PARAM_COMP, METADATA);
appendSASTokenToQuery(path, SASTokenProvider.FIXED_SAS_STORE_OPERATION, abfsUriQueryBuilder);
appendSASTokenToQuery(path, SASTokenProvider.SET_PROPERTIES_OPERATION, abfsUriQueryBuilder);

final URL url = createRequestUrl(path, abfsUriQueryBuilder.toString());
final AbfsRestOperation op = getAbfsRestOperation(
Expand Down Expand Up @@ -1197,7 +1199,7 @@ public AbfsRestOperation getPathStatus(final String path,
final AbfsUriQueryBuilder abfsUriQueryBuilder = createDefaultUriQueryBuilder();
abfsUriQueryBuilder.addQuery(HttpQueryParams.QUERY_PARAM_UPN,
String.valueOf(getAbfsConfiguration().isUpnUsed()));
appendSASTokenToQuery(path, SASTokenProvider.FIXED_SAS_STORE_OPERATION,
appendSASTokenToQuery(path, SASTokenProvider.GET_PROPERTIES_OPERATION,
abfsUriQueryBuilder);

final URL url = createRequestUrl(path, abfsUriQueryBuilder.toString());
Expand Down Expand Up @@ -1276,7 +1278,7 @@ public AbfsRestOperation read(final String path,
}

final AbfsUriQueryBuilder abfsUriQueryBuilder = createDefaultUriQueryBuilder();
String sasTokenForReuse = appendSASTokenToQuery(path, SASTokenProvider.FIXED_SAS_STORE_OPERATION,
String sasTokenForReuse = appendSASTokenToQuery(path, SASTokenProvider.READ_OPERATION,
abfsUriQueryBuilder, cachedSasToken);

URL url = createRequestUrl(path, abfsUriQueryBuilder.toString());
Expand Down Expand Up @@ -1438,7 +1440,7 @@ public AbfsRestOperation getBlockList(final String path,
final List<AbfsHttpHeader> requestHeaders = createDefaultHeaders();

final AbfsUriQueryBuilder abfsUriQueryBuilder = createDefaultUriQueryBuilder();
String operation = SASTokenProvider.FIXED_SAS_STORE_OPERATION;
String operation = SASTokenProvider.READ_OPERATION;
appendSASTokenToQuery(path, operation, abfsUriQueryBuilder);

abfsUriQueryBuilder.addQuery(QUERY_PARAM_COMP, BLOCKLIST);
Expand Down Expand Up @@ -1476,9 +1478,9 @@ public AbfsRestOperation copyBlob(Path sourceBlobPath,
String dstBlobRelativePath = destinationBlobPath.toUri().getPath();
String srcBlobRelativePath = sourceBlobPath.toUri().getPath();
appendSASTokenToQuery(dstBlobRelativePath,
SASTokenProvider.FIXED_SAS_STORE_OPERATION, abfsUriQueryBuilderDst);
SASTokenProvider.COPY_BLOB_DST_OPERATION, abfsUriQueryBuilderDst);
appendSASTokenToQuery(srcBlobRelativePath,
SASTokenProvider.FIXED_SAS_STORE_OPERATION, abfsUriQueryBuilderSrc);
SASTokenProvider.COPY_BLOB_SRC_OPERATION, abfsUriQueryBuilderSrc);
final URL url = createRequestUrl(dstBlobRelativePath,
abfsUriQueryBuilderDst.toString());
final String sourcePathUrl = createRequestUrl(srcBlobRelativePath,
Expand Down Expand Up @@ -1512,7 +1514,7 @@ public AbfsRestOperation deleteBlobPath(final Path blobPath,
AbfsUriQueryBuilder abfsUriQueryBuilder = createDefaultUriQueryBuilder();
String blobRelativePath = blobPath.toUri().getPath();
appendSASTokenToQuery(blobRelativePath,
SASTokenProvider.FIXED_SAS_STORE_OPERATION, abfsUriQueryBuilder);
SASTokenProvider.DELETE_OPERATION, abfsUriQueryBuilder);
final URL url = createRequestUrl(blobRelativePath,
abfsUriQueryBuilder.toString());
final List<AbfsHttpHeader> requestHeaders = createDefaultHeaders();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ public final class AbfsErrors {
/**
* Exception message on filesystem init if token-provider-auth-type configs are provided
*/
public static final String UNAUTHORIZED_SAS = "Incorrect SAS token provider configured for non-hierarchical namespace account.";
public static final String UNAUTHORIZED_SAS
= "Incorrect SAS token provider configured for non-hierarchical namespace account with DFS service type.";
public static final String ERR_RENAME_BLOB =
"FNS-Blob rename was not successful for source and destination path: ";
public static final String ERR_DELETE_BLOB =
Expand Down
10 changes: 7 additions & 3 deletions hadoop-tools/hadoop-azure/src/site/markdown/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -652,13 +652,17 @@ To know more about how SAS Authentication works refer to
[Grant limited access to Azure Storage resources using shared access signatures (SAS)](https://learn.microsoft.com/en-us/azure/storage/common/storage-sas-overview)

There are three types of SAS supported by Azure Storage:
- [User Delegation SAS](https://learn.microsoft.com/en-us/rest/api/storageservices/create-user-delegation-sas): Recommended for use with ABFS Driver with HNS Enabled ADLS Gen2 accounts. It is Identity based SAS that works at blob/directory level)
- [User Delegation SAS](https://learn.microsoft.com/en-us/rest/api/storageservices/create-user-delegation-sas):
SAS-based authentication works with HNS-enabled ADLS Gen2 accounts
(recommended for use with ABFS) and is also supported with non-HNS (FNS) Blob
accounts. However, it is **NOT SUPPORTED** with FNS-DFS accounts.
- [Service SAS](https://learn.microsoft.com/en-us/rest/api/storageservices/create-service-sas): Global and works at container level.
- [Account SAS](https://learn.microsoft.com/en-us/rest/api/storageservices/create-account-sas): Global and works at account level.

#### Known Issues With SAS
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SAS-based authentication works with HNS-enabled ADLS Gen2 accounts (recommended for use with ABFS) and is also supported with non-HNS (FNS) Blob accounts. However, it is not supported with FNS-DFS accounts. - better to frame like this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Taken

- SAS Based Authentication works only with HNS Enabled ADLS Gen2 Accounts which
is a recommended account type to be used with ABFS.
- SAS Based Authentication works with HNS Enabled ADLS Gen2 Accounts (which
is a recommended account type to be used with ABFS). It is also supported with
non-HNS (FNS) Blob accounts. It is **NOT SUPPORTED** with FNS-DFS accounts.
- Certain root level operations are known to fail with SAS Based Authentication.

#### Using User Delegation SAS with ABFS
Expand Down
Loading