Repository: aws-ia/taskcat
Issue: Boto3Cache._get_account_info determines incorrect partition due to unexpected upstream behavior.

**Describe the bug**
The `Boto3Cache._get_account_info` function relies on `boto3.Session.get_available_regions('s3')`; However this function defaults to the 'commerical' partition, even when the session is created in another partition. See below. 

** Logic used in taskcat **
https://github.com/aws-quickstart/taskcat/blob/ca4bfe765d92203e5ca58b3ea89313431dc30001/taskcat/_client_factory.py#L105-L118

**To Reproduce**
```
>>> import pprint as pp
>>> import boto3
>>> from taskcat._client_factory import Boto3Cache
>>> boto3_cache = Boto3Cache()
>>> s = boto3_cache.session(profile='govcloud')
>>> s
Session(region_name='us-gov-west-1')
>>>
>>>
>>> s.get_available_regions('s3')
['ap-east-1',
 'ap-northeast-1',
 'ap-northeast-2',
 'ap-south-1',
 'ap-southeast-1',
 'ap-southeast-2',
 'ca-central-1',
 'eu-central-1',
 'eu-north-1',
 'eu-west-1',
 'eu-west-2',
 'eu-west-3',
 'me-south-1',
 'sa-east-1',
 'us-east-1',
 'us-east-2',
 'us-west-1',
 'us-west-2']
>>>
>>>
>>>
```

**Expected behavior**
The partition is correctly identified.

**Screenshots**
[ERROR  ] : ClientError An error occurred (InvalidClientTokenId) when calling the GetCallerIdentity operation: The security token included in the request is invalid

**Version (Please make sure you are running the latest version of taskcat)
0.9.13



Changed files:
taskcat/_client_factory.py, tests/test_client_factory.py

diff --git a/taskcat/_client_factory.py b/taskcat/_client_factory.py
index 3d06905..e46b702 100644
--- a/taskcat/_client_factory.py
+++ b/taskcat/_client_factory.py
@@ -100,17 +100,8 @@ class Boto3Cache:
         )["account_id"]
 
     def _get_account_info(self, profile):
-        region = self._get_region(None, profile)
+        partition, region = self._get_partition(profile)
         session = self.session(profile, region)
-        avail_regions = session.get_available_regions("s3")
-        partition = "aws"
-        region = "us-east-1"
-        if "us-gov-east-1" in avail_regions:
-            partition = "aws-us-gov"
-            region = "us-gov-east-1"
-        elif "cn-north-1" in avail_regions:
-            partition = "aws-cn"
-            region = "cn-north-1"
         sts_client = session.client("sts", region_name=region)
         try:
             account_id = sts_client.get_caller_identity()["Account"]
@@ -186,6 +177,24 @@ class Boto3Cache:
             region = self.get_default_region(profile)
         return region
 
+    def _get_partition(self, profile):
+        partition_regions = [
+            ("aws", "us-east-1"),
+            ("aws-cn", "cn-north-1"),
+            ("aws-us-gov", "us-gov-west-1"),
+        ]
+        for partition, region in partition_regions:
+            try:
+                self.session(profile, region).client(
+                    "sts", region_name=region
+                ).get_caller_identity()
+                return (partition, region)
+            except ClientError as e:
+                if "InvalidClientTokenId" in str(e):
+                    continue
+                raise
+        raise ValueError("cannot find suitable AWS partition")
+
     def get_default_region(self, profile_name="default") -> str:
         try:
             region = self._boto3.session.Session(profile_name=profile_name).region_name
@@ -194,6 +203,8 @@ class Boto3Cache:
                 raise
             region = self._boto3.session.Session().region_name
         if not region:
-            LOG.warning("Region not set in credential chain, defaulting to us-east-1")
-            region = "us-east-1"
+            _, region = self._get_partition(profile_name)
+            LOG.warning(
+                "Region not set in credential chain, defaulting to {}".format(region)
+            )
         return region
diff --git a/tests/test_client_factory.py b/tests/test_client_factory.py
index 094bc58..de5fd16 100644
--- a/tests/test_client_factory.py
+++ b/tests/test_client_factory.py
@@ -40,10 +40,14 @@ class TestBoto3Cache(unittest.TestCase):
             x, x._session_cache, ["imported_session_foobar", "us-east-1"], mock_boto3
         )
 
+    @mock.patch("taskcat._client_factory.Boto3Cache._get_partition", autospec=True)
     @mock.patch("taskcat._client_factory.Boto3Cache._cache_set", autospec=True)
     @mock.patch("taskcat._client_factory.Boto3Cache._cache_lookup", autospec=True)
     @mock.patch("taskcat._client_factory.boto3.Session", autospec=True)
