Commands were hanging for several minutes when run with low-privilege
credentials against accounts where opt-in regions (e.g. me-south-1)
are not enabled. Each unreachable region exhausted the full AWS SDK
retry budget (3 attempts × ~30s timeout) per service call, compounding
across every check in all-checks mode.
Three-part fix:
1. TCP reachability probe: Before making any API calls, probe
sts.REGION.amazonaws.com:443 with a 3-second timeout. Unreachable
regions are filtered out concurrently and cached for the run so the
probe only happens once per region. This eliminates multi-minute
stalls caused by regions that are not enabled for the account.
2. --max-retries flag: New persistent flag on the aws subcommand
(default 3, matching prior behaviour). Setting --max-retries 0
skips all retries, useful when running black-box against an account
where most API calls will return 403/401 anyway.
3. Remove me-south-1 from the static fallback region list: This
Bahrain opt-in region was the primary offender reported in issue
#132. It is excluded from the hardcoded list so accounts that never
opted in skip it entirely even before the TCP probe runs.
Also fixes a pre-existing bug in GetIamSimResult where a redundant
second isRoleAdminBool check after the cache-or-compute block would
overwrite a correct "YES" result from the cache with "No" (because
isRoleAdminBool stays false on a cache hit).
Fixes#132
The NetworkPortsModule cached EC2 NACLs and Security Groups in two
per-region maps. The cache lookup read the maps without holding a
lock while the cache population path took a write lock, producing a
"concurrent map read and map write" runtime panic when multiple
region goroutines hit the cache check concurrently. The panic
reproduced reliably in low-density AWS accounts where every region
goroutine raced through the cache check at the same instant.
Take the read lock on the cache fast-path in both
getEC2NACLsPerRegion and getEC2SecurityGroupsPerRegion. Move
naclsMutex and securityGroupsMutex from package-level vars onto the
module struct so concurrent module instances stop serializing on
each other's locks.
Convert CommandCounter from plain int fields with naked ++/-- to
int64 fields with Incr/Decr/Load methods that use atomic.AddInt64
and atomic.LoadInt64 internally. Worker goroutines and the spinner
goroutine were updating and reading the same counters without
synchronization, producing torn integer writes that could show as
off-by-one progress in the spinner. Plain int64 storage keeps the
struct copyable so existing constructors and helpers that take
modules by value continue to vet-clean.
Add TestNetworkPortsCacheConcurrentAccess as a regression test.
With the read lock removed, "go test -race" reports the
mapaccess2_faststr read racing the locked write; with the fix in
place the test passes.