Azure Function access to Storage Account with System Assigned Identity Failing - azure

I should start out by mentioning that I am very new to Azure functions, so I don't know the idiosyncrasies inherent to this particular service. I think the issue is probably that I'm not looking at this in a very "functionesque" way.
I am trying to access storage account from an Azure Function using System Assigned service identities. However, no matter how I configure it, it throws an exception saying "This request is not authorized to perform this operation using this permission."
I know that I can access KeyVault by giving it access and using #Microsoft.KeyVault, where I could store the connection string, but I want to be able to auto-rotate the storage connection string without having to update the app settings of my function. I've seen using storage as an input binding, but these seem to also require the use of an App Setting anyways, identifying the connection string. I just wanted to be able to take advantage of assigning an identity to the function so that I don't need to store this information in an app setting.
Here is how I am currently attempting it:
Function:
public static class Function1
{
[FunctionName("WebHook-Func")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
var azureServiceTokenProvider = new AzureServiceTokenProvider();
string accessToken = await azureServiceTokenProvider.GetAccessTokenAsync("https://storage.azure.com/");
TokenCredential creds = new TokenCredential(accessToken);
log.LogInformation($"Token: {accessToken}");
StorageCredentials storageCreds = new StorageCredentials(creds);
try
{
CloudBlobClient client = new CloudBlobClient(new StorageUri(new Uri("https://<storageAccount>.blob.core.windows.net")), storageCreds);
CloudBlobContainer container = client.GetContainerReference("fltd");
CloudBlockBlob blob = container.GetBlockBlobReference("shopping.txt");
string content = await blob.DownloadTextAsync();
return (ActionResult)new OkObjectResult($"File contents: {content}");
}catch(Exception ex)
{
return new BadRequestObjectResult($"Exception when calling web hook: {ex.StackTrace} {ex.Message}");
}
}
}
I have enabled System Assigned Identity
I have added this to the storage account as a contributor:
And the container I am trying to access exists:
However it keeps throwing an exception saying I am not authorized. Note the exception is happening at DownloadTextAsync, meaning it is able to retrieve the reference for the container and blob:
Exception when calling web hook: at Microsoft.WindowsAzure.Storage.Core.Executor.Executor.ExecuteAsyncInternal[T](RESTCommand`1 cmd, IRetryPolicy policy, OperationContext operationContext, CancellationToken token)
at Microsoft.WindowsAzure.Storage.Blob.CloudBlob.DownloadRangeToStreamAsync(Stream target, Nullable`1 offset, Nullable`1 length, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, IProgress`1 progressHandler, CancellationToken cancellationToken)
at Microsoft.WindowsAzure.Storage.Blob.CloudBlockBlob.DownloadTextAsync(Encoding encoding, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, IProgress`1 progressHandler, CancellationToken cancellationToken)
at codeupdated.Function1.Run(HttpRequest req, ILogger log) in C:\...\webhook-func.cs:line 40 This request is not authorized to perform this operation using this permission.
Is there any reason this doesn't work as is? I could set up the storage account to be managed by the keyvault and request SAS tokens using the KeyVault reference bindings, but this seems like an extra step and defeats the purpose and ease of use of maintaining a system assigned service identity. Furthermore, without actually setting this up I don't know if it would just return the same error.
Thanks for any help you can offer.

The Contributor just permits your MSI to manage the storage account, it will not provide access to the blob data within that account.
To solve the issue, just need to add the MSI(System Assigned Identity) to the storage account as a Storage Blob Data Owner / Storage Blob Data Contributor, then it will work fine.

It seems you haven't assign the right permission to your function in azure key vault. Please have a try by following the steps below:
Go to your azure key vault and click "Access policies" --> "Add Access Policy.
Then select the permission which you want in "Key permissions", "Secret permissions" and "Certificate permissions" select boxes. And select your azure function to the "Select principal" box.
Hope it would be helpful to your problem~

Related

How to programmatically find out what operations I can do in a blob storage?

I am using libraries Microsoft.Azure.Storage.Blob 11.2.3.0 and Microsoft.Azure.Storage.Common 11.2.3.0 to connect to an Azure BlobStorage from a .NET Core 3.1 application.
When I started working on this, I had been given connection strings that gave me full access to the BlobStorage (or rather, the entire cloud storage account). Based upon those, I chose to write my connection code "defensively", making use of Exists() and CreateIfNotExists() from the CloudBlobContainer class to ensure the application would not fail when a container was not yet existing.
Now, I'm connecting a BlobStorage container using a SAS. While I can freely retrieve and upload blobs within the container like this, unfortunately, it seems that I am not allowed to do anything on the container level. Not only CreateIfNotExists, but even the mere querying of existence by Exists() throws a StorageException saying
This request is not authorized to perform this operation.
The documentation does not mention the exception.
Is there any way to check preemptively whether I am allowed to check the container's existence?
I have tried looking into the container permissions retrieved from GetPermissions, but that will throw an exception, as well.
The only other alternative I can see is to check for container existence within a try-catch-block and assume existence if an exception is thrown ...
There's a no definitive way to identify if an operation can be performed using a SAS token other than performing that operation and catching any exception that may be thrown by the operation. The exception that is of your interest is Unauthorized (403).
However you can try to predict if an operation can be performed by looking at the SAS token. If it is a Service SAS Token and not an Account SAS Token, that means all the account related operations are not not allowed. The way to distinguish between an Account SAS token and a Service SAS token is that the former will contain attributes like SignedServices (ss) and SignedResourceTypes (srt).
Next thing you would want to do is look for SignedPermissions (sp) attribute in your SAS token. This attribute will tell you what all operations are possible with the SAS token. For example, if your SAS token is a Service SAS token and if it includes Delete (d) permission, that would mean you can use this SAS token to delete a blob.
Please see these tables for the permissions/allowed operations combinations:
Service SAS Token: https://learn.microsoft.com/en-us/rest/api/storageservices/create-service-sas#permissions-for-a-directory-container-or-blob
Account SAS Token: https://learn.microsoft.com/en-us/rest/api/storageservices/create-service-sas#permissions-for-a-directory-container-or-blob
Please note that the operation might still fail for any number of reasons like SAS token has expired, account key has changed since the generation of SAS token, IP restrictions etc.
I tried in in my system to check whether the container exist or not able check it and if container not exists created container and able to upload file.
You need to give proper permission for your SAS Token
const string sasToken = “SAS Token”
const string accountName = "teststorage65";
const string blobContainerName = "example";
const string blobName = "test.txt";
const string myFileLocation = #"Local Path ";
var storageAccount = new CloudStorageAccount(storageCredentials, accountName, null, true);
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
CloudBlobContainer blobContainer = blobClient.GetContainerReference(blobContainerName);
var result=blobContainer.Exists();
if (result == true)
{
Console.WriteLine("Container exists");
}
else
{
// blobContainer.CreateIfNotExists();
Console.WriteLine("Conatiner not exists");
Console.WriteLine("Creating Container "+ blobContainerName);
blobContainer.CreateIfNotExists();
}
// blobContainer.CreateIfNotExists();
//Console.WriteLine("Creating Container ");
CloudBlockBlob cloudBlob = blobContainer.GetBlockBlobReference(blobName);
cloudBlob.UploadFromFile(myFileLocation);
OUTPUT

Unprotect selective Azure function from azure Azure AD

Just want to check if there is a way to unprotect certain Azure Function from Azure AD?
My use case to get information from azure function (e.g environment)and display at the login page. Hence the user is not login, so I can’t call any protected Azure function.
Is there possible?
Assuming that you have a HTTP Triggered functions you can specify the level of authority one needs to have in order to execute it.
[FunctionName("Anonymous")]
public static HttpResponseMessage Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)]
HttpRequestMessage req,
ILogger logger)
{
// your code
return req.CreateResponse(HttpStatusCode.OK);
}
thanks your reply. based on the documentation, AuthLevel is referring to the Keys created not so much of make a azure function to be unprotected by Azure Active Directory.
https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-http-webhook-trigger?tabs=javascript#configuration
AuthLevel Determines what keys, if any, need to be present on the request in order to invoke the function. The authorization level can be one of the following values:
anonymous—No API key is required.
function—A function-specific API key is required. This is the default value if none is provided.
admin—The master key is required.
For more information, see the section about authorization keys.
https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-http-webhook-trigger?tabs=javascript#authorization-keys

Azure storage account rejects the issuer of a managed service identity

I'm using the Microsoft.Azure.Services.AppAuthentication library (v1.0.3) for .NET to connect from Azure Function app to blob storage using managed service identity. Auth code:
var tokenProvider = new AzureServiceTokenProvider();
string accessToken = await tokenProvider.GetAccessTokenAsync("https://storage.azure.com/");
var tokenCredential = new TokenCredential(accessToken);
var credentials = new StorageCredentials(tokenCredential);
var storageUri = new Uri($"https://{accountName}.blob.core.windows.net");
var client = new CloudBlobClient(storageUri, credentials);
One existing storage account refuses to accept the MSI regardless of given RBAC roles:
Microsoft.WindowsAzure.Storage.StorageException: Server failed to authenticate the request.
Make sure the value of Authorization header is formed correctly including the signature.
at Microsoft.WindowsAzure.Storage.Core.Executor.Executor.ExecuteAsyncInternal[T](RESTCommand`1 cmd, IRetryPolicy policy, OperationContext operationContext, CancellationToken token)
at Microsoft.WindowsAzure.Storage.Blob.CloudBlobContainer.CreateIfNotExistsAsync(BlobContainerPublicAccessType accessType, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken)
Additional exception details of storageException.RequestInformation.ExtendedErrorInformation.AdditionalDetails complain that AuthenticationErrorDetail: Issuer validation failed. Issuer did not match.
When decoding the failing jwt token, the issuer seems ok:
{
"aud": "https://storage.azure.com/",
"iss": "https://sts.windows.net/<my directory guid>/",
...
}
When I created new identically set up storage accounts then the same function app MSI and auth code worked and even the issuer in token were exactly the same. So the client function app and it's MSI identity are not the culprit here.
Why does this one storage account fail to authorize and how to get it to accept the MSI?
UPDATE: Cross posted to MS forum to get MS attention in verifying if this is an azure bug.
I test with your Auth code and both existing and new created Storage account accept MSI. So I approve with what juunas said, it may be an bug on Azure Storage.
You could go to here to give your feedback to let developers fix it.
Even after checking with MS, it is unclear what was the cause, but moving the affected subscriptions to another Azure AD directory seems to have fixed the issue.

