Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
11 changes: 11 additions & 0 deletions .changes/next-release/configure-tempcreds.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"type": "enhancement",
"category": "``configure``",
"description": "Added support for temporary credentials with ``aws configure``. The CLI will prompt for an ``aws_session_token`` if the provided access key ID is temporary."
},
{
"type": "enhancement",
"category": "``configure``",
"description": "Added the new ``aws configure mfa-login` command, which creates a profile with temporary credentials corresponding to an IAM user with an MFA code."
},
]
46 changes: 43 additions & 3 deletions awscli/customizations/configure/configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# language governing permissions and limitations under the License.
import logging
import os
import sys

from botocore.exceptions import ProfileNotFound

Expand All @@ -25,6 +26,7 @@
from awscli.customizations.configure.importer import ConfigureImportCommand
from awscli.customizations.configure.list import ConfigureListCommand
from awscli.customizations.configure.listprofiles import ListProfilesCommand
from awscli.customizations.configure.mfalogin import ConfigureMFALoginCommand
from awscli.customizations.configure.set import ConfigureSetCommand
from awscli.customizations.configure.sso import (
ConfigureSSOCommand,
Expand All @@ -43,9 +45,13 @@ def register_configure_cmd(cli):

class InteractivePrompter:
def get_value(self, current_value, config_name, prompt_text=''):
if config_name in ('aws_access_key_id', 'aws_secret_access_key'):
if config_name in (
'aws_access_key_id',
'aws_secret_access_key',
'aws_session_token',
):
current_value = mask_value(current_value)
response = compat_input("%s [%s]: " % (prompt_text, current_value))
response = compat_input(f"{prompt_text} [{current_value}]: ")
if not response:
# If the user hits enter, we return a value of None
# instead of an empty string. That way we can determine
Expand Down Expand Up @@ -84,6 +90,7 @@ class ConfigureCommand(BasicCommand):
{'name': 'list-profiles', 'command_class': ListProfilesCommand},
{'name': 'sso', 'command_class': ConfigureSSOCommand},
{'name': 'sso-session', 'command_class': ConfigureSSOSessionCommand},
{'name': 'mfa-login', 'command_class': ConfigureMFALoginCommand},
{
'name': 'export-credentials',
'command_class': ConfigureExportCredentialsCommand,
Expand All @@ -95,35 +102,64 @@ class ConfigureCommand(BasicCommand):
# (logical_name, config_name, prompt_text)
('aws_access_key_id', "AWS Access Key ID"),
('aws_secret_access_key', "AWS Secret Access Key"),
('aws_session_token', "AWS Session Token"),
('region', "Default region name"),
('output', "Default output format"),
]

def __init__(self, session, prompter=None, config_writer=None):
super(ConfigureCommand, self).__init__(session)
super().__init__(session)
if prompter is None:
prompter = InteractivePrompter()
self._prompter = prompter
if config_writer is None:
config_writer = ConfigFileWriter()
self._config_writer = config_writer

def _needs_session_token(self, new_values):
"""Check if session token is needed based on access key ID."""
access_key = new_values.get('aws_access_key_id')
return access_key and access_key.startswith('ASIA')

def _should_prompt_for_session_token(self, new_values, config):
"""Determine if we should prompt for session token."""
# Don't prompt if explicitly switching to long-term credentials
new_access_key = new_values.get('aws_access_key_id')
if new_access_key and not self._needs_session_token(new_values):
return False

# Prompt if needed for temporary credentials or if already exists
return self._needs_session_token(new_values) or config.get('aws_session_token')

def _run_main(self, parsed_args, parsed_globals):
# Called when invoked with no args "aws configure"

new_values = {}
# This is the config from the config file scoped to a specific
# profile.
try:
config = self._session.get_scoped_config()
except ProfileNotFound:
config = {}

for config_name, prompt_text in self.VALUES_TO_PROMPT:
if config_name == 'aws_session_token' and not self._should_prompt_for_session_token(new_values, config):
continue

current_value = config.get(config_name)
new_value = self._prompter.get_value(
current_value, config_name, prompt_text
)
if new_value is not None and new_value != current_value:
new_values[config_name] = new_value

# Remove session token for non-temporary credentials
if (
'aws_access_key_id' in new_values
and not self._needs_session_token(new_values)
and config.get('aws_session_token')
):
new_values['aws_session_token'] = None
config_filename = os.path.expanduser(
self._session.get_config_variable('config_file')
)
Expand All @@ -150,6 +186,10 @@ def _write_out_creds_file_values(self, new_values, profile_name):
credential_file_values['aws_secret_access_key'] = new_values.pop(
'aws_secret_access_key'
)
if 'aws_session_token' in new_values:
credential_file_values['aws_session_token'] = new_values.pop(
'aws_session_token'
)
if credential_file_values:
if profile_name is not None:
credential_file_values['__section__'] = profile_name
Expand Down
Loading
Loading