I'm trying to generate a SAS signed URL to download a file from an Azure file storage (using this as an example):
using Azure.Storage;
using Azure.Storage.Files.Shares;
using Azure.Storage.Files.Shares.Models;
using Azure.Storage.Sas;
(...)
public Uri getFileUri(string fileName)
{
string AccountName = WebConfigurationManager.AppSettings["AzureStorageDepotAccountName"];
string AccountKey = WebConfigurationManager.AppSettings["AzureStorageDepotAccountKey"];
sharedKeyCredential = new StorageSharedKeyCredential(AccountName, AccountKey);
shareClient = new ShareClient(new Uri("https://sanitizedShare.file.core.windows.net/"), sharedKeyCredential);
ShareDirectoryClient directory = shareClient.GetDirectoryClient("sanitizedDir");
ShareFileClient file = directory.GetFileClient(fileName);
var shareSasBuilder = new ShareSasBuilder
{
ShareName = "sanitizedShare",
FilePath = file.Uri.LocalPath,
Protocol = SasProtocol.None,
StartsOn = DateTime.UtcNow.AddHours(-1),
ExpiresOn = DateTime.UtcNow.AddHours(+2),
IPRange = new SasIPRange(IPAddress.None, IPAddress.None)
};
shareSasBuilder.SetPermissions(ShareFileSasPermissions.Read);
return new Uri(file.Uri + "?" + shareSasBuilder.ToSasQueryParameters(sharedKeyCredential).ToString());
}
It returns a correct looking URL (https://sanitizedShare.file.core.windows.net/sanitizedDir/sanitizedFile?sv=2019-07-07&st=2020-05-27T19:36:55Z&se=2020-05-27T22:36:55Z&sr=f&sp=r&sig=l3bLiYlA9Y+Se1jC1g/F5A0T4yOT0nUJHUxyLhNksw8=) but when I try it I get this error:
<Error>
<Code>AuthenticationFailed</Code>
<Message>
Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature. RequestId:8c400781-e01a-0040-4266-347d43000000 Time:2020-05-27T20:36:56.2303652Z
</Message>
<AuthenticationErrorDetail>Signature fields not well formed.</AuthenticationErrorDetail>
</Error>
At first I thought that I had the wrong credentials, but I'm using the same credentials elsewhere in my code and it can access the share. Do you know what the problem could be?
Make the following changes to your code:
1.add the file share name at the end of the url when create the ShareClient(Note:for the url, I see you're using fileshareName.file.core.windows.net, it should be your_storage_account_name.file.core.windows.net), like below:
var shareClient = new ShareClient(new Uri("https://your_storage_account_name.file.core.windows.net/the_share_name"), sharedKeyCredential);
2.in the code block of new ShareSasBuilder{}, remove FilePath = file.Uri.LocalPath,
Then I tested the code(with the latest version of Azure.Storage.Files.Shares 12.2.1), it generates a valid and working url with sastoken. My code as below:
string storageAccount= "yy1";
string password = "xxxx";
var sharedKeyCredential = new StorageSharedKeyCredential(storageAccount, password);
//the file share name is aaa
var shareClient = new ShareClient(new Uri("https://yy1.file.core.windows.net/aaa"), sharedKeyCredential);
ShareDirectoryClient directory = shareClient.GetDirectoryClient("a11");
ShareFileClient file = directory.GetFileClient("1.txt");
var shareSasBuilder = new ShareSasBuilder
{
ShareName = "aaa",
//FilePath = file.Uri.LocalPath,
Protocol = SasProtocol.None,
StartsOn = DateTime.UtcNow.AddHours(-1),
ExpiresOn = DateTime.UtcNow.AddHours(+2),
IPRange = new SasIPRange(IPAddress.None, IPAddress.None)
};
shareSasBuilder.SetPermissions(ShareFileSasPermissions.Read);
var url = new Uri(file.Uri + "?" + shareSasBuilder.ToSasQueryParameters(sharedKeyCredential).ToString());
Related
I'm trying to update my upload and delete code for Azure.Storage.Blobs 12.10 version and just not having any luck. This is how I used to do it in 12.8:
'''
var name = Guid.NewGuid().ToString();
var fullUri = new UriBuilder()
{
Scheme = "https",
Host = "MyStorageAccount.blob.core.windows.net",
Path = $"myimages/{App.User.EmailAddress}/{name}.jpg",
Query = subToken
};
var blobClient = new BlobClient(fullUri.Uri);
try
{
using var myStream = File.OpenRead(file.Path);
await blobClient.UploadAsync(myStream);
myStream.Close();
'''
All the examples on github are using the default storage connections string and not a SaS token. I have a serverless function that gets me a SaS token and I already have the container created. I just need a simple sample that uses a pre-existing SaS token and container. Any ideas would be appreciated very much.
John
EDIT: This is this 12.10 code I have been trying but it doesn't seem to like the URI.
'''
var name = Guid.NewGuid().ToString();
var blobUri = new BlobUriBuilder(new Uri(
$"https://myAccount.blob.core.windows.net/blobcontainer/{App.User.EmailAddress}/{name}.jpg?{subToken}"));
var completeUri = blobUri.ToUri();
var blobClientNew = new BlobClient(completeUri, null);
using var myStream3 = File.OpenRead(file.Path);
await blobClientNew.UploadAsync(myStream3);
myStream3.Close();
'''
A file name that contains a hash(#) in the name like (image#12.png) in m Blob storage. Usually, we are generating a SAS token to download the file. Regular files are downloading properly with that SAS token-based URL. But the file name which contains has not been downloaded. Below is the error I am getting in the browser.
<Code>AuthenticationFailed</Code>
<Message>Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature. RequestId:ab30ae48-001e-000f-46da-8969ff000000 Time:2021-08-05T09:15:13.1495259Z</Message>
<AuthenticationErrorDetail>Signature did not match. String to sign used was r 2021-08-05T09:15:11Z 2021-08-08T03:15:11Z /blob/<blob-account-name>/container/image#14.png 2020-08-04 b </AuthenticationErrorDetail>
</Error>
Below is my Node.js code to generate the SAS-based URL.
let originalname = "image#12.png";
let FPath = "<BlobPath>";
let urlParts = url.parse(FPath);
var blobName = urlParts.path.split('/').slice(2).join('/');
blobName = decodeURIComponent(blobName);
if (urlParts.hash !== null) {
blobName += decodeURIComponent(urlParts.hash);
blobName = blobName.replace(originalname, encodeURIComponent(originalname));
}
const blockBlobClient = containerClient.getBlockBlobClient(blobName);
let expiry = new Date();
expiry.setHours(expiry.getMinutes() + 60);
const sasToken = generateBlobSASQueryParameters(
{
containerName: containerClient._containerName,
blobName: blobName,
startsOn: new Date(),
expiresOn: expiry,
permissions: BlobSASPermissions.parse('r')
},
sharedKeyCredential
);
const sasUrl = `${decodeURIComponent(blockBlobClient.url)}?${sasToken}`;
That SasUrl URL I am accessing through the browser.
Can anyone help me with this?
I have file into Azure Blob Storage, container name = app-files, and path to the file = /files/{guid}/1.docx
I have the following code to get SAS link to that file:
BlobServiceClient blobServiceClient = new BlobServiceClient("con_string");
BlobContainerClient blobContainerClient = blobServiceClient.GetBlobContainerClient("app-files");
BlobClient blobClient = blobContainerClient.GetBlobClient(path);
var blobSasBuilder = new BlobSasBuilder
{
StartsOn = DateTime.UtcNow,
ExpiresOn = DateTime.UtcNow.AddMinutes(10),
BlobContainerName = blobClient.BlobContainerName,
BlobName = blobClient.Name
};
blobSasBuilder.SetPermissions(BlobSasPermissions.Read);
var storageSharedKeyCredential = new StorageSharedKeyCredential("account_name", "account_key");
BlobSasQueryParameters sasQueryParameters = blobSasBuilder.ToSasQueryParameters(storageSharedKeyCredential);
var url = blobClient.Uri.ToString() + "?" + sasQueryParameters.ToString();
return url;
This code works fine but downloaded file name = files_{guid}_1.docx, it seems it show full path to file but replace "/" with "_". I would like to see just file name = 1.docx .
Can I change downloaded file name somehow?
I fixed it with changing one line of code from:
var url = blobClient.Uri.ToString() + "?" + sasQueryParameters.ToString();
to:
var url = blobClient.Uri.AbsoluteUri.Replace("%2F", "/").ToString() + "?" + sasQueryParameters.ToString();
To read an Application setting in Azure function I can do
Environment.GetEnvironmentVariable("MyVariable", EnvironmentVariableTarget.Process);
Is it possible to get a Host key in a similar way? I like to identify the caller of my azure function based on the key they are using but hate to have a copy of this key in Application settings
You could install Microsoft.Azure.Management.ResourceManager.Fluent and Microsoft.Azure.Management.Fluent to do that easily.
The following is the demo that how to get kudu credentials and run Key management API .I test it locally, it works correctly on my side.
For more detail, you could refer to this SO thread with C# code or use powershell to get it.
string clientId = "client id";
string secret = "secret key";
string tenant = "tenant id";
var functionName ="functionName";
var webFunctionAppName = "functionApp name";
string resourceGroup = "resource group name";
var credentials = new AzureCredentials(new ServicePrincipalLoginInformation { ClientId = clientId, ClientSecret = secret}, tenant, AzureEnvironment.AzureGlobalCloud);
var azure = Azure
.Configure()
.Authenticate(credentials)
.WithDefaultSubscription();
var webFunctionApp = azure.AppServices.FunctionApps.GetByResourceGroup(resourceGroup, webFunctionAppName);
var ftpUsername = webFunctionApp.GetPublishingProfile().FtpUsername;
var username = ftpUsername.Split('\\').ToList()[1];
var password = webFunctionApp.GetPublishingProfile().FtpPassword;
var base64Auth = Convert.ToBase64String(Encoding.Default.GetBytes($"{username}:{password}"));
var apiUrl = new Uri($"https://{webFunctionAppName}.scm.azurewebsites.net/api");
var siteUrl = new Uri($"https://{webFunctionAppName}.azurewebsites.net");
string JWT;
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", $"Basic {base64Auth}");
var result = client.GetAsync($"{apiUrl}/functions/admin/token").Result;
JWT = result.Content.ReadAsStringAsync().Result.Trim('"'); //get JWT for call funtion key
}
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + JWT);
var key = client.GetAsync($"{siteUrl}/admin/functions/{functionName}/keys").Result.Content.ReadAsStringAsync().Result;
}
The output:
I've been following the AWS example on how to generate a V4 HMAC signature. I've done this successfully in Java but I'm trying to get it to work in Node/JavaScript. When I use my code I generate all the correct intermediary keys in their 1st example below but on the next example when given the test StringToSign the same code that generated the correct intermediary keys fails to generate the supposed correct signature.
Correct Intermediary Keys:
secretkey = 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY'
dateStamp = '20120215'
regionName = 'us-east-1'
serviceName = 'iam'
kSecret = '41575334774a616c725855746e46454d492f4b374d44454e472b62507852666943594558414d504c454b4559'
kDate = '969fbb94feb542b71ede6f87fe4d5fa29c789342b0f407474670f0c2489e0a0d'
kRegion = '69daa0209cd9c5ff5c8ced464a696fd4252e981430b10e3d3fd8e2f197d7a70c'
kService = 'f72cfd46f26bc4643f06a11eabb6c0ba18780c19a8da0c31ace671265e3c87fa'
kSigning = 'f4780e2d9f65fa895f9c67b32ce1baf0b0d8a43505a000a1a9e090d414db404d'
http://docs.aws.amazon.com/general/latest/gr/signature-v4-examples.html.
Fails With the Following Input
secretkey = 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY'
dateStamp = '20151229'
regionName = 'us-east-1'
serviceName = 's3'
StringToSign
eyAiZXhwaXJhdGlvbiI6ICIyMDE1LTEyLTMwVDEyOjAwOjAwLjAwMFoiLA0KICAiY29uZGl0aW9ucyI6IFsNCiAgICB7ImJ1Y2tldCI6ICJzaWd2NGV4YW1wbGVidWNrZXQifSwNCiAgICBbInN0YXJ0cy13aXRoIiwgIiRrZXkiLCAidXNlci91c2VyMS8iXSwNCiAgICB7ImFjbCI6ICJwdWJsaWMtcmVhZCJ9LA0KICAgIHsic3VjY2Vzc19hY3Rpb25fcmVkaXJlY3QiOiAiaHR0cDovL3NpZ3Y0ZXhhbXBsZWJ1Y2tldC5zMy5hbWF6b25hd3MuY29tL3N1Y2Nlc3NmdWxfdXBsb2FkLmh0bWwifSwNCiAgICBbInN0YXJ0cy13aXRoIiwgIiRDb250ZW50LVR5cGUiLCAiaW1hZ2UvIl0sDQogICAgeyJ4LWFtei1tZXRhLXV1aWQiOiAiMTQzNjUxMjM2NTEyNzQifSwNCiAgICB7IngtYW16LXNlcnZlci1zaWRlLWVuY3J5cHRpb24iOiAiQUVTMjU2In0sDQogICAgWyJzdGFydHMtd2l0aCIsICIkeC1hbXotbWV0YS10YWciLCAiIl0sDQoNCiAgICB7IngtYW16LWNyZWRlbnRpYWwiOiAiQUtJQUlPU0ZPRE5ON0VYQU1QTEUvMjAxNTEyMjkvdXMtZWFzdC0xL3MzL2F3czRfcmVxdWVzdCJ9LA0KICAgIHsieC1hbXotYWxnb3JpdGhtIjogIkFXUzQtSE1BQy1TSEEyNTYifSwNCiAgICB7IngtYW16LWRhdGUiOiAiMjAxNTEyMjlUMDAwMDAwWiIgfQ0KICBdDQp9
Correct Signature: 46503978d3596de22955b4b18d6dfb1d54e8c5958727d5bdcd02cc1119c60fc9
My Signature: e7318f0bfd7d86fb9ba81c314f62192ee2baf7273792ef01ffafeb430fc2fb68
http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-post-example.html
My Code
var crypto = require("crypto-js")
module.exports.getSignatureKey = function(key, dateStamp, regionName, serviceName) {
var kSecret = "AWS4" + key
var kDate = crypto.HmacSHA256(dateStamp, kSecret)
var kRegion = crypto.HmacSHA256(regionName, kDate)
var kService = crypto.HmacSHA256(serviceName, kRegion)
var kSigning = crypto.HmacSHA256("aws4_request", kService)
return kSigning;
}
module.exports.sign = function(signatureKey,stringToSign) {
var unencodedSignature = crypto.HmacSHA256(stringToSign,signatureKey)
return unencodedSignature
}
module.exports.getSignature = function(stringToSign,secretKey,dateStamp,regionName, serviceName) {
var signingKey = this.getSignatureKey(secretKey,dateStamp,regionName,serviceName)
return this.sign(signingKey,stringToSign)
}
The AWS example in the second link has the wrong signature. Using my solution I am able to successfully upload to s3.
Something else to consider is that the crypto-js node library outputs the signatures already in hex. Theres no need to do a manual conversion yourself as you would if you we in Java using the example code they provide.