Unable to Get/Post Blob on Azure Storage via Rest API in C# - azure

Upon successful consumption of Azure Rest api in c# code. I'm able to create, fetch and fetch list of containers but not blob. While accessing or uploading blobs, this gives permission issue and i.e.,
you are not authorized to perform this request with assigned permission
Progress that i have made so far:
Able to create and fetch the container.
Able to fetch the list of all the containers of storage account.
When tries to Get/Put a blob via below source code it give me error:
This request is not authorized to perform this operation using this permission.
string endPointUri = $"{azureApplicationConfiguration.Resource}/{inpContainerName}/{inpBlobName}";
var request = (HttpWebRequest)WebRequest.Create(endPointUri);
request.Method = HTTPMethod.GET.ToString();
request.Headers.Add("Authorization", $"Bearer {sasToken.access_token}");
request.Headers.Add("x-ms-date", DateTime.UtcNow.ToString("R"));
request.Headers.Add("x-ms-version", "2018-03-28");
request.ProtocolVersion = HttpVersion.Version11;
using (HttpWebResponse resp = (HttpWebResponse)request.GetResponse())
{
Console.WriteLine(resp.StatusCode.ToString());
Console.ReadKey();
}

The most likely answer is that the SAS token you're using to authenticate does not support object level access. You can confirm this by checking if there is a letter o in the Signed Resource Types srt= parameter in your SAS token. Based on the fact you can list and create containers, I'd guess the current value is srt=sc. To be able to perform GET/PUT operations on a blob as well, you'll need to generate a new SAS token that includes object level permissions, which should give you a token containing srt=sco.
In case you aren't aware of it already, there is a .NET Azure Storage SDK available, which provides a layer of abstraction over the REST API and may save you some time in the long run.

Related

AzureBlobStorage fails when enumerating Pageable<BlobContainerItem> result from GetBlobContainers with RequestFailedException, not authorized

I am building an infrastructure wrapper around Azure Blob Storage for a shared component in a .Net Core 6 suite of applications, both web and desktop-based applications. I’m using version 12.14.1 of Azure.Storage.Blobs.
public BlobContainerItem[] GetContainers(string sasToken)
{
blobUri = "https://spiffyaccountname.blob.core.windows.net?" + sasToken;
var client = new BlobServiceClient(blobUri);
var containers = client.GetBlobContainers(); // Succeeds!
var array = containers.array() // Fails with Azure.RequestFailedException
return array;
}
Exception Details:
Azure.RequestFailedException: This request is not authorized to perform this operation using this resource type.
RequestId:...
Time:...
Status: 403 (This request is not authorized to perform this operation using this resource type.)
ErrorCode: AuthorizationResourceTypeMismatch
I don't think it's a code issue but rather a configuration issue, as creating and deleting containers work fine, though I haven't been able to find the setting to fix it.
More than likely the reason you are getting this error is because of incorrect permissions in your SAS token.
Please check for 2 things:
Use Account SAS - Listing containers require that you use Account SAS instead of Service SAS.
Use proper permissions - Listing containers require that your account SAS has SignedServices as Blob (ss=b), SignedResourceTypes as Service (srt=s) and SignedPermission should have at least List permission (sp=l).

How to programmatically find out what operations I can do in a blob storage?