Camel Azure credentials as option instead of bean

I want to pass credentials, to Azure blob component, without having to create and register a bean.
I have a route which consumes a file and routes it to an Azure Blob
I want this route to be generic.
The challenge I am facing is.
I have to support multiple storage accounts
We are supposed to fetch the credentials from a key vault. The credentials can change any time and I don't have control. The blob component requires a bean for credentials. So the only option I am left is a bean. So while I was able to achieve this, wondering, if camel should be able to receive the credentials, not the object or client , but the account name and key as an option. Camel doesn't support key vault which is kind of a bummer.I might be able to help with implementing the keyVault solution
This is the bean I wrote to achieve what I wanted.
from(file://somedir).to(bean:azureService?sendFiletoAzure(name,key,container);
AzureService{
public void sendFileToAzure(String storageAccountName,
String storageAccountKey, String containerName, Exchange exchange) {
StorageCredentials credentials = new StorageCredentialsAccountAndKey(
storageAccountName, storageAccountKey);
SimpleRegistry registry = new SimpleRegistry();
GenericFile<?> file = (GenericFile<?>) exchange.getIn().getBody();
String blobName = (String) exchange.getIn().getHeader("blobName");
CamelContext context = new DefaultCamelContext(registry);
registry.put("blobCredentials", credentials);
String endpointUri = String.format(azureEndpointUri, storageAccountName,
containerName, blobName);
context.createProducerTemplate().sendBody(endpointUri, file);
}
}

Azure storage connection string without the account key - public containers

I am having a blob container which ACL is set up to allow full public read access so anyone can read and list the blobs in that container.
I store the files so the WPF client app my clients use could read them but I don't want to allow them to modify/delete/create files.
Does anyone knows what connection string should be used in this scenario?
I hoped to specify the connection string without the account key and/or shared access key due to the fact blobs are public but that didn't work - StorageAccount.Parse throws FormatException
As mentioned by the previous answers, the best practice is usually to control the access to your blob container using shared access signatures (SAS) or a stored access policy. These can be used to create an access token (string) you can pass to your client without revealing your account key.
However, it is also possible to specify the level of public read access to the blobs and metadata saved in the container. Public access is the level of read permission automatically given an anonymous user that is in possession the public access url for the container or blob. You cannot use public access to give anonymous users write permissions to the container. If you need to give write permission to users that are not in possession of the account key of your Azure storage account, then you will need to provide those users with a token in the form of a url the references a shared access signature or a shared access policy.
If the public access to the blob container is not currently off (private,) anonymous user will be able to read all blobs in the container using a public access url such as the following.
http://grassy.blob.core.windows.net/container1/image2.jpg
When you create the container, you can set the value of the publicAccess property to the appropriate constant of the BlobContainerPublicAccessType enum. The value of the publicAccess property can be one of the following three constants which specify the level of public read access.
• BLOB – The public can read the content and metadata of blobs within this container, but cannot read container metadata or list the blobs within the container.
• CONTAINER – The public can read blob content and metadata and container metadata, and can list the blobs within the container.
• OFF – Specifies no public access. Only the account owner can read resources in this container.
So in this case the public access level might be set to CONTAINER. For example:
public static void main(String[] args) throws InvalidKeyException, URISyntaxException, StorageException
{
Account creds = new Account();
final String storageConnectionString = creds.getstorageconnectionstring();
CloudStorageAccount storageAccount = CloudStorageAccount.parse(storageConnectionString);
CloudBlobClient blobClient = storageAccount.createCloudBlobClient();
CloudBlobContainer container = blobClient.getContainerReference("container1");
container.createIfNotExist();
BlobContainerPermissions containerPermissions = new BlobContainerPermissions();
containerPermissions.setPublicAccess(BlobContainerPublicAccessType.CONTAINER);
container.uploadPermissions(containerPermissions);
BlobContainerPublicAccessType access1 = containerPermissions.getPublicAccess();
System.out.println("Public access to " + container.getName() + " is set to:
" + access1);
}
If the public access level on container1 has been set to CONTAINER, an anonymous user should be able to list the blobs in container1 knowing only the storage account AccountName ("grassy") and the container name, but without needing to know the AccountKey. For example, an anonymous application might use java code similar to the following:
public static void main(String[] args) throws InvalidKeyException, URISyntaxException, StorageException, FileNotFoundException, IOException
{
URI baseuri = new URI("http://grassy.blob.core.windows.net");
CloudBlobClient blobclient = new CloudBlobClient(baseuri);
CloudBlobContainer container = blobclient.getContainerReference("container1");
for (ListBlobItem blobItem : container.listBlobs()){System.out.println(blobItem.getUri());}
}
However, as discussed, it is a better practice to avoid giving anonymous users access. Instead control access to the container using a SAS or policy and pass on the token to only known users.
StorageAccount is not meant to connect to public blobs as far as I know. You simply can get at the public blobs via public URL by using something like WebClient or any other tool that can download data over public http/https endpoint.
You could use shared access signature for that purpose. What you could do is create the SAS on a blob container which only allows list and read permissions on the blob container and then distribute that SAS URI to your clients. Your code could then create an instance of BlobContainer object using that SAS URI.
Here's the sample code for listing blobs in a blob container using SAS URI:
static void ListBlobsWithStorageClientLibrary(string blobContainerSasUri)
{
CloudBlobContainer blobContainer = new CloudBlobContainer(new Uri(blobContainerSasUri));
var blobs = blobContainer.ListBlobs(null, true);
foreach (var blob in blobs)
{
Console.WriteLine(blob.Uri);
}
}
Other alternative is to create an instance of StorageCredentials object using SAS token: http://msdn.microsoft.com/en-us/library/windowsazure/jj682529.aspx. Then you could create an instance of CloudStorageAccount object using that StorageCredentials object.
I wrote a detailed post on using Shared Access Signatures with blob storage which you can read here: http://gauravmantri.com/2013/02/13/revisiting-windows-azure-shared-access-signature/

Resources