Force HTTPS connection to Azure Blob Storage - azure

I would like to only allow secure HTTP (HTTPS) connection / access to blob container through Shared Access Signature (SAS) URL on Azure Blob Storage (ABS).
Can that be achieved? How?

Please use this override of CloudBlobContainer.GetSharedAccessSignature for including the protocol restriction.
Here's sample code to do the same:
static void GetHttpsOnlySas()
{
var storageAccount = new CloudStorageAccount(new StorageCredentials(accountName, accountKey), true);
var blobClient = storageAccount.CreateCloudBlobClient();
var blobContainer = blobClient.GetContainerReference("container-name");
var sas = blobContainer.GetSharedAccessSignature(new SharedAccessBlobPolicy()
{
Permissions = SharedAccessBlobPermissions.List,
SharedAccessExpiryTime = DateTime.UtcNow.AddHours(1)
},
null,
SharedAccessProtocol.HttpsOnly,//This option will force SAS to work only on HTTPS
null);
}

When you create the sas token you can set a "protocol" parameter. If you set it to https only https will be allowed. More info can be found here: https://azure.microsoft.com/en-us/documentation/articles/storage-dotnet-shared-access-signature-part-1/
When using the sdk to generate the sas token you have to use this method: https://msdn.microsoft.com/en-us/library/azure/mt616571.aspx and set the SharedAccessProtocol parameter.

Related

How to create Azure Storage SAS token using DefaultAzureCredential class

I want to create SAS token to download a blob stored in container in azure storage. I can easily generate SAS token using shared credential but this requires storage access key. How can I generate sas token using managed Identity.
credential, err := azblob.NewSharedKeyCredential(accountName, accountKey)
sasQueryParams, err := azblob.BlobSASSignatureValues{
Protocol: azblob.SASProtocolHTTPS,
ExpiryTime: time.Now().UTC().Add(4 * time.Hour),
ContainerName: containerName,
BlobName: blobName,
Permissions: azblob.BlobSASPermissions{Add: false,
Read: true, Write: false}.String(),
}.NewSASQueryParameters(credential)
How can I generate sas token using managed Identity?
You can generate it by using DefaultAzureCredential and the proper access to that blob in the storage container.
Connect to the storage account by using the Azure AD credentials of Default Azure Credential class.
Sample Code:
var strgAccName = _configuration.GetValue<string>("YourStorageAccountName");
var saUri = $"https://{strgAccName}.blob.core.windows.net";
var blobServiceClient = new BlobServiceClient(new Uri(saUri), new DefaultAzureCredential());
var blobContainerClient = blobServiceClient.GetBlobContainerClient(_configuration.GetValue<string>("YourContainerName"));
var blobClient = blobContainerClient.GetBlobClient("YourImage.jpg");
// We can issue the SAS token till a maximum of 7 days.
var userDelegationKey = blobServiceClient.GetUserDelegationKey(DateTimeOffset.UtcNow,
DateTimeOffset.UtcNow.AddHours(4));
var sasBuilder = new BlobSasBuilder()
{
BlobContainerName = blobClient.BlobContainerName,
BlobName = blobClient.Name,
Resource = "b", // b: blob, c: container
StartsOn = DateTimeOffset.UtcNow,
ExpiresOn = DateTimeOffset.UtcNow.AddHours(4),
};
sasBuilder.SetPermissions(BlobSasPermissions.Read);
var blobUriBuilder = new BlobUriBuilder(blobClient.Uri)
{
Sas = sasBuilder.ToSasQueryParameters(userDelegationKey,blobServiceClient.AccountName)
};
// Read this in any view like `blobUriBuilder.ToUri().ToString();`
}
And re-check the delegated access is there or not for that blob.
So, we don't use any access key and connection string for this.
Thanks to #Anupam Maiti for this Article, please refer this for step-by-step procedure.

Azure Blob SAS with IP Range Restriction