I am using libraries Microsoft.Azure.Storage.Blob 11.2.3.0 and Microsoft.Azure.Storage.Common 11.2.3.0 to connect to an Azure BlobStorage from a .NET Core 3.1 application.
When I started working on this, I had been given connection strings that gave me full access to the BlobStorage (or rather, the entire cloud storage account). Based upon those, I chose to write my connection code "defensively", making use of Exists() and CreateIfNotExists() from the CloudBlobContainer class to ensure the application would not fail when a container was not yet existing.
Now, I'm connecting a BlobStorage container using a SAS. While I can freely retrieve and upload blobs within the container like this, unfortunately, it seems that I am not allowed to do anything on the container level. Not only CreateIfNotExists, but even the mere querying of existence by Exists() throws a StorageException saying
This request is not authorized to perform this operation.
The documentation does not mention the exception.
Is there any way to check preemptively whether I am allowed to check the container's existence?
I have tried looking into the container permissions retrieved from GetPermissions, but that will throw an exception, as well.
The only other alternative I can see is to check for container existence within a try-catch-block and assume existence if an exception is thrown ...
There's a no definitive way to identify if an operation can be performed using a SAS token other than performing that operation and catching any exception that may be thrown by the operation. The exception that is of your interest is Unauthorized (403).
However you can try to predict if an operation can be performed by looking at the SAS token. If it is a Service SAS Token and not an Account SAS Token, that means all the account related operations are not not allowed. The way to distinguish between an Account SAS token and a Service SAS token is that the former will contain attributes like SignedServices (ss) and SignedResourceTypes (srt).
Next thing you would want to do is look for SignedPermissions (sp) attribute in your SAS token. This attribute will tell you what all operations are possible with the SAS token. For example, if your SAS token is a Service SAS token and if it includes Delete (d) permission, that would mean you can use this SAS token to delete a blob.
Please see these tables for the permissions/allowed operations combinations:
Service SAS Token: https://learn.microsoft.com/en-us/rest/api/storageservices/create-service-sas#permissions-for-a-directory-container-or-blob
Account SAS Token: https://learn.microsoft.com/en-us/rest/api/storageservices/create-service-sas#permissions-for-a-directory-container-or-blob
Please note that the operation might still fail for any number of reasons like SAS token has expired, account key has changed since the generation of SAS token, IP restrictions etc.
I tried in in my system to check whether the container exist or not able check it and if container not exists created container and able to upload file.
You need to give proper permission for your SAS Token
const string sasToken = “SAS Token”
const string accountName = "teststorage65";
const string blobContainerName = "example";
const string blobName = "test.txt";
const string myFileLocation = #"Local Path ";
var storageAccount = new CloudStorageAccount(storageCredentials, accountName, null, true);
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
CloudBlobContainer blobContainer = blobClient.GetContainerReference(blobContainerName);
var result=blobContainer.Exists();
if (result == true)
{
Console.WriteLine("Container exists");
}
else
{
// blobContainer.CreateIfNotExists();
Console.WriteLine("Conatiner not exists");
Console.WriteLine("Creating Container "+ blobContainerName);
blobContainer.CreateIfNotExists();
}
// blobContainer.CreateIfNotExists();
//Console.WriteLine("Creating Container ");
CloudBlockBlob cloudBlob = blobContainer.GetBlockBlobReference(blobName);
cloudBlob.UploadFromFile(myFileLocation);
OUTPUT

Getting a blob content using user delegation SAS created using user delegation key

I have created an AAD app as per https://learn.microsoft.com/en-us/azure/storage/common/storage-auth-aad-app.
The access is given to the azure storage account for the AAD app created.
Got the client id and client secret.
To create a user delegation key and user delegation sas, I am using the approach and code as defined in
https://learn.microsoft.com/en-us/azure/storage/blobs/storage-blob-user-delegation-sas-create-dotnet.
(set environment variables as mentioned in article).
I am able to generate the user delegation key using method GetUserDelegationSasBlob.
The container and blob file is existing one.
Now I am using the method ReadBlobWithSasAsync to read the contents of the blob using the SAS uri as generated above.
But, I get error as below.
This request is not authorized to perform this operation using this
permission. RequestId:5d127eaf-101e-00be-6666-6a3875000000
Time:2019-09-13T19:04:15.4109144Z
Status: 403 (This request is not authorized to perform this operation
using this permission.)
ErrorCode: AuthorizationPermissionMismatch
In another approach, I am generating the user delegation key using rest api.
https://learn.microsoft.com/en-us/rest/api/storageservices/get-user-delegation-key
I am able to get user delegation key in xml format.
I am creating SAS from it as per steps in
https://learn.microsoft.com/en-us/rest/api/storageservices/create-user-delegation-sas
For signature, I am using this code, using StringToSign and secret value as received from delegation key.
var encoding = new System.Text.ASCIIEncoding();
byte[] keyByte = encoding.GetBytes(secret);
byte[] messageBytes = encoding.GetBytes(ToSign);
using (var hmacsha256 = new HMACSHA256(keyByte))
{
byte[] hashmessage = hmacsha256.ComputeHash(messageBytes);
String sig= Convert.ToBase64String(hashmessage);
}
I am doing the GET request.
I have tried various set of parameter values, like,
sr: b and c
sks: b and c
sp: racwd and r and rw and few more
skv and sv is 2018-11-09 because this version is required for creating user delegation key.
But the GET api returns the error.
AuthenticationFailed
Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the
signature. RequestId:e4bc8f0f-d01e-0046-7367-6af368000000
Time:2019-09-13T19:12:27.7780695Z
Signature fields not well formed.
Try to assign the Storage Blob Data Contributor role to the storage account.
The Reader role is an Azure Resource Manager role that permits users to view storage account resources, but not modify them. It does not provide read permissions to data in Azure Storage, but only to account management resources.
Refer to this article.

Azure SAS token AuthorizationResourceTypeMismatch

