Azure blob read SAS token throws AuthorizationPermissionMismatch exception - azure

I'm trying to generate a SAS token for a blob, so that any user with the token can read the blob. Below is the code I have. I get an exception when I try to read the blob. If I grant "Storage Blob Data Reader" access to the user, then it works. My understanding is that user with SAS token should be able to read the blob without granting specific permission. what am I missing here ?
BlobServiceClient blobServiceClient = new BlobServiceClient(new Uri("https://accountname.blob.core.windows.net/"), new DefaultAzureCredential());
UserDelegationKey key = await blobServiceClient.GetUserDelegationKeyAsync(DateTimeOffset.UtcNow,
DateTimeOffset.UtcNow.AddDays(1));
BlobSasBuilder sasBuilder = new BlobSasBuilder()
{
BlobContainerName = "containerName",
BlobName = "file.json",
Resource = "b",
StartsOn = DateTimeOffset.UtcNow,
ExpiresOn = DateTimeOffset.UtcNow.AddHours(1)
};
sasBuilder.SetPermissions(BlobSasPermissions.Read);
string sasToken = sasBuilder.ToSasQueryParameters(key, "accountname").ToString();
UriBuilder fullUri = new UriBuilder()
{
Scheme = "https",
Host = string.Format("{0}.blob.core.windows.net", "accountname"),
Path = string.Format("{0}/{1}", "containerName", "file.json"),
Query = sasToken
};
var blobClient = new Azure.Storage.Blobs.BlobClient(fullUri.Uri);
using (var stream = await blobClient.OpenReadAsync()) // throws exception
{ }
Exception : Service request failed.
Status: 403 (This request is not authorized to perform this operation using this permission.)
ErrorCode: AuthorizationPermissionMismatch

I believe you are getting this error is because the user for which you are getting the user delegation key does not have permissions to access the data in the storage account.
Assigning Owner permission enables the user to manage the storage account itself, it does not give them permissions to manage the data.
Please try by assigning the user one of the data roles described here: https://learn.microsoft.com/en-us/azure/storage/blobs/authorize-access-azure-active-directory#azure-built-in-roles-for-blobs.
To learn more about RBAC roles to manage data, please see this link: https://learn.microsoft.com/en-us/azure/storage/blobs/assign-azure-role-data-access?tabs=portal.

Related

Azure Data Storage: Unable to upload file (Not authorized to perform this operation using this permission)

I'm trying to follow the example to upload a file to Azure Data Storage as mentioned in the documentation : https://learn.microsoft.com/en-us/azure/storage/blobs/storage-quickstart-blobs-dotnet?tabs=visual-studio%2Cmanaged-identity%2Croles-azure-portal%2Csign-in-azure-cli%2Cidentity-visual-studio
Following is my code:
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
using System;
using System.IO;
using Azure.Identity;
// TODO: Replace <storage-account-name> with your actual storage account name
var blobServiceClient = new BlobServiceClient(
new Uri("https://[some azure storage]"),
new DefaultAzureCredential());
// Set container name
string containerName = "data";
// Get container
BlobContainerClient containerClient = blobServiceClient.GetBlobContainerClient(containerName);
// Create a local file in the ./data/ directory for uploading and downloading
string localPath = "data";
Directory.CreateDirectory(localPath);
string fileName = "testupload" + Guid.NewGuid().ToString() + ".txt";
string localFilePath = Path.Combine(localPath, fileName);
// Write text to the file
await File.WriteAllTextAsync(localFilePath, "Hello, World!");
// Get a reference to a blob
BlobClient blobClient = containerClient.GetBlobClient(fileName);
Console.WriteLine("Uploading to Blob storage as blob:\n\t {0}\n", blobClient.Uri);
// Upload data from the local file
await blobClient.UploadAsync(localFilePath, true);
But I'm getting an error message that the request is not authorized.
Error message:
Azure.RequestFailedException: 'This request is not authorized to perform this operation using this permission.
I have Contributor role (which based on description is Grant full access to manage all resources ....), is this role still not enough to perform the operation?
I tried in my environment and got below results:
Initially I tried same code in my environment and got same error
Console:
Azure.RequestFailedException: 'This request is not authorized to perform this operation using this permission.
The above error occurs when your principal doesn't has access to azure blob storage.
For accessing blob storage through identity, Gaurav Mantri comment it is correct, you need a role to access blob storage,
The roles are
Storage-blob-contributor(or)
Storage-blob-owner
Go to portal -> storage accounts -> Access Control (IAM) ->Add -> Add role assignments -> storage-blob-contributor or storage-blob-owner role to the storage account.
Portal:
After assigning role to my storage account, I executed same code and it successfully uploaded file in azure blob storage.
Code:
using Azure.Storage.Blobs;
using System;
using System.IO;
using Azure.Identity;
// TODO: Replace <storage-account-name> with your actual storage account name
var blobServiceClient = new BlobServiceClient(
new Uri("https://[some azure storage]"),
new DefaultAzureCredential());
// Set container name
string containerName = "test";
// Get container
BlobContainerClient containerClient = blobServiceClient.GetBlobContainerClient(containerName);
// Create a local file in the ./data/ directory for uploading and downloading
string localPath = "data";
Directory.CreateDirectory(localPath);
string fileName = "testupload" + Guid.NewGuid().ToString() + ".txt";
string localFilePath = Path.Combine(localPath, fileName);
// Write text to the file
await File.WriteAllTextAsync(localFilePath, "Hello, World!");
// Get a reference to a blob
BlobClient blobClient = containerClient.GetBlobClient(fileName);
Console.WriteLine("Uploading to Blob storage as blob:\n\t {0}\n", blobClient.Uri);
// Upload data from the local file
await blobClient.UploadAsync(localFilePath, true);
Console:
Portal:
Make sure you have to change the Network Access to Enable Public to All, if you're not using VPN or dedicated Network to access Azure Environment.

