How to enable Azure IoT Hub's file upload functionality through an Azure Storage only allowing private connections.
The file upload functionality provided by Azure IoT Hub serves as a secure bridge to the Azure Storage account, eliminating the need for IoT client devices to rely on external custom services for secure connections to the Storage account. For a comprehensive understanding of this feature, refer to the official Azure IoT Hub file upload overview.
This repo offers guidance on securing Azure Storage account through a private endpoint while still allowing IoT Hub to interact with it securely, as well as allowing client IoT devices to reach the account through a gateway (Application Gateway and optionally Azure Firewall). With this approach the Storage account no longer allows public Internet access, which is a common requirement in enterprise deployments.
This project provides different options for configuring Azure IoT Hub file upload to an Azure Storage private endpoint:
- Documentation with guidance and details on how you can accomplish this configuration in a hub-spoke network topology, using Azure Application Gateway, and Azure Firewall for traffic inspection.
- Quickstart: Azure CLI based step-by-step scripts with a simplified setup (single virtual network & resource group) and sample client application.
- An Azure subscription. If you don't have an Azure subscription, create one for free before you begin.
- Azure subscription RBAC permissions: either Contributor role, or if using an existing resource group, use an identity that has Microsoft/Authorization/roleAssignments/write permissions at the resource group level.
- Azure Command Line.
- Visual Studio Code installed on your development machine. For more information, see Download Visual Studio Code.
- OpenSSL Command Line Tool
- Bash terminal, on Windows you can use WSL
- .NET SDK 8 (if you wish to run the client and server samples for file upload)
- DNS provider if you wish to test HTTPS with your custom domain, and SSL Certificate for your custom domain
Tip
Without a custom DNS and an SSL certificate available, you can still test the entire setup but the IoT client device portion of this quickstart will not be completed.
This quickstart provides instructions on how to set up Azure IoT Hub's file upload functionality through an Azure Storage account that only allows private connections, through step-by-step scripts. The scripts deploy and configure a simplified set of Azure resources to showcase Azure Storage with private endpoint, Application Gateway and Azure IoT Hub routing to Azure Storage. The Storage account disables any public Internet access and only accessible within the Virtual Network. In this quickstart there is no Azure Firewall for traffic inspection. Finally, two sample .NET apps interact with the resources deployed to showcase the end-to-end data flows.
Warning
This sample currently generates self-signed certificate and key files on disk and stores these on Azure Key Vault. In a production environment usage of self-signed certificates is not an option, though we would still recommend leveraging Azure Key Vault for storing the secrets.
-
Open a Bash terminal.
-
Clone this repository and go to the root directory.
git clone https://github.com/Azure-Samples/azure-edge-extensions-iothub-fileupload-privatelink cd azure-edge-extensions-iothub-fileupload-privatelink
-
Open the project in Visual Studio Code and open a Bash terminal inside.
-
Log into your Azure account and select your subscription.
az login [--tenant xxxx-xxx]
-
Prepare required environment variables to run the deployment scripts. We create a file in the folder
./temp
which is excluded from Git. -
Create the
./temp/envvars.sh
file. ForLOCATION
you can choose any Azure region supporting IoT Hub and Application Gateway.if [ ! -d "./temp" ]; then mkdir ./temp fi >./temp/envvars.sh cat <<EOF # change the below set to match your environment based on Readme export TENANT_ID="xxx-xxxx-xxx-xxxx-xxxx" export LOCATION="westeurope" export PREFIX="xxx" export RESOURCE_GROUP="rg-xxx-xxx" EOF code ./temp/envvars.sh
The newly created
.sh
file should now open in Visual Studio Code. -
Edit the values to your preference, ensure
TENANT_ID
corresponds to your Azure tenant. -
Set
PREFIX
to a short 5-character string that is unique for the resource name composition. -
Load the variables.
source ./temp/envvars.sh
Deploy the Azure components for setting up Virtual Network, self-signed SSL certificate, Storage, Private Link, custom private DNS and Application Gateway configured to talk to the Storage account. This allows you to validate the flow before configuring Azure IoT Hub and client communication. The SSL certificate is stored in Azure Key Vault.
Warning
Azure Key Vault in this sample has network rules to deny public internet access, but has an exception to allow the current user's IP address for this quickstart setup. In real environments we recommend removing this rule and integrating your service access through Private Endpoint.
-
From the root directory of this repo, run the first part of the deployment. The script will use the environment variables and build composed resource names by appending the
PREFIX
variable as Azure resource names../deploy/quickstart-agw-storage.sh
It will take a few minutes to deploy all resources. Keep the terminal open and take note of some of the generated DNS entries.
This scripts also uploads a
sample.txt
file to a Blob storage container and generates a SAS URI token for testing. -
From a bash terminal, try out a
CURL
command to the DNS of the Public IP address attached to the Application Gateway.curl --insecure "<APP_GATEWAY_SASURI copied output>"
-
Run the same command with the Blob Public URI printed out by the script
BLOB_SASURI
value.Verify this does not succeed since this Storage account is blocking direct Internet traffic.
-
Configure your custom DNS (through your DNS provider) and SSL certificate for end-to-end SSL encryption.
-
Ensure you create a
CNAME
orA
Record pointing to the DNS of the Azure Public IP created above.CNAME
is the simpler approach and you can point it to the value output of the script in the form ofxxx.<region>.cloudapp.azure.com
. -
Create an SSL certificate for this domain, or ensure you have a valid wildcard domain.
-
Export the PFX file into a base64 encoded file.
For example.
base64 -w 0 ./temp/<YOURFILENAME_LOCATION>.pfx > ./temp/<YOURFILENAME_LOCATION>.pfx.base64
-
-
Update the Key vault secret:
keyvault_name="kv-$PREFIX" az keyvault secret set --vault-name $keyvault_name --name "AppGatewayCertPfx" --file ./temp/<YOURFILENAME_LOCATION>.pfx.base64 --content-type "application/x-pkcs12"
Application Gateway polls the Key Vault every four-hour interval. You might need to force this to be sooner by updating a rule, listener or setting on the Application Gateway. See Supported certificates - Tip.
-
Test the name resolution works for your CNAME record and directs to the Public IP address used by the application gateway.
If you want to ensure the SSL certificate from Key Vault is up to date and refreshed in Application Gateway, use OpenSSL to verify the currently attached certificate.
You can also go the Azure portal, select the Application Gateway instance. In the Listeners section, choose Listener TLS certificates and note the Common Name and expiry of the
appGatewaySslCert
, should match your custom certificate.openssl s_client -connect <yourcustommappeddomain>:443 -showcerts </dev/null
-
Finally, test the custom URL and SAS URI without
--insecure
option as end to end SSL is now configured. theCURL
should now be successful.curl "https://<yourcustommappeddomain>/test/sample.txt?<SAS>"
Deploy Azure IoT Hub and configure service communication to Azure Storage for File upload functionality.
Tip
If you don't have custom DNS and SSL setup completed, use a dummy value when calling the script.
-
Run the following script to deploy and configure Azure IoT Hub and Storage.
The names of the resources are identical to the first script and composed by the environment variables loaded upfront.
./deploy/quickstart-add-iothub.sh "<your_custom_dns>"
The script will output a sample device connection string, and a service connect endpoint connection string. You will use these in the IoT client sample.
Warning
It is a prerequisite to have a valid DNS custom domain and associated SSL certificate configured for this part to work. If you don't have this requirement, we recommend you review the sample code projects and the flow to understand the process.
To validate a client device on public Internet can leverage file upload with a custom domain mapping, you will use two Terminal windows to run a client and a server .NET sample app.
Run the File Upload notifications server application:
-
In a new terminal go into the application directory
cd ./src/ServerSideFileNotification/
-
Build the .NET app.
dotnet build
-
Run the sample application.
You will need to pass in the Service connection string from the
./deploy/quickstart-add-iothub.sh
script.dotnet run "<iot hub service connection string>"
-
Leave this application running.
Run the IoT Client sample application:
-
In a new bash terminal change into the IoT client directory
./src/SampleIoTClientFileUpload/
-
Prepare a
.env
file for the required variables.>.env cat <<EOF IOT_HUB_HOSTNAME="TODO.azure-devices.net" IOT_HUB_CONNSTRING="TODO" DEVICE_ID="myDeviceOne" AUTH_TYPE="symmetric_key" EOF code .env
Ensure you replace the variable contents based on values output by the IoT Hub creation script.
-
Build and run the .NET application.
dotnet build dotnet run
-
Review the upload is successful.
Switch to the terminal running the server application and note the notification has arrived.
This quickstart deploys all Azure resources within a single resource group. It's enough to delete the resource group to clean-up all cloud resources. From the terminal where you ran the scripts:
az group delete --name $RESOURCE_GROUP