Skip to content

Instantly share code, notes, and snippets.

@torson
Last active February 1, 2021 17:17
Show Gist options
  • Save torson/6b7ea2f3bfeb61ea99d5c5f289bf1458 to your computer and use it in GitHub Desktop.
Save torson/6b7ea2f3bfeb61ea99d5c5f289bf1458 to your computer and use it in GitHub Desktop.
Script to detach a policy from a AWS IAM user after a defined period / ttl
#!/bin/bash
set -e
# This script is for removing a policy from a IAM user after a predefined TTL
# It adds a crontab that runs this script every hour
# If run with arguments then that task is added to the .state file
# In addition it attaches the policy to the user if run with '-a'
# If run without arguments it parses the .state file and detaches policies from users if TTL expired
## WORKFLOW
# 1. check if crontab entry exists and create one if missing
# 2. check if aws-cli command exists
# 3. if arguments are passed then create the task
# 4. check if .state file exists and parse it
# a) if task TTL has expired, remove the policy from the user and remove the task from the .state file
# b) if task TTL has not expired, do nothing
function help() {
echo "$(basename $0) -u IAM_USERNAME -p IAM_POLICY_ARN -t TTL [-a]"
echo
echo "Options:"
echo "-u IAM_USERNAME : IAM user (friendly name, not ARN) for which the policy should be detached"
echo "-p IAM_POLICY_ARN : IAM ARN of the policy to be detached from the user"
echo "-t TTL 1h|1d|1w|1m : how much time to wait before detaching the policy from the user"
echo "-a : attach the policy to the user (now)"
echo
echo "Example:"
echo "$(basename $0) -u User1 -p arn:aws:iam::aws:policy/IAMFullAccess -t 3d -a"
}
# if a character is followed by a colon, the option is expected to have an argument
while getopts "u:p:t:a" optname
do
case "$optname" in
"u")
IAM_USERNAME="${OPTARG}"
;;
"p")
IAM_POLICY_ARN="${OPTARG}"
;;
"t")
TTL="${OPTARG}"
;;
"a")
ATTACH_POLICY_TO_USER=true
;;
"?")
echo "Unknown option $OPTARG"
help
exit 1
;;
":")
echo "No argument value for option $OPTARG"
help
exit 1
;;
*)
# Should not occur
echo "Unknown error while processing options"
help
exit 1
;;
esac
done
if [ "$#" -gt "0" ]; then
# if arguments set then check for all required
if [ "${IAM_USERNAME}" = "" ]; then
echo "IAM_USERNAME not set, exiting..."
echo ; help
exit 1
fi
if [ "${IAM_POLICY_ARN}" = "" ]; then
echo "IAM_POLICY_ARN not set, exiting..."
echo ; help
exit 1
fi
if [ "${TTL}" = "" ]; then
echo "TTL not set, exiting..."
echo ; help
exit 1
fi
fi
# 1. check if crontab entry exists and create one if missing
if echo $0 | grep -P "\./" >/dev/null 2>&1 ; then
# run in current folder
SCRIPT_PATH=$(pwd)/$(echo $0 | sed -r 's/\.\///')
STATEFILE_PATH=$(pwd)/$(dirname $0)/.$(basename $0).state
elif echo $0 | grep -P "^/" >/dev/null 2>&1 ; then
# run with full path
SCRIPT_PATH=$0
STATEFILE_PATH=$(dirname $0)/.$(basename $0).state
else
# run with relative path
SCRIPT_PATH=$(pwd)/$0
STATEFILE_PATH=$(pwd)/$(dirname $0)/.$(basename $0).state
fi
# set the script to be executable
chmod +x ${SCRIPT_PATH}
if ! crontab -l | grep ${SCRIPT_PATH} >/dev/null 2>&1 ; then
# 2. check if aws-cli command exists
aws help >/dev/null 2>&1 || ( echo "aws-cli is not installed!" ; exit 1)
echo "Adding crontab task."
(crontab -l 2>/dev/null ; echo "0 * * * * ${SCRIPT_PATH}") | crontab -
fi
# 3. if arguments are passed then create the task
if [ "$#" -gt "0" ]; then
# check if aws-cli command exists
aws help >/dev/null 2>&1 || ( echo "Error: aws-cli is not installed!" ; exit 1)
echo "adding the task to .state file : ${STATEFILE_PATH}"
TTL_TMP=${TTL}
if echo ${TTL_TMP} | grep -P "h$" >/dev/null ; then TTL_TMP=$(echo ${TTL_TMP} | sed 's/h$//') ; TTL_TMP=$((TTL_TMP*3600))
elif echo ${TTL_TMP} | grep -P "d$" >/dev/null ; then TTL_TMP=$(echo ${TTL_TMP} | sed 's/d$//') ; TTL_TMP=$((TTL_TMP*86400))
elif echo ${TTL_TMP} | grep -P "w$" >/dev/null ; then TTL_TMP=$(echo ${TTL_TMP} | sed 's/w$//') ; TTL_TMP=$((TTL_TMP*604800))
elif echo ${TTL_TMP} | grep -P "m$" >/dev/null ; then TTL_TMP=$(echo ${TTL_TMP} | sed 's/m$//') ; TTL_TMP=$((TTL_TMP*2592000))
fi
# checking if we're updating a task already present
if grep "$IAM_USERNAME,$IAM_POLICY_ARN" ${STATEFILE_PATH} >/dev/null ; then
echo "Updating the task already in .state file"
# removing the old task
set +e
grep -v "$IAM_USERNAME,$IAM_POLICY_ARN" ${STATEFILE_PATH} > ${STATEFILE_PATH}.tmp
set -e
mv ${STATEFILE_PATH}.tmp ${STATEFILE_PATH}
fi
echo "$IAM_USERNAME,$IAM_POLICY_ARN,$TTL_TMP,$(date +%s)" >> ${STATEFILE_PATH}
if [ "${ATTACH_POLICY_TO_USER}" = "true" ]; then
echo "Attaching policy to the user"
aws iam attach-user-policy --user-name ${IAM_USERNAME} --policy-arn ${IAM_POLICY_ARN}
fi
exit
fi
# continue if no arguments
# 4. check if .state file exists and parse it
if [ ! -f ${STATEFILE_PATH} ]; then
exit
fi
# parse the file
while IFS=, read -r IAM_USERNAME IAM_POLICY_ARN TTL TASK_CREATION_UNIXTIME
do
# echo "$IAM_USERNAME,$IAM_POLICY_ARN,$TTL,$TASK_CREATION_UNIXTIME"
if [ "$IAM_USERNAME" = "" ]; then
continue
fi
CURRENT_UNIXTIME=$(date +%s)
DIFF=$((CURRENT_UNIXTIME-TASK_CREATION_UNIXTIME))
if [[ "${DIFF}" -gt "${TTL}" ]]; then
# a) if task TTL has expired, remove the policy from the user and remove the task from the .state file
echo "TTL expired for policy '$IAM_POLICY_ARN' attached to user '$IAM_USERNAME'"
echo "Detaching the policy from the user"
# https://docs.aws.amazon.com/cli/latest/reference/iam/detach-user-policy.html
set +e
aws iam detach-user-policy --user-name ${IAM_USERNAME} --policy-arn ${IAM_POLICY_ARN} 2>${SCRIPT_PATH}.err
EXIT_CODE=$?
set -e
if [ -s ${SCRIPT_PATH}.err ]; then
echo "---"
cat ${SCRIPT_PATH}.err
echo "---"
fi
if grep -P "Policy .+ was not found." ${SCRIPT_PATH}.err >/dev/null ; then
# IAM policy is not attached to the user anymore but it's still set in the .state file
# removing task from the .state file
echo
echo "Policy is not attached to the user, removing entry from the .state file"
set +e
grep -v "$IAM_USERNAME,$IAM_POLICY_ARN" ${STATEFILE_PATH} > ${STATEFILE_PATH}.tmp
set -e
mv ${STATEFILE_PATH}.tmp ${STATEFILE_PATH}
fi
rm ${SCRIPT_PATH}.err
if [ "${EXIT_CODE}" != "0" ]; then
echo
echo "ERROR: aws-cli produced error, exit code ${EXIT_CODE}"
exit 1
fi
# removing task from the .state file
echo "Removing entry from the .state file"
set +e
grep -v "$IAM_USERNAME,$IAM_POLICY_ARN" ${STATEFILE_PATH} > ${STATEFILE_PATH}.tmp
set -e
mv ${STATEFILE_PATH}.tmp ${STATEFILE_PATH}
fi
done < ${STATEFILE_PATH}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment