Azure Blob how to properly create and consume a SAS token - c#

Before I begin, allow me to say that I have scoured MSFTs docs, everything seems to imply that I need to manually handroll the GET request? Which seems wrong, given that the SDK handles that for us.
What I am experiencing from my Xamarin app is the following 403 error when I try to run my code to get a list of blobs.
<?xml version="1.0" encoding="utf-8"?><Error><Code>AuthenticationFailed</Code><Message>Server failed to authenticate the request. Make sure the value of the Authorization header is formed correctly including the signature.</Message></Error>
The way my workflow goes is as follows:
App Makes request to API to get Azure SAS token
API Responds using the following code to return the SAS token (blobServiceClient is defined using the default emulator storage connection string):
try
{
var client = blobServiceClient.GetBlobContainerClient(request.VenueId);
var permissions = BlobContainerSasPermissions.Read | BlobContainerSasPermissions.List;
var sas = client.GenerateSasUri(permissions, DateTimeOffset.Now.AddHours(1));
var containerUri = "";
#if DEBUG
var temp = sas.AbsoluteUri;
var replaced = temp.Replace("http://127.0.0.1:10000/", "http://myngrokproxy.ngrok.io/");
containerUri = replaced;
#else
containerUri = sas.AbsoluteUri;
#endif
//var sas = containerUri + container.GetSharedAccessSignature(accessPolicy);
return new AzureSASResponse
{
SAS = containerUri
};
} catch (Exception e)
{
return null;
}
As you can see, the replace is there since localhost URL is meaningless for the emulator.
App side I try to consume it as follows:
try
{
var uri = new Uri(SAS);
var containerClient = new BlobContainerClient(uri);
var blobs = containerClient.GetBlobs().AsPages();
foreach(var blob in blobs)
{
Console.WriteLine(blob);
}
} catch (Exception e)
{
Console.WriteLine(e);
}
My resulting SAS token looks like so: "http://myngrokproxy.ngrok.io/devstoreaccount1/8dc9e4831d634629b386680ad7c9a324?sv=2020-08-04&se=2021-10-21T21%3A43%3A16Z&sr=c&sp=rl&sig=oncjUlSLMsOS3WbxUWqjXDp28WACYxxVqUElrK%2BYNlY%3D"
How can I go about A) setting the auth header on it, even the GET request that fails is the .GetBlobs method in the Xamarin app?

After much trial and error my ways to fix it were as follows:
Use latest version of azurite from Microsoft, I used the original old one (Arafuto/azurite)
change code to look as follows;
var sasBuilder = new BlobSasBuilder()
{
BlobContainerName = containerClient.Name,
Resource = "c",
StartsOn = DateTimeOffset.UtcNow.AddMinutes(-15),
ExpiresOn = DateTimeOffset.UtcNow.AddDays(7)
};
sasBuilder.SetPermissions(BlobSasPermissions.Read | BlobSasPermissions.List);
var client = blobServiceClient.GetBlobContainerClient(request.VenueId);
var permissions = BlobContainerSasPermissions.Read | BlobContainerSasPermissions.List;
var sas = client.GenerateSasUri(sasBuilder);
var containerUri = "";
#if DEBUG
var temp = sas.AbsoluteUri;
var replaced = temp.Replace("http://127.0.0.1:10000/", "http://myngrokproxy.ngrok.io/");
containerUri = replaced;
#else
containerUri = sas.AbsoluteUri;
#endif
return new AzureSASResponse
{
SAS = containerUri
};
The inspiration for the BlobSasBuilder came from this document: https://learn.microsoft.com/en-us/azure/storage/blobs/storage-blob-user-delegation-sas-create-dotnet#get-a-user-delegation-sas-for-a-container

Related

Problems with Google Cloud Platform authentication

