Why we work the way we do
Door Avisi / apr 2015 / 1 Min
Door Gert-Jan van de Streek / / 2 min
Our security policy forces all employees to use Multi Factor Authentication (MFA) whenever possible. It's easy enough to adhere to this requirement for most serious cloud services. Turning it on for access to the service website is mostly a no-brainer. Using Multi Factor for access to the command line however, needs a bit more attention. We noticed developers turn on Multi Factor for the AWS console for example, but keep using static keys for the command line. Ouch, time to fix that.
I hear people say 'security first' more than often. Security's weakest link is the one in the driver seat, in this case, the developer. That's why I advocate investing time to pick or develop the proper tools supporting the security policies. Make things easy for the developer/user.
Enough talk, show me the code:
We choose to force MFA as soon as you change directory to a project containing cloud deployment scripts or code. We already use direnv to force the use of a named profile for AWS to make sure the developer is always on the right account. We can simply add some code to that to force the availability of a valid session token.
ACCOUNT=123456789100
KEY_BASE_USER_ID=$(keybase status -j | jq -r '.Username')
if [ -z "$AVISI_ID" ]; then
WHOAMI=$(whoami)
else
WHOAMI=$AVISI_ID
fi
STS=$(keybase fs read "/keybase/private/${KEY_BASE_USER_ID}/.${ACCOUNT}.json")
RENEW=1
if [ ! -z "${STS}" ]; then
TOKEN_EXPIRES=$(echo "${STS}" | jq -r '.Credentials.Expiration')
DATE_NOW=$(date +%s)
DATE_TOKEN=$(date -d "${TOKEN_EXPIRES}" +%s)
if [ "$DATE_TOKEN" -ge "$DATE_NOW" ]; then
echo Found a valid MFA token for account "${ACCOUNT}"
RENEW=0
fi
fi
if [ ${RENEW} -eq 1 ]; then
echo -n "Enter MFA code for ${WHOAMI}@${ACCOUNT}: "
read -r TOKENCODE
STS=$(aws sts get-session-token --serial-number "arn:aws:iam::${ACCOUNT}:mfa/${WHOAMI}" --token-code "${TOKENCODE}")
echo "${STS}" | keybase fs write "/keybase/private/${KEY_BASE_USER_ID}/.${ACCOUNT}.json"
fi
export AWS_ACCESS_KEY_ID=$(echo "${STS}" | jq -r '.Credentials.AccessKeyId')
export AWS_SECRET_ACCESS_KEY=$(echo "${STS}" | jq -r '.Credentials.SecretAccessKey')
export AWS_SESSION_TOKEN=$(echo "${STS}" | jq -r '.Credentials.SessionToken')
The key functionality of this script is on:
This script assumes the AWS SDK to be installed as well as the jq utility. Next to that, we use Keybase to encrypt / decrypt that session file.
Some considerations about this script:
Not everybody might appreciate the use of Keybase. Our team was ok with it, so we left it at that. If you don't want that, or you are not using Keybase, you can cache the session token somewhere else (possibly encrypting/decrypting it through Keybase or another pgp setup).
The above script assumes that it's perfectly ok to have a pretty long open session time. If you want to make things more secure, you can alter the script to use a smaller window. Pass `--duration-seconds` to `get-session-token`.
You can also require that a user be authenticated using an MFA to perform particular API actions by using the aws:MultiFactorAuthPresent or aws:MultiFactorAuthAge conditions in an IAM policy. When you have educated your developers, this is the only conclusive way to ensure that that static keys are no longer used.
You might argue that it is better for a developer to authenticate on his own request, instead of being forced by a .envrc script. With .envrc, you have to grab your phone as soon as you change directory, maybe you were just looking for something or need time to make code changes first. It depends on your workflow, investigate what works best, maybe integrate the script in a Makefile!
| Software Development
Door Gert-Jan van de Streek / jun 2023
Dan denken we dat dit ook wat voor jou is.