Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add account ID to static credentials providers #3348

Open
wants to merge 3 commits into
base: account-id-endpoint-routing
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions botocore/credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -1297,6 +1297,7 @@ class SharedCredentialProvider(CredentialProvider):
# aws_security_token, but the SDKs are standardizing on aws_session_token
# so we support both.
TOKENS = ['aws_security_token', 'aws_session_token']
ACCOUNT_ID = 'aws_account_id'

def __init__(self, creds_filename, profile_name=None, ini_parser=None):
self._creds_filename = creds_filename
Expand All @@ -1323,15 +1324,23 @@ def load(self):
config, self.ACCESS_KEY, self.SECRET_KEY
)
token = self._get_session_token(config)
account_id = self._get_account_id(config)
return Credentials(
access_key, secret_key, token, method=self.METHOD
access_key,
secret_key,
token,
method=self.METHOD,
account_id=account_id,
)

def _get_session_token(self, config):
for token_envvar in self.TOKENS:
if token_envvar in config:
return config[token_envvar]

def _get_account_id(self, config):
return config.get(self.ACCOUNT_ID)


class ConfigProvider(CredentialProvider):
"""INI based config provider with profile sections."""
Expand All @@ -1345,6 +1354,7 @@ class ConfigProvider(CredentialProvider):
# aws_security_token, but the SDKs are standardizing on aws_session_token
# so we support both.
TOKENS = ['aws_security_token', 'aws_session_token']
ACCOUNT_ID = 'aws_account_id'

def __init__(self, config_filename, profile_name, config_parser=None):
"""
Expand Down Expand Up @@ -1381,8 +1391,13 @@ def load(self):
profile_config, self.ACCESS_KEY, self.SECRET_KEY
)
token = self._get_session_token(profile_config)
account_id = self._get_account_id(profile_config)
return Credentials(
access_key, secret_key, token, method=self.METHOD
access_key,
secret_key,
token,
method=self.METHOD,
account_id=account_id,
)
else:
return None
Expand All @@ -1392,6 +1407,9 @@ def _get_session_token(self, profile_config):
if token_name in profile_config:
return profile_config[token_name]

def _get_account_id(self, config):
return config.get(self.ACCOUNT_ID)


class BotoProvider(CredentialProvider):
METHOD = 'boto-config'
Expand Down
18 changes: 16 additions & 2 deletions botocore/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,9 @@ def set_default_client_config(self, client_config):
"""
self._client_config = client_config

def set_credentials(self, access_key, secret_key, token=None):
def set_credentials(
self, access_key, secret_key, token=None, account_id=None
):
"""
Manually create credentials for this session. If you would
prefer to use botocore without a config file, environment variables,
Expand All @@ -495,9 +497,12 @@ def set_credentials(self, access_key, secret_key, token=None):
:type token: str
:param token: An option session token used by STS session
credentials.

:type account_id: str
:param account_id: An optional account ID part of the credentials.
"""
self._credentials = botocore.credentials.Credentials(
access_key, secret_key, token
access_key, secret_key, token, account_id=account_id
)