I'm trying to create SAS URIs / Tokens to allow download of my Azure Storage Blobs.
I'd like to do this on a blob-level, in order to not inadvertently give access to an unintended resource.
The current code I use to do this is:
public static string GetBlobSasUri(string containerName, string reference)
{
// Create the CloudBlobContainer object
CloudBlobContainer container = blobClient.GetContainerReference(containerName);
container.CreateIfNotExists();
// Get a reference to a blob within the container.
CloudBlockBlob blob = container.GetBlockBlobReference(reference);
// Set the expiry time and permissions for the blob.
// In this case, the start time is specified as a few minutes in the past, to mitigate clock skew.
// The shared access signature will be valid immediately.
SharedAccessBlobPolicy sasConstraints = new SharedAccessBlobPolicy();
sasConstraints.SharedAccessStartTime = DateTimeOffset.UtcNow.AddMinutes(-5);
sasConstraints.SharedAccessExpiryTime = DateTimeOffset.UtcNow.AddMonths(1);
sasConstraints.Permissions = SharedAccessBlobPermissions.Read;
// Generate the shared access signature on the blob, setting the constraints directly on the signature.
string sasBlobToken = blob.GetSharedAccessSignature(sasConstraints);
// Return the URI string for the container, including the SAS token.
return blob.Uri + sasBlobToken;
}
This is largely based on the example in Documentation here:
Generate a shared access signature URI for a blob
This works. However, I see in other SAS documentation that it is possible to restrict to a certain IP range as well:
Service SAS Uri Example
My understanding of SAS tokens is that the signature signs all parameters, so I don't think this is as easy as just appending my IP range to the SAS URI returned from the code I pasted above, since the signature would then not match.
However, the SharedAccessBlobPolicy only has three fields, which are the start/end times of the access, as well as the permissions. I don't see anything about IP ranges.
Is it possible to set these permitted ranges when generating SAS URIs at the blob level, not for a full account?
Please use the code below:
public static string GetBlobSasUri(string ipAddressFrom, string ipAddressTo)
{
CloudStorageAccount storageAccount = new CloudStorageAccount(new StorageCredentials("account_name", "account_key"), true);
CloudBlobClient cloudBlobClient = storageAccount.CreateCloudBlobClient();
var cloudBlobContainer = cloudBlobClient.GetContainerReference("test-1");
cloudBlobContainer.CreateIfNotExists();
CloudBlockBlob blob = cloudBlobContainer.GetBlockBlobReference("a.txt");
var ipAddressRange = new IPAddressOrRange(ipAddressFrom, ipAddressTo);
var sasBlobToken = blob.GetSharedAccessSignature(new SharedAccessBlobPolicy()
{
Permissions = SharedAccessBlobPermissions.List,
SharedAccessExpiryTime = new DateTimeOffset(DateTime.UtcNow.AddHours(1))
}, null, null,null, ipAddressRange);
return blob.Uri + sasBlobToken;
}

How to provide an Azure Storage CNAME as part of the connection string?

All the connection strings for an Azure Storage account seem to only be the original url + account.
Is there any way to change the connection string to an Azure Storage account to be my CNAME instead?
EDIT: The answer below is awesome and a few hours AFTER the answer was posted (and ticked) I found some official documentation on this/the same answer :)
Great Question! I think you can but it will only work with Blob Storage as you can only specify a CNAME mapping for blob endpoint plus you will need to use your account name somewhere as it is used for authorization header computation. This is how I did it:
static void ConnectViaCname()
{
var cred = new StorageCredentials("account-name", "account-key");
var account = new CloudStorageAccount(cred, new Uri("https://cnamemapping.com"), null, null, null);
var client = account.CreateCloudBlobClient();
var containers = client.ListContainers();
foreach (var container in containers)
{
Console.WriteLine(container.Uri.AbsoluteUri);
}
}
UPDATE
If you wish to use connection string, please specify the connection string in this format:
DefaultEndpointsProtocol=https;
BlobEndpoint=https://cnamemapping.com;
AccountName=account-name;
AccountKey=account-key
So the code would be:
static void ConnectViaCname()
{
var connectionString = "DefaultEndpointsProtocol=https;BlobEndpoint=https://cnamemapping.com;AccountName=account-name;AccountKey=account-key";
var account = CloudStorageAccount.Parse(connectionString);
var client = account.CreateCloudBlobClient();
var containers = client.ListContainers();
foreach (var container in containers)
{
Console.WriteLine(container.Uri.AbsoluteUri);
}
}

How do i find bloburl with shared access token expired?

