Windows Azure Storage - 公有云存储签名授权与访问控制

微软公有云存储Windows Azure Storage(WAS)存储提供持久的、可持续的、易扩展的存储空间,并且提供基于Internet的多种访问方式。包含3种类型的存储服务:

  • „ 二进制大对象(Binary Large Object(Blob)Storage)服务,主要用于存储文件或二进制数据,用于文件存储分发、流媒体库存储、数据备份等。存储的文件提供对外的HTTP(s)访问地址。
  • „ 队列(Queue Storage)服务,用于存储实时消息数据,确保应用程序之间可靠的、持久的消息传递。
  • „ 表(Table Storage)服务,用于可被查询的动态结构化数据存储。与SQL Server等传统数据库中数据表不同的是,同一个Table Storage表中的数据实体可以拥有不同的数据结构,如学生成绩信息和客户联系信息,而SQL Server中同一个数据表内不可能同时含有学生成绩信息和客户联系信息。

进一步单独了解WAS提供的编程接口和访问方式,请参考:

如何编程访问Blob存储:https://www.windowsazure.cn/zh-cn/develop/net/how-to-guides/blob-storage-v17/

如何编程访问Table存储:https://www.windowsazure.cn/zh-cn/develop/net/how-to-guides/table-services/

如何编程访问队列存储:https://www.windowsazure.cn/zh-cn/develop/net/how-to-guides/queue-service/

本文将对WAS访问过程中常用的签名授权(Shared Access Signature:SAS)方式及WAS访问控制进行分析。

WAS访问控制

在默认情况下,只有WAS账户所有者能访问该账户下存储的Blob、Table和Queue,如果需要将WAS中存储的部分资源共享给更多的用户,WAS账户所有者除了直接将WAS存储密钥共享之外(一般不推荐),还可以采用以下两种方法。

  • „ 设置某个容器的属性为公开,使得匿名用户可以访问该容器下的Blob文件,该方法仅使用于Blob共享。
  • „ 使用授权签名的方式,设定WAS账户下的某些Blob、Table和Queue在某段时间内对外部分开放,授权客户端可以在有效时间内使用带有数字签名的请求对目标Blob、Table和Queue执行授权的操作。

授权签名(Shared Access Signature:SAS)是包含有指定权限和有效时间的URI地址,客户端可以在不使用WAS密钥的情况下,使用该URI地址直接操作存储在WAS上的文件、表和队列数据。授权签名在使用过程中主要有两种形式。

  • „ 自组织签名(Ad hoc SAS):创建一个独立的授权签名,包括详细的起止时间、授予权限等,适用于Container、Blob、Table和Queue。
  • „ 规则授权(SAS with stored access policy):针对Container、Table和Queue创建授权规则,规则中包含一个或多个授权签名。

两种形式的主要区别在于撤销授权上,如果使用自组织签名,含有数字签名的URL一旦被客户端获取,开发者就不能撤销授权,若要使得客户端不能访问WAS上的对应资源,开发者只能等待已存在的授权过期或手动删除原始数据源。但在规则授权方式中,开发者可以在授权到期之前修改规则、删除规则或者重置WAS账户密钥(不推荐),实现终止授权。需要注意的是,若选择删除已有规则来撤销授权,则在原授权到期之前,如需创建新的授权规则,注意一定选用不同的规则名称,否则原有授权规则依然会生效。

下面针对两种不同的授权方式进行编程实践。

1. 使用自组织签名方式进行数据访问:

// using local emulator to practice

var account = CloudStorageAccount.DevelopmentStorageAccount; // or your cloud account

var container = account.CreateCloudBlobClient(). GetContainerReference("testcontainer");

container.CreateIfNotExist();

//create a file in blob storage

var blob = container.GetBlobReference("test.txt");

blob.Properties.ContentType = "text/plain";

blob.UploadText("Hello, World!");

//set the container private

BlobContainerPermissions containerAccess = new BlobContainerPermissions();

containerAccess.PublicAccess = BlobContainerPublicAccessType.Off;

container.SetPermissions(containerAccess);

// create a shared access signature (looks like a query param: ?se=...) to the blob file

