Access Control for Azure Blobs

UPDATE 3/10/2011 – Fixed formatting glitches in code samples.

This post is a sequel to an earlier post introducing Azure Blobs.

The Azure Storage Service provides the ability to store blobs, tables and queues in the Azure cloud. Blobs, tables and queues are identified by URL and could, in theory, be accessed by anyone who knows the appropriate URL. This could be an enormous security hole so the Azure Storage Service requires all access to Azure tables and queues to be authenticated. By default, the Azure Storage Service also requires all access to Azure blobs to be authenticated as well. However, because of the utility of blobs in storing media files, such as images and music, the Azure Storage Service supports various methods of loosening the authentication requirements for blobs and the containers they are located in.

The Azure Storage Service supports the association of an access permission with a container through public access control. This access control allows public read access to the container and the blobs in it or public read access merely to the blobs in the container and not to the container itself. The latter would, for example, prohibit the unauthenticated listing all the blobs in the container. The Azure Storage Service also supports of shared access signatures which can be used to provide a time-limited token allowing unauthenticated users a time-limited ability to access a container or the blobs in it. Shared access can be further managed through container-level access policy.

Public Access Control

Public access control can be used to allow various types of public access to containers and the blobs in them. This control is achieved through the GetPermissions() and SetPermissions() methods of the CloudBlobContainer class which also provides asynchronous versions of these methods. GetPermissions() and SetPermissions() are declared:

public BlobContainerPermissions GetPermissions();
public void SetPermissions(BlobContainerPermissions permissions);

As with many of the Storage Client methods there are also versions that take a BlobRequestOptions parameter. SetPermissions() sets the public access control permissions on a container and GetPermissions() retrieves them. The public access control permissions are contained in an object of class BlobContainerPermissions declared:

public class BlobContainerPermissions
{
    public BlobContainerPermissions();
    public BlobContainerPublicAccessType PublicAccess { get; set; }
    public SharedAccessPolicies SharedAccessPolicies { get; }
}

public class BlobContainerPermissions
{
    public BlobContainerPermissions();
    public BlobContainerPublicAccessType PublicAccess { get; set; }
    public SharedAccessPolicies SharedAccessPolicies { get; }
}

 

Public access control is managed through the PublicAccess property while container-level access policy is managed through the SharedAccessPolicies property. The PublicAccess property is of type BlobContainerPublicAccessType, an enum declared:

public enum BlobContainerPublicAccessType {
    Off,
    Container,
    Blob
}

The value Off indicates that public access control is not being used for this container. The values Container and Blob both allow read access to all the blobs in a container. However, the value Container also allows read access to the container so that its properties and metadata may be accessed and, furthermore, the blobs in the container may be listed.

The following example demonstrates public access control of type Container being applied to a container specified by containerName.

protected void SetContainerPermissions(String containerName)
{
    CloudStorageAccount cloudStorageAccount =
       CloudStorageAccount.FromConfigurationSetting(“DataConnectionString”);
    CloudBlobClient cloudBlobClient =
        cloudStorageAccount.CreateCloudBlobClient();
    CloudBlobContainer cloudBlobContainer =
         new CloudBlobContainer(containerName, cloudBlobClient);
    BlobContainerPermissions blobContainerPermissions =
        new BlobContainerPermissions();
    blobContainerPermissions.PublicAccess =
        BlobContainerPublicAccessType.Container;
    cloudBlobContainer.SetPermissions(blobContainerPermissions);
}

Note that this example, as do any others in this post, assumes that CloudStorageAccount.SetConfigurationSettingPublisher() has been invoked previously so that DataConnectionString is appropriately hooked in. Once this method has been invoked any blobs in the container will be publically accessible without the use of any authentication. This means, for example, that blobs containing images can be downloaded directly from a web browser just by entering their URLs into the browser. GetPermissions() can be invoked similarly to retrieve the BlobContainerPermissions applicable currently to the container.

The CloudBlobContainer.SetPermissions() method in the example sends the following request and payload to the Azure Storage Service:

PUT /mycontainer?restype=container&comp=acl&timeout=90 HTTP/1.1
x-ms-version: 2009-09-19
x-ms-blob-public-access: container
x-ms-date: Wed, 20 Jan 2010 10:17:39 GMT
Authorization: SharedKey myaccount:ez/6DWjKNLTFQZG5QfI9K3k+b7pNeBQnOhRCNC1gXyA=
Host: myaccount.blob.core.windows.net
Content-Length: 62<?xml version=”1.0″ encoding=”utf-8″?>
<SignedIdentifiers />