I have created azure storage account. I have created file storage. I have generated SAS token. when I try to access file using sas token showing error "The remote server returned an error: (403) Forbidden."
I am able to generate SAS token. when I try to access file in file storage throwing exception. I have tried to copy and paste url on browser throws error "
<Error>
<Code>AuthorizationResourceTypeMismatch</Code>
<Message>
This request is not authorized to perform this operation using
this resource type. RequestId:4cbc0cbe-401a-00c2-2edf-
202bc4000000 Time:2019-06-12T05:26:39.4816687Z
</Message>
</Error>"
Code I am using to Generate SAS token
Static string GetAccountSASToken()
SharedAccessAccountPolicy policy = new
SharedAccessAccountPolicy()
{
Permissions = SharedAccessAccountPermissions.Read |
SharedAccessAccountPermissions.Write |
SharedAccessAccountPermissions.List,
Services = SharedAccessAccountServices.File,
ResourceTypes = SharedAccessAccountResourceTypes.Service,
SharedAccessExpiryTime = DateTime.UtcNow.AddHours(24),
Protocols = SharedAccessProtocol.HttpsOnly,
};
Code I am using to access file
XDocument objdoc = XDocument.Load(filepath+ sasToken);
After loading file to XDocument I have to perform some read and write operations.Please help in finding mistake that I am doing
I was encountering the same problem, and the solution from user3404686 (2019-07-13) is correct. After the fact it's much clearer, but when it's still a problem without resolution it can be baffling.
Resource types are authorised independently of each other, rather than there being a hierarchy, ie 'service' does not include 'container' and 'object' authorisations (which was my misunderstanding).
The storageservices API documentation describes how resource type permissions are assigned:
Service (s): Access to service-level APIs (e.g., Get/Set Service Properties, Get Service Stats, List Containers/Queues/Tables/Shares)
Container (c): Access to container-level APIs (e.g., Create/Delete Container, Create/Delete Queue, Create/Delete Table, Create/Delete
Share, List Blobs/Files and Directories)
Object (o): Access to object-level APIs for blobs, queue messages, table entities, and files(e.g. Put Blob, Query Entity, Get Messages,
Create File, etc.)
Further down in the same document, it provides examples of the service, resource type and permissions required for various operations that you may be using, allowing minimum-required-permissions granularity with regard to assigning permissions to a service using the SA token.
After understanding this, the error code AuthorizationResourceTypeMismatch makes more sense - the resource type(s) the SAS token is authorised for, mismatches the resource types you're attempting to access.
In SharedAccessAccountPolicy I have changed
ResourceTypes =SharedAccessAccountResourceTypes.Service to
ResourceTypes = SharedAccessAccountResourceTypes.Object. Then It's working for me.

Access a blob file via URI over a web browser using new AAD based access control

