C# - Using Azure Key Vault with Azure Storage on Native App - c#

I'm using the following code to upload to my image container in my Azure storange account. The connection string in app.config is:
<appSettings>
<add key="StorageConnectionString" value="MyConnectionString" />
</appSettings>
CloudStorageAccount storageAccount = CloudStorageAccount.Parse
CloudConfigurationManager.GetSetting("StorageConnectionString"));
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
// Retrieve reference to a previously created container.
CloudBlobContainer container = blobClient.GetContainerReference("imagestorage");
// Retrieve reference to a blob named "myblob".
CloudBlockBlob blockBlob = container.GetBlockBlobReference("IMG1.png");
// Create or overwrite the "myblob" blob with contents from a local file.
using (var fileStream = System.IO.File.OpenRead(#"D:\Untitled.png"))
{
blockBlob.UploadFromStream(fileStream);
}
Question is how do I intergrate Azure Key Vault into my native application so that my API keys will not be compromised by some annoying reverse engineers?
I've registered my app in Azure Active Directory and given permissions for Azure Key Vault.
Also, who ever tries to use my native desktop app has to log in to my ASP.NET Web API app with Individual Accounts and receive a token, before using any other features. All of my controllers require authorization.

I believe what you're trying to do is integrate your Azure KeyVault with your C# Application. You can do this my using 2 API. One being the Microsoft.Azure.KeyVault and the other being ADAL.
Following these steps may get you answer:
public async Task<string> GetToken(string authority, string resource, string scope)
{
var authContext = new AuthenticationContext(authority);
ClientCredential clientCred = new ClientCredential(ConfigurationManager.AppSettings["ClientID"], ConfigurationManager.AppSettings["ClientSecret"]);
AuthenticationResult result = await authContext.AcquireTokenAsync(resource, clientCred);
if(result == null)
{
throw new InvalidOperationException("Failed to obtain the JWT Token");
}
Console.WriteLine("Retrieved Password");
return result.AccessToken;
}
And then get the value of what you're trying to return by running this:
public async Task getvaluesAsync()
{
var kv = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(GetToken));
var sec = await kv.GetSecretAsync(ConfigurationManager.AppSettings["SecretURI"]);
EncryptSecret = sec.Value;
}
Replace the appropriate values of ClientID, Client Secret and SecretURI with your values in the App.config file. Use a getter and setter method with the "EcryptSecret" by doing something like,
public static string EncryptSecret { get; set; }
This will continuously store the password / DB connections for further use.
A few helpful articles would be:
https://learn.microsoft.com/en-us/azure/key-vault/key-vault-developers-guide

Related

Azure C# KeyVaultErrorException: Operation returned an invalid status code 'Forbidden'

I am writing a program that tries to access a secret (OneAuthZAuthentication) to an Azure Table Storage through accessing KeyVault. I am following the steps listed in this tutorial: https://jeanpaul.cloud/2019/12/07/azure-key-vault-access-from-c/
I have created a Key Vault called ITALocalBuildSecrets:
With the following DNS Name: https://italocalbuildsecrets.vault.azure.net/
I also have another secret with the following name (OneAuthZAuthentication):
I have created an app in the active directory (OneAuthZUserApplication), and you can see the Application (client) ID displayed below:
I created a client secret for OneAuthZUserApplication:
I authorized a Console Application (OneAuthZUserApplication) as an access policy:
And you can clearly see the access policy being registered:
Below is the code I am running:
// Retrieves the access token necessary to gain authentication into the key vault
[FunctionName("GetToken")]
public static async System.Threading.Tasks.Task<string> GetToken(string authority, string resource, string scope)
{
var clientId = "5cf497b0-3467-456a-a03a-4d4414b*****"; // Stars are for security reasons :D
var clientSecret = "468.26i5Wc.nQ6TYL-eOvBmcto.t.*****"; // Stars are for security reasons
ClientCredential credential = new ClientCredential(clientId, clientSecret);
var context = new AuthenticationContext(authority, TokenCache.DefaultShared);
var result = await context.AcquireTokenAsync(resource, credential);
return result.AccessToken;
}
// Retrieves the access key vault accountKey (needed to authenticate access into the role assignments table)
public static string GetVaultValue()
{
KeyVaultClient client = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(GetToken));
var vaultAddress = "https://italocalbuildsecrets.vault.azure.net/";
var secretName = "OneAuthZAuthentication";
var secret = client.GetSecretAsync(vaultAddress, secretName).GetAwaiter().GetResult();
return secret.Value;
}
[FunctionName("Function1")]
// Function that reads a small portion of the role assignments table (OneAuthZRoleAssignments) every
// configurable number of times
public static async System.Threading.Tasks.Task RunAsync([TimerTrigger("%TimerTriggerPeriod%")]TimerInfo myTimer, ILogger log)
{
Console.WriteLine($"Secret Value from Vault is: {GetVaultValue()}");
}
I get the following error:
Function1. Microsoft.Azure.KeyVault: Operation returned an invalid status code 'Forbidden'.
This does seems strange, considering that I authorized the OneAuthZUserApplication application to the key vault.
I follow you steps and use your code to test, and it all works very well.
Please go to confirm after adding Access policy, remember to click save button.
What is the authority you are using? Further, I think you are missing the step of configuring scopes when getting the token. Similar here, but using MSAL.
string[] scopeArray = new string[] { "https://vault.azure.net/.default" };
And provide that to your token request.
Also, if these are Azure Functions, why don't you use the function MSI to retrieve the secret? See here