The public access permissions are submitted in the x-ms-blob-public-access header. Note that the SignedIdentifiers payload is empty.There is no time limit on the public access provided through public access control. Once provided, public access is available until it is revoked through invoking SetPermissions() with a BlobContainerPublicAccessType of Off.

Shared Access Signatures

Shared access signatures are used to permit read/write public access to a container or the individual blogs in it. This is a very powerful capability so requires the use of a special authentication token  – a shared access signature. This is a URL query string encoding the permissions and the time span over which they are applicable. A public unauthenticated user can access the resource protected by a shared access signature by appending it as a query string to the resource URL. The following is an example of a shared access signature appended to the resource URL for a blob named TextBlob in a container named mycontainer under an account named myaccount:

http://myaccount.blob.core.windows.net/mycontainer/TextBlob
  ?st=2010-01-20T02%3A40%3A32Z&se=2010-01-20T03%3A30%3A32Z&sr=b&sp=r
  &sig=2WmooPzzh2Z0rpenuUUBhYLyfrJZH%2BU088Rl1t2bHqY%3D

This shared access signature provides read access to the blob only between 02:40 and 03:30 on January 20, 2010 in UTC. Once the time has expired the shared access signature is rendered invalid and can no longer be used to access the blob. The blob is inacessible without the shared access signature.

The basic use of shared access signatures limits the time span of applicability to no more than one hour. Furthermore, it is not possible to recall or otherwise cancel a basic shared access signature. A container-level access policy remedies both these problems and allows shared access signatures valid for more than one hour and also allows shared access signatures to be cancelled consequently revoking the access privileges they confer.

The permissions and time spans of a shared access signature are expressed in a SharedAccessPolicy declared:

public class SharedAccessPolicy
{
    public SharedAccessPolicy();
    public SharedAccessPermissions Permissions { get; set; }
    public Nullable<DateTime> SharedAccessExpiryTime { get; set; }
    public Nullable<DateTime> SharedAccessStartTime { get; set; }
    public static SharedAccessPermissions PermissionsFromString(String value);
    public static String PermissionsToString( SharedAccessPermissions permissions);
}

The applicable time span of the shared access signature is specified by SharedAccessExpiryTime and SharedAccessStartTime which must be in UTC. The permissions are specified in the Permissions property an enum, SharedAccessPermissions, declared:

[FlagsAttribute]
public enum SharedAccessPermissions {
    None,
    Read,
    Write,
    Delete,
    List
}

The SharedAccessPermissions enum has the FlagsAttribute so values can be combined as in:

SharedAccessPermissions.Read | SharedAccessPermissions.Write

indicating both read and write permissions. Note that the Azure Storage Services REST documentation for Shared Access Signatures indicates they can be used for leasing a blob and creating a snapshot of a blob. Neither of these capabilities is exposed in the Storage Client.

The static methods, PermissionsFromString() and PermissionsToString(), are convenience methods allowing permissions to be specified as a simple string of the form “rwdl” corresponding to the applicable values of the SharedAccessPermissions enum.

Both CloudBlobContainer and CloudBlob contain two methods which can be used to generate a shared access signature for containers and blobs respectively:

public String GetSharedAccessSignature(SharedAccessPolicy policy);
public String GetSharedAccessSignature(
    SharedAccessPolicy policy, String groupPolicyIdentifier);

GetSharedAccessSignature() generates a shared access signature, i.e. URL query string, based on the specified SharedAccessPolicy. The second method associates the shared access signature with a container-level access policy which means that the shared access signature can be revoked and that it can be valid for more than one hour. Note that when a shared access signature is associated with a container-level access policy then individual features of the SharedAccessPolicy can only appear either in the container-level access policy or the the GetSharedAccessSignature() request. Microsoft suggests a best practice for shared access signatures is to always associate them with a container-level access policy precisely so they can be revoked if necessary.

The following example generates two shared access signatures – one containing a SharedAccessPolicy and the other that inherits a SharedAccessPolicy from a container-level access policy named adele.

