Best practice when working with AWS (along with most things that
require authentication) is to use some form of multi-factor
authentication (MFA). After enabling MFA for your account, interactions
with AWS using the CLI needs to provide the corresponding additional
information. The CLI itself doesn’t provide particular support for this
need, and so often additional helpers such as awsmfa
are
used. While such a tool is very effective and as the one referenced is
written in Python it is unlikely to represent significant overhead
beyond the AWS CLL/boto, I’ve had cases in the past for which it was
less than ideal.
The particular case where a solution such as awsmfa
did
not readily help was where I wanted to rely on the relevant ticket
information to be present in the environment which could then be
accessed by the standard AWS credentials lookup chain. As forking an
additional process does not lend itself to modifying the current
environment, adopting an approach which can more directly modify that
environment would be preferred. The shell/Bash can coordinate calls to
the AWS CLI to support this functionality across a range of flows.
Different organizations manage their AWS IAM differently, so far I’ve used variations of this script to support. I’ve typically only had to use one of these at a time so in the past I’ve modified the script in ways that may or may not preserve backwards compatibility, so some flows may need some tightening.
The most straightforward scenario is supporting MFA for a given IAM user, analogous to logging directly into the desired account using the AWS console. This can be supported by a basic population of the associated environment variables.
Multiple accounts can be accessed by associating the ticket
information in a profile for each account and then referencing the
profile while accessing AWS. In this scenario the environment doesn’t
need to be modified and therefore this is likely to not be any value in
this approach compared to awscli
. A wrinkle when using this
approach is that the named profiles risk coupling the calls that are
being made with the environment in which they are executed thereby
inviting additional complexity of one form or other.
A more elaborate AWS configuration may involve a network of accounts where most of the resources themselves are housed in accounts which are not that which has been logged into and is managed through cross account access which requires assuming a role in such resource accounts.
As referenced earlier modifying an environment is best done without forking a process and therefore modifying the environment of the current shell is best done within the current shell process. This can be accomplished through invoking functions which provide the desired functionality as opposed to calling a separate process, and this file is therefore intended to be sourced rather than run.
As many of these variables reflect the current environment they will be left mutable so as not to lead to incidentally limiting use of the shell.
The ARN for the MFA token should be defined in a local configuration file so the location of that file will be indicated.
declare AWS_CONFIG_FILE="${AWS_CONFIG_FILE:-${HOME}/.aws/config}"
Some of the use cases for this script can benefit from referencing an
AWS_PROFILE, so the variable is defined here with a default of
default
.
declare AWS_PROFILE="${AWS_PROFILE:-default}"
If a role needs to be assumed its ARN should be stored in this variable.
declare AWS_ROLE_ARN
Store a local access key id for later use.
declare my_aws_access_key_id
The serial number for the MFA device will often be defined in a
profile’s configuration but is less likely to be explicitly provided. An
awk script which parses the configuration file while keeping track of
the current profile and printing the mfa_serial
if the
profile in the context is the desired one serves to extract the relevant
value.
This adopts the pattern of inline awk with associated escaping hoops.
TODO: The profile matching seems like it would be more idiomatic if
there were two rules where one set thisP and the other unset it rather
than the one that sets it to the test result. Also thisP
could stand a rename.
aws::lookup_mfa_serial() {
AWS_MFA_SERIAL=$(awk -s "BEGIN { thisP=0; } /\[.*]/ { thisP=(\$0==\"[${AWS_PROFILE}]\") } /mfa_serial/ { if (thisP) { print \$3 } }" "${AWS_CONFIG_FILE}")
}