-    def test_session_no_profile(self, mock_boto3, mock_cache_lookup, mock_cache_set):
+    def test_session_no_profile(
+        self, mock_boto3, mock_cache_lookup, mock_cache_set, mock_get_partition
+    ):
+        mock_get_partition.return_value = (None, "us-east-1")
         mock_cache_lookup.side_effect = ProfileNotFound(profile="non-existent-profile")
         Boto3Cache().session()  # default value should be "default" profile
         self.assertEqual(mock_boto3.called, True)
@@ -92,9 +96,13 @@ class TestBoto3Cache(unittest.TestCase):
         Boto3Cache().account_id()
         self.assertEqual(mock_cache_lookup.called, True)
 
+    @mock.patch("taskcat._client_factory.boto3", autospec=True)
+    @mock.patch("taskcat._client_factory.Boto3Cache._get_partition", autospec=True)
     @mock.patch("taskcat._client_factory.Boto3Cache._get_region", autospec=True)
     @mock.patch("taskcat._client_factory.Boto3Cache.session")
-    def test__get_account_info(self, mock_session, mock__get_region):
+    def test__get_account_info(
+        self, mock_session, mock__get_region, mock__get_partition, mock_boto3
+    ):
         mock__get_region.return_value = "us-east-1"
         session = boto3.Session()
         session.get_available_regions = mock.Mock()
@@ -103,22 +111,23 @@ class TestBoto3Cache(unittest.TestCase):
         sts.get_caller_identity.return_value = {"Account": "123412341234"}
         session.client.return_value = sts
         mock_session.return_value = session
-        cache = Boto3Cache()
+        mock_boto3.session.Session = mock.Mock()
+        mock_boto3.session.Session.return_value = session
+        cache = Boto3Cache(_boto3=mock_boto3)
 
-        session.get_available_regions.return_value = ["us-gov-east-1"]
+        mock__get_partition.return_value = ("aws-us-gov", "us-gov-east-1")
         partition = cache._get_account_info("default")["partition"]
         self.assertEqual(partition, "aws-us-gov")
 
-        session.get_available_regions.return_value = ["cn-north-1"]
+        mock__get_partition.return_value = ("aws-cn", "cn-north-1")
         partition = cache._get_account_info("default")["partition"]
         self.assertEqual(partition, "aws-cn")
 
-        session.get_available_regions.return_value = ["us-east-1"]
+        mock__get_partition.return_value = ("aws", "us-east-1")
         partition = cache._get_account_info("default")["partition"]
         self.assertEqual(partition, "aws")
 
-        self.assertEqual(mock_session.call_count, 3)
-        self.assertEqual(sts.get_caller_identity.call_count, 3)
+        self.assertEqual(3, sts.get_caller_identity.call_count)
 
         sts.get_caller_identity.side_effect = ClientError(
             error_response={"Error": {"Code": "test"}}, operation_name="test"
@@ -141,3 +150,38 @@ class TestBoto3Cache(unittest.TestCase):
         )
         with self.assertRaises(TaskCatException):
             cache._get_account_info("default")
+
+    @mock.patch("taskcat._client_factory.boto3", autospec=True)
+    @mock.patch("taskcat._client_factory.Boto3Cache.session")
+    def test__get_partition(self, mock_session, mock_boto3):
+        session = boto3.Session()
+        session.client = mock.Mock()
+        sts = mock.Mock()
+        sts.get_caller_identity.return_value = {"Account": "123412341234"}
+        session.client.return_value = sts
+        mock_session.return_value = session
+        mock_boto3.session.Session = mock.Mock()
+        mock_boto3.session.Session.return_value = session
+        cache = Boto3Cache(_boto3=mock_boto3)
+
+        invalid_token_exception = ClientError(
+            error_response={"Error": {"Code": "InvalidClientTokenId"}},
+            operation_name="test",
+        )
+
+        sts.get_caller_identity.side_effect = [True]
+        result = cache._get_partition("default")
+        self.assertEqual(result, ("aws", "us-east-1"))
+
+        sts.get_caller_identity.side_effect = [invalid_token_exception, True]
+        result = cache._get_partition("default")
+        self.assertEqual(result, ("aws-cn", "cn-north-1"))
+
+        sts.get_caller_identity.side_effect = [
+            invalid_token_exception,
+            invalid_token_exception,
+            True,
+        ]
+        result = cache._get_partition("default")
+        self.assertEqual(result, ("aws-us-gov", "us-gov-west-1"))
+        self.assertEqual(sts.get_caller_identity.call_count, 6)