With the announcement of Azure Storage support for Azure Active Directory based access control, is it possible to serve a blob (a specific file) over a web browser just by it's URI?
The use case I want to simplify is giving a few people access to files on the blob without the need of having to append a SAS token to the URI. Instead it would be brilliant to have the typical OAuth flow started when trying to open the plain URI in his/her web browser.
In my case we want to give access to files that have been uploaded to the blob storage by users through our support bot, build on Microsoft Bot framework. Links in our support system should be accessible by a support agent in their web browser of choice.
It this use case supported by this announcement or does this only work for coded OAuth flows, meaning we still have to implement some code?
If so, is there a good sample on how to start the OAuth flow from a Azure Function app and use the resulting token to download the file (over Azure Storage REST endpoint)?
While this answer is technically correct, it wasn't a direct response to my initial question.
I was looking for a way to provide the direct uri of any blob to business users, so they can simply open it in any web browser and see the file.
In my case we wanted to give access to files that have been uploaded to the blob storage by users through our support bot, build on Microsoft Bot framework. E.g. serving the attachment as a link in our support system to be accessed by a support agent.
After digging into this, I can answer the question my self:
With the announcement of Azure Storage support for Azure Active Directory based access control, is it possible to serve a blob (a specific file) over a web browser just by it's URI?
No, this is not possible. More specifically, simply opening the direct uri to a blob in the browser doesn't trigger the OAuth flow. Instead it will always give you ResourceNotFound response unless you provide a SAS query token or set the blob to public. Both solutions are bad from security perspective (when normal users involved) and obviously bad UX.
Solution
Looking for a way to achieve exactly what I want, I came up with the idea of a azure function serving the attachment to any business user by passing the fileName as url parameter and constructing the path using a route template.
Thinking of security and the need for an access token anyway, you could protect the function app through platform authentication (a.k.a. easyAuth).
However, this is not enough and configuring all parts of the solution is not straight forward. That is why I'm sharing it.
TL;DR high-level steps:
Create a new Function App (v2 recommended)
Enable the function App for authentication (easyAuth)
Create a service principal (a.k.a. app registration) for the function app (implicit by step 2)
Add additional allowed token audience https://storage.microsoft.com on the app registration
Edit the manifest of the app registration to include Azure Storage API permission (see special remarks below)
Modify authSettings in Azure Resource explorer to include additionalLoginParams for token response and resourceId
Give at least the Storage Blob Data Reader permission on the blob to all users accessing the files
Deploy your function app, call it, access the user token, call the blob storage and present the result to the user (see code samples below)
Remarks on Azure Storage API permission and access token (Step 5 & 6)
As stated in the latest documentation for AAD authentication support on azure storage, the app must grand user_impersonation permission scope for resourceId https://storage.azure.com/. Unfortunately the documentation did not state on how to set this API permission as it is not visible in the portal (at least I did not find it).
So the only way is to set it through its global GUID (can be found on the internet) by editing the app registration manifest directly in the azure portal.
Update:
As it turned out, not finding the right permission in the portal is a bug. See my answer here. Modifying the manifest manually results in the same, but directly doing it in the portal is much more convenient.
"requiredResourceAccess": [
{
"resourceAppId": "e406a681-f3d4-42a8-90b6-c2b029497af1",
"resourceAccess": [
{
"id": "03e0da56-190b-40ad-a80c-ea378c433f7f",
"type": "Scope"
}
]
},
{
"resourceAppId": "00000002-0000-0000-c000-000000000000",
"resourceAccess": [
{
"id": "311a71cc-e848-46a1-bdf8-97ff7156d8e6",
"type": "Scope"
}
]
}
]
The first one is the user_impersonation scope on Azure Storage and the second is the graph permission for User.Read, which in most cases is helpful or needed.
After you uploaded your modified manifest, you can verify it on the API Permissions tab on your app registration.
As easyAuth is using the v1 endpoint of AAD, your app needs to request those permission statically by passing resource=https://storage.azure.com/ when triggering the OAuth flow.
Additionally Azure Storage requires the bearer schema for authentication header and therefore a JWT token is needed. To get a JWT token from the endpoint, we need to pass response_type=code id_token as an additional login parameter.
Both can only be done through Azure Resource explorer or powershell.
Using Azure Resource explorer you have to navigate all your way down to the authSettings on your function app and set the additionalLoginParams accordingly.
"additionalLoginParams": [
"response_type=code id_token",
"resource=https://storage.azure.com/"
]
Code Sample
Here is a complete code sample for an easy azure function using all aboves mechanisms.
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
namespace Controller.Api.v1.Org
{
public static class GetAttachment
{
private const string defaultContentType = "application/octet-stream";
[FunctionName("GetAttachment")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = "v1/attachments")] HttpRequest req,
ILogger log)
{
if (!req.Query.ContainsKey("fileName"))
return new BadRequestResult();
// Set the file name from query parameter
string fileName = req.Query["fileName"];
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
fileName = fileName ?? data?.name;
// Construct the final uri. In this sample we have a applicaiton setting BLOB_URL
// set on the function app to store the target blob
var blobUri = Environment.GetEnvironmentVariable("BLOB_URL") + $"/{fileName}";
// The access token is provided as this special header by easyAuth.
var accessToken = req.Headers.FirstOrDefault(p => p.Key.Equals("x-ms-token-aad-access-token", StringComparison.OrdinalIgnoreCase));
// Construct the call against azure storage and pass the user token we got from easyAuth as bearer
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken.Value.FirstOrDefault());
client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate");
client.DefaultRequestHeaders.Add("Accept", "*/*");
client.DefaultRequestHeaders.Add("x-ms-version", "2017-11-09");
// Serve the response directly in users browser. This code works against any browser, e.g. chrome, edge or even internet explorer
var response = await client.GetAsync(blobUri);
var contentType = response.Content.Headers.FirstOrDefault(p => p.Key.Equals("Content-Type", StringComparison.OrdinalIgnoreCase));
var byteArray = await response.Content.ReadAsByteArrayAsync();
var result = new FileContentResult(byteArray, contentType.Value.Any() ? contentType.Value.First() : defaultContentType);
return result;
}
}
}
}
If you want to use Azure Active Directory based access control for the storage, what you need to get is the access token. Here are the steps for your reference.
Register an application
2.Assign a build-in RBAC role to this application It depends on you which role to be assigned to the application.
3.Get the access token.
4.With the access token, now you can call the storage rest api.

Resources