protected void GetSharedAccessSignature(
   String containerName, String blobName)
{
    CloudStorageAccount cloudStorageAccount =
       CloudStorageAccount.FromConfigurationSetting(“DataConnectionString”);
    CloudBlobClient cloudBlobClient = cloudStorageAccount.CreateCloudBlobClient();
    CloudBlobContainer cloudBlobContainer =
       new CloudBlobContainer(containerName, cloudBlobClient);
    CloudBlockBlob cloudBlockBlob =
         cloudBlobContainer.GetBlockBlobReference(blobName);
    SharedAccessPolicy sharedAccessPolicy = new SharedAccessPolicy();
    sharedAccessPolicy.Permissions = SharedAccessPermissions.Read;
    sharedAccessPolicy.SharedAccessStartTime = DateTime.UtcNow.AddMinutes(-10);
    sharedAccessPolicy.SharedAccessExpiryTime = DateTime.UtcNow.AddMinutes(40);
    String sharedAccessSignature1 =
        cloudBlockBlob.GetSharedAccessSignature(sharedAccessPolicy);
    String sharedAccessSignature2 =
       cloudBlockBlob.GetSharedAccessSignature( new SharedAccessPolicy(), “adele”);
}

Note that sharedAccessSignature2, which inherits its SharedAccessPolicy from the container-level access policy named adele, is generated with a default SharedAccessPolicy since the container-level access policy already specifies all the properties in the shared access policy. The shared access signatures created are as follows respectively for sharedAccessSignature1 and sharedAccessSignature2:

?st=2010-01-20T08%3A22%3A43Z&se=2010-01-20T09%3A12
   %3A44Z&sr=b&sp=r&sig=gF5lUjYbEikNvYRZS0j0vgnqRxLU2B8%2Flz%2FbA4QC3gQ%3D?sr=b&si=adele&sig=PKhACJEChGDxjO%2BAd1GiEAXIRTUw%2F%2BveZFSiU2XunMs%3D

sharedAccessSignature1 contains: st specifying the start time; se specifying the expiry time; sr=b indicating that it applies to a blob; sp=r specifying it provides read access; and a sig containing the signed request. Meanwhile sharedAccessSignature2 contains: sr=b indicating that it applies to a blob; si=adele specifying the signed identifier of the container-level access policy; and a sig containing the signed request. Note that sharedAccessSignature2 exposes much less information about the capabilities of the signature immediately making it a more secure option.

Note that GetSharedAccessSignature() does not access the Azure Storage Service.

Container-Level Access Policy

One or more named container-level access policies can be associated with a container. An individual shared access policy is defined, as before, using the SharedAccessPolicy class. Consequently, a container-level access policy specifies the start and end times when an access permission is applicable for a container. These shared access policies are inherited by any container or blob shared access signature associated with the container-level access policy. Unlike a basic shared access signature there are no restrictions on the applicable time spans for container-level access policies and for any shared access signatures associated with them. Note that unlike the public access control described earlier container-level access policies do not provide access to the container or the blobs in it – they merely provide access policies which can be used by shared access signatures.

Container-level access policies are created using the same BlobContainerPermissions class as is the public access control except that container-level access policies are specified through the SharedAccessPolicies property rather than the PublicAccess property. Unlike shared access signatures, container-level access policies actually upload metadata describing the access policies to the Azure Storage Service where they are associated with the container. This is why shared access signatures associated with container-level access policies do not need the policies to be embedded in the URL query string.

The following example demonstrates two separate container-level access policies, named adele and marling, being added to a container:

protected void SetSharedAccessPolicies(String containerName)
{
    CloudStorageAccount cloudStorageAccount =
       CloudStorageAccount.FromConfigurationSetting(“DataConnectionString”);
    CloudBlobClient cloudBlobClient = cloudStorageAccount.CreateCloudBlobClient();
    CloudBlobContainer cloudBlobContainer =
        new CloudBlobContainer(containerName, cloudBlobClient);
    SharedAccessPolicy sharedAccessPolicy = new SharedAccessPolicy();
    sharedAccessPolicy.Permissions =
        SharedAccessPermissions.Read | SharedAccessPermissions.Write;
    sharedAccessPolicy.SharedAccessStartTime = DateTime.UtcNow.AddMinutes(-10);
    sharedAccessPolicy.SharedAccessExpiryTime = DateTime.UtcNow.AddMinutes(40);
    SharedAccessPolicy anotherSharedAccessPolicy = new SharedAccessPolicy();
    anotherSharedAccessPolicy.Permissions = SharedAccessPermissions.Read;
    anotherSharedAccessPolicy.SharedAccessStartTime = DateTime.UtcNow.AddDays(1);
    anotherSharedAccessPolicy.SharedAccessExpiryTime = DateTime.UtcNow.AddDays(7);
    BlobContainerPermissions blobContainerPermissions =
        new BlobContainerPermissions();
    blobContainerPermissions.SharedAccessPolicies.Add(“adele”, sharedAccessPolicy);
    blobContainerPermissions.SharedAccessPolicies.Add(
        “marling”, anotherSharedAccessPolicy);
    cloudBlobContainer.SetPermissions(blobContainerPermissions);
}