var sas = blob.GetSharedAccessSignature(new SharedAccessPolicy()

{

    Permissions = SharedAccessPermissions.Read

                    | SharedAccessPermissions.Write,

    SharedAccessExpiryTime = DateTime.UtcNow + TimeSpan.FromSeconds(30)

});

Console.WriteLine("This link should work for the next 30 seconds:");

Console.WriteLine(blob.Uri.AbsoluteUri + sas);

 

//the above can be done by some server

//the information below can be sent to client for using SAS

string tmp = blob.Uri.AbsoluteUri + sas;

Uri blobEndpoint = account.BlobEndpoint;

 

以上代码在WAS存储模拟器中创建了一个新的文件,并对该文件进行了授权签名,客户端根据上述产生的授权签名在30秒内可以对该Blob文件进行读写操作。最终得到的授权签名为:

?se=2013-09-16T03%3A57%3A15Z&sr=b&sp=rw&sig=XANHKt2V%2FzWg0URAYnn%2BVzuYJpzTohvn%2Bto0TvwgRog%3D

完整用于访问该Blob文件的授权地址为:

https://127.0.0.1:10000/devstoreaccount1/testcontainer/test.txt?se=2013-09-16T03%3A57%3A15Z\&sr=b\&sp=rw\&sig=XANHKt2V%2FzWg0URAYnn%2BVzuYJpzTohvn%2Bto0TvwgRog%3D

在上述签名和地址中,可以清晰地看到授权的终止时间(se)、授权的对象(sr=blob)、授予的权限(sp= read & write),以及详细的数字签名标识(sig)。

基于上述生成的授权签名,客户端可以在不使用WAS密钥的情况下访问指定的非公开(private)Blob文件。代码如下:

// now just use the SAS to do blob operations

var sasCreds = new StorageCredentialsSharedAccessSignature(sas);

// new client using the same endpoint (including account name)

// but using the SAS as the credentials

var sasBlob = new CloudBlobClient(blobEndpoint, sasCreds)

    .GetBlobReference("testcontainer/test.txt");

//write operation here to test

sasBlob.UploadText("Hello again!");

Console.WriteLine("Message after SAS based operation : " + sasBlob. DownloadText());

//read operation here to test

sasBlob.FetchAttributes();

 

//wait for some time to get SAS expired, then try to do write operation

System.Threading.Thread.Sleep(30000);

try

{

    sasBlob.UploadText("Hello again + 3!");

}

catch (Exception exx)

{

    Console.WriteLine(exx.Message);

}

客户端使用授权签名在规定的时间内(30秒)可以对非公开的(private)Blob文件进行读写操作,但30秒过后,客户端的后续操作会失败,并得到以下错误信息:

Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.

这是由于签名已过期,基于签名的操作无法通过WAS身份验证。

 

2. 使用规则授权方式访问WAS资源:

// using local emulator to practice

var account = CloudStorageAccount.DevelopmentStorageAccount; // or your cloud account

var container = account.CreateCloudBlobClient(). GetContainerReference("testcontainer");

container.CreateIfNotExist();

 

//create a file in blob storage

var blob = container.GetBlobReference("test.txt");

blob.Properties.ContentType = "text/plain";

blob.UploadText("Hello, World!");

 

//set the container private

BlobContainerPermissions containerAccess = new BlobContainerPermissions();

containerAccess.PublicAccess = BlobContainerPublicAccessType.Off;

 

//create one share access policy

SharedAccessPolicy sap0 = new SharedAccessPolicy()

{

    Permissions = SharedAccessPermissions.Read

                   | SharedAccessPermissions.Write,

    SharedAccessExpiryTime = DateTime.UtcNow + TimeSpan.FromSeconds(30)

};

 

//add the policy to the target container

containerAccess.SharedAccessPolicies.Add("mypolicy", sap0);

container.SetPermissions(containerAccess);

 

// create a shared access signature (looks like a query param: ?se=...)

var sas0 = container.GetSharedAccessSignature(new SharedAccessPolicy(), "mypolicy");

Console.WriteLine("This link should work for the next 30 seconds:");

