Storage local-debug: switch from Deny+ipRule to Allow strategy¶
Date: 2026-05-20
Scope: api/services/storage_public_access.py, scripts/dev/storage-public-access.sh, api/tests/test_storage_public_access.py
Motivation¶
/api/blast/databases was returning firewall_blocked degraded state, causing the DB list to show 0 items.
Root cause confirmed after live debugging:
| Strategy | Config | Result after propagation |
|---|---|---|
defaultAction=Deny + ipRule[61.80.8.142] |
24h+ | HTTP 403 (never works) |
defaultAction=Deny + ipRule[61.80.8.142] |
2 min fresh | HTTP 403 (still broken) |
defaultAction=Allow |
90 s | HTTP 200 ✓ |
For ADLS Gen2 (isHnsEnabled: true) accounts with an approved private endpoint in the AKS VNet, defaultAction=Deny + ipRule does not reliably propagate to the data plane — even after 24+ hours. defaultAction=Allow is the only reliable mechanism for developer laptop access.
allowSharedKeyAccess: false remains in effect, so Azure AD authentication is still enforced at every data-plane request. The change only affects network-layer access control.
The previous ensure_local_storage_access code:
1. Detected caller's public IP via api.ipify.org
2. Set defaultAction=Deny + ipRules=[callerIP]
3. Returned already_open if the IP was already in the rules (even though it was still blocked)
This combination silently masked the issue — ARM state said "open" but data plane was blocked.
User-facing change¶
GET /api/blast/databasesnow returns the full database list instead of thefirewall_blockeddegraded state whenLOCAL_DEBUG_AUTO_OPEN_STORAGE=trueand the storage account is reachable.scripts/dev/storage-public-access.sh onsetsdefaultAction=Allow(notDeny+ipRule) and waits 90 s for propagation.--ipflag tostorage-public-access.sh onis ignored (no longer needed, kept for CLI compatibility).
Code diff summary¶
api/services/storage_public_access.py¶
ensure_local_storage_access:
- already_ok condition: Enabled + defaultAction==Allow (was: Enabled + Deny + callerIP in rules)
- ARM update now sets defaultAction=Allow, no ip_rules (was: Deny + [callerIP])
- caller_ip detection moved to after the update (informational only, None no longer blocks)
- Removed IPRule import (unused)
- Removed existing_ips collection (unused with Allow strategy)
- Docstring updated to describe new strategy and rationale
scripts/dev/storage-public-access.sh¶
on case:
- Removed IP detection, validation, and network-rule add/remove steps
- --default-action Allow (was Deny)
- Propagation wait bumped from 10 s → 90 s to match observed propagation time
- Header comment updated with rationale for Allow strategy
api/tests/test_storage_public_access.py¶
_make_accounthelper: accepts explicitdefault_actionparameter (was inferred fromip_ruleslength)test_ensure_already_open: assertsdefaultAction=Allowas open condition, no IP checktest_ensure_opens_when_disabled: assertsdefaultAction=Allowin update params, noip_rulestest_ensure_appends_caller_ip_when_partially_open→ renamedtest_ensure_updates_to_allow_when_enabled_with_denytest_ensure_returns_failed_when_caller_ip_unknown→ renamedtest_ensure_opened_when_caller_ip_unknown(IP unknown no longer blocks)test_ensure_already_open_is_cached: updated mock account toAllow, removed_detect_caller_ipmock
Validation evidence¶
# 1. Unit tests
uv run pytest -q api/tests/test_storage_public_access.py
# → 16 passed in 1.93s ✓
# 2. Storage set to Allow, 90s propagation, data-plane test
az storage account update -n elbstg01 -g rg-elb-01 --default-action Allow --bypass AzureServices
# HTTP:200 after 90s ✓
# 3. API smoke test
curl "http://127.0.0.1:8085/api/blast/databases?subscription_id=...&storage_account=elbstg01&resource_group=rg-elb-01"
# → {"databases": [{"name": "16S_ribosomal_RNA", ...}, {"name": "18S_fungal_sequences", ...},
# {"name": "ITS_RefSeq_Fungi", ...}, {"name": "core_nt", ...}, {"name": "elb_compare_tiny", ...}]}
# 5 databases returned, no firewall_blocked ✓