Repository navigation
Description
Describe the bug
When Quickwit authenticates to Azure Blob Storage via Azure Workload Identity (no access_key configured), the indexer loses access to object storage exactly 24 hours after process start, and never recovers on its own.
WARN uploader: quickwit_indexing::actors::uploader: Failed to upload split. Killing!
cause=failed uploading key 01K….split in bucket azure://<container>/quickwit-indexes/<index>
0: storage error(kind=Unauthorized, source=Azure error wrapper(inner=non-io error occurred which will not be retried))
Every split upload fails from that moment on. Because the error is classified non-retryable, the indexing pipeline is killed and never recovers — the only remedy is restarting the pod, after which it works for exactly another 24 hours.
The root cause is that azure_identity 0.21 reads AZURE_FEDERATED_TOKEN_FILE once, at credential construction, and reuses that assertion for the lifetime of the credential.
Steps to reproduce
- Run Quickwit on AKS with Azure Workload Identity enabled (
azure.workload.identity/use: "true", service account annotated with a client ID, webhook injectingAZURE_CLIENT_ID/AZURE_TENANT_ID/AZURE_FEDERATED_TOKEN_FILE). - Configure Azure storage without an access key, so the token-credential path is taken:
default_index_root_uri: azure://<container>/quickwit-indexes storage: azure: account: <account>
- Run an indexer with a continuously ingesting source and wait 24 hours.
Expected behavior
The credential is refreshed transparently and indexing continues indefinitely, as it does with S3 / IRSA.
Actual behavior
At T+24h every object storage write fails with kind=Unauthorized until the process is restarted.
Configuration
-
quickwit --version:quickwit version: 0.9.0 (x86_64-unknown-linux-gnu 2026-07-28T09:58:34Z 4b2775e)Image:
quickwit/quickwit:edge@sha256:687cea395588fb328a413b3ef5ef58889597a164eae43f4006f69e89d505ab6a -
Relevant
node.yaml:default_index_root_uri: azure://<container>/quickwit-indexes storage: azure: account: <account> # no access_key -> token credential path
Loaded config confirms
access_key: None:storage_configs: StorageConfigs([Azure(AzureStorageConfig { account_name: Some("<account>"), access_key: None })])
Root cause analysis
AzureBlobStorage delegates credential selection to azure_identity when no access key is set:
With the three workload-identity env vars present, the resolution chain is
create_credential() → DefaultAzureCredential → EnvironmentCredential → WorkloadIdentityCredential.
In azure_identity 0.21.0, that credential reads the token file exactly once, inside create(), and stores it:
// azure_identity-0.21.0/src/token_credentials/workload_identity_credentials.rs
let token = std::fs::read_to_string(token_file.clone())?; // read ONCE
return Ok(WorkloadIdentityCredential::new(..., token));
...
async fn get_token(&self, scopes: &[&str]) -> Result<AccessToken> {
federated_credentials_flow::perform(..., self.token.secret(), ...) // frozen copy, forever
}Two lifetimes then collide. Both measured on a live cluster:
| Token | Lifetime | Measurement |
|---|---|---|
| Projected service-account token (the client assertion) | 1 hour | kubectl create token --audience api://AzureADTokenExchange → exp - iat = 3600; pod spec shows expirationSeconds: 3600 |
Entra ID access token for https://storage.azure.com/.default |
24 hours | live token exchange → expires_in: 86399 |
Sequence:
- T+0 — the token file is read once, exchanged, and a 24h access token is cached in
TokenCache. - T+0 → T+24h — kubelet rotates the token file hourly, but Quickwit never re-reads it. No refresh is attempted, so the growing staleness stays invisible and everything works normally.
- T+24h —
TokenCache::is_expired()fires and the exchange is retried with an assertion that expired 23 hours earlier. Entra rejects it (AADSTS700024: Client assertion is not within its valid time range). DefaultAzureCredentialfalls through to App Service / IMDS / Azure CLI credentials, none of which are available in the pod, so the whole chain fails and surfaces askind=Unauthorized.- No recovery, because the frozen assertion lives in a credential owned by a long-lived pipeline. Only a process restart re-reads the file.
Observed timing matches to the second — pod started 2026-08-08T09:12:10Z, first Unauthorized at 2026-08-09T09:12:19Z: 24h 00m 09s. Across a week of restarts the interval was consistently 24.2–25.4h (the drift is human reaction time).
Only the indexer is affected. StorageResolver::resolve() constructs a fresh Storage — and therefore a fresh credential and a fresh file read — on every call. The janitor and searcher re-resolve per operation and stayed healthy for 67h+ uptime on the same service account; the indexer resolves once when the indexing pipeline spawns and holds that credential for the process lifetime.
Upstream status
This is a known, fixed bug in the Azure SDK:
- Issue: Azure Workload Identity - Expired token Azure/azure-sdk-for-rust#1739 — "Azure Workload Identity - Expired token", same
AADSTS700024signature - Fix: Periodically read workload identity token from file Azure/azure-sdk-for-rust#1997 — "Periodically read workload identity token from file" (re-reads every 10 minutes)
The fix shipped in azure_identity 0.22.0.
Why simply bumping the dependency does not work
azure_storage and azure_storage_blobs were never published beyond 0.21.0 — the legacy Rust SDK storage crates are end-of-life. azure_storage 0.21 requires azure_core ^0.21, and StorageCredentials::token_credential() takes an Arc<dyn TokenCredential> from azure_core 0.21, whereas azure_identity ≥ 0.22 implements azure_core ≥ 0.22's trait — a different, incompatible trait. The new-SDK replacement azure_storage_blob is currently 1.1.0-beta.1.
So the fix cannot arrive via https://github.com/quickwit-oss/quickwit/blob/main/quickwit/Cargo.toml#L378-L385 without migrating off the storage crates.
Proposed fix
A small, self-contained wrapper inside quickwit-storage that implements azure_core 0.21's TokenCredential and re-reads AZURE_FEDERATED_TOKEN_FILE (mirroring what upstream #1997 does), used in place of azure_identity::create_credential() at azure_blob_storage.rs:182. No dependency changes, no API break, and it can be scoped to the workload-identity case with a fallback to the existing behaviour otherwise.
Activity
guilload commentedon Aug 13, 2026
@cds-nitaliya, @siva-abstract-security do we want to look into upgrading the Azure SDK to
azure_storage_blob1.0.0 instead?siva-abstract-security commentedon Aug 13, 2026
Yes — I think that's the right destination, and it does fix this properly rather than working around it. I checked
azure_identity1.0.0 and it re-reads the federated token file whenever the cached copy is older than 600s (workload_identity_credential.rs), so the staleness this issue is about goes away on its own once you're on the new SDK.Some scoping in case it's useful for planning — the blast radius looks smaller than I expected:
quickwit-storagecarries the Azure dependenciesstorage_config.rsand one integration testClientBuilder/ContainerClient,StorageCredentials,put_block/put_block_list,list_blobs,get_properties,delete,Pageableazure_storage_blob1.0.0 has been stable since 2026-05-14, so this only became a realistic option fairly recentlyTwo things I'd want to check early, since they'd shape the work more than the blob calls do:
ClientBuilder::emulator()backs theintegration-testsuitefeature. If the new SDK has no emulator equivalent, that testing story needs rethinking as part of the port.On this PR specifically: I'd gently suggest decoupling the two. The failure is live on Azure workload-identity tenants today on a 24h cycle, and the risk profiles are quite different — #6687 touches one credential branch and adds an isolated module (it can't affect access-key or non-Azure users), whereas the migration rewrites the whole Azure data path for everyone on Azure. It's also written to be deleted: when the migration lands,
azure_workload_identity.rsand its call site go in one commit, with nothing else entangled.That said, if you'd rather not carry a workaround in a module you're about to rewrite, that's completely reasonable and I won't be precious about it — we can run it on our side in the meantime. Happy either way.
guilload commentedon Aug 13, 2026
https://github.com/vectordotdev/vector/blob/master/AI_POLICY.md#keep-conversations-human
siva-abstract-security commentedon Aug 14, 2026
@guilload :
yes, azure_storage_blob 1.0.0 is where this should land. azure_identity 1.0.0 already re-reads the federated token file once the cached copy is >600s old, so this bug just disappears there rather than needing a fix at all.
only thing i'd want to look at before starting is azurite - ClientBuilder::emulator() is what integration-testsuite runs on, and i don't know if the new SDK has an equivalent.
on #6687 - happy for you to close it if you'd rather not carry a workaround in a file you're about to rewrite. it's live for us on a 24h cycle so we'll run it in our fork either way. no strong feelings.
guilload commentedon Aug 14, 2026
Much better :)
Let's close the other PR and upgrade to 1.0.0.
emulator()doesn't do much: it hardcodes the endpoint ip and port, the account name, and forces use of hardcoded access key + token for auth. I would expect the new version of the SDK to support customizing the endpoint.siva-abstract-security commentedon Aug 15, 2026
@guilload : #6693 - Can you review ?