How to identify disabled regions in AWS?
Question:
AWS regularly add new regions. While “old” regions are enabled by default in every AWS account, new regions are disabled by default 1.
I’m trying to scan a particular resource in all available regions using the following Python (pseudo)code:
regions = boto3_session.get_available_regions('rds')
for region in regions:
boto_rds_client = boto3_session.client('rds', region_name=region)
r_paginator = boto_rds_client.get_paginator('describe_db_instances')
for rdses in r_paginator.paginate():
for rds in rdses['DBInstances']:
do_stuff(rds)
However, this fails with a cryptic An error occurred (InvalidClientTokenId) when calling the DescribeDBInstances operation: The security token included in the request is invalid
when accessing a “new” region.
Other services fail with other errors: e.g. Lambda fails with An error occurred (UnrecognizedClientException) when calling the ListFunctions operation: The security token included in the request is invalid
How can I identify if a region is enabled or not? There does not seem to be an API call to do this…
Answers:
I found an edge case of the API that can be (ab)used to identify enabled regions: The ec2:DescribeRegions API call (and probably others as well, haven’t tried) exhibit a slightly different failure mode in disabled regions:
-
Either the call succeeds, and you know the region is enabled
-
The call fails with an UnauthorizedOperation
error. This indicates you don’t have IAM permissions, but the region is enabled
-
The call fails with AuthFailure
. This indicates the region is disabled
The following code successfully filters regions in my test cases:
def get_enabled_regions(boto3_session: boto3.Session, service: str) -> typing.Set[str]:
regions = boto3_session.get_available_regions(service)
enabled_regions = set()
for region in regions:
ec2_client = boto3_session.client('ec2', region_name=region)
try:
ec2_client.describe_regions()
except botocore.exceptions.ClientError as e:
if e.response['Error']['Code'] == "AuthFailure":
print(f"region {region} seems disabled, skipping")
continue # Account is disabled
elif e.response['Error']['Code'] == "UnauthorizedOperation":
print(f"region {region} seems enabled (but not sure)")
pass # Access denied is good: we have access to the region, just not to the ec2:DescribeRegions call
else:
raise
enabled_regions.add(region)
return enabled_regions
I worked a bit more on the problem, and found a way that relies less on edge case behaviour: Using the sts:GetCallerIdentity
call.
This has several advantages over ec2:DescribeRegions
in that the API is always enabled (can’t be restricted by IAM). You can disable STS for a region, but even then, GetCallerIdentity still works (only issueing of temporary credentials is disabled 1).
def get_enabled_regions(boto3_session: boto3.Session, service: str) -> typing.Set[str]:
regions = boto3_session.get_available_regions(service)
enabled_regions = set()
for region in regions:
sts_client = boto3_session.client('sts', region_name=region)
try:
sts_client.get_caller_identity()
enabled_regions.add(region)
except botocore.exceptions.ClientError as e:
if e.response['Error']['Code'] == "InvalidClientTokenId":
# error code received when region is disabled
print(f"region {region} is disabled")
pass
else:
raise
return enabled_regions
We can use account
client to filter all the enabled disabled regions:
account_client = boto_session.client("account")
response = account_client.list_regions(RegionOptStatusContains=['ENABLED', 'ENABLED_BY_DEFAULT'])
print ([x["RegionName"] for x in response["Regions"]])
AWS regularly add new regions. While “old” regions are enabled by default in every AWS account, new regions are disabled by default 1.
I’m trying to scan a particular resource in all available regions using the following Python (pseudo)code:
regions = boto3_session.get_available_regions('rds')
for region in regions:
boto_rds_client = boto3_session.client('rds', region_name=region)
r_paginator = boto_rds_client.get_paginator('describe_db_instances')
for rdses in r_paginator.paginate():
for rds in rdses['DBInstances']:
do_stuff(rds)
However, this fails with a cryptic An error occurred (InvalidClientTokenId) when calling the DescribeDBInstances operation: The security token included in the request is invalid
when accessing a “new” region.
Other services fail with other errors: e.g. Lambda fails with An error occurred (UnrecognizedClientException) when calling the ListFunctions operation: The security token included in the request is invalid
How can I identify if a region is enabled or not? There does not seem to be an API call to do this…
I found an edge case of the API that can be (ab)used to identify enabled regions: The ec2:DescribeRegions API call (and probably others as well, haven’t tried) exhibit a slightly different failure mode in disabled regions:
-
Either the call succeeds, and you know the region is enabled
-
The call fails with an
UnauthorizedOperation
error. This indicates you don’t have IAM permissions, but the region is enabled -
The call fails with
AuthFailure
. This indicates the region is disabled
The following code successfully filters regions in my test cases:
def get_enabled_regions(boto3_session: boto3.Session, service: str) -> typing.Set[str]:
regions = boto3_session.get_available_regions(service)
enabled_regions = set()
for region in regions:
ec2_client = boto3_session.client('ec2', region_name=region)
try:
ec2_client.describe_regions()
except botocore.exceptions.ClientError as e:
if e.response['Error']['Code'] == "AuthFailure":
print(f"region {region} seems disabled, skipping")
continue # Account is disabled
elif e.response['Error']['Code'] == "UnauthorizedOperation":
print(f"region {region} seems enabled (but not sure)")
pass # Access denied is good: we have access to the region, just not to the ec2:DescribeRegions call
else:
raise
enabled_regions.add(region)
return enabled_regions
I worked a bit more on the problem, and found a way that relies less on edge case behaviour: Using the sts:GetCallerIdentity
call.
This has several advantages over ec2:DescribeRegions
in that the API is always enabled (can’t be restricted by IAM). You can disable STS for a region, but even then, GetCallerIdentity still works (only issueing of temporary credentials is disabled 1).
def get_enabled_regions(boto3_session: boto3.Session, service: str) -> typing.Set[str]:
regions = boto3_session.get_available_regions(service)
enabled_regions = set()
for region in regions:
sts_client = boto3_session.client('sts', region_name=region)
try:
sts_client.get_caller_identity()
enabled_regions.add(region)
except botocore.exceptions.ClientError as e:
if e.response['Error']['Code'] == "InvalidClientTokenId":
# error code received when region is disabled
print(f"region {region} is disabled")
pass
else:
raise
return enabled_regions
We can use account
client to filter all the enabled disabled regions:
account_client = boto_session.client("account")
response = account_client.list_regions(RegionOptStatusContains=['ENABLED', 'ENABLED_BY_DEFAULT'])
print ([x["RegionName"] for x in response["Regions"]])