How to create Azure Storage SAS token using DefaultAzureCredential class

I want to create SAS token to download a blob stored in container in azure storage. I can easily generate SAS token using shared credential but this requires storage access key. How can I generate sas token using managed Identity.
credential, err := azblob.NewSharedKeyCredential(accountName, accountKey)
sasQueryParams, err := azblob.BlobSASSignatureValues{
Protocol: azblob.SASProtocolHTTPS,
ExpiryTime: time.Now().UTC().Add(4 * time.Hour),
ContainerName: containerName,
BlobName: blobName,
Permissions: azblob.BlobSASPermissions{Add: false,
Read: true, Write: false}.String(),
}.NewSASQueryParameters(credential)
How can I generate sas token using managed Identity?
You can generate it by using DefaultAzureCredential and the proper access to that blob in the storage container.
Connect to the storage account by using the Azure AD credentials of Default Azure Credential class.
Sample Code:
var strgAccName = _configuration.GetValue<string>("YourStorageAccountName");
var saUri = $"https://{strgAccName}.blob.core.windows.net";
var blobServiceClient = new BlobServiceClient(new Uri(saUri), new DefaultAzureCredential());
var blobContainerClient = blobServiceClient.GetBlobContainerClient(_configuration.GetValue<string>("YourContainerName"));
var blobClient = blobContainerClient.GetBlobClient("YourImage.jpg");
// We can issue the SAS token till a maximum of 7 days.
var userDelegationKey = blobServiceClient.GetUserDelegationKey(DateTimeOffset.UtcNow,
DateTimeOffset.UtcNow.AddHours(4));
var sasBuilder = new BlobSasBuilder()
{
BlobContainerName = blobClient.BlobContainerName,
BlobName = blobClient.Name,
Resource = "b", // b: blob, c: container
StartsOn = DateTimeOffset.UtcNow,
ExpiresOn = DateTimeOffset.UtcNow.AddHours(4),
};
sasBuilder.SetPermissions(BlobSasPermissions.Read);
var blobUriBuilder = new BlobUriBuilder(blobClient.Uri)
{
Sas = sasBuilder.ToSasQueryParameters(userDelegationKey,blobServiceClient.AccountName)
};
// Read this in any view like `blobUriBuilder.ToUri().ToString();`
}
And re-check the delegated access is there or not for that blob.
So, we don't use any access key and connection string for this.
Thanks to #Anupam Maiti for this Article, please refer this for step-by-step procedure.

Azure Function - Managed IDs to write to storage table - failing with 403 AuthorizationPermissionMismatch

