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 :)
Microsoft recommends using Azure AD to authorize requests to Azure Storage. However, if you must use Shared Key authorization, then secure your account keys with Azure Key Vault. You can retrieve the keys from the key vault at runtime, instead of saving them with your application.
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!
So, here is what I ended up doing. Thanks to Neil and Ognyan for getting me there.
It works as following:
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!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With