def get_credentials(self):
Expand Down Expand Up @@ -840,6 +845,7 @@ def create_client(
aws_access_key_id=None,
aws_secret_access_key=None,
aws_session_token=None,
aws_account_id=None,
alexgromero marked this conversation as resolved.
Show resolved Hide resolved
config=None,
):
"""Create a botocore client.
Expand Down Expand Up @@ -898,6 +904,13 @@ def create_client(
:param aws_session_token: The session token to use when creating
the client. Same semantics as aws_access_key_id above.

:type aws_account_id: string
alexgromero marked this conversation as resolved.
Show resolved Hide resolved
:param aws_account_id: The account id to use when creating
the client. This is entirely optional, and if not provided,
the credentials configured for the session will automatically
be used. You only need to provide this argument if you want
to override the credentials used for this specific client.

:type config: botocore.client.Config
:param config: Advanced client configuration options. If a value
is specified in the client config, its value will take precedence
Expand Down Expand Up @@ -945,6 +958,7 @@ def create_client(
access_key=aws_access_key_id,
secret_key=aws_secret_access_key,
token=aws_session_token,
account_id=aws_account_id,
)
elif self._missing_cred_vars(aws_access_key_id, aws_secret_access_key):
raise PartialCredentialsError(
Expand Down
10 changes: 8 additions & 2 deletions tests/integration/test_credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,10 @@ def test_access_secret_vs_profile_code(self, credentials_cls):
)

credentials_cls.assert_called_with(
access_key='code', secret_key='code-secret', token=mock.ANY
access_key='code',
secret_key='code-secret',
token=mock.ANY,
account_id=mock.ANY,
)

def test_profile_env_vs_code(self):
Expand All @@ -97,7 +100,10 @@ def test_access_secret_env_vs_code(self, credentials_cls):
)

credentials_cls.assert_called_with(
access_key='code', secret_key='code-secret', token=mock.ANY
access_key='code',
secret_key='code-secret',
token=mock.ANY,
account_id=mock.ANY,
)

def test_access_secret_env_vs_profile_code(self):
Expand Down
41 changes: 41 additions & 0 deletions tests/unit/test_credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -1428,6 +1428,28 @@ def test_credentials_file_does_not_exist_returns_none(self):
creds = provider.load()
self.assertIsNone(creds)

def test_credentials_file_exists_with_account_id(self):
self.ini_parser.return_value = {
'default': {
'aws_access_key_id': 'foo',
'aws_secret_access_key': 'bar',
'aws_session_token': 'baz',
'aws_account_id': 'bin',
}
}
provider = credentials.SharedCredentialProvider(
creds_filename='~/.aws/creds',
profile_name='default',
ini_parser=self.ini_parser,
)
creds = provider.load()
self.assertIsNotNone(creds)
self.assertEqual(creds.access_key, 'foo')
self.assertEqual(creds.secret_key, 'bar')
self.assertEqual(creds.token, 'baz')
self.assertEqual(creds.method, 'shared-credentials-file')
self.assertEqual(creds.account_id, 'bin')


class TestConfigFileProvider(BaseEnvVar):
def setUp(self):
Expand Down Expand Up @@ -1490,6 +1512,25 @@ def test_partial_creds_is_error(self):
with self.assertRaises(botocore.exceptions.PartialCredentialsError):
provider.load()

def test_config_file_with_account_id(self):
profile_config = {
'aws_access_key_id': 'foo',
'aws_secret_access_key': 'bar',
'aws_session_token': 'baz',
'aws_account_id': 'bin',
}
parsed = {'profiles': {'default': profile_config}}
parser = mock.Mock()
parser.return_value = parsed
provider = credentials.ConfigProvider('cli.cfg', 'default', parser)
creds = provider.load()
self.assertIsNotNone(creds)
self.assertEqual(creds.access_key, 'foo')
self.assertEqual(creds.secret_key, 'bar')
self.assertEqual(creds.token, 'baz')
self.assertEqual(creds.method, 'config-file')
self.assertEqual(creds.account_id, 'bin')


class TestBotoProvider(BaseEnvVar):
def setUp(self):
Expand Down
20 changes: 20 additions & 0 deletions tests/unit/test_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -781,6 +781,26 @@ def test_param_api_version_overrides_config_value(self, client_creator):
]
self.assertEqual(call_kwargs['api_version'], override_api_version)

@mock.patch('botocore.client.ClientCreator')
def test_create_client_with_credentials(self, client_creator):
self.session.create_client(
'sts',
'us-west-2',
aws_access_key_id='foo',
aws_secret_access_key='bar',
aws_session_token='baz',
aws_account_id='bin',
)
credentials = (
client_creator.return_value.create_client.call_args.kwargs[
'credentials'
]
)
self.assertEqual(credentials.access_key, 'foo')
self.assertEqual(credentials.secret_key, 'bar')
self.assertEqual(credentials.token, 'baz')
self.assertEqual(credentials.account_id, 'bin')


class TestSessionComponent(BaseSessionTest):
def test_internal_component(self):
Expand Down
Loading