I have an Azure function application (HTTP trigger) that writes to the storage queue and table. Both fail when I try to change to managed Id. This post / question is about just the storage table part.
Here's the code that does the actual writing to the table:
GetStorageAccountConnectionData();
try
{
WorkspaceProvisioningRecord provisioningRecord = new PBIWorkspaceProvisioningRecord();
provisioningRecord.status = requestType;
provisioningRecord.requestId = requestId;
provisioningRecord.workspace = request;
#if DEBUG
Console.WriteLine(Environment.GetEnvironmentVariable("AZURE_TENANT_ID"));
Console.WriteLine(Environment.GetEnvironmentVariable("AZURE_CLIENT_ID"));
DefaultAzureCredentialOptions options = new DefaultAzureCredentialOptions()
{
Diagnostics =
{
LoggedHeaderNames = { "x-ms-request-id" },
LoggedQueryParameters = { "api-version" },
IsLoggingContentEnabled = true
},
ExcludeVisualStudioCodeCredential = true,
ExcludeAzureCliCredential = true,
ExcludeManagedIdentityCredential = true,
ExcludeAzurePowerShellCredential = true,
ExcludeSharedTokenCacheCredential = true,
ExcludeInteractiveBrowserCredential = true,
ExcludeVisualStudioCredential = true
};
#endif
DefaultAzureCredential credential = new DefaultAzureCredential();
Console.WriteLine(connection.storageTableUri);
Console.WriteLine(credential);
var serviceClient = new TableServiceClient(new Uri(connection.storageTableUri), credential);
var tableClient = serviceClient.GetTableClient(connection.tableName);
await tableClient.CreateIfNotExistsAsync();
var entity = new TableEntity();
entity.PartitionKey = provisioningRecord.status;
entity.RowKey = provisioningRecord.requestId;
entity["requestId"] = provisioningRecord.requestId.ToString();
entity["status"] = provisioningRecord.status.ToString();
entity["workspace"] = JsonConvert.SerializeObject(provisioningRecord.workspace);
//this is where I get the 403
await tableClient.UpsertEntityAsync(entity);
//other stuff...
catch(AuthenticationFailedException e)
{
Console.WriteLine($"Authentication Failed. {e.Message}");
WorkspaceResponse response = new PBIWorkspaceResponse();
response.requestId = null;
response.status = "failure";
return response;
}
catch (Exception ex)
{
Console.WriteLine($"whoops! Failed to create storage record:{ex.Message}");
WorkspaceResponse response = new WorkspaceResponse();
response.requestId = null;
response.status = "failure";
return response;
}
I have the client id/ client secret for this security principal defined in my local.settings.json as AZURE_TENANT_ID/AZURE_CLIENT_ID/AZURE_CLIENT_SECRET.
The code dies trying to do the upsert. And it never hits the AuthenticationFailedException - just the general exception.
The security principal defined in the AZURE* variables was used to created this entire application including the storage account.
To manage data inside a storage account (like creating table etc.), you will need to assign different sets of permissions. Owner role is a control-plane role that enables you to manage storage accounts themselves and not the data inside them.
From this link:
Only roles explicitly defined for data access permit a security
principal to access blob data. Built-in roles such as Owner,
Contributor, and Storage Account Contributor permit a security
principal to manage a storage account, but do not provide access to
the blob data within that account via Azure AD.
Even though the text above is for Blobs, same thing applies for Tables as well.
Please assign Storage Table Data Contributor to your Managed Identity and then you should not get this error.

How to read Azure Blob Url?

I am creating list of blog posts in react and express/ Azure SQL db. I am able to use the Azure blob storage to store the image associated to the post. I am also able to get the blob url and I am storing that in my SQL db. However when I want to read the url directly it threw an error resource not found. After searching docs and other stackoverflow answers I could infer that it has something to do with SAS token. Can anyone explain what would be the better way to approach this?
https://yourdomain.blob.core.windows.net/imagecontainer/yourimage.png
Below is the nodejs code.
router.post('/image', async function (req, res) {
try {
console.log(req.files.files.data);
const blobName = 'test' + uuidv1() + '.png';
const containerClient = blobServiceClient.getContainerClient(containerName);
const blockBlobClient = containerClient.getBlockBlobClient(blobName);
const uploadBlobResponse = await blockBlobClient.upload(req.files.files.data, req.files.files.data.length)
res.send({tempUrl:blockBlobClient.url});
} catch (e) {
console.log(e);
}
})
However when I want to read the url directly it threw an error
resource not found.
Most likely you're getting this error because the blob container containing the blob has a Private ACL and because of that anonymous access is disabled. To enable anonymous access, please change the blob container's ACL to Blob or Public and that will solve this problem.
If you can't (or don't want to) change the blob container's ACL, other option would be to create a Shared Access Signature (SAS) on a blob. A SAS essentially gives time and permission bound access to a blob. For your needs, you would need to create a short-lived SAS token with just Read permission.
To generate a SAS token, you will need to use generateBlobSASQueryParameters method. Once you create a SAS token, you will need to append it to your blob's URL to get a SAS URL.
Here's the sample code to do so. It makes use of #azure/storage-blob node package.
const permissions = new BlobSASPermissions();
permissions.read = true;//Set read permission only.
const currentDateTime = new Date();
const expiryDateTime = new Date(currentDateTime.setMinutes(currentDateTime.getMinutes()+5));//Expire the SAS token in 5 minutes.
var blobSasModel = {
containerName: 'your-blob-container-name',
blobName: 'your-blob-name',
permissions: permissions,
expiresOn: expiryDateTime
};
const sharedKeyCredential = new StorageSharedKeyCredential('your-storage-account-name', 'your-storage-account-key');
const sasToken = generateBlobSASQueryParameters(blobSasModel, sharedKeyCredential);
const sasUrl = blockBlobClient + "?" + sasToken;//return this SAS URL to the client.

