User Delegation SAS (#358)

This commit is contained in:
JinmingHu 2020-07-29 16:11:21 +08:00 committed by GitHub
parent 6e3c3d9acf
commit b0b5a18d18
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 196 additions and 68 deletions

View File

@ -42,7 +42,7 @@ namespace Azure { namespace Storage { namespace Blobs {
/**
* @brief The list of permissions that can be set for a blob container's access policy.
*/
*/
enum class BlobContainerSasPermissions
{
/**
@ -191,7 +191,7 @@ namespace Azure { namespace Storage { namespace Blobs {
* @brief Optionally specify the time at which the shared access signature becomes
* valid.
*/
std::string StartsOn;
Azure::Core::Nullable<std::string> StartsOn;
/**
* @brief The time at which the shared access signature becomes invalid. This field must
@ -220,7 +220,7 @@ namespace Azure { namespace Storage { namespace Blobs {
/**
* @brief The name of the blob being made accessible, or empty for a container SAS..
*/
*/
std::string BlobName;
/**
@ -237,7 +237,7 @@ namespace Azure { namespace Storage { namespace Blobs {
/**
* @brief Specifies which resources are accessible via the shared access signature.
*/
*/
BlobSasResource Resource;
/**
@ -267,7 +267,7 @@ namespace Azure { namespace Storage { namespace Blobs {
/**
* @brief Sets the permissions for the blob container SAS.
*
*
* @param
* permissions The allowed permissions.
*/
@ -275,7 +275,7 @@ namespace Azure { namespace Storage { namespace Blobs {
/**
* @brief Sets the permissions for the blob SAS.
*
*
* @param permissions The
* allowed permissions.
*/
@ -284,7 +284,7 @@ namespace Azure { namespace Storage { namespace Blobs {
/**
* @brief Uses the SharedKeyCredential to sign this shared access signature, to produce
* the proper SAS query parameters for authentication requests.
*
*
* @param credential
* The storage account's shared key credential.
* @return The SAS query parameters used for
@ -295,7 +295,7 @@ namespace Azure { namespace Storage { namespace Blobs {
/**
* @brief Uses an account's user delegation key to sign this shared access signature, to
* produce the proper SAS query parameters for authentication requests.
*
*
* @param
* credential UserDelegationKey retruned from BlobServiceClient.GetUserDelegationKey.
* @param accountName The name of the storage account.

View File

@ -28,6 +28,11 @@ namespace Azure { namespace Storage {
HttpsOnly,
};
inline std::string SasProtocolToString(SasProtocol protocol)
{
return protocol == SasProtocol::HttpsAndHtttp ? "https,http" : "https";
}
/**
* @brief Specifies the resource types accessible from an account level shared access
* signature.

View File

@ -42,7 +42,7 @@ namespace Azure { namespace Storage {
}
// query must be encoded
void SetQuery(const std::string& query);
void AppendQueries(const std::string& query);
void AppendQuery(const std::string& key, const std::string& value, bool do_encoding = false)
{

View File

@ -7,6 +7,32 @@
namespace Azure { namespace Storage { namespace Blobs {
namespace {
std::string BlobSasResourceToString(BlobSasResource resource)
{
if (resource == BlobSasResource::Container)
{
return "c";
}
else if (resource == BlobSasResource::Blob)
{
return "b";
}
else if (resource == BlobSasResource::BlobSnapshot)
{
return "bs";
}
else if (resource == BlobSasResource::BlobVersion)
{
return "bv";
}
else
{
throw std::runtime_error("unknown BlobSasResource value");
}
}
} // namespace
void BlobSasBuilder::SetPermissions(BlobContainerSasPermissions permissions)
{
Permissions.clear();
@ -87,37 +113,14 @@ namespace Azure { namespace Storage { namespace Blobs {
{
canonicalName += "/" + BlobName;
}
std::string protocol;
if (Protocol == SasProtocol::HttpsAndHtttp)
{
protocol = "https,http";
}
else
{
protocol = "https";
}
std::string resource;
if (Resource == BlobSasResource::Container)
{
resource = "c";
}
else if (Resource == BlobSasResource::Blob)
{
resource = "b";
}
else if (Resource == BlobSasResource::BlobSnapshot)
{
resource = "bs";
}
else if (Resource == BlobSasResource::BlobVersion)
{
resource = "bv";
}
std::string stringToSign = Permissions + "\n" + StartsOn + "\n" + ExpiresOn + "\n"
+ canonicalName + "\n" + Identifier + "\n" + (IPRange.HasValue() ? IPRange.GetValue() : "")
+ "\n" + protocol + "\n" + Version + "\n" + resource + "\n" + Snapshot + "\n" + CacheControl
+ "\n" + ContentDisposition + "\n" + ContentEncoding + "\n" + ContentLanguage + "\n"
+ ContentType;
std::string protocol = SasProtocolToString(Protocol);
std::string resource = BlobSasResourceToString(Resource);
std::string stringToSign = Permissions + "\n" + (StartsOn.HasValue() ? StartsOn.GetValue() : "")
+ "\n" + ExpiresOn + "\n" + canonicalName + "\n" + Identifier + "\n"
+ (IPRange.HasValue() ? IPRange.GetValue() : "") + "\n" + protocol + "\n" + Version + "\n"
+ resource + "\n" + Snapshot + "\n" + CacheControl + "\n" + ContentDisposition + "\n"
+ ContentEncoding + "\n" + ContentLanguage + "\n" + ContentType;
std::string signature
= Base64Encode(HMAC_SHA256(stringToSign, Base64Decode(credential.GetAccountKey())));
@ -125,7 +128,10 @@ namespace Azure { namespace Storage { namespace Blobs {
UriBuilder builder;
builder.AppendQuery("sv", Version);
builder.AppendQuery("spr", protocol);
builder.AppendQuery("st", StartsOn);
if (StartsOn.HasValue())
{
builder.AppendQuery("st", StartsOn.GetValue());
}
builder.AppendQuery("se", ExpiresOn);
if (IPRange.HasValue())
{
@ -166,8 +172,69 @@ namespace Azure { namespace Storage { namespace Blobs {
const UserDelegationKey& userDelegationKey,
const std::string& accountName)
{
unused(userDelegationKey, accountName);
return std::string();
std::string canonicalName = "/blob/" + accountName + "/" + ContainerName;
if (Resource == BlobSasResource::Blob || Resource == BlobSasResource::BlobSnapshot)
{
canonicalName += "/" + BlobName;
}
std::string protocol = SasProtocolToString(Protocol);
std::string resource = BlobSasResourceToString(Resource);
std::string stringToSign = Permissions + "\n" + (StartsOn.HasValue() ? StartsOn.GetValue() : "")
+ "\n" + ExpiresOn + "\n" + canonicalName + "\n" + userDelegationKey.SignedObjectId + "\n"
+ userDelegationKey.SignedTenantId + "\n" + userDelegationKey.SignedStartsOn + "\n"
+ userDelegationKey.SignedExpiresOn + "\n" + userDelegationKey.SignedService + "\n"
+ userDelegationKey.SignedVersion + "\n" + (IPRange.HasValue() ? IPRange.GetValue() : "")
+ "\n" + protocol + "\n" + Version + "\n" + resource + "\n" + Snapshot + "\n" + CacheControl
+ "\n" + ContentDisposition + "\n" + ContentEncoding + "\n" + ContentLanguage + "\n"
+ ContentType;
std::string signature
= Base64Encode(HMAC_SHA256(stringToSign, Base64Decode(userDelegationKey.Value)));
UriBuilder builder;
builder.AppendQuery("sv", Version);
builder.AppendQuery("sr", resource);
if (StartsOn.HasValue())
{
builder.AppendQuery("st", StartsOn.GetValue());
}
builder.AppendQuery("se", ExpiresOn);
builder.AppendQuery("sp", Permissions);
if (IPRange.HasValue())
{
builder.AppendQuery("sip", IPRange.GetValue());
}
builder.AppendQuery("spr", protocol);
builder.AppendQuery("skoid", userDelegationKey.SignedObjectId);
builder.AppendQuery("sktid", userDelegationKey.SignedTenantId);
builder.AppendQuery("skt", userDelegationKey.SignedStartsOn);
builder.AppendQuery("ske", userDelegationKey.SignedExpiresOn);
builder.AppendQuery("sks", userDelegationKey.SignedService);
builder.AppendQuery("skv", userDelegationKey.SignedVersion);
if (!CacheControl.empty())
{
builder.AppendQuery("rscc", CacheControl);
}
if (!ContentDisposition.empty())
{
builder.AppendQuery("rscd", ContentDisposition);
}
if (!ContentEncoding.empty())
{
builder.AppendQuery("rsce", ContentEncoding);
}
if (!ContentLanguage.empty())
{
builder.AppendQuery("rscl", ContentLanguage);
}
if (!ContentType.empty())
{
builder.AppendQuery("rsct", ContentType);
}
builder.AppendQuery("sig", signature, true);
return builder.ToString();
}
}}} // namespace Azure::Storage::Blobs

View File

@ -96,10 +96,10 @@ namespace Azure { namespace Storage { namespace Details {
std::string sas = getWithDefault(connectionStringMap, "SharedAccessSignature");
if (!sas.empty())
{
connectionStringParts.BlobServiceUri.SetQuery(sas);
connectionStringParts.DataLakeServiceUri.SetQuery(sas);
connectionStringParts.FileServiceUri.SetQuery(sas);
connectionStringParts.QueueServiceUri.SetQuery(sas);
connectionStringParts.BlobServiceUri.AppendQueries(sas);
connectionStringParts.DataLakeServiceUri.AppendQueries(sas);
connectionStringParts.FileServiceUri.AppendQueries(sas);
connectionStringParts.QueueServiceUri.AppendQueries(sas);
}
return connectionStringParts;

View File

@ -47,7 +47,7 @@ namespace Azure { namespace Storage {
if (pos != url.end() && *pos == '?')
{
auto queryIter = std::find(pos + 1, url.end(), '#');
SetQuery(std::string(pos + 1, queryIter));
AppendQueries(std::string(pos + 1, queryIter));
pos = queryIter;
}
@ -152,10 +152,8 @@ namespace Azure { namespace Storage {
return encoded;
}
void UriBuilder::SetQuery(const std::string& query)
void UriBuilder::AppendQueries(const std::string& query)
{
m_query.clear();
std::string::const_iterator cur = query.begin();
if (cur != query.end() && *cur == '?')
{

View File

@ -33,6 +33,7 @@ namespace Azure { namespace Storage { namespace Test {
auto keyCredential
= Details::ParseConnectionString(StandardStorageConnectionString()).KeyCredential;
auto accountName = keyCredential->AccountName;
auto blobServiceClient0
= Blobs::BlobServiceClient::CreateFromConnectionString(StandardStorageConnectionString());
auto blobContainerClient0 = blobServiceClient0.GetBlobContainerClient(m_containerName);
@ -42,6 +43,14 @@ namespace Azure { namespace Storage { namespace Test {
auto containerUri = blobContainerClient0.GetUri();
auto blobUri = blobClient0.GetUri();
auto blobServiceClient1 = Blobs::BlobServiceClient(
serviceUri,
std::make_shared<Azure::Core::Credentials::ClientSecretCredential>(
AadTenantId(), AadClientId(), AadClientSecret()));
auto userDelegationKey = *blobServiceClient1.GetUserDelegationKey(
ToISO8601(std::chrono::system_clock::now() - std::chrono::minutes(5)),
ToISO8601(std::chrono::system_clock::now() + std::chrono::minutes(60)));
auto verify_blob_read = [&](const std::string& sas) {
EXPECT_NO_THROW(blobClient0.Create());
auto blobClient = Blobs::AppendBlobClient(blobUri + sas);
@ -172,42 +181,50 @@ namespace Azure { namespace Storage { namespace Test {
{
blobSasBuilder.SetPermissions(permissions);
auto sasToken = blobSasBuilder.ToSasQueryParameters(*keyCredential);
auto sasToken2 = blobSasBuilder.ToSasQueryParameters(userDelegationKey, accountName);
if ((permissions & Blobs::BlobSasPermissions::Read) == Blobs::BlobSasPermissions::Read)
{
verify_blob_read(sasToken);
verify_blob_read(sasToken2);
}
if ((permissions & Blobs::BlobSasPermissions::Write) == Blobs::BlobSasPermissions::Write)
{
verify_blob_write(sasToken);
verify_blob_write(sasToken2);
}
if ((permissions & Blobs::BlobSasPermissions::Delete) == Blobs::BlobSasPermissions::Delete)
{
verify_blob_delete(sasToken);
verify_blob_delete(sasToken2);
}
if ((permissions & Blobs::BlobSasPermissions::Add) == Blobs::BlobSasPermissions::Add)
{
verify_blob_add(sasToken);
verify_blob_add(sasToken2);
}
if ((permissions & Blobs::BlobSasPermissions::Create) == Blobs::BlobSasPermissions::Create)
{
verify_blob_create(sasToken);
verify_blob_create(sasToken2);
}
if ((permissions & Blobs::BlobSasPermissions::Tags) == Blobs::BlobSasPermissions::Tags)
{
verify_blob_tags(sasToken);
verify_blob_tags(sasToken2);
}
if ((permissions & Blobs::BlobSasPermissions::DeleteVersion)
== Blobs::BlobSasPermissions::DeleteVersion)
{
verify_blob_delete_version(sasToken);
verify_blob_delete_version(sasToken2);
}
}
accountSasBuilder.SetPermissions(AccountSasPermissions::All);
// Expires
{
AccountSasBuilder builder2 = accountSasBuilder;
builder2.SetPermissions(AccountSasPermissions::All);
builder2.StartsOn = ToISO8601(std::chrono::system_clock::now() - std::chrono::minutes(5));
builder2.ExpiresOn = ToISO8601(std::chrono::system_clock::now() - std::chrono::minutes(1));
auto sasToken = builder2.ToSasQueryParameters(*keyCredential);
@ -217,7 +234,6 @@ namespace Azure { namespace Storage { namespace Test {
// Without start time
{
AccountSasBuilder builder2 = accountSasBuilder;
builder2.SetPermissions(AccountSasPermissions::All);
builder2.StartsOn.Reset();
auto sasToken = builder2.ToSasQueryParameters(*keyCredential);
EXPECT_NO_THROW(verify_blob_create(sasToken));
@ -226,39 +242,32 @@ namespace Azure { namespace Storage { namespace Test {
// IP
{
AccountSasBuilder builder2 = accountSasBuilder;
builder2.SetPermissions(AccountSasPermissions::All);
builder2.IPRange = "1.1.1.1";
auto sasToken = builder2.ToSasQueryParameters(*keyCredential);
auto blobClient = Blobs::AppendBlobClient(blobUri + sasToken);
EXPECT_THROW(verify_blob_create(sasToken), StorageError);
builder2.IPRange = "0.0.0.0-255.255.255.255";
sasToken = builder2.ToSasQueryParameters(*keyCredential);
blobClient = Blobs::AppendBlobClient(blobUri + sasToken);
EXPECT_NO_THROW(verify_blob_create(sasToken));
}
// Account SAS Service
{
AccountSasBuilder builder2 = accountSasBuilder;
builder2.SetPermissions(AccountSasPermissions::All);
builder2.Services = AccountSasServices::Files;
auto sasToken = builder2.ToSasQueryParameters(*keyCredential);
auto blobClient = Blobs::AppendBlobClient(blobUri + sasToken);
EXPECT_THROW(verify_blob_create(sasToken), StorageError);
builder2.Services = AccountSasServices::All;
sasToken = builder2.ToSasQueryParameters(*keyCredential);
blobClient = Blobs::AppendBlobClient(blobUri + sasToken);
EXPECT_NO_THROW(verify_blob_create(sasToken));
}
// Account SAS Resource Types
{
AccountSasBuilder builder2 = accountSasBuilder;
builder2.SetPermissions(AccountSasPermissions::All);
builder2.ResourceTypes = AccountSasResource::Service;
auto sasToken = builder2.ToSasQueryParameters(*keyCredential);
auto blobClient = Blobs::AppendBlobClient(blobUri + sasToken);
EXPECT_THROW(verify_blob_create(sasToken), StorageError);
auto serviceClient = Blobs::BlobServiceClient(serviceUri + sasToken);
@ -277,40 +286,49 @@ namespace Azure { namespace Storage { namespace Test {
{
containerSasBuilder.SetPermissions(permissions);
auto sasToken = containerSasBuilder.ToSasQueryParameters(*keyCredential);
auto sasToken2 = containerSasBuilder.ToSasQueryParameters(userDelegationKey, accountName);
if ((permissions & Blobs::BlobContainerSasPermissions::Read)
== Blobs::BlobContainerSasPermissions::Read)
{
verify_blob_read(sasToken);
verify_blob_read(sasToken2);
}
if ((permissions & Blobs::BlobContainerSasPermissions::Write)
== Blobs::BlobContainerSasPermissions::Write)
{
verify_blob_write(sasToken);
verify_blob_write(sasToken2);
}
if ((permissions & Blobs::BlobContainerSasPermissions::Delete)
== Blobs::BlobContainerSasPermissions::Delete)
{
verify_blob_delete(sasToken);
verify_blob_delete(sasToken2);
}
if ((permissions & Blobs::BlobContainerSasPermissions::List)
== Blobs::BlobContainerSasPermissions::List)
{
verify_blob_list(sasToken);
verify_blob_list(sasToken2);
}
if ((permissions & Blobs::BlobContainerSasPermissions::Add)
== Blobs::BlobContainerSasPermissions::Add)
{
verify_blob_add(sasToken);
verify_blob_add(sasToken2);
}
if ((permissions & Blobs::BlobContainerSasPermissions::Create)
== Blobs::BlobContainerSasPermissions::Create)
{
verify_blob_create(sasToken);
verify_blob_create(sasToken2);
}
if ((permissions & Blobs::BlobContainerSasPermissions::Tags)
== Blobs::BlobContainerSasPermissions::Tags)
{
verify_blob_tags(sasToken);
verify_blob_tags(sasToken2);
}
}
@ -322,6 +340,19 @@ namespace Azure { namespace Storage { namespace Test {
builder2.ExpiresOn = ToISO8601(std::chrono::system_clock::now() - std::chrono::minutes(1));
auto sasToken = builder2.ToSasQueryParameters(*keyCredential);
EXPECT_THROW(verify_blob_create(sasToken), StorageError);
auto sasToken2 = builder2.ToSasQueryParameters(userDelegationKey, accountName);
EXPECT_THROW(verify_blob_create(sasToken2), StorageError);
}
// Without start time
{
Blobs::BlobSasBuilder builder2 = blobSasBuilder;
builder2.StartsOn.Reset();
auto sasToken = builder2.ToSasQueryParameters(*keyCredential);
EXPECT_NO_THROW(verify_blob_create(sasToken));
auto sasToken2 = builder2.ToSasQueryParameters(userDelegationKey, accountName);
EXPECT_NO_THROW(verify_blob_create(sasToken2));
}
// IP
@ -329,12 +360,15 @@ namespace Azure { namespace Storage { namespace Test {
Blobs::BlobSasBuilder builder2 = blobSasBuilder;
builder2.IPRange = "0.0.0.0-0.0.0.1";
auto sasToken = builder2.ToSasQueryParameters(*keyCredential);
auto blobClient = Blobs::AppendBlobClient(blobUri + sasToken);
EXPECT_THROW(verify_blob_create(sasToken), StorageError);
auto sasToken2 = builder2.ToSasQueryParameters(userDelegationKey, accountName);
EXPECT_THROW(verify_blob_create(sasToken2), StorageError);
builder2.IPRange = "0.0.0.0-255.255.255.255";
sasToken = builder2.ToSasQueryParameters(*keyCredential);
blobClient = Blobs::AppendBlobClient(blobUri + sasToken);
EXPECT_NO_THROW(verify_blob_create(sasToken));
sasToken2 = builder2.ToSasQueryParameters(userDelegationKey, accountName);
EXPECT_NO_THROW(verify_blob_create(sasToken2));
}
// response headers override
@ -361,6 +395,15 @@ namespace Azure { namespace Storage { namespace Test {
EXPECT_EQ(p->HttpHeaders.ContentDisposition, headers.ContentDisposition);
EXPECT_EQ(p->HttpHeaders.CacheControl, headers.CacheControl);
EXPECT_EQ(p->HttpHeaders.ContentEncoding, headers.ContentEncoding);
auto sasToken2 = builder2.ToSasQueryParameters(userDelegationKey, accountName);
blobClient = Blobs::AppendBlobClient(blobUri + sasToken2);
p = blobClient.GetProperties();
EXPECT_EQ(p->HttpHeaders.ContentType, headers.ContentType);
EXPECT_EQ(p->HttpHeaders.ContentLanguage, headers.ContentLanguage);
EXPECT_EQ(p->HttpHeaders.ContentDisposition, headers.ContentDisposition);
EXPECT_EQ(p->HttpHeaders.CacheControl, headers.CacheControl);
EXPECT_EQ(p->HttpHeaders.ContentEncoding, headers.ContentEncoding);
}
blobClient0.Create();
@ -369,14 +412,24 @@ namespace Azure { namespace Storage { namespace Test {
std::string blobSnapshotUri;
auto create_snapshot = [&]() {
std::string snapshot = blobClient0.CreateSnapshot()->Snapshot;
BlobSnapshotSasBuilder.Snapshot = snapshot;
blobSnapshotUri = blobClient0.WithSnapshot(snapshot).GetUri();
};
auto verify_blob_snapshot_read = [&](const std::string sas) {
auto blobSnapshotClient = Blobs::AppendBlobClient(blobSnapshotUri + "&" + sas.substr(1));
UriBuilder blobSnapshotUriWithSas(blobSnapshotUri);
blobSnapshotUriWithSas.AppendQueries(sas);
auto blobSnapshotClient = Blobs::AppendBlobClient(blobSnapshotUriWithSas.ToString());
auto downloadedContent = blobSnapshotClient.Download();
EXPECT_TRUE(ReadBodyStream(downloadedContent->BodyStream).empty());
};
auto verify_blob_snapshot_delete = [&](const std::string sas) {
auto blobSnapshotClient = Blobs::AppendBlobClient(blobSnapshotUri + "&" + sas.substr(1));
UriBuilder blobSnapshotUriWithSas(blobSnapshotUri);
blobSnapshotUriWithSas.AppendQueries(sas);
auto blobSnapshotClient = Blobs::AppendBlobClient(blobSnapshotUriWithSas.ToString());
EXPECT_NO_THROW(blobSnapshotClient.Delete());
};
@ -386,19 +439,24 @@ namespace Azure { namespace Storage { namespace Test {
Blobs::BlobSasPermissions::Delete,
})
{
std::string snapshot = blobClient0.CreateSnapshot()->Snapshot;
BlobSnapshotSasBuilder.Snapshot = snapshot;
blobSnapshotUri = blobClient0.WithSnapshot(snapshot).GetUri();
create_snapshot();
BlobSnapshotSasBuilder.SetPermissions(permissions);
auto sasToken = BlobSnapshotSasBuilder.ToSasQueryParameters(*keyCredential);
auto sasToken2 = BlobSnapshotSasBuilder.ToSasQueryParameters(userDelegationKey, accountName);
if ((permissions & Blobs::BlobSasPermissions::Read) == Blobs::BlobSasPermissions::Read)
{
verify_blob_snapshot_read(sasToken);
verify_blob_snapshot_read(sasToken2);
}
if ((permissions & Blobs::BlobSasPermissions::Delete) == Blobs::BlobSasPermissions::Delete)
{
create_snapshot();
sasToken = BlobSnapshotSasBuilder.ToSasQueryParameters(*keyCredential);
verify_blob_snapshot_delete(sasToken);
create_snapshot();
sasToken2 = BlobSnapshotSasBuilder.ToSasQueryParameters(userDelegationKey, accountName);
verify_blob_snapshot_delete(sasToken2);
}
}
}