Skip to content

Commit

Permalink
Add workflow that creates scheduled job to audit AWS account (#14845)
Browse files Browse the repository at this point in the history
  • Loading branch information
berryd authored Apr 2, 2024
1 parent 8b8de76 commit 4ccd90e
Show file tree
Hide file tree
Showing 2 changed files with 141 additions and 0 deletions.
77 changes: 77 additions & 0 deletions .github/audit-account.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#!/bin/bash
set -o pipefail -o nounset -u
git fetch --all > /dev/null

#Parse inputs
case ${1-} in
"ci_active"|"ci_inactive"|"cf_other"|"untagged")
OP=${1-}
;;
*)
echo "Error: unkown operation"
echo "Usage: ${0} [ci_active|ci_inactive|cf_other|untagged] [resource_tagging_response|null]" && exit 1
;;
esac

shift
if [ ! -z "${1-}" ]; then
if [ -f "${1-}" ]; then
RESOURCES=$(<"${1-}")
else
RESOURCES="${@-}"
fi
jq empty <<< "${RESOURCES}"
[ "$?" != 0 ] && echo "Error: supplied JSON is invalid." && echo ${RESOURCES} && exit 1
else
RESOURCES=$(aws resourcegroupstaggingapi get-resources)
fi

#Create array of objects with the branch name and the interpolated branch name (for bot created branches)
get_branches () {
RAW_BRANCHES=$(git for-each-ref --format='%(refname)' refs/remotes/origin | sed 's|^.\+\/||g')
BRANCHES=()
for B in $RAW_BRANCHES; do
[ "${B}" == "HEAD" ] && continue
IBRANCH=$(./setBranchName.sh ${B})
BRANCHES+=($(echo '{"BRANCH":"'${B}'","IBRANCH":"'${IBRANCH}'"}'))
done

jq -s '{BRANCHES:.}' <<< ${BRANCHES[*]}
}

get_composite_ci () {
local BRANCHES=$(get_branches)
local RESOURCES=$(jq -r '{RESOURCES:[.ResourceTagMappingList[] | select(.Tags[]?.Key?=="STAGE")]}' <<< "${1}")
jq -rs 'reduce .[] as $item ({}; . * $item)
| [JOIN(INDEX(.BRANCHES[]; .IBRANCH); .RESOURCES[]; .Tags[].Value; add)]
| [.[]
| {"BRANCH":.BRANCH, "STAGE":.Tags[]
| select(.Key=="STAGE").Value, "ResourceARN":.ResourceARN}]' <<< $(echo ${BRANCHES}${RESOURCES})
}

#Produce report for active stacks created by the ci pipeline (has a corresponding branch)
ci_active () {
jq -r '[.[] | select(.BRANCH != null)] | sort_by(.STAGE)' <<< $(get_composite_ci "${1}")
}

#Produce report for active stacks created by the ci pipeline (does NOT have a corresponding branch)
ci_inactive () {
jq -r '[.[] | select(.BRANCH == null)] | del(.[].BRANCH) | sort_by(.STAGE)' <<< $(get_composite_ci "${1}")
}

#Produce report for resources that have tags but were not created by the ci pipeline
cf_other () {
jq -r '[.ResourceTagMappingList[] | select((.Tags? | length) > 0) | del(select(.Tags[].Key=="STAGE")) // empty |
{
InferredId: .Tags[] | select(.Key=="aws:cloudformation:stack-name" or .Key=="cms-cloud-service" or .Key=="Name").Value,
ResourceARN: .ResourceARN
}] | sort' <<< "${1}"
}

#Produce report for resources that are untagged (some are still created by the ci pipeline)
untagged () {
jq -r '[{ResourceARN:.ResourceTagMappingList[] | select((.Tags? | length) < 1).ResourceARN}] | sort' <<< "${1}"
}

#Execute operation
$OP "${RESOURCES}"
64 changes: 64 additions & 0 deletions .github/workflows/audit-account.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
name: Audit Account

on:
schedule:
- cron: "0 16 * * 1" # Every Monday at 1600 UTC
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.event.ref }}
cancel-in-progress: false

permissions:
id-token: write

jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: set variable values
run: ./.github/build_vars.sh set_values
env:
AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }}
AWS_OIDC_ROLE_TO_ASSUME: ${{ secrets[env.BRANCH_SPECIFIC_VARNAME_AWS_OIDC_ROLE_TO_ASSUME] || secrets.AWS_OIDC_ROLE_TO_ASSUME }}
- name: Configure AWS credentials for GitHub Actions
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ env.AWS_OIDC_ROLE_TO_ASSUME }}
aws-region: ${{ env.AWS_DEFAULT_REGION }}
- name: Collect resources from account
run: pushd .github && aws resourcegroupstaggingapi get-resources > resources.json
- name: List active resources created by CI pipeline
run: pushd .github && ./audit-account.sh ci_active resources.json
- name: List orphaned resources created by CI pipeline
run: pushd .github && ./audit-account.sh ci_inactive resources.json
- name: List resources created by Cloudformation but not from CI pipeline
run: pushd .github && ./audit-account.sh cf_other resources.json
- name: List untagged resources
run: pushd .github && ./audit-account.sh untagged resources.json
- name: Create reports dir
run: pushd .github && mkdir -p reports
- name: Assemble CSV files
run: |
#!/bin/bash
pushd .github
echo "Reports with no entries will be omitted"
CI_ACTIVE="$(./audit-account.sh ci_active resources.json)"
[[ $(jq -r 'length' <<< "${CI_ACTIVE}") -gt 0 ]] && jq -r '(.[0]
| keys_unsorted) as $keys | $keys, map([.[ $keys[] ]])[] | @csv' <<< "${CI_ACTIVE}" > reports/ci_active.csv
CI_INACTIVE="$(./audit-account.sh ci_inactive resources.json)"
[[ $(jq -r 'length' <<< "${CI_INACTIVE}") -gt 0 ]] && jq -r '(.[0]
| keys_unsorted) as $keys | $keys, map([.[ $keys[] ]])[] | @csv' <<< "${CI_INACTIVE}" > reports/ci_inactive.csv
CF_OTHER="$(./audit-account.sh cf_other resources.json)"
[[ $(jq -r 'length' <<< "${CF_OTHER}") -gt 0 ]] && jq -r '(.[0]
| keys_unsorted) as $keys | $keys, map([.[ $keys[] ]])[] | @csv' <<< "${CF_OTHER}" > reports/cf_other.csv
UNTAGGED="$(./audit-account.sh untagged resources.json)"
[[ $(jq -r 'length' <<< "${UNTAGGED}") -gt 0 ]] && jq -r '(.[0]
| keys_unsorted) as $keys | $keys, map([.[ $keys[] ]])[] | @csv' <<< "${UNTAGGED}" > reports/untagged.csv
- name: Upload reports
uses: actions/upload-artifact@v4
with:
name: resource-reports
path: .github/reports/
retention-days: 14

0 comments on commit 4ccd90e

Please sign in to comment.