Skip to content

Commit 07c52f2

Browse files
Register feature ids for credentials set using environment variables (aws#9746)
1 parent 83f1565 commit 07c52f2

File tree

4 files changed

+135
-87
lines changed

4 files changed

+135
-87
lines changed

awscli/botocore/credentials.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1216,6 +1216,7 @@ def load(self):
12161216
logger.info('Found credentials in environment variables.')
12171217
fetcher = self._create_credentials_fetcher()
12181218
credentials = fetcher(require_expiry=False)
1219+
register_feature_id('CREDENTIALS_ENV_VARS')
12191220

12201221
expiry_time = credentials['expiry_time']
12211222
if expiry_time is not None:

awscli/botocore/session.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
from botocore.model import ServiceModel
5959
from botocore.parsers import ResponseParserFactory
6060
from botocore.regions import EndpointResolver
61-
from botocore.useragent import UserAgentString
61+
from botocore.useragent import UserAgentString, register_feature_id
6262

6363
logger = logging.getLogger(__name__)
6464

@@ -913,6 +913,8 @@ def create_client(
913913
f"an access key id and secret key on the session or client: {ignored_credentials}"
914914
)
915915
credentials = self.get_credentials()
916+
if getattr(credentials, 'method', None) == 'explicit':
917+
register_feature_id('CREDENTIALS_CODE')
916918
auth_token = self.get_auth_token()
917919
endpoint_resolver = self._get_internal_component('endpoint_resolver')
918920
exceptions_factory = self._get_internal_component('exceptions_factory')

awscli/botocore/useragent.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@
7676
'FLEXIBLE_CHECKSUMS_REQ_WHEN_REQUIRED': 'a',
7777
'FLEXIBLE_CHECKSUMS_RES_WHEN_SUPPORTED': 'b',
7878
'FLEXIBLE_CHECKSUMS_RES_WHEN_REQUIRED': 'c',
79+
'CREDENTIALS_CODE': 'e',
80+
'CREDENTIALS_ENV_VARS': 'g',
7981
'CREDENTIALS_HTTP': 'z',
8082
'CREDENTIALS_IMDS': '0',
8183
'BEARER_SERVICE_ENV_VARS': '3',

tests/functional/botocore/test_credentials.py

Lines changed: 129 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1093,89 +1093,132 @@ def add_credential_response(self, stubber):
10931093
stubber.add_response(body=json.dumps(response).encode('utf-8'))
10941094

10951095

1096-
class TestFeatureIdRegistered:
1097-
@patch(
1098-
"botocore.utils.InstanceMetadataFetcher.retrieve_iam_role_credentials"
1099-
)
1100-
@patch("botocore.credentials.ContainerProvider.load", return_value=None)
1101-
@patch("botocore.credentials.ConfigProvider.load", return_value=None)
1102-
@patch(
1103-
"botocore.credentials.SharedCredentialProvider.load", return_value=None
1104-
)
1105-
@patch("botocore.credentials.EnvProvider.load", return_value=None)
1106-
def test_user_agent_has_imds_credentials_feature_id(
1107-
self,
1108-
_unused_mock_env_load,
1109-
_unused_mock_shared_load,
1110-
_unused_mock_config_load,
1111-
_unused_mock_container_load,
1112-
mock_retrieve_iam_role_credentials,
1113-
patched_session,
1114-
):
1115-
fake_creds = {
1116-
"role_name": "FAKEROLE",
1117-
"access_key": "FAKEACCESSKEY",
1118-
"secret_key": "FAKESECRET",
1119-
"token": "FAKETOKEN",
1120-
"expiry_time": "2099-01-01T00:00:00Z",
1121-
}
1122-
mock_retrieve_iam_role_credentials.return_value = fake_creds
1123-
1124-
client = patched_session.create_client("s3", region_name="us-east-1")
1125-
with ClientHTTPStubber(client, strict=True) as http_stubber:
1126-
# We want to call this twice to assert that the feature id exists
1127-
# for multiple calls with the same credentials
1128-
http_stubber.add_response()
1129-
http_stubber.add_response()
1130-
client.list_buckets()
1131-
client.list_buckets()
1132-
1133-
ua_string = get_captured_ua_strings(http_stubber)
1134-
feature_list_one = parse_registered_feature_ids(ua_string[0])
1135-
feature_list_two = parse_registered_feature_ids(ua_string[1])
1136-
assert '0' in feature_list_one and '0' in feature_list_two
1137-
1138-
@patch("botocore.credentials.ContainerMetadataFetcher.retrieve_full_uri")
1139-
@patch("botocore.credentials.ConfigProvider.load", return_value=None)
1140-
@patch(
1141-
"botocore.credentials.SharedCredentialProvider.load", return_value=None
1142-
)
1143-
@patch("botocore.credentials.EnvProvider.load", return_value=None)
1144-
def test_user_agent_has_http_credentials_feature_id(
1145-
self,
1146-
_unused_mock_env_load,
1147-
_unused_mock_shared_load,
1148-
_unused_mock_config_load,
1149-
mock_load_http_credentials,
1150-
monkeypatch,
1151-
patched_session,
1152-
):
1153-
environ = {
1154-
'AWS_CONTAINER_CREDENTIALS_FULL_URI': 'http://localhost/foo',
1155-
'AWS_CONTAINER_AUTHORIZATION_TOKEN': 'Basic auth-token',
1156-
}
1157-
for var in environ:
1158-
monkeypatch.setenv(var, environ[var])
1159-
1160-
fake_creds = {
1161-
"AccessKeyId": "FAKEACCESSKEY",
1162-
"SecretAccessKey": "FAKESECRET",
1163-
"Token": "FAKETOKEN",
1164-
"Expiration": "2099-01-01T00:00:00Z",
1165-
"AccountId": "01234567890",
1166-
}
1167-
mock_load_http_credentials.return_value = fake_creds
1168-
1169-
client = patched_session.create_client("s3", region_name="us-east-1")
1170-
with ClientHTTPStubber(client, strict=True) as http_stubber:
1171-
# We want to call this twice to assert that the feature id exists
1172-
# for multiple calls with the same credentials
1173-
http_stubber.add_response()
1174-
http_stubber.add_response()
1175-
client.list_buckets()
1176-
client.list_buckets()
1177-
1178-
ua_string = get_captured_ua_strings(http_stubber)
1179-
feature_list_one = parse_registered_feature_ids(ua_string[0])
1180-
feature_list_two = parse_registered_feature_ids(ua_string[1])
1181-
assert 'z' in feature_list_one and 'z' in feature_list_two
1096+
@pytest.mark.parametrize(
1097+
"environ_vars,fake_credentials,patches,expected_feature_id",
1098+
[
1099+
# Test case 1: IMDS credentials
1100+
(
1101+
{},
1102+
{},
1103+
[
1104+
patch(
1105+
"botocore.utils.InstanceMetadataFetcher.retrieve_iam_role_credentials",
1106+
return_value={
1107+
"role_name": "FAKEROLE",
1108+
"access_key": "FAKEACCESSKEY",
1109+
"secret_key": "FAKESECRET",
1110+
"token": "FAKETOKEN",
1111+
"expiry_time": "2099-01-01T00:00:00Z",
1112+
},
1113+
),
1114+
patch(
1115+
"botocore.credentials.ContainerProvider.load",
1116+
return_value=None,
1117+
),
1118+
patch(
1119+
"botocore.credentials.ConfigProvider.load",
1120+
return_value=None,
1121+
),
1122+
patch(
1123+
"botocore.credentials.SharedCredentialProvider.load",
1124+
return_value=None,
1125+
),
1126+
patch(
1127+
"botocore.credentials.EnvProvider.load", return_value=None
1128+
),
1129+
],
1130+
'0',
1131+
),
1132+
# Test case 2: HTTP credentials (container)
1133+
(
1134+
{
1135+
'AWS_CONTAINER_CREDENTIALS_FULL_URI': 'http://localhost/foo',
1136+
'AWS_CONTAINER_AUTHORIZATION_TOKEN': 'Basic auth-token',
1137+
},
1138+
{},
1139+
[
1140+
patch(
1141+
"botocore.credentials.ContainerMetadataFetcher.retrieve_full_uri",
1142+
return_value={
1143+
"AccessKeyId": "FAKEACCESSKEY",
1144+
"SecretAccessKey": "FAKESECRET",
1145+
"Token": "FAKETOKEN",
1146+
"Expiration": "2099-01-01T00:00:00Z",
1147+
"AccountId": "01234567890",
1148+
},
1149+
),
1150+
patch(
1151+
"botocore.credentials.ConfigProvider.load",
1152+
return_value=None,
1153+
),
1154+
patch(
1155+
"botocore.credentials.SharedCredentialProvider.load",
1156+
return_value=None,
1157+
),
1158+
patch(
1159+
"botocore.credentials.EnvProvider.load", return_value=None
1160+
),
1161+
],
1162+
'z',
1163+
),
1164+
# Test case 3: Credentials set via Environment variables
1165+
(
1166+
{
1167+
'AWS_ACCESS_KEY_ID': 'FAKEACCESSKEY',
1168+
'AWS_SECRET_ACCESS_KEY': 'FAKESECRET',
1169+
'AWS_SESSION_TOKEN': 'FAKETOKEN',
1170+
},
1171+
{},
1172+
[],
1173+
'g',
1174+
),
1175+
# Test case 4: Credentials set via code
1176+
(
1177+
{},
1178+
{
1179+
'aws_access_key_id': 'FAKEACCESSKEY',
1180+
'aws_secret_access_key': 'FAKESECRET',
1181+
'aws_session_token': 'FAKETOKEN',
1182+
},
1183+
[],
1184+
'e',
1185+
),
1186+
],
1187+
)
1188+
def test_user_agent_feature_ids(
1189+
environ_vars,
1190+
fake_credentials,
1191+
patches,
1192+
expected_feature_id,
1193+
monkeypatch,
1194+
patched_session,
1195+
):
1196+
for var, value in environ_vars.items():
1197+
monkeypatch.setenv(var, value)
1198+
1199+
for patch_obj in patches:
1200+
patch_obj.start()
1201+
1202+
try:
1203+
client = patched_session.create_client(
1204+
"s3", region_name="us-east-1", **fake_credentials
1205+
)
1206+
_assert_feature_ids_in_ua(client, expected_feature_id)
1207+
finally:
1208+
for patch_obj in patches:
1209+
patch_obj.stop()
1210+
1211+
1212+
def _assert_feature_ids_in_ua(client, expected_feature_ids):
1213+
"""Helper to test feature IDs appear in user agent for multiple calls."""
1214+
with ClientHTTPStubber(client, strict=True) as http_stubber:
1215+
http_stubber.add_response()
1216+
http_stubber.add_response()
1217+
client.list_buckets()
1218+
client.list_buckets()
1219+
1220+
ua_strings = get_captured_ua_strings(http_stubber)
1221+
for ua_string in ua_strings:
1222+
feature_list = parse_registered_feature_ids(ua_string)
1223+
for expected_id in expected_feature_ids:
1224+
assert expected_id in feature_list

0 commit comments

Comments
 (0)