i have written this below code to get the blob url with cache expiry token, actually have set 2 hours to expire the blob url,
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(CloudConfigurationManager.GetSetting("StorageConnectionString"));
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
CloudBlobContainer container = blobClient.GetContainerReference(containerName);
CloudBlockBlob blockBlob = container.GetBlockBlobReference("blobname");
//Create an ad-hoc Shared Access Policy with read permissions which will expire in 2 hours
SharedAccessBlobPolicy policy = new SharedAccessBlobPolicy()
{
Permissions = SharedAccessBlobPermissions.Read,
SharedAccessExpiryTime = DateTime.UtcNow.AddHours(2),
};
SharedAccessBlobHeaders headers = new SharedAccessBlobHeaders()
{
ContentDisposition = string.Format("attachment;filename=\"{0}\"", "blobname"),
};
var sasToken = blockBlob.GetSharedAccessSignature(policy, headers);
blobUrl = blockBlob.Uri.AbsoluteUri + sasToken;
using this above code i get the blob url with valid expiry token, now i want to check blob url is valid or not in one client application.
I tried web request and http client approach by passing the URL and get the response status code. if the response code is 404 then I assuming the URL is expired if not the URL is still valid,but this approach taking more time.
Please suggest me any other way.
I tried running code very similar to yours, and I am getting a 403 error, which is actually what is expected in this case. Based on your question, I am not sure whether the 403 is more helpful to you than the 404. Here is code running in a console application that returns a 403:
class Program
{
static void Main(string[] args)
{
string blobUrl = CreateSAS();
CheckSAS(blobUrl);
Console.ReadLine();
}
//This method returns a reference to the blob with the SAS, and attempts to read it.
static void CheckSAS(string blobUrl)
{
CloudBlockBlob blob = new CloudBlockBlob(new Uri(blobUrl));
//If the DownloadText() method is run within the two minute period that the SAS is valid, it succeeds.
//If it is run after the SAS has expired, it returns a 403 error.
//Sleep for 3 minutes to trigger the error.
System.Threading.Thread.Sleep(180000);
Console.WriteLine(blob.DownloadText());
}
//This method creates the SAS on the blob.
static string CreateSAS()
{
string containerName = "forum-test";
string blobName = "blobname";
string blobUrl = "";
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(CloudConfigurationManager.GetSetting("StorageConnectionString"));
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
CloudBlobContainer container = blobClient.GetContainerReference(containerName);
container.CreateIfNotExists();
CloudBlockBlob blockBlob = container.GetBlockBlobReference(blobName + DateTime.Now.Ticks);
blockBlob.UploadText("Blob for forum test");
//Create an ad-hoc Shared Access Policy with read permissions which will expire in 2 hours
SharedAccessBlobPolicy policy = new SharedAccessBlobPolicy()
{
Permissions = SharedAccessBlobPermissions.Read,
SharedAccessExpiryTime = DateTime.UtcNow.AddMinutes(2),
};
SharedAccessBlobHeaders headers = new SharedAccessBlobHeaders()
{
ContentDisposition = string.Format("attachment;filename=\"{0}\"", blobName),
};
var sasToken = blockBlob.GetSharedAccessSignature(policy, headers);
blobUrl = blockBlob.Uri.AbsoluteUri + sasToken;
return blobUrl;
}
}
There are cases in which SAS failures do return a 404, which can create problems for troubleshooting operations using SAS. The Azure Storage team is aware of this issue and in future releases SAS failures may return a 403 instead. For help troubleshooting a 404 error, see http://azure.microsoft.com/en-us/documentation/articles/storage-monitoring-diagnosing-troubleshooting/#SAS-authorization-issue.
I also ran into the same issue a few days back. I was actually expecting storage service to return a 403 error code when the SAS token has expired but storage service returns 404 error.
Given that we don't have any other option, the way you're doing it is the only viable way but it is still not correct because you could get 404 error if the blob is not present in the storage account.
Maybe you can parse "se" argument from the generated SAS, which means expiry time, e.g. "se=2013-04-30T02%3A23%3A26Z". However, since the server time might not be the same as client time, the solution may be unstable.
http://azure.microsoft.com/en-us/documentation/articles/storage-dotnet-shared-access-signature-part-1/
You're using UTC time for SharedAccessExpiryTime (see "Expiry Time" in https://learn.microsoft.com/en-us/azure/storage/common/storage-dotnet-shared-access-signature-part-1#parameters-common-to-account-sas-and-service-sas-tokens).
The expiry time then is registered under the se claim in the token the value of which can be checked against current UTC time on the client side before actually using the token. This way you'd save yourself from making an extra call to the Blob storage only to find out whether the token is expired.

Create Shared Access Token with Microsoft.WindowsAzure.Storage returns 403

