Skip to content

Instantly share code, notes, and snippets.

@rowleyaj
Created October 10, 2017 11:28
Show Gist options
  • Save rowleyaj/c46e72bfda41ccb4d38db27e0bbd4363 to your computer and use it in GitHub Desktop.
Save rowleyaj/c46e72bfda41ccb4d38db27e0bbd4363 to your computer and use it in GitHub Desktop.
AWS IAM Import -> Terraform
#!/bin/bash
pgname=$0
LOG_INFO=false
#
# Colours
#
RED="\\033[31m"
GREEN="\\033[32m"
YELLOW="\\033[33m"
RESET="\\033[0m"
#
# Logging functions
#
function log {
echo "$*"
}
function log_info {
if [ "$LOG_INFO" = false ]; then
echo "$*"
fi
}
function log_warn {
echo -e "${YELLOW}$*${RESET}"
}
function log_err {
echo -e "${RED}$*${RESET}"
}
#
# Helpers
#
function prompt {
local message=$1
local yes_cmd=$2
local no_cmd=$3
local rc=0
log "${message} (y/n)"
while read -r yn; do
case $yn in
[Yy] ) eval "${yes_cmd}"; rc=0; break;;
[Nn] ) eval "${no_cmd}"; rc=1; break;;
* ) echo "${message}";
esac
done
return $rc
}
function file_exists {
local filename=$1
local rc=0
if [ -e "${filename}" ]; then
prompt \
"${filename} exists, overwrite file?" \
"rm ${filename}" \
"log_warn \"Skipping ${filename}\"; exit 1"
rc=$?
fi
}
function timestamp_label {
echo "# Generated by import script on $(date -u)."
}
function cache_lookup {
local cmd=$1
local cache_dir='.cache'
local cache_file="${cache_dir}/${cmd//[[:space:]\:\/]/}"
# Create .cache directory if doesn't exist.
if [ ! -d .cache ]; then
mkdir "${cache_dir}"
fi
if [ "${USE_CACHE}" == "false" ] || [ ! -e "${cache_file}" ]; then
eval "${cmd}" > "${cache_file}"
fi
cat "${cache_file}"
}
function safe_tf_run {
local resource=$1
terraform plan -target="${resource}" -out=plan.out -detailed-exitcode > /dev/null
case $? in
0) # Succeeded with empty diff (no changes)
return
;;
1) # Error
log_err "Error planning resource";
exit 1
;;
2) # Succeeded with non-empty diff (changes present)
log_warn "Plan shows changes:"
;;
esac
terraform show plan.out
prompt 'Apply plan?' 'terraform apply plan.out' 'log_warn skipping apply'
}
function managed_policy_attachments {
# subject is the group, role or users that we want to get the attached policies for.
local subject=$1
# type should be one of group, role, user
local type=$2
local run=$3
safe_name="${subject//\./}"
filename="${type}_${safe_name}.tf"
aws_cmd="aws iam list-attached-${type}-policies --${type}-name ${subject}"
for managed_policy in $(cache_lookup "${aws_cmd}" | jq -r '.AttachedPolicies[].PolicyName'); do
log "Creating aws_iam_${type}_policy_attachment resource: ${subject}_${managed_policy}"
echo "
resource \"aws_iam_${type}_policy_attachment\" \"${subject}_${managed_policy}\" {
${type} = \"${subject}\"
# This contents of this policy can be viewed in policy_${managed_policy}.tf
policy_arn = \"\${aws_iam_policy.${managed_policy}.arn}\"
}" >> "${filename}"
if [ "${run}" == 'true' ]; then
safe_tf_run "aws_iam_${type}_policy_attachment.${subject}_${managed_policy}"
fi
done
}
function inline_policies {
# subject is the group, role or users that we want to get the inline policies for.
local subject=$1
# type should be one of group, role, user
local type=$2
local run=$3
safe_name="${subject//\./}"
filename="${type}_${safe_name}.tf"
aws_cmd="aws iam list-${type}-policies --${type}-name ${subject}"
for inline_policy in $(cache_lookup "${aws_cmd}" | jq -r '.PolicyNames[]'); do
policy=$(cache_lookup "aws iam get-${type}-policy --${type}-name ${subject} --policy-name ${inline_policy}" | jq -r '.PolicyDocument | walk(if type == "string" then gsub("\\$"; "$$") else . end)')
log "Creating aws_iam_${type}_policy resource: ${subject}_${inline_policy}"
echo "
resource \"aws_iam_${type}_policy\" \"${subject}_${inline_policy}\" {
name = \"${inline_policy}\"
${type} = \"${subject}\"
policy = <<POLICY_DOCUMENT
${policy}
POLICY_DOCUMENT
}" >> "${filename}"
if [ "${run}" == 'true' ]; then
safe_tf_run "aws_iam_${type}_policy.${subject}_${inline_policy}"
fi
done
}
#
# main
#
if [ "$IMPORT" == 'true' ] || [ "$IMPORT" == 'false' ] ; then
IMPORT_MANAGED_POLICIES="$IMPORT"
IMPORT_USERS="$IMPORT"
IMPORT_GROUPS="$IMPORT"
IMPORT_ROLES="$IMPORT"
IMPORT_INSTANCE_PROFILES="$IMPORT"
else
if [ -z "${IMPORT_MANAGED_POLICIES}" ]; then
prompt 'Managed Policies - Import?' 'IMPORT_MANAGED_POLICIES=true' 'IMPORT_MANAGED_POLICIES=false'
fi
if [ -z "${IMPORT_USERS}" ]; then
prompt 'Users - Import?' 'IMPORT_USERS=true' 'IMPORT_USERS=false'
fi
if [ -z "${IMPORT_GROUPS}" ]; then
prompt 'Groups - Import?' 'IMPORT_GROUPS=true' 'IMPORT_GROUPS=false'
fi
if [ -z "${IMPORT_ROLES}" ]; then
prompt 'Roles - Import?' 'IMPORT_ROLES=true' 'IMPORT_ROLES=false'
fi
if [ -z "${IMPORT_INSTANCE_PROFILES}" ]; then
prompt 'Instance Profiles - Import?' 'IMPORT_INSTANCE_PROFILES=true' 'IMPORT_INSTANCE_PROFILES=false'
fi
fi
if [ "$RUN" == 'true' ] || [ "$RUN" == 'false' ] ; then
RUN_MANAGED_POLICIES="$RUN"
RUN_USERS="$RUN"
RUN_GROUPS="$RUN"
RUN_ROLES="$RUN"
RUN_INSTANCE_PROFILES="$RUN"
else
if [ -z "${RUN_MANAGED_POLICIES}" ]; then
prompt 'Managed Policies - Run terraform?' 'RUN_MANAGED_POLICIES=true' 'RUN_MANAGED_POLICIES=false'
fi
if [ -z "${RUN_USERS}" ]; then
prompt 'Users - Run terraform?' 'RUN_USERS=true' 'RUN_USERS=false'
fi
if [ -z "${RUN_GROUPS}" ]; then
prompt 'Groups - Run terraform?' 'RUN_GROUPS=true' 'RUN_GROUPS=false'
fi
if [ -z "${RUN_ROLES}" ]; then
prompt 'Roles - Run terraform?' 'RUN_ROLES=true' 'RUN_ROLES=false'
fi
if [ -z "${RUN_INSTANCE_PROFILES}" ]; then
prompt 'Instance Profiles - Run terraform?' 'RUN_INSTANCE_PROFILES=true' 'RUN_INSTANCE_PROFILES=false'
fi
fi
terraform init
# Pull managed policies
timestamp_label > policies.txt
for managed_policy in $(cache_lookup 'aws iam list-policies' | jq -r '.Policies[].PolicyName'); do
arn=$(cache_lookup 'aws iam list-policies' | jq -r ".Policies[] | select(.PolicyName==\"${managed_policy}\").Arn")
aws_managed=$(cache_lookup "aws iam get-policy --policy-arn ${arn}" | jq -r '.Policy.Arn | test("::aws")')
path=$(cache_lookup "aws iam get-policy --policy-arn ${arn}" | jq -r '.Policy.Path')
# -r is not used here as we want to keep the JSON escaped quotes
description=$(cache_lookup "aws iam get-policy --policy-arn ${arn}" | jq '.Policy.Description')
# however we don't want double quotes around the outside, so strip the first and last "
description="${description#\"}"
description="${description%\"}"
version=$(cache_lookup "aws iam get-policy --policy-arn ${arn}" | jq -r '.Policy.DefaultVersionId')
policy=$(cache_lookup "aws iam get-policy-version --policy-arn ${arn} --version-id ${version}" | jq -r '.PolicyVersion.Document | walk(if type == "string" then gsub("\\$"; "$$") else . end)')
safe_name="${managed_policy//\./}"
filename="policy_${safe_name}.tf"
log "Creating aws_iam_policy resource: ${safe_name}"
timestamp_label > "${filename}"
if [ "${aws_managed}" == 'true' ]; then
echo "### AWS MANAGED POLICY - READ ONLY - DO NOT EDIT" >> "${filename}"
fi
echo "
resource \"aws_iam_policy\" \"${safe_name}\" {
name = \"${managed_policy}\"
description = \"${description#null}\"
path = \"${path}\"" >> "${filename}"
if [ "${aws_managed}" == 'true' ]; then
echo "### AWS MANAGED POLICY - READ ONLY - DO NOT EDIT" >> "${filename}"
fi
echo "
policy = <<POLICY_DOCUMENT
${policy}
POLICY_DOCUMENT" >> "${filename}"
if [ "${aws_managed}" == 'true' ]; then
echo "
# As this is an AWS managed policy, ignore any changes to the policy
lifecycle {
ignore_changes = [ \"policy\" ]
}" >> "${filename}"
fi
echo "}" >> "${filename}"
echo "${safe_name}" >> policies.txt
if [ "$IMPORT_MANAGED_POLICIES" == true ]; then
log "Importingaws_iam_policy resource: ${safe_name}"
terraform import "aws_iam_policy.${safe_name}" "${arn}"
fi
if [ "$RUN_MANAGED_POLICIES" == true ]; then
safe_tf_run "aws_iam_policy.${safe_name}" "{RUN_MANAGED_POLICIES}"
fi
done
# Create a per user file with any policies attached to user
timestamp_label > users.txt
for user in $(cache_lookup 'aws iam list-users' | jq -r '.Users[].UserName'); do
path=$(cache_lookup 'aws iam list-users' | jq -r ".Users[] | select(.UserName==\"${user}\").Path")
safe_name=${user//\./}
filename="user_${safe_name}.tf"
log "Creating aws_iam_user resource: ${safe_name}"
timestamp_label > "${filename}"
echo "
resource \"aws_iam_user\" \"${safe_name}\" {
name = \"${user}\"
path = \"${path}\"
force_destroy = false
}" >> "${filename}"
echo "${safe_name}" >> users.txt
if [ "$IMPORT_USERS" == true ]; then
log "Importing aws_iam_user resource: ${safe_name}"
terraform import "aws_iam_user.${safe_name}" "${user}"
fi
if [ "$RUN_USERS" == true ]; then
safe_tf_run "aws_iam_user.${safe_name}" "{RUN_USERS}"
fi
managed_policy_attachments "${user}" 'user'
inline_policies "${user}" 'user'
done
# Create file per group with memberships
timestamp_label > groups.txt
for group in $(cache_lookup 'aws iam list-groups' | jq -r '.Groups[].GroupName'); do
path=$(cache_lookup 'aws iam list-groups' | jq -r ".Groups[] | select(.GroupName==\"${group}\").Path")
safe_name=${group//\./}
filename="group_${safe_name}.tf"
log "Creating aws_iam_group resource: ${safe_name}"
timestamp_label > "${filename}"
echo "
resource \"aws_iam_group\" \"${safe_name}\" {
name = \"${group}\"
path = \"${path}\"
}" >> "${filename}"
echo "${safe_name}" >> groups.txt
if [ "$IMPORT_GROUPS" == true ]; then
log "Importing aws_iam_group resource: ${safe_name}"
terraform import "aws_iam_group.${safe_name}" "${group}"
fi
if [ "$RUN_GROUPS" == true ]; then
safe_tf_run "aws_iam_group.${safe_name}" "{RUN_GROUPS}"
fi
# Whilst looping group also write membership
log "Creating aws_iam_group_membership resource: ${safe_name}"
echo "
resource \"aws_iam_group_membership\" \"${safe_name}\" {
name = \"${safe_name}-membership\"
group = \"\${aws_iam_group.${safe_name}.name}\"
users = [" >> "${filename}"
for groupmember in $(cache_lookup "aws iam get-group --group-name ${group}" | jq -r '.Users[].UserName' | sort); do
member_safe_name=${groupmember//\./}
echo "
\"\${aws_iam_user.${member_safe_name}.name}\"," >> "${filename}"
done
echo "
]}" >> "${filename}"
managed_policy_attachments "${group}" 'group'
inline_policies "${group}" 'group'
done
# Create file per role
timestamp_label > roles.txt
for role in $(cache_lookup 'aws iam list-roles' | jq -r '.Roles[].RoleName'); do
path=$(cache_lookup 'aws iam list-roles' | jq -r ".Roles[] | select(.RoleName==\"${role}\").Path")
# -r is not used here as we want to keep the JSON escaped quotes
description=$(cache_lookup 'aws iam list-roles' | jq ".Roles[] | select(.RoleName==\"${role}\").Description")
# however we don't want double quotes around the outside, so strip the first and last "
description=${description#\"}
description=${description%\"}
assume_role_policy=$(cache_lookup 'aws iam list-roles' | jq -r ".Roles[] | select(.RoleName==\"${role}\").AssumeRolePolicyDocument | walk(if type == \"string\" then gsub(\"\\\\$\"; \"\$\$\") else . end)")
safe_name=${role//\./}
filename="role_${safe_name}.tf"
log "Creating aws_iam_role resource: ${safe_name}"
timestamp_label > "${filename}"
echo "
resource \"aws_iam_role\" \"${safe_name}\" {
name = \"${role}\"
description = \"${description#null}\"
path = \"${path}\"
assume_role_policy = <<POLICY_DOCUMENT
${assume_role_policy}
POLICY_DOCUMENT
}" >> "${filename}"
echo "${safe_name}" >> roles.txt
if [ "$IMPORT_ROLES" == true ]; then
log "Importing aws_iam_role resource: ${safe_name}"
terraform import "aws_iam_role.${safe_name}" "${role}"
fi
if [ "$RUN_ROLES" == true ]; then
safe_tf_run "aws_iam_role.${safe_name}" "{RUN_ROLES}"
fi
managed_policy_attachments "${role}" 'role'
inline_policies "${role}" 'role'
done
# Pull instance profiles, add to role file based on linked role
timestamp_label > instance_profiles.txt
for instance_profile in $(cache_lookup 'aws iam list-instance-profiles' | jq -r '.InstanceProfiles[].InstanceProfileName'); do
path=$(cache_lookup 'aws iam list-instance-profiles' | jq -r ".InstanceProfiles[] | select(.InstanceProfileName==\"${instance_profile}\").Path")
role=$(cache_lookup 'aws iam list-instance-profiles' | jq -r ".InstanceProfiles[] | select(.InstanceProfileName==\"${instance_profile}\").Roles[0].RoleName")
safe_name=${instance_profile//\./}
role_safe_name=${role//\./}
filename="role_${role_safe_name}.tf"
log "Creating aws_iam_instance_profile resource: ${safe_name}"
echo "
resource \"aws_iam_instance_profile\" \"${safe_name}\" {
name = \"${instance_profile}\"
path = \"${path}\"
role = \"\${aws_iam_role.${role_safe_name}.name}\"
}" >> "${filename}"
echo "${safe_name}" >> instance_profiles.txt
if [ "$IMPORT_INSTANCE_PROFILES" == true ]; then
log "Importing aws_iam_instance_profile resource: ${safe_name}"
terraform import "aws_iam_instance_profile.${safe_name}" "${instance_profile}"
fi
if [ "$RUN_INSTANCE_PROFILES" == true ]; then
safe_tf_run "aws_iam_instance_profile.${safe_name}" "{RUN_INSTANCE_PROFILES}"
fi
done
terraform fmt > /dev/null
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment