Search code examples
azure-functionsauthorizationazure-storageazure-managed-identityazure-file-share

Azure ShareClient: This request is not authorized to perform this operation


I am trying to use the Azure ShareFile client to export data from a SQL Server table to an XLSX file through a function app. When I run the code in VSCode locally, the file is created successfully. I believe this success could be chalked up to the fact that I am the owner of the storage account. When the code is executed on machine, Azure uses my login for authentication. However, the export fails with a 403 authorization error in the Azure function app deployed to the cloud.

The code initializing the ShareFile client is as follows:

def getFileClient(filePath):
    accountName = accountKey = os.environ["AZURE_STORAGE_ACCOUNT_NAME"]
    accountKey = os.environ["AZURE_STORAGE_ACCOUNT_KEY"]
    connectStr = os.environ["AZURE_STORAGEFILE_CONNECTIONSTRING"]

    shareName = "dev"

    sasToken = generate_account_sas(
        account_name=accountName,
        account_key=accountKey,
        resource_types=ResourceTypes(service=True, object=True),
        permission=AccountSasPermissions(read=True, write=True),
        expiry=datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(hours=1),
        services=Services(fileshare=True)
    )
    shareClient = ShareClient.from_connection_string(conn_str=connectStr, share_name=shareName, credential=sasToken)

    # Get a reference to the file
    fileClient = shareClient.get_file_client(filePath)
    return(fileClient)

The code for exporting data to the XLSX file is as follows:

XLBytes = BytesIO()
dataMartDF[export_cols].to_excel(XLBytes, index=False)
XLBytes.seek(0)
countyDispFC.create_file(size=XLBytes.getbuffer().nbytes)
countyDispFC.upload_file(XLBytes)

where CountyDispFC is the file client returned by the getFileClient function.

The following error is logged when the function is invoked via https: Error log extract

Note that the network settings include "Enabled from selected virtual networks and IP addresses". I have added my local machine's IP address and the public IP address of the function app.

I've also created a managed identity, assigned it to the function app, and granted it the Storage File Privileged Contributor role in the storage account (Managed Identity role in IAM). I would prefer to use this managed identity for access, but ran into a "token_intent is required" error when I tried to create a credential object and using it in the ShareClient function.

What am I missing?


Solution

  • Network settings include "Enabled from selected virtual networks and IP addresses". I have added my local machine's IP address and the public IP address of the function app. There are no other network settings, virtual network, private endpoints, etc.

    When this setting is set, then you need to connect your storage account and function to a virtual network , then only it will work and below approach worked for me:

    Firstly, Create a Function app and enable managed identity:

    enter image description here

    Then link V-net to Storage account:

    enter image description here

    Now link V-net to Function App :

    Click on Not configured:

    enter image description here

    After linking:

    enter image description here

    Note: Make sure that storage account, function app and vnet are in same location.

    Establishing connection from Function app to storage account you can enable the Microsoft.storage Service Endpoint In Function app subnet:

    enter image description here

    Deployed below code:

    import os
    import logging
    from io import BytesIO
    import azure.functions as func
    import pandas as ch
    from azure.identity import DefaultAzureCredential
    from azure.storage.fileshare import ShareClient
    
    def ri_gt_cl(file_path):
        
        ri_acc_nme = "tset123"
        ri_cs="DefaultEndpointsProtocol=https;AccountName=filehh;AccountKey=/aQKGeeEsa43klLpgjQ==;EndpointSuffix=core.windows.net"
        rith_file_share = "rithfile"  
        ri_scl = ShareClient(
            account_url=f"https://{ri_acc_nme}.file.core.windows.net/",
            conn_str=ri_cs,
            share_name=rith_file_share
        )
        r_ch = ri_scl.get_file_client(file_path)
        return r_ch
    
    import os
    import logging
    from io import BytesIO
    import azure.functions as func
    import pandas as ch
    from azure.storage.fileshare import ShareClient
    
    def ri_gt_cl(file_path):
        
        ri_cs = "DefaultEndpointsProtocol=https;AccountName=tset123;AccountKey=d+t5237NGVidW+AStjkzsFA==;EndpointSuffix=core.windows.net"
        ri_file_shr_name = "rithfile"  
        risl = ShareClient.from_connection_string(
            conn_str=ri_cs,
            share_name=ri_file_shr_name
        )
    
        ri_fcl = risl.get_file_client(file_path)
        return ri_fcl
    
    def ri_up():
        
        ri_dt = {
            "Name": ["Rithwik", "Chotu"],
            "Age": [8, 7]
        }
        rid = ch.DataFrame(ri_dt)
        ri_ex_b = BytesIO()
        rid.to_excel(ri_ex_b, index=False)
        ri_ex_b.seek(0)
        rifp = "rithtest.xlsx" 
        rifl = ri_gt_cl(rifp)
        fs = ri_ex_b.getbuffer().nbytes
        rifl.create_file(size=fs)
        rifl.upload_file(ri_ex_b)
    
        return f"Hello Rithwik, File Uploaded '{rifp}'"
    
    def main(req: func.HttpRequest) -> func.HttpResponse:
        logging.info("Hello Rithwik, Function Started")
        try:
            ri_out = ri_up()
            return func.HttpResponse(
                f"Success: {ri_out}",
                status_code=200
            )
        except Exception as ri:
            logging.error(f"Hello Rithwik, there is an error: {str(ri)}")
            return func.HttpResponse(f"Hello Rithwik, Error is:  {str(ri)}",status_code=500)
    

    Output:

    Function App Executed:

    enter image description here

    In Storage account download the file:

    enter image description here

    File:

    enter image description here