I have a fairly simple method that uses the NEW Storage API to create a SAS and copy a blob from one container to another.
I am trying to use this to Copy blob BETWEEN STORAGE ACCOUNTS. So I have TWo Storage accounts, with the exact same Containers, and I am trying to copy a blob from the Storage Account's Container to another Storage Account's Container.
I don't know if the SDK is built for that, but it seems like it would be a common scenario.
Some additional information:
I create the token on the Destination Container.
Does that token need to be created on both the source and destination? Does it take time to register the token? Do I need to create it for each request, or only once per token "lifetime"?
I should mention a 403 is an Unauthorized Result http error code.
private static string CreateSharedAccessToken(CloudBlobClient blobClient, string containerName)
{
var container = blobClient.GetContainerReference(containerName);
var blobPermissions = new BlobContainerPermissions();
// The shared access policy provides read/write access to the container for 10 hours:
blobPermissions.SharedAccessPolicies.Add("SolutionPolicy", new SharedAccessBlobPolicy()
{
// To ensure SAS is valid immediately we don’t set start time
// so we can avoid failures caused by small clock differences:
SharedAccessExpiryTime = DateTime.UtcNow.AddHours(1),
Permissions = SharedAccessBlobPermissions.Write |
SharedAccessBlobPermissions.Read
});
blobPermissions.PublicAccess = BlobContainerPublicAccessType.Blob;
container.SetPermissions(blobPermissions);
return container.GetSharedAccessSignature(new SharedAccessBlobPolicy(), "SolutionPolicy");
}
Down the line I use this token to call a copy operation, which returns a 403:
var uri = new Uri(srcBlob.Uri.AbsoluteUri + blobToken);
destBlob.StartCopyFromBlob(uri);
My version of Azure.Storage is 2.1.0.2.
Here is the full copy method in case that helps:
private static void CopyBlobs(
CloudBlobContainer srcContainer, string blobToken,
CloudBlobContainer destContainer)
{
var srcBlobList
= srcContainer.ListBlobs(string.Empty, true, BlobListingDetails.All); // set to none in prod (4perf)
//// get the SAS token to use for all blobs
//string token = srcContainer.GetSharedAccessSignature(
// new SharedAccessBlobPolicy(), "SolutionPolicy");
bool pendingCopy = true;
foreach (var src in srcBlobList)
{
var srcBlob = src as ICloudBlob;
// Determine BlobType:
ICloudBlob destBlob;
if (srcBlob.Properties.BlobType == BlobType.BlockBlob)
{
destBlob = destContainer.GetBlockBlobReference(srcBlob.Name);
}
else
{
destBlob = destContainer.GetPageBlobReference(srcBlob.Name);
}
// Determine Copy State:
if (destBlob.CopyState != null)
{
switch (destBlob.CopyState.Status)
{
case CopyStatus.Failed:
log.Info(destBlob.CopyState);
break;
case CopyStatus.Aborted:
log.Info(destBlob.CopyState);
pendingCopy = true;
destBlob.StartCopyFromBlob(destBlob.CopyState.Source);
return;
case CopyStatus.Pending:
log.Info(destBlob.CopyState);
pendingCopy = true;
break;
}
}
// copy using only Policy ID:
var uri = new Uri(srcBlob.Uri.AbsoluteUri + blobToken);
destBlob.StartCopyFromBlob(uri);
//// copy using src blob as SAS
//var source = new Uri(srcBlob.Uri.AbsoluteUri + token);
//destBlob.StartCopyFromBlob(source);
}
}
And finally the account and client (vetted) code:
var credentials = new StorageCredentials("BAR", "FOO");
var account = new CloudStorageAccount(credentials, true);
var blobClient = account.CreateCloudBlobClient();
var sasToken = CreateSharedAccessToken(blobClient, "content");
When I use a REST client this seems to work... any ideas?
Consider also this problem:
var uri = new Uri(srcBlob.Uri.AbsoluteUri + blobToken);
Probably you are calling the "ToString" method of Uri that produce a "Human redable" version of the url. If the blobToken contain special caracters like for example "+" this will cause a token malformed error on the storage server that will refuse to give you the access.
Use this instead:
String uri = srcBlob.Uri.AbsoluteUri + blobToken;
Shared Access Tokens are not required for this task. I ended up with two accounts and it works fine:
var accountSrc = new CloudStorageAccount(credsSrc, true);
var accountDest = new CloudStorageAccount(credsSrc, true);
var blobClientSrc = accountSrc.CreateCloudBlobClient();
var blobClientDest = accountDest.CreateCloudBlobClient();
// Set permissions on the container.
var permissions = new BlobContainerPermissions {PublicAccess = BlobContainerPublicAccessType.Blob};
srcContainer.SetPermissions(permissions);
destContainer.SetPermissions(permissions);
//grab the blob
var sourceBlob = srcContainer.GetBlockBlobReference("FOO");
var destinationBlob = destContainer.GetBlockBlobReference("BAR");
//create a new blob
destinationBlob.StartCopyFromBlob(sourceBlob);
Since both CloudStorageAccount objects point to the same account, copying without a SAS token would work just fine as you also mentioned.
On the other hand, you need either a publicly accessible blob or a SAS token to copy from another account. So what you tried was correct, but you established a container-level access policy, which can take up to 30 seconds to take effect as also documented in MSDN. During this interval, a SAS token that is associated with the stored access policy will fail with status code 403 (Forbidden), until the access policy becomes active.
One more thing that I would like to point is; when you call Get*BlobReference to create a new blob object, the CopyState property will not be populated until you do a GET/HEAD operation such as FetchAttributes.

Resources