7
votes

I am working on a solution where a small number of authenticated users should have full access to a set of Azure Blob Storage containers. I have currently implemented a system with public access, and wonder if I need to complicate the system further, or if this system would be sufficiently secure. I have looked briefly into how Shared Access Signatures (SAS) works, but I am not sure if this really is necessary, and therefore ask for your insight. The goal is to allow only authenticated users to have full access to the blob containers and their content.

The current system sets permissions in the following manner (C#, MVC):

// Retrieve a reference to my image container 
myContainer = blobClient.GetContainerReference("myimagescontainer");

// Create the container if it doesn't already exist
if (myContainer.CreateIfNotExists())
{
    // Configure container for public access
    var permissions = myContainer.GetPermissions();
    permissions.PublicAccess = BlobContainerPublicAccessType.Container;
    myContainer.SetPermissions(permissions);
}

As a result, all blobs are fully accessible as long as you have the complete URL, but it does not seem to be possible to list the blobs in the container directly through the URL:

// This URL allows you to view one single image directly:
'https://mystorageaccount.blob.core.windows.net/mycontainer/mycontainer/image_ea644f08-3263-4a7f-9be7-bc42efbf8939.jpg'

// These URLs appear to return to nothing but an error page:
'https://mystorageaccount.blob.core.windows.net/mycontainer/mycontainer/'
'https://mystorageaccount.blob.core.windows.net/mycontainer/'
'https://mystorageaccount.blob.core.windows.net/'

I do not find it an issue that authenticated users share complete URLs, allowing public access to a single image; however, no one but the authenticated users should be able to list, browse or access the containers directly to retrieve other images.

My question then becomes whether I should secure the system further, for instance using SAS, when it right now appears to work as intended, or leave the system as-is. You might understand that I would like to not complicate the system if not strictly needed. Thanks!

The solution I ended up using has been given below :)

4

4 Answers

4
votes

I use Ognyan Dimitrov's "Approach 2" to serve small PDFs stored in a private blob container ("No public read access") inside a browser window like this:

public ActionResult ShowPdf()
{
    string fileName = "fileName.pdf";

    var storageAccount = CloudStorageAccount.Parse(CloudConfigurationManager.GetSetting("StorageConnectionString"));
    var blobClient = storageAccount.CreateCloudBlobClient();
    var container = blobClient.GetContainerReference("containerName");
    var blockBlob = container.GetBlockBlobReference(fileName);

    Response.AppendHeader("Content-Disposition", "inline; filename=" + fileName);
    return File(blockBlob.DownloadByteArray(), "application/pdf");
}

with config file

<configuration>
    <appSettings>
        <add key="StorageConnectionString" value="DefaultEndpointsProtocol=https;AccountName=account-name;AccountKey=account-key" />
    </appSettings>
</configuration>

...which works perfect for me!

3
votes

So, here is what I ended up doing. Thanks to Neil and Ognyan for getting me there.

It works as following:

  • All images are private, and cannot be viewed at all without having a valid SAS
  • Adding, deletion and modification of blobs are made within the controller itself, all privately. No SAS or additional procedures are needed for these tasks.
  • When an image is to be displayed to the user (either anonymously or authenticated), a function generates an SAS with a fast expiry is that merely allows the browser to download the image (or blob), upon page generation and refresh, but not copy/paste a useful URL to the outside.

I first explicitly set the container permissions to Private (this is also the default setting, according to Ognyan):

// Connect to storage account
...

// Retrieve reference to a container. 
myContainer= blobClient.GetContainerReference("mycontainer");

// Create the container if it doesn't already exist.
if (myContainer.CreateIfNotExists())
{
    // Explicitly configure container for private access
    var permissions = myContainer.GetPermissions();
    permissions.PublicAccess = BlobContainerPublicAccessType.Off;
    myContainer.SetPermissions(permissions);   
}

Then later, when wanting to display the image, I added an SAS string to the original storage path of the blob:

public string GetBlobPathWithSas(string myBlobName)
{
    // Get container reference
    ...

    // Get the blob, in my case an image
    CloudBlockBlob blob = myContainer.GetBlockBlobReference(myBlobName);        

    // Generate a Shared Access Signature that expires after 1 minute, with Read and List access 
    // (A shorter expiry might be feasible for small files, while larger files might need a 
    // longer access period)
    string sas = myContainer.GetSharedAccessSignature(new SharedAccessBlobPolicy()
    {
        SharedAccessExpiryTime = DateTime.UtcNow.AddMinutes(1),
        Permissions = SharedAccessBlobPermissions.Read | SharedAccessBlobPermissions.List
    });
    return (blob.Uri.ToString() + sas).ToString();
}

I then called the GetBlobPathWithSas()-function from within the razor view, so that each page refresh would give a valid path+sas for displaying the image:

<img src="@GetPathWithSas("myImage")" />

In general, I found this reference useful:

http://msdn.microsoft.com/en-us/library/ee758387.aspx

Hope that helps someone!

3
votes

If you want only your auth. users to have access you have to make the container private. Otherwise it will be public and it is only a matter of time that somebody else gets to the "almost private" content and you as a developer get embarrassed.

Approach 1 : You send a link to your authorized user. In this case you give a SAS link to the user and he downloads his content from the blob directly. You have to generate SAS signatures with short access window so that your users can get your content and download it/ open it and after they are gone from the site the link will expire and the content will be no longer available. This is in case that they accidentally send the link over the wire and somebody else gets to the private content later.

Approach 2 : Your web server gets the content and delivers it to your clients In this case only your web app will have the access and no SAS signatures have to be generated. You return FileContentResult ( in case of MVC ) and you are ready. The downside is that your web server have to download the file prior to giving it to the client - double traffic. Here you have to handle the Blob->Web download carefully because if 3 users try to download a 200 MB file in together and you are storing it in your RAM - it will be depleted.

** UPDATE **

@Intexx provided an updated link to the docs you need.

2
votes

If you are using a public container then you are not really restricting access to authenticated users.

If the spec said "only authenticated users should have access" then I personally would find using a public container to be unacceptable. SAS is not very hard - the libraries do most of the work.

BTW: the format to list the items in a container is: https://myaccount.blob.core.windows.net/mycontainer?restype=container&comp=list