Last active
February 1, 2021 17:17
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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