Note that SharedAccessPolicies is a collection of SharedAccessPolicy objects. The name, or Id, for the container-level access policy is provided as the key and the shared access policy is provided as the value to the SharedAccessPolicies collection. The CloudBlobContainer.SetPermissions() method sends the following request and payload to the Azure Storage Service:

PUT /mycontainer?restype=container&comp=acl&timeout=90 HTTP/1.1
x-ms-version: 2009-09-19
x-ms-date: Wed, 20 Jan 2010 09:38:08 GMT
Authorization: SharedKey myaccount:jNKP3df5tA4qP0oxKIwTD6KQR1nJkiefp9qxtRTTUt4=
Host: myaccount.blob.core.windows.net
Content-Length: 439<?xml version=”1.0″ encoding=”utf-8″?>
<SignedIdentifiers>
    <SignedIdentifier>
        <Id>adele</Id>
        <AccessPolicy>
            <Start>2010-01-20T09:28:03Z</Start>
            <Expiry>2010-01-20T10:18:03Z</Expiry>
            <Permission>rw</Permission>
        </AccessPolicy>
    </SignedIdentifier>
    <SignedIdentifier>
        <Id>marling</Id>
        <AccessPolicy>
            <Start>2010-01-21T09:38:05Z</Start>
            <Expiry>2010-01-27T09:38:05Z</Expiry>
            <Permission>r</Permission>
        </AccessPolicy>
    </SignedIdentifier>
</SignedIdentifiers>

The two access policies are visible in the payload. Note that there is no x-ms-blob-public-access header as there was with public access control.The container-level access policies associated with a container can be retrieved by invoking CloudBlobContainer.GetPermissions(). This retrieves the SignedIdentifiers payload just shown and converts it into SharedAccessPolicy objects.A specific container-level access policy can be modified by uploading a new version with the desired name. However, any existing container-level access policy not uploaded during an update is deleted. The container-level access policies can be removed by uploading a BlobContainerPermissions object with no SharedAccessPolicies. The following example removes any container-level access policies and public access control associated with the specified container:

protected void SetPermissions(String containerName)
{
    CloudStorageAccount cloudStorageAccount =
       CloudStorageAccount.FromConfigurationSetting(“DataConnectionString”);
    CloudBlobClient cloudBlobClient = cloudStorageAccount.CreateCloudBlobClient();
    CloudBlobContainer cloudBlobContainer =
        new CloudBlobContainer(containerName, cloudBlobClient);
    cloudBlobContainer.SetPermissions(new BlobContainerPermissions());
}

Warning

While using public access control merely allows the public read access to containers and blobs, shared access signatures and container-level access control can lead to public write access to a cloud-based resource – if, for example, a write shared access signature were to leak out. This could lead to unwanted destruction of valuable resources. Extreme care should be taken that access is restricted as much as possible both in terms of what access permissions are provided but also the applicable duration of that permission.

Note

Given the above warning, an application like Cloud Storage Studio which provides a GUI into the access control on containers, can be very useful during the development process.

About Neil Mackenzie

Cloud Solutions Architect. Microsoft
This entry was posted in Storage Service, Windows Azure. Bookmark the permalink.

7 Responses to Access Control for Azure Blobs

  1. Pingback: Yet another application to handle Windows Azure Storage Services - Paolo Salvatori's Blog - Site Home - MSDN Blogs

  2. Pingback: Azure Blob Storage – The remote server returned an error: NotFound « Mike#

  3. rixin liu says:

    hi,Neil, In this post, i found all policy are defined based on resource itself, namely at which time what operation can be executed on the specified resource ,but in some scene, additional constraints need to be defined: which authorized user can do these operation on those resource?
    could you give comments or articles on how to achieve this?

    • Rixin –

      The following is documented for Stored Access Policy:

      — A stored access policy gives you greater control over Shared Access Signatures you have released. Instead of specifying the signature’s lifetime and permissions on the URL, you can specify these parameters within the stored access policy stored on the blob, container, queue, or table that is being shared. To change these parameters for one or more signatures, you can modify the stored access policy, rather than reissuing the signatures. You can also quickly revoke the signature by modifying the stored access policy.

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

      This should provide the functionality you want.

  4. Pingback: Control Azure Container-Level Access Policy with Java | BlogoSfera

  5. Pingback: Introduction to Windows Azure Media Services | Convective

  6. Pingback: Introduction to Windows Azure Media Services - Windows Azure Blog

Leave a comment