Skip to main content

Overview

Tornado can deliver completed downloads straight into a blob container in your own Azure Storage account. The dashboard asks for exactly three values: the storage account name, the container name, and a storage account access key. This page covers the dashboard path (the onboarding wizard and Settings › Default storage destination), which writes an organization-level configuration through POST /settings/storage. Credentials are stored in Azure Key Vault and are returned masked when the configuration is read back.
The dashboard route is not the same contract as the per-API-key endpoint POST /user/blob. That endpoint names the container field container and additionally accepts sas_token, folder_prefix and base_folder. The dashboard route names the field container_name and accepts three fields only. Do not copy a payload from one to the other.

Current limitations

Read these before you create anything in Azure. Both are properties of the dashboard route as it exists today, not Azure restrictions.
The blob endpoint is not configurable. The service URL is built as https://<account_name>.blob.core.windows.net and there is no field to override it. Storage accounts that live on a different endpoint suffix cannot be reached through this route: Azure Government (blob.core.usgovcloudapi.net), Azure operated by 21Vianet (China), and any storage account you front with a custom domain. Only the global public cloud suffix works.SAS tokens are not accepted. The dashboard authenticates with Shared Key only, using the storage account access key. The sas_token field that exists on POST /user/blob has no equivalent here, so do not generate a SAS for this configuration.
The container must already exist. Tornado never creates it. The connection test writes a probe blob directly into the container you name, so a container that does not exist fails the test with ContainerNotFound: azure:blob.

1. Create a storage account

Skip to step 2 if you already have a dedicated account for Tornado deliveries. Read Protect your access keys first if you plan to reuse an existing one.
1

Open the storage account creation flow

In the Azure portal, select Storage accounts from the portal menu, then select Create. Portal layouts change; if the menu item is not visible, search for “Storage accounts” from the top search bar.
2

Fill in the Basics tab

Pick a subscription and resource group, then set Storage account name.The name must be between 3 and 24 characters and can contain only lowercase letters and numbers. It is globally unique because it becomes a DNS label: <name>.blob.core.windows.net.Standard performance on a general-purpose v2 account is the right default for download delivery. Choose a region close to wherever you consume the files.
3

Leave Shared Key authorization enabled

Tornado authenticates with the account key. If your organization sets Allow storage account key access to Disabled (the allowSharedKeyAccess property, under Settings › Configuration on the account), Tornado cannot write to that account at all. See Troubleshooting.

2. Create a container

1

Open Containers

In your storage account, find the Data storage section and select Containers.
2

Add the container

Select + Container, give it a name, and select Create.Container names must be 3 to 63 characters, must start with a letter or a number, and may contain only lowercase letters, numbers and the dash (-) character.
3

Keep anonymous access off

Set the anonymous access level to Private (no anonymous access). Tornado does not need public read: download URLs in webhook payloads are signed per-object, and signing works on a private container.

3. Copy an access key

1

Open Access keys

In your storage account, find the Security + networking section and select Access keys.
2

Reveal and copy

Select Show keys. Under key1, copy the Key value.Copy the Key, not the Connection string. The dashboard expects the bare base64 key. A connection string pasted into the key field fails at signing time before any request reaches Azure.
Viewing keys requires the Microsoft.Storage/storageAccounts/listkeys/action permission, which is carried by the Owner, Contributor and Storage Account Key Operator Service Role built-in roles.

An account key is not a container credential

A storage account access key grants full access to every resource in the storage account: all containers, all blobs, and the account’s queues, tables and file shares. It cannot be scoped down to one container, and it also carries the power to mint SAS tokens for anything in the account. There are two keys (key1 and key2) and both have identical, unrestricted scope.
Because the credential cannot be narrowed, narrow the account instead. Create a storage account used only for Tornado deliveries. Concretely, that buys you:
  • Blast radius. The key you hand over reaches nothing but delivered downloads. On a shared account, the same string reaches your application data.
  • Rotation on your own schedule. You can regenerate the key whenever you want without coordinating with other workloads that hold the same key.
  • Policies that fit one job. Lifecycle rules, retention, soft delete, firewall rules and diagnostic logging can be tuned for delivery traffic without side effects elsewhere.
  • Clean attribution. Storage metrics, transaction costs and account-level throughput limits reflect Tornado alone.
Tornado stores the key in Azure Key Vault, never in plaintext in its database, and masks it on read-back. That protects the key at rest; it does not change what the key can do if it leaks elsewhere. Azure is the odd one out here. Every other provider lets you write down a permission set; this route accepts a Shared Key, and a Shared Key has no permission set to write down. So the minimum and the recommendation are both about the account, and the RBAC and SAS vocabularies below are yardsticks for judging what you are handing over rather than things you can configure.

Minimum

The RBAC row is a measurement, not an instruction. Assigning Storage Blob Data Contributor to a principal changes nothing for Tornado, because there is no Microsoft Entra ID, managed identity or user-delegation-SAS path in this route. It is here so you can see how far the account key over-grants: Contributor is three data actions on one container, the account key is everything in the account.
The minimum plus two things, neither of which narrows the key, because the key cannot be narrowed: Nothing else is worth adding. Container-level anonymous access, blob public access and account-level firewall exceptions are all things to leave alone or off.