Getting Error while Creating SAS Token for Azure Storage Blob with MSI

I'm trying to create a SAS token for a storage blob. I use a StorageCredentials which was created with MSI (Managed Service Identity) to create the CloudBlobClient. When creating the SAS I'm getting "Cannot create Shared Access Signature unless Account Key credentials are used". Is there support to SAS with MSI?
var container = blobClient.GetContainerReference(containerName);
var blockBlob = container.GetBlockBlobReference(snapname);
var sas = string.Concat(blockBlob.Uri.ToString(), blockBlob.GetSharedAccessSignature(sasConstraints));
This is how I create the StorageCredentials:
tokenCallback = CreateMsiCallback();
var initToken = await tokenCallback(audience);
return new StorageCredentials(
new TokenCredential(initToken, async (state, token) =>
{
var accessToken = await _tokenCallback(audience);
return new NewTokenAndFrequency(accessToken, TimeSpan.FromMinutes(1));
}, null, TimeSpan.FromMinutes(1))
);
To create the token callback I use HttpClient
public Func<string, Task<string>> CreateMsiCallback()
{
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback =
(httpRequestMessage, cert, certChain, policyErrors) =>
{
if (policyErrors == SslPolicyErrors.None)
{
return true;
}
return 0 == string.Compare(cert.GetCertHashString(), FabricThumbprint, StringComparison.OrdinalIgnoreCase);
}
};
var client = new HttpClient(handler)
{
DefaultRequestHeaders =
{
{"secret", FabricAuthenticationCode }
}
};
return async (resource) =>
{
var requestUri = $"{FabricMsiEndpoint}?api-version={FabricApiVersion}&resource={HttpUtility.UrlEncode(resource)}";
var requestMessage = new HttpRequestMessage(HttpMethod.Get, requestUri);
var response = await client.SendAsync(requestMessage);
response.EnsureSuccessStatusCode();
var tokenResponseString = await response.Content.ReadAsStringAsync();
var tokenResponseObject =
JsonConvert.DeserializeObject<ManagedIdentityTokenResponse>(tokenResponseString);
return tokenResponseObject.AccessToken;
};
}
}
Based on this Github issue, you will need to assign Storage data roles to your MSI in order to generate SAS token. From this thread:
The error is because your oauth account don't have permission to
generateUserDelegationKey. To get SAS with Oauth storage context
(New-AzStorageContext -UseConnectedAuth), we need first generate
UserDelegationKey from server , then use the key to generate the SAS
token.
Please check have you assigned correct roles to the Oauth login user
(with Connect-AzAccount). like at least one of the following 4 roles
on the specific storage account:
Storage Blob Data Owner
Storage Blob Data Contributor
Storage Blob Data Reader
Storage Blob Delegator

Resources