we are experiencing problems with API authentication of our project in asp-net core 3.1. Specifically we have integrated the text-to-speech service provided by Google. Locally everything works correctly, but this does not happen when the web-app is online.
try
{
var path = "C://GoogleVoice//food-safety-trainer-47a9337eda0f.json";
var credential = GoogleCredential.FromFile(path);
var storage = StorageClient.Create(credential);
TextToSpeechClient client = TextToSpeechClient.Create();
var test = client.GrpcClient;
// The input can be provided as text or SSML.
SynthesisInput input = new SynthesisInput
{
Text = text
};
VoiceSelectionParams voiceSelection = new VoiceSelectionParams();
voiceSelection.LanguageCode = "it-IT";
voiceSelection.Name = "it-IT-Wavenet-A";
voiceSelection.SsmlGender = SsmlVoiceGender.Female;
// The audio configuration determines the output format and speaking rate.
AudioConfig audioConfig = new AudioConfig
{
AudioEncoding = AudioEncoding.Mp3
};
SynthesizeSpeechResponse response = client.SynthesizeSpeech(input, voiceSelection, audioConfig);
var result = _mp3Helper.SaveFile(response);
if (result.Item1 == "Success")
return Json(new { Result = true, Value = result.Item2 });
else
return Json(new { Result = false, Error = result.ToString() });
}
catch(Exception ex)
{
return Json(new { Result = false, Error = ex.Message.ToString() });
}
The Application Default Credentials are not available. They are available if running in Google Compute Engine. Otherwise, the environment variable GOOGLE_APPLICATION_CREDENTIALS must be defined pointing to a file defining the credentials. See https://developers.google.com/accounts/docs/application-default-credentials for more information.
Assuming you want to use the same service account for both Speech and Storage, you need to specify the credentials for the text-to-speech client. Options:
Set the GOOGLE_APPLICATION_DEFAULT_CREDENTIALS environment variable to refer to the JSON file. Ideally, do that as part of deployment configuration rather than in your code, but you can set the environment variable in your code if you want to. At that point, you can remove any explicit loading/setting of the credential for the Storage client.
Specify the CredentialPath in TextToSpeechClientBuilder:
var client = new TextToSpeechClientBuilder { CredentialPath = path }.Build();
This will load a separate credential.
Specify the credential's token access method via the TokenAccessMethod property in TextToSpeechClientBuilder:
var client = new TextToSpeechClientBuilder
{
TokenAccessMethod = credential.GetAccessTokenForRequestAsync
}.Build();

How to create a new Microsoft Azure HybridConnection via code?