retrieve secret from azure key vault

I am not able to retrieve a secret from azure key vault to a .net console app which runs in azure windows VM. Below is the code i have used and i have given service principal all permission in key vault.
var kvc = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(
async (string authority, string resource, string scope) => {
var authContext = new AuthenticationContext(authority);
var credential = new ClientCredential("App id, "secret identifier uri");
AuthenticationResult result = await authContext.AcquireTokenAsync(resource, credential);
if (result == null) {
throw new InvalidOperationException("Failed to retrieve JWT token");
}
return result.AccessToken;
}
));
Please reference this tutorial in the Microsoft documentation, where you can find the correct way to use Azure Key Vault inside a Windows VM, and using .NET. Note: In this solution, you will use Managed Service Identity, instead of the traditional Service Principal.

How to fix "KeyVaultErrorException: Operation returned an invalid status code 'Forbidden'" when downloading a storage blob

I'm getting a KeyVaultErrorException: Operation returned an invalid status code 'Forbidden'when i tried to download a storage blob (DownloadToStream).
I tried creating another rsa key. But the same error occured.
When i use a secret. Everthing is working fine.
I checked the keyvault access policies.
The User Account has full permissions.
The App has get and list permission for keys and secrets.
Here is the complete code.
It's based on this sample.
using System;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System.Configuration;
using Microsoft.WindowsAzure.Storage.Auth;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using Microsoft.Azure.KeyVault;
using System.Threading;
using System.IO;
namespace KeyVaultTest
{
class Program
{
static void Main(string[] args)
{
// This is standard code to interact with Blob Storage
StorageCredentials creds = new StorageCredentials(
ConfigurationManager.AppSettings["accountName"],
ConfigurationManager.AppSettings["accountKey"]);
CloudStorageAccount account = new CloudStorageAccount(creds, useHttps: true);
CloudBlobClient client = account.CreateCloudBlobClient();
CloudBlobContainer contain = client.GetContainerReference("test123");
contain.CreateIfNotExists();
// The Resolver object is used to interact with Key Vault for Azure Storage
// This is where the GetToken method from above is used
KeyVaultKeyResolver cloudResolver = new KeyVaultKeyResolver(GetToken);
// Retrieve the key that you created previously
// The IKey that is returned here is an RsaKey
// Remember that we used the names contosokeyvault and testrsakey1
var rsa = cloudResolver.ResolveKeyAsync("https://mykeystorage.vault.azure.net/keys/testkey9000/", CancellationToken.None).GetAwaiter().GetResult();
// Now you simply use the RSA key to encrypt by setting it in the BlobEncryptionPolicy.
BlobEncryptionPolicy policy = new BlobEncryptionPolicy(rsa, null);
BlobRequestOptions options = new BlobRequestOptions() { EncryptionPolicy = policy };
// Reference a block blob
CloudBlockBlob blob = contain.GetBlockBlobReference("MyFile.txt");
// Upload using the UploadFromStream method
using (var stream = System.IO.File.OpenRead(#"C:\temp\MyFile.txt"))
blob.UploadFromStream(stream, stream.Length, null, options, null);
// In this case we will not pass a key and only pass the resolver because
// this policy will only be used for downloading / decrypting
policy = new BlobEncryptionPolicy(null, cloudResolver);
options = new BlobRequestOptions() { EncryptionPolicy = policy };
using (var np = File.Open(#"C:\temp\MyFileDecrypted.txt", FileMode.Create))
blob.DownloadToStream(np, null, options, null);
}
private async static Task<string> GetToken(string authority, string resource, string scope)
{
var authContext = new AuthenticationContext(authority);
ClientCredential clientCred = new ClientCredential(
ConfigurationManager.AppSettings["clientId"],
ConfigurationManager.AppSettings["clientSecret"]);
AuthenticationResult result = await authContext.AcquireTokenAsync(resource, clientCred);
if (result == null)
throw new InvalidOperationException("Failed to obtain the JWT token");
return result.AccessToken;
}
}
}
If you want to access key vault resource, you need to configure access policy for your AD application and let it have enough permissions to access key vault resource. For more details, please refer to Azure key vault: access denied.

Azure Blob Container, Can't Generate Token

I'm working on azure storage but I cannot create a proper SAS token to pass to my frontend javascript. Following multiple tutorials and examples, I can't seem to get a working token for JS.
I'm validating my token at on the tutorial here so that my own javascript doesn't get in my way: https://dmrelease.blob.core.windows.net/azurestoragejssample/samples/sample-blob.html
I've spent hours trying out different solutions, but my token generated looks so similar to the one generated by azure. What am I missing?
code
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(connectionString);
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
CloudBlobContainer container = blobClient.GetContainerReference(containerName);
//Set the expiry time and permissions for the container.
//In this case no start time is specified, so the shared access signature becomes valid immediately.
SharedAccessBlobPolicy sasConstraints = new SharedAccessBlobPolicy();
sasConstraints.SharedAccessExpiryTime = DateTimeOffset.UtcNow.AddHours(24);
sasConstraints.Permissions = SharedAccessBlobPermissions.List | SharedAccessBlobPermissions.Write;
//Generate the shared access signature on the container, setting the constraints directly on the signature.
string sasContainerToken = container.GetSharedAccessSignature(sasConstraints);
//Return the URI string for the container, including the SAS token.
return sasContainerToken;
Based on my test, the code is ok for generating SAS token. If you want to list the blobs in the container, you need to add &comp=list&restype=container to your SAS URL. Then it should work.
Get https://xxxxx.blob.core.windows.net/test?sv=2018-03-28&sr=c&sig=xxxxxxxxx&sp=rwl&comp=list&restype=container
Azure Storage Service is not able to identify if the resource you're trying to access is a blob or a container and assumes it's a blob. Since it assumes the resource type is blob, it makes use of $root blob container for SAS calculation (which you can see from your error message). Since SAS was calculated for mark blob container, you get this Signature Does Not Match error. By specifying restype=container you're telling storage service to treat the resource as container. comp=list is required as per REST API specification.
For more information, please refer to another SO thread.
Regarding the issue, have you tried to use JS to create a SAS token.
var azure = require('azure-storage');
var fs = require('fs');
var SasConstants = azure.Constants.AccountSasConstants;
var blobService = azure.createBlobService();
var containerName = 'containername';
var blobName = 'blobname';
var startDate = new Date('');
var expiryDate = new Date(startDate);
expiryDate.setDate(startDate.getDate() + 1);
var sharedAccessPolicy = {
AccessPolicy: {
Permissions: azure.BlobUtilities.SharedAccessPermissions.READ + azure.BlobUtilities.SharedAccessPermissions.ADD + azure.BlobUtilities.SharedAccessPermissions.CREATE+ azure.BlobUtilities.SharedAccessPermissions.WRITE,
Start: startDate,
Expiry: expiryDate
},
};
var token = blobService.generateSharedAccessSignature(containerName, null, sharedAccessPolicy);
Generate a token for the storage account instead. The permissions in the tutorial listed are granted by the storage account policy.
public static string GenerateAccountSASToken(string connectionString)
{
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(connectionString);
SharedAccessAccountPolicy accountpolicy = new SharedAccessAccountPolicy();
accountpolicy.SharedAccessStartTime = DateTimeOffset.UtcNow.AddHours(-24);
accountpolicy.SharedAccessExpiryTime = DateTimeOffset.UtcNow.AddHours(24);
accountpolicy.Permissions = SharedAccessAccountPermissions.Add | SharedAccessAccountPermissions.Create | SharedAccessAccountPermissions.List | SharedAccessAccountPermissions.ProcessMessages | SharedAccessAccountPermissions.Read | SharedAccessAccountPermissions.Update | SharedAccessAccountPermissions.Write;
accountpolicy.Services = SharedAccessAccountServices.Blob;
accountpolicy.ResourceTypes = SharedAccessAccountResourceTypes.Container | SharedAccessAccountResourceTypes.Object | SharedAccessAccountResourceTypes.Service;
return storageAccount.GetSharedAccessSignature(accountpolicy);
}

Generate blob sasurl using SAS

I want to get a reference to the blob and generate a SAS URL for it.
How? Without exposing my storage account key?
What all have I tried? Getting the reference to blob by using SAS (of blob container or storage account).
My references: https://learn.microsoft.com/en-us/azure/storage/common/storage-dotnet-shared-access-signature-part-1?toc=%2fazure%2fstorage%2fblobs%2ftoc.json
The exception that I see: "Can not create Shared Access Signature unless Account Key credentials are used"
But I do not (obviously) want to expose my account key! Is this even possible? If not, is there any other way of doing it?
In short: no, there's no other way to do that besides using one of the keys. You need one of the Access Keys to be able to create a SAS token. Here's why you cannot do that with an existing SAS token:
The signature is an HMAC computed over the string-to-sign and key using the SHA256 algorithm, and then encoded using Base64 encoding.
This means the signature that is part of your SAS token is a calculated value. Part of that calculation is based on (one of the) key(s), since that is used to calculate the non-reversible hash. The fact that this hash is non-reversible means you cannot retrieve the Access Key used to calculate the hash. And therefor, you cannot use a SAS token to create another SAS token: you don't have an Access Key available to calculate the signature.
When you create a storage account, you get two storage access keys, which provide full control over the storage account contents. These keys are admin credentials.
More information: Constructing a Service SAS
This is an interpretation of this example
First we have this to get the accountKey:
public static async Task<StorageAccountKey> GetAccountKeys(string KeyName)
{
IAzure storageAccounts;
if (!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable(#"AZURE_TENANT_ID"))
&& !string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable(#"AZURE_SUBSCRIPTION_ID")))
{
storageAccounts = GetStorageAccountWithTenantAndSubscription();
}
else
{
AzureCredentials credentials = SdkContext.AzureCredentialsFactory.FromSystemAssignedManagedServiceIdentity(MSIResourceType.AppService, AzureEnvironment.AzureGlobalCloud);
storageAccounts = Microsoft.Azure.Management.Fluent.Azure
.Authenticate(credentials)
.WithDefaultSubscription();
}
IStorageAccount storageAccount = await storageAccounts.StorageAccounts.GetByResourceGroupAsync(
Environment.GetEnvironmentVariable("STORAGE_ACCOUNT_GROUP"),
Environment.GetEnvironmentVariable("STORAGE_ACCOUNT_NAME")
);
IReadOnlyList<StorageAccountKey> accountKeys = storageAccount.GetKeys();
return accountKeys.FirstOrDefault(k => k.KeyName == KeyName);
}
private static IAzure GetStorageAccountWithTenantAndSubscription()
{
DefaultAzureCredential tokenCred = new DefaultAzureCredential(includeInteractiveCredentials: true);
string armToken = tokenCred.GetToken(new TokenRequestContext(scopes: new[] { "https://management.azure.com/.default" }, parentRequestId: null), default).Token;
TokenCredentials armCreds = new TokenCredentials(armToken);
string graphToken = tokenCred.GetToken(new TokenRequestContext(scopes: new[] { "https://graph.windows.net/.default" }, parentRequestId: null), default).Token;
TokenCredentials graphCreds = new TokenCredentials(graphToken);
AzureCredentials credentials = new AzureCredentials(armCreds, graphCreds, Environment.GetEnvironmentVariable(#"AZURE_TENANT_ID"), AzureEnvironment.AzureGlobalCloud);
return Microsoft.Azure.Management.Fluent.Azure
.Authenticate(credentials)
.WithSubscription(Environment.GetEnvironmentVariable(#"AZURE_SUBSCRIPTION_ID"));
}
Where you need to define the next environment variables:
AZURE_TENANT_ID
AZURE_SUBSCRIPTION_ID
STORAGE_ACCOUNT_GROUP
STORAGE_ACCOUNT_NAME
All of them can be found on the https://portal.azure.com/ and if you run az login
then you can do this to generate the connection string:
private static async Task<string> GetAccountSASToken()
{
StorageAccountKey accountKeyObj = await GetAccountKeys(Environment.GetEnvironmentVariable("STORAGE_ACCOUNT_KEY"));
string accountKey = accountKeyObj.Value;
string accountName = Environment.GetEnvironmentVariable("STORAGE_ACCOUNT_NAME");
StorageSharedKeyCredential key = new StorageSharedKeyCredential(accountName, accountKey);
AccountSasBuilder sasBuilder = new AccountSasBuilder()
{
Services = AccountSasServices.Blobs | AccountSasServices.Files,
ResourceTypes = AccountSasResourceTypes.Container | AccountSasResourceTypes.Object,
ExpiresOn = DateTimeOffset.UtcNow.AddHours(1),
Protocol = SasProtocol.Https
};
sasBuilder.SetPermissions(AccountSasPermissions.List | AccountSasPermissions.Read);
string sasToken = sasBuilder.ToSasQueryParameters(key).ToString();
return sasToken;
}
And that's all you need.

Categories