1
votes

I have an azure resource group which contains Web App Service and Storage with BLOB container. My web app (.NET Core) tries to retrieve and show an image from container. The container has no public access to content (access level is private). I created system assigned identity for my app and gave it Reader role in storage access control (IAM).

This is how I get access to blobs in app's code:

const string blobName = "https://storagename.blob.core.windows.net/img/Coast.jpg";
string storageAccessToken = await GetStorageAccessTokenAsync();
var tokenCredential = new TokenCredential(storageAccessToken);
var storageCredentials = new StorageCredentials(tokenCredential);
var blob = new CloudBlockBlob(new Uri(blobName), storageCredentials);
ImageBlob = blob.Uri;

GetStorageAccessTokenAsync() does this:

var tokenProvider = new AzureServiceTokenProvider();
return await tokenProvider.GetAccessTokenAsync("https://storage.azure.com/");

Then the image is displayed by

<img src="@Model.ImageBlob" />

I don't get any exceptions in my code, but image from the BLOB container isn't shown with 404 error (specified resource doesn't exist) in browser console. When I change container's access level to "blob" (public access), app works fine and the image is displayed. Apparently, it is something wrong with getting credentials part, but I couldn't find any working example nor detailed explanations how it actually should work. Any help is very appreciated.

UDPATE: Thank you all who responded. So, it seems I've got two problems here.

1) I don't get credentials properly. I can see that "AzureServiceTokenProvider" object (Microsoft.Azure.Services.AppAuthentication) that I create, has empty property PrincipalUsed at the runtime.

My application deployed to Azure App Service, which has system managed identity and that identity (service principal) is given permissions in Azure storage (I changed permission from account Reader to Storage Blob Data Reader as was suggested).

Shouldn't it get all data needed from the current context? If not, what I can do here?

2) I use wrong method to show image, but since the app has no access to storage anyway I can't fix it yet.

But still - what is the common way to do that in my case? I mean there is no public access to storage and I use "CloudBlockBlob" to reach images.

3
In your GetAccessTokenAsync() method, please try to add the 2nd parameter: TenantIdIvan Yang
This won't work as you are returning the url to the browser. Then the request to the blob is done from the browser and your client is not authenticated. You can otherwise stream the image or return a sas token in the url but you will need the connectionstring to generate a sas tokenThomas
have you made any pogress ?Thomas
Thank you @Thomas, but I've got an impression that if I'm dealing with native azure application and system identities, I don't need SAS and other keys to access data in Azure resources. Azure AD and RBAC should do the trick. And if not, what is this all about?Svetlana
@IvanYang, I got from documentation that AzureServiceTokenProvider only can get as input connection string like "RunAs=App;AppId=AppId;TenantId=TenantId;AppKey=Secret" and I don't have an AppKey since it system managed identity. Correct me please if I'm wrong. And again, all that shouldn't be needed since I try to use AAD and RBAC.Svetlana

3 Answers

1
votes

Reader gives access to read the control plane, but not the data plane. The role you need is Storage Blob Data Reader, which gives access to read blob contents.

For more details about this, check out: https://docs.microsoft.com/en-us/azure/role-based-access-control/role-definitions#data-operations-example

1
votes

Finally, I got it to work. First of all, the part of code regarding getting token and image from Azure storage was OK. The second problem with displaying image in RazorPages application I resolved, using this code in view:

<form asp-page-handler="GetImage" method="get">
    <img src="/MyPageName?handler=GetImage" />
</form>

and corresponding code in model:

public async Task<ActionResult> OnGetGetImageAsync()
{
   //getting image code and returning FileContentResult
}

But I'm still thinking: whether is more simple way to do that? Something like to add image collection to the model, fill it using "OnGet..." handler and then display its content using in view. I didn't find a way to use model properties in <img> tag. Does anyone have some suggestions?

0
votes

When you use <img src="@Model.ImageBlob" />, no authorization header is sent in the request by the browser. In your code, you are fetching the token, but the token is not being sent in the authorization header when the image is being fetched. So, storage API thinks this is an anonymous request. This is the reason you are getting a 404.

You need to send auth code when fetching the image. This code works for me

public async Task<ActionResult> Image()
        {
            const string blobName = "https://storage.blob.core.windows.net/images/image.png";
            string storageAccessToken = await GetStorageAccessTokenAsync().ConfigureAwait(false);

            var tokenCredential = new TokenCredential(storageAccessToken);
            var storageCredentials = new StorageCredentials(tokenCredential);

            var blob = new CloudBlockBlob(new Uri(blobName), storageCredentials);
            Stream blobStream = blob.OpenRead();
            return File(blobStream, blob.Properties.ContentType, "image.png");
        }

In the view, I use

<img src="/Home/Image" />