0

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.

Svetlana
  • 163
  • 1
  • 15
  • In your GetAccessTokenAsync() method, please try to add the 2nd parameter: TenantId – Ivan Glasenberg Jun 24 '19 at 09:10
  • 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 token – Thomas Jun 24 '19 at 09:56
  • have you made any pogress ? – Thomas Jun 25 '19 at 07:25
  • 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 Jun 27 '19 at 08:23
  • @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 Jun 27 '19 at 08:31
  • Please see my answer below on why this is not working. W.r.t PrincipalUsed property, I tested and it works fine. It is set after the token is acquired, so please use it like this var token = await tokenProvider.GetAccessTokenAsync("https://storage.azure.com/"); ViewData["Principal"] = tokenProvider.PrincipalUsed.ToString(); – Varun Sharma Jun 27 '19 at 18:52

3 Answers3

1

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://learn.microsoft.com/en-us/azure/role-based-access-control/role-definitions#data-operations-example

0

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" />
Varun Sharma
  • 568
  • 4
  • 5
  • Thank you, @Varun, but I'm afraid I need some explanations here. Where and how exactly this Image() should be called? – Svetlana Jun 28 '19 at 17:41
  • Did some research and changed image tag to ``. Retrieving image process seems to work fine (at least I can see that blob has non-zero length, and corresponding request (/ModelName?action=Image) gets 200 response. But image still isn't shown. Content type is set correctly. – Svetlana Jun 30 '19 at 19:20
  • If you set , you should see the image. In my case, it was in the HomeController, so I set src to /Home/Image – Varun Sharma Jul 01 '19 at 20:08
  • So, in case of Razor pages when `public async Task Image()` is part of "About" model, I can use ``? But it isn't working, what else I could did wrong? @Varun, could you explain or provide me with some references how exactly this Task works? – Svetlana Jul 02 '19 at 16:00
  • Have a look at these links - https://www.codeproject.com/Tips/1028915/How-To-Download-a-File-in-MVC-2 and https://www.c-sharpcorner.com/UploadFile/db2972/file-result-in-controller-sample-in-mvc-day-15/ for downloading a file in MVC. One way to troubleshoot is to browse directly to the image and see the response. If you are getting the image blob correctly from storage, then it is the MVC part that might need fixing. – Varun Sharma Jul 03 '19 at 20:46
0

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?

Svetlana
  • 163
  • 1
  • 15