Console.WriteLine(blob.Uri.AbsoluteUri + sas0);

string tmp = blob.Uri.AbsoluteUri + sas0;

创建的授权签名为:

https://127.0.0.1:10000/devstoreaccount1/testcontainer/test.txt?sr=c\&si=mypolicy\&sig=GFxDKt2wktNbnBC3XemZGdfndN8kFH45rIsLShf7ScY%3D

签名中指定了规则应用对象(sr=container)、规则名称(si=mypolicy)和数字签名标识(sig)。客户端可以在获取上述签名信息后,在不使用WAS密钥的情况下直接访问指定Container下的数据。代码如下:

// now just use the SAS to do blob operations

var sasCreds = new StorageCredentialsSharedAccessSignature(sas0);

// new client using the same endpoint (including account name)

// but using the SAS as the credentials

var sasBlob = new CloudBlobClient(account.BlobEndpoint, sasCreds)

    .GetBlobReference("testcontainer/test.txt");

sasBlob.UploadText("Hello again!");

 

Console.WriteLine("Message after SAS based operation : " + sasBlob. DownloadText());

 

System.Threading.Thread.Sleep(30000);

try

{

    sasBlob.UploadText("Hello again + 3!");

}

catch (Exception exx)

{

    Console.WriteLine(exx.Message);

}

与自组织签名一样,一旦规则授权中的数字签名过期,客户端的后续操作就不会成功,并得到以下身份验证错误信息:

Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.

 

对于已授权的签名,若开发者需要及时撤销授权,则可以通过管理Container对应的SharedAccessPolicies来实现。代码如下:

// now just use the SAS to do blob operations

var sasCreds = new StorageCredentialsSharedAccessSignature(sas0);

// new client using the same endpoint (including account name)

// but using the SAS as the credentials

var sasBlob = new CloudBlobClient(account.BlobEndpoint, sasCreds)

    .GetBlobReference("testcontainer/test.txt");

sasBlob.UploadText("Hello again!");

 

Console.WriteLine("Message after SAS based operation : " + sasBlob. DownloadText());

 

//delete the policy when the signature is still valid

containerAccess.SharedAccessPolicies.Remove("mypolicy");

container.SetPermissions(containerAccess);

 

//operation will fail since SAS was revoked

try

{

sasBlob.UploadText("Hello again + 2!");

}

catch (Exception exx)

{

    Console.WriteLine(exx.Message);

}

调试运行,可以发现,撤销已存在的授权规则后,客户端将不能访问指定的WAS资源,并获得错误信息:

Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.

上述介绍中均以Blob为例,实现基于授权签名的WAS访问和授权控制,同样的方法也适用于Table Storage和Queue Storage。

 

将本地存储模拟器切换为Azure存储

以上的SAS测试是在本地模拟器上测试的,要将测试对象切换为真正的Azure存储,只需将

var account = CloudStorageAccount.DevelopmentStorageAccount; // or your cloud account

改为开发者在Azure Cloud中已有的Storage帐号即可。

 

如开发者使用China Azure时,WAS连接字符串类似以下样例:

CloudStorageAccount storageAccount = CloudStorageAccount.Parse("BlobEndpoint=https://portalvhds88zyhq6kndn2p.blob.core.chinacloudapi.cn/;AccountName=portalvhds88zyhq6kndn2p;AccountKey=tbLt4dmW0vJFFYobuBvpN2RB6On63XjwKDkoxD/v7Hg1vkeS3y3FJ+BK0MT6Q1MYXCWbKOJSM8+9mUrHUzrLDg==");

如开发者使用Global Windows Azure,WAS连接字符串类似以下样例:

CloudStorageAccount.Parse.("DefaultEndpointsProtocol=https;AccountName=mytest3939393;AccountKey=sZjfJiETVsXC****&&$$$m4yfjdajf*****%%U3fvxVHqGmJhIcaVaX3g=="); 

 

结论

开发者在使用WAS存储时,一定要尽可能的保护原始WAS密钥信息(连接字符串),对于Client/Server架构的应用,尽可能采用上述授权签名(SAS)的方式动态的安全授权。