I am using HybridConnectionNamespace and create multiple HybridConnections via Azure portal. The question is quite simple. How can I create it programmatically (Azure SDK, PowerShell scripts, etc.)?
According to this article, there are currently two different ways to create a relay namespace.
Azure portal and Azure Resource Manager templates
If you want to create it programmatically, I suggest you could use azure rest api to send the deployment templates by codes.
More details, you could refer to this article and codes:
Notice: If you want to use rest api to send request to azure, you need firstly create an Azure Active Directory application and service principal. After you generate the service principal, you could get the applicationid,access key and talentid. More details, you could refer to this article.
Rest Body(json.txt):
Notice: You need change the parameters' name and location value.
{"properties":{"mode":"incremental","debugSetting":{"detailLevel":"RequestContent, ResponseContent"},"parameters":{"name":{"value":"yourrelayname"},"location":{"value":"location"}},"template":{"$schema":"http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json#","contentVersion":"1.0.0.0","parameters":{"name":{"type":"string"},"location":{"type":"string"}},"resources":[{"apiVersion":"2016-07-01","name":"[parameters('name')]","location":"[parameters('location')]","type":"Microsoft.Relay/namespaces","properties":{"namespaceType":"Relay"}}]}}}
Code:
string body = File.ReadAllText(#"D:\json.txt");
// Display the file contents to the console. Variable text is a string.
string tenantId = "tenantId";
string clientId = "clientId(applicationid)";
string clientSecret = "applicationSecret";
string subscription = "subscriptionId";
string resourcegroup = "Youresourcegroup";
string authContextURL = "https://login.windows.net/" + tenantId;
var authenticationContext = new AuthenticationContext(authContextURL);
var credential = new ClientCredential(clientId, clientSecret);
var result = authenticationContext.AcquireTokenAsync(resource: "https://management.azure.com/", clientCredential: credential).Result;
if (result == null)
{
throw new InvalidOperationException("Failed to obtain the JWT token");
}
string token = result.AccessToken;
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(string.Format("https://management.azure.com/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Resources/deployments/Microsoft.Relay?api-version=2016-07-01", subscription, resourcegroup));
request.Method = "PUT";
request.Headers["Authorization"] = "Bearer " + token;
request.ContentType = "application/json";
try
{
using (var streamWriter = new StreamWriter(request.GetRequestStream()))
{
streamWriter.Write(body);
streamWriter.Flush();
streamWriter.Close();
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
//Get the response
var httpResponse = (HttpWebResponse)request.GetResponse();
Console.WriteLine(httpResponse.StatusCode);
Console.ReadLine();
Result:
Use PowerShell CmdLets | Hybrid Connection Manager:
Add-HybridConnection
Update-HybridConnection
Remove-HybridConnection
Get-HybridConnection
Set-HybridConnectionManagerConfiguration
https://msdn.microsoft.com/en-us/library/azure/dn789178.aspx
GoodLuck

C# Getting result status when deploing Azure Virtual Machine using Azure Resource Manager(ARM)

I'd like to get result of azure vm deployment using Azure Resource Manager(ARM) with .NET C# to recognize its success or fail.
I found following sample.
https://learn.microsoft.com/en-us/azure/virtual-machines/virtual-machines-windows-csharp-template
In this article, when deploing, "return await" statement is used.
public static async Task<DeploymentExtended> CreateTemplateDeploymentAsync(
TokenCredentials credential,
string groupName,
string deploymentName,
string subscriptionId){
Console.WriteLine("Creating the template deployment...");
var deployment = new Deployment();
deployment.Properties = new DeploymentProperties
{
Mode = DeploymentMode.Incremental,
Template = File.ReadAllText("..\\..\\VirtualMachineTemplate.json"),
Parameters = File.ReadAllText("..\\..\\Parameters.json")
};
var resourceManagementClient = new ResourceManagementClient(credential)
{ SubscriptionId = subscriptionId };
return await resourceManagementClient.Deployments.CreateOrUpdateAsync(
groupName,
deploymentName,
deployment);
}
How can I handle the result?
I want to devide program according to the result.
We can get the deployment status using the Properties.ProvisioningState. But when it deploy VM failed, may throw exception, so we need to catch the exception with code.
1.Code demo :
var token = GetAccessTokenAsync();
var credential = new TokenCredentials(token.Result.AccessToken);
string provisoningStatus = "Failed";
try
{
var result =CreateTemplateDeploymentAsync(credential, "tom", "MyWindowsVM", "you subscription Id")
.Result;
provisoningStatus = result.Properties.ProvisioningState;
}
catch (Exception)
{
//ToDo
}
if (provisoningStatus.Equals("Failed"))
{
//TODo
}
}
Create a VM successfully
Check from the Azure Portal
If it is failed without catching exception

IIS random hangs with Windows Azure Storage Encryption

we are currently developping a web application in ASP NET MVC 4 (C#) and Angularjs where our generated content is encrypted and kept on Windows Azure.
Sometimes, randomly, IIS hangs on requests and they remain in ExecuteRequestHandler stage. Once this happened, all subsequent requests will hang and no error could be found in our IIS logs.
We use Microsoft.WindowsAzure SDK to handle everything with Windows Azure and encrypt all blobs with an encryption policy given to blob during download/upload operations (EncryptionPolicy property). We keep our encryption key in a key vault on Windows Azure also.
Here's the code to retrieve our policy:
public async static Task<string> GetToken(string authority, string resource, string scope)
{
var authContext = new AuthenticationContext(authority);
var credentials = new ClientCredential(AzureSection.ClientId, AzureSection.ClientSecret);
var result = await authContext.AcquireTokenAsync(resource, credentials);
if(result == null)
throw new InvalidOperationException("Error when obtaining KeyVault token");
return result.AccessToken;
}
public static BlobEncryptionPolicy GetUploadPolicy()
{
if (_policyUpload == null && !IsDebug)
{
var resolver = new KeyVaultKeyResolver(KeyVaultHelper.GetToken);
var key = resolver.ResolveKeyAsync(AzureSection.ResourceGroup.KeyVault.Url, CancellationToken.None).GetAwaiter().GetResult();
_policyUpload = new BlobEncryptionPolicy(key, null);
}
return _policyUpload;
}
public static BlobEncryptionPolicy GetDownloadPolicy()
{
if (_policyDownload == null && !IsDebug)
{
var resolver = new KeyVaultKeyResolver(KeyVaultHelper.GetToken);
_policyDownload = new BlobEncryptionPolicy(null, resolver);
}
return _policyDownload;
}
And how we use it during a download/upload operation:
_policyDownload = PolicyHelper.GetDownloadPolicy();
public Task DownloadToStreamAsync(Stream output)
{
return !BaseBlob.Exists() ? null
: BaseBlob.DownloadToStreamAsync(output, null, new BlobRequestOptions{
DisableContentMD5Validation = true,
EncryptionPolicy = _policyDownload
}, null);
}
Where BaseBlob is a Microsoft.WindowsAzure.Storage.Blob.CloudBlockBlob and AzureSection is a section in web.config.
Our server is IIS 7.5 running on Windows Server 2008 R2.
We tried on a Windows Server 2012 and IIS 8, but same issue.
All works fine when we remove EncryptionPolicy from blob operations.
Has anyone an idea why it is happening, because I couldn't find anything (since azure encryption has been implemented recently and an hanging request doesn't provide us any error) ?
EDIT
I tried to use this instead to retrieve our encryption policy (as proposed in comments of https://azure.microsoft.com/en-us/documentation/articles/storage-encrypt-decrypt-blobs-key-vault/):
using Microsoft.Azure.KeyVault.WebKey;
var kv = new KeyVaultClient(Utils.GetToken);
var key = kv.GetKeyAsync("https://contosokeyvault.vault....").Result;
var rsa = new RsaKey(key.KeyIdentifier.Identifier, key.Key.ToRSA());
but still same issue.
EDIT 2
I also tried to not use a static property to keep our policies for upload and download, but still same issue.
Know this is a bit of an old one, but we recently ran into this problem and managed to fix it. The issue is caused by a deadlock created in the async methods.
In the end a combination of suggestions made here https://stackoverflow.com/a/32429753/3391469 and here https://stackoverflow.com/a/27581462/3391469 did the trick.
blockBlob.DownloadToStream(stream, null, Task.Run(() => GetOptions()).Result);
private async static Task<BlobRequestOptions> GetOptions()
{
var cloudResolver = new KeyVaultKeyResolver(GetToken);
var rsa = await cloudResolver.ResolveKeyAsync(Config.Get("AzureKeyVault"), CancellationToken.None).ConfigureAwait(false);
var policy = new BlobEncryptionPolicy(rsa, null);
return new BlobRequestOptions() { EncryptionPolicy = policy };
}

Consume WSDL that requires action level authorization in C#

I'm attempting to consume a 3rd party WSDL. I have added it as a service reference. I initalize the client and query paramaters like this:
var ltRequest = new SearchEmailAddressStatus
{
EmailAddress = emailAddressList.ToArray()
};
var ltClient = new CommunicationPreferenceServiceClient
{
ClientCredentials =
{
UserName =
{
UserName = ltProperties.CompanyCredential.UserName,
Password = ltProperties.CompanyCredential.Password
}
}
};
var ltResponse = ltClient.searchEmailAddressStatusWS(ltRequest);
After watching the packets in Fiddler, I've noticed the Auth header is never sent to the server. Is there any way to manually insert an authorization header in my request?
Okay, after a lot of digging I found the answer. After declaring the client, I used the following code:
using (var scope = new OperationContextScope(ltClient.InnerChannel))
{
var reqProperty = new HttpRequestMessageProperty();
reqProperty.Headers[HttpRequestHeader.Authorization] = "Basic "
+ Convert.ToBase64String(Encoding.ASCII.GetBytes(
ltClient.ClientCredentials.UserName.UserName + ":" +
ltClient.ClientCredentials.UserName.Password));
OperationContext.Current
.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = reqProperty;
var ltResponse = ltClient.searchEmailAddressStatusWS(ltRequest);
}
I believe this is the least-dirty means of getting a customized header inside wsdl request. If someone has a better method, I'd love to hear it.

Categories