Dave McGinnis
Dave McGinnis

Reputation: 615

How to Access Azure WASB Container Based on SAS Token in Java?

I'm trying to use a SAS key to retrieve a container from Azure WASB in Java code. This is being done in the HDFS code, but for whatever reason I can't seem to get it to work. I've managed to simplify it down to the application below, which also does not work. I think it is either an issue with how we are generating the SAS token, or permissions on the Azure account. Can someone look at this and point me in the right direction as to what the issue might be? Thanks!

public static void main(String[] arguments)
{
    try {
        String storage_account = "wasbvalidation";
        String container = "demoengagement1";
        CloudBlobClient blobClient = getBlobClient(storage_account);

        CloudBlobContainer blobContainer = blobClient.getContainerReference(container);

        blobContainer.downloadAttributes(); // This call succeeds

        SharedAccessBlobPolicy policy = new SharedAccessBlobPolicy();
        policy.setPermissions(EnumSet.allOf(SharedAccessBlobPermissions.class));
        policy.setSharedAccessStartTime(Date.valueOf(LocalDate.now().minusYears(2)));
        policy.setSharedAccessExpiryTime(Date.valueOf(LocalDate.now().plusYears(2)));

        String sas = blobContainer.getUri().toString() + "?" + blobContainer.generateSharedAccessSignature(policy, null, null, SharedAccessProtocols.HTTPS_ONLY);

        // Code after this point is emulating what HDFS is doing, so I'd rather not change it.
        URI blobUri = new URI(blobContainer.getUri().toString());
        StorageCredentials credentials = new StorageCredentialsSharedAccessSignature(sas);
        CloudBlobContainer sasContainer = new CloudBlobContainer(blobUri, credentials);
        sasContainer.downloadAttributes(); // This call fails, however.
    } catch (Exception e) {
        e.printStackTrace();
    }
}

private static CloudBlobClient getBlobClient(String storageAccount) throws NullPointerException {
    String storageConnectionString = "DefaultEndpointsProtocol=https;" + "AccountName=" + storageAccount + ";" + "AccountKey=" + accountKey;
    CloudStorageAccount csa = null;
    try {
        csa = CloudStorageAccount.parse(storageConnectionString);
    } catch (Exception ex) {
        ex.printStackTrace();
    }
    CloudBlobClient blobClient = csa.createCloudBlobClient();
    return blobClient;
}

Upvotes: 1

Views: 1396

Answers (1)

Peter Pan
Peter Pan

Reputation: 24148

According to your code, I think you want to get the properties and metadata of a blob container by building a url with SAS for a container. However, the SAS string generated by SharedAccessBlobPolicy is like sig=1G7tiQnLEtbjk2RSNuUSKH7gLNVZjqhuLQL%2Fci%2FXS50%3D&st=2017-01-30T16%3A00%3A00Z&se=2021-01-30T16%3A00%3A00Z&sv=2018-03-28&sp=racwdl&sr=b for blob (sr=b), not for container (sr=c, such as st=2019-01-31T08%3A38%3A46Z&se=2019-02-01T08%3A38%3A46Z&sp=rl&sv=2018-03-28&sr=c&sig=KnynNYBUtzNSYtBEcYakMrhAXPRIk60wztB3BFv5b%2Bs%3D copied from Azure Storage Explorer).

I tried to use CloudStorageAccount with SharedAccessAccountPolicyto generate an Account SAS for a blob via the code below, but it still not works.

Account SAS. The account SAS delegates access to resources in one or more of the storage services. All of the operations available via a service SAS are also available via an account SAS. Additionally, with the account SAS, you can delegate access to operations that apply to a given service, such as Get/Set Service Properties and Get Service Stats. You can also delegate access to read, write, and delete operations on blob containers, tables, queues, and file shares that are not permitted with a service SAS. See Constructing an Account SAS for in-depth information about constructing the account SAS token.

SharedAccessAccountPolicy accountPolicy = new SharedAccessAccountPolicy();
accountPolicy.setPermissions(EnumSet.allOf(SharedAccessAccountPermissions.class));
accountPolicy.setSharedAccessStartTime(Date.valueOf(LocalDate.now().minusYears(2)));
accountPolicy.setSharedAccessExpiryTime(Date.valueOf(LocalDate.now().plusYears(2)));

String sas = csa.generateSharedAccessSignature(accountPolicy);

I test the code below,

StorageCredentials credentials = new StorageCredentialsSharedAccessSignature(sas);
CloudBlobContainer sasContainer = new CloudBlobContainer(new URI(container2.getUri().toString()+"?"+sas), credentials);
sasContainer.downloadAttributes();

Then to get the exception.

Exception in thread "main" java.lang.IllegalArgumentException: Cannot provide credentials as part of the address and as constructor parameter. Either pass in the address or use a different constructor.

Or to test the code CloudBlobContainer sasContainer = new CloudBlobContainer(new URI(container2.getUri().toString()+"?"+sas)) to get the exception.

Exception in thread "main" com.microsoft.azure.storage.StorageException: Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.

It seems to be caused by the implementation of Azure Java Storage SDK v8.0.0 after I researched the SDK source codes. Maybe you can report it to Microsoft to ask for this issue.

I tried to generate a container url with SAS using Azure Java Storage SDK v10 by the code below, it works fine.

Maven dependency for v10:

<!-- https://mvnrepository.com/artifact/com.microsoft.azure/azure-storage-blob -->
<dependency>
    <groupId>com.microsoft.azure</groupId>
    <artifactId>azure-storage-blob</artifactId>
    <version>10.4.0</version>
</dependency>

Code for generating a container url with SAS:

String accountName = "<your account name>";
String accountKey = "<your account key>";
SharedKeyCredentials credentials = new SharedKeyCredentials(accountName, accountKey);
final ServiceURL serviceURL = new ServiceURL(new URL("http://" + accountName + ".blob.core.windows.net"), StorageURL.createPipeline(credentials, new PipelineOptions()));
String containerName = "<container name>";
ServiceSASSignatureValues values = new ServiceSASSignatureValues()
                .withProtocol(SASProtocol.HTTPS_ONLY) // Users MUST use HTTPS (not HTTP).
                .withExpiryTime(OffsetDateTime.now().plusDays(2)) // 2 days before expiration.
                .withContainerName(containerName)
                .withBlobName(blobName);
ContainerSASPermission permission = new ContainerSASPermission()
                .withRead(true)
                .withAdd(true)
                .withWrite(true);
values.withPermissions(permission.toString());
SASQueryParameters serviceParams = values.generateSASQueryParameters(credentials);
String sas = serviceParams.encode();

String containerUrlWithSAS = String.format(Locale.ROOT, "https://%s.blob.core.windows.net/%s%s",
                accountName, containerName, sas);
HttpPipeline pipeline = new HttpPipelineBuilder().build();
ContainerURL sasContainer = new ContainerURL(new URL(containerUrlWithSAS), pipeline);
sasContainer.getProperties();

Note: the function getProperties of ContainerURL in SDK v10 is similar with downloadAttributes of CloudBlobContainer in SDK v8, which also returns the container's metadata and system properties.

Upvotes: 2

Related Questions