If you use the per-API-key route instead

POST /user/blob does accept a sas_token. Sized correctly, that is an account SAS with signed services ss=b (blob), signed resource types srt=o (object), and the permission string sp=rwd. A service SAS scoped to the container (sr=c) with the same rwd works equally well; nothing Tornado does needs account-level or container-level operations. Why those three letters and no others: Azure’s permission table defines w as “Create or write content, properties, metadata, or block list”, which is exactly what Put Blob, Put Block and Put Block List need, and c alone is not enough because a retry overwrites an existing blob. d covers the probe cleanup. r covers reads. l (list), a (add), t (tags), i and p are not used by anything, and l is not even valid on a blob-scoped SAS.
A SAS gets you uploads and no download URLs at all. Minting the 24-hour download SAS requires the account key, and the job API’s own Azure presign path is not implemented. A SAS-only Azure configuration delivers files that nobody can be handed a link to.

4. Enter the three fields

Open the dashboard, choose Azure Blob as the destination, and fill in the fields. Labels differ slightly between the onboarding wizard and the settings page; both write the same three API fields. The equivalent payload:
Setting the storage destination is an organization-level action restricted to Admin and Owner members, and one provider is active at a time. Saving an Azure configuration replaces whatever provider was configured before.

5. Test connection

The onboarding wizard’s Test connection button runs a real round trip against your container before saving anything. On success the button reports the measured latency, for example “test object written and deleted in 340 ms”. What the probe actually does:
  1. Opens a client against https://<account_name>.blob.core.windows.net with your account name and key as a Shared Key credential.
  2. Uploads a small blob to tornado-connection-test/<random>.txt inside container_name, with overwrite enabled.
  3. Deletes that blob.
Connect timeout is 5 seconds, read timeout is 10 seconds, and the whole probe is bounded at 20 seconds.
The Azure probe is not the S3 probe. The S3 branch of the same endpoint was extended to read the probe object back and to open and abort a multipart upload, so it now proves the read half. The Azure branch was not: it is still write then delete. Do not carry over what the AWS S3 or Cloudflare R2 pages say about the read leg.
  • <account_name>.blob.core.windows.net resolves and is reachable from Tornado’s network.
  • Shared Key authorization is permitted on the account and your key is accepted.
  • The container exists and is spelled correctly.
  • The credential can both create and delete a blob in that container.
  • The measured latency is a real network round trip, not an estimate.
  • Nothing about reads. The probe issues no GET. On Azure this is a smaller gap than the same sentence means on S3, and it is worth being precise about why. There is no read-only failure mode here: the credential is the account key, so anything it can write it can also read, and the download URL is a service SAS minted from that same key. What the probe cannot see is a restriction that bites on the read path specifically, such as an immutability policy, an encryption-scope rule, or a network rule that your customers hit and Tornado does not.
  • Nothing about durability of the result. It is a point-in-time check, not a monitor. Regenerating the key, tightening the account firewall, or disabling Shared Key access after the test all break delivery silently on the next job.
  • Nothing about policies that trigger later. Immutability policies, legal holds, encryption-scope restrictions, capacity limits or lifecycle rules that do not apply to the tornado-connection-test/ prefix will not surface here.
  • Nothing about the block upload path. Blobs above 256 MB are uploaded as blocks and committed with Put Block List. The probe writes a few bytes in a single Put Blob, so it never exercises that path. Both are covered by the same write permission, so this is a throughput question rather than a permissions one.
  • Nothing about the signed download URLs included in job.completed webhook payloads. Those are signed locally, without contacting Azure.
  • It does not run on every save. Only the onboarding wizard probes. Saving from Settings › Default storage destination writes the configuration without testing it. To re-verify later, call POST /settings/storage/test with the same body you would send to POST /settings/storage.

Troubleshooting

Failures from the Azure branch of the connection test are formatted as <AzureErrorCode>: azure:blob, where the code is the error code Azure Storage returned. Match on the part before the colon.
A failed probe can leave a stray object behind. The probe writes then deletes tornado-connection-test/<random>.txt; if it fails between the two steps, the object stays. Objects under the tornado-connection-test/ prefix are safe to delete.

Rotating the access key

The two keys exist so that rotation does not cause an outage. Update Tornado before you regenerate.
1

Switch Tornado to the other key

If Tornado holds key1, save key2 in the dashboard and confirm delivery on a test job.
2

Regenerate the key you retired

Back in Access keys, select Regenerate next to key1.
Regenerating the key that Tornado currently holds breaks delivery immediately, and it also invalidates every signed download URL already issued. Download URLs in job.completed webhook payloads are service SAS URLs signed with the account key and valid for 24 hours; rotating the key that signed them makes outstanding links return 403 before they expire.

Not available on this route