How to Track and Review Permissions in AWS IAM and GitHub
AWS IAM and GitHub are the two highest-risk permission surfaces for most software companies. AWS admin access means the ability to create resources, access all data, and destroy infrastructure. GitHub admin access means the ability to exfiltrate your entire codebase, modify CI/CD pipelines, and inject malicious code into your releases. Both are tested directly in SOC 2 CC6.1 and CC6.3 audits. This guide covers the full review process for both platforms, including the AWS IAM Access Analyzer, CloudTrail evidence, and GitHub Teams vs direct collaborator access.
Prerequisites
- AWS: IAM permissions — iam:GenerateCredentialReport, iam:GetCredentialReport, iam:ListUsers, iam:ListRoles, iam:ListAttachedUserPolicies
- AWS CLI v2 installed and configured with appropriate credentials (aws configure)
- GitHub: Organisation Owner or Admin access
- GitHub CLI (gh) installed — optional but recommended
- Our free SOC 2 Access Review Template to document findings
What evidence you'll produce
- • AWS IAM Credential Report (all users, MFA status, access key ages, last activity) — CC6.1
- • AWS privileged policy holders (AdministratorAccess, PowerUser, custom policies) — CC6.1
- • AWS IAM roles with high-privilege policies (Lambda, EC2, cross-account roles) — CC6.1
- • AWS IAM Access Analyzer findings (over-permissive policies, public-access findings) — CC6.1
- • GitHub org member list with roles (Owner vs Member) — CC6.1
- • GitHub outside collaborators list — CC6.3 completeness
- • GitHub repo-level admin access (who can change branch protection) — CC6.1
- • Completed access review spreadsheet (decisions and reviewer names) — CC6.3
Part 1: AWS IAM
Generate and review the IAM Credential Report
10 minutes
The IAM Credential Report is AWS's built-in audit export — one CSV covering every IAM user in the account. It includes password status, MFA status, access key ages, and last-used dates.
Via the AWS Console:
AWS Console → IAM → Credential report (left sidebar) → Download Report
Via CLI (recommended — allows scripting):
# Generate the report (takes ~30 seconds to prepare)
aws iam generate-credential-report
# Download and decode the report
aws iam get-credential-report --query 'Content' --output text | \
base64 --decode > iam-credential-report-$(date +%Y%m%d).csv
Key columns to flag in your review:
- mfa_active = FALSE + password_enabled = TRUE: This user can log in to the AWS Console without MFA. Immediate CC6.1 finding. Every human IAM user with console access must have MFA.
- access_key_1_last_rotated: Any access key not rotated in 90+ days is a SOC 2 CC6.1 finding under key rotation policy.
- access_key_1_last_used_date = N/A: An access key that has never been used is either orphaned or was never needed. Candidate for deletion.
- password_last_used = no_information: User has never logged into the console. If they also have access keys, this is an account that only uses programmatic access — document this as a service account.
- user_creation_time vs password_last_used gap: A user created 2 years ago who last signed in 300 days ago is stale.
Identify over-privileged users and policies
15–20 minutes
Find all users with AdministratorAccess:
# Users directly attached to AdministratorAccess policy
aws iam list-entities-for-policy \
--policy-arn arn:aws:iam::aws:policy/AdministratorAccess \
--entity-filter User --output table
# Groups attached to AdministratorAccess (indirect admin access)
aws iam list-entities-for-policy \
--policy-arn arn:aws:iam::aws:policy/AdministratorAccess \
--entity-filter Group --output table
# Check what policies are attached to a specific user
aws iam list-attached-user-policies --user-name alice --output table
aws iam list-user-policies --user-name alice --output table
Find users with wildcard policies (Action: "*"):
# Get all inline policies for a user and inspect for wildcards
for user in $(aws iam list-users --query 'Users[*].UserName' --output text); do
policies=$(aws iam list-user-policies --user-name $user --query 'PolicyNames' --output text)
if [ ! -z "$policies" ]; then
echo "=== $user ==="
for policy in $policies; do
aws iam get-user-policy --user-name $user --policy-name $policy \
--query 'PolicyDocument' --output json | grep -i 'Action.*\*'
done
fi
done
Review IAM roles and service account permissions
15 minutes
IAM roles (used by EC2, Lambda, ECS, and cross-account access) are frequently overlooked in access reviews. A Lambda function with AdministratorAccess is the same risk as an admin IAM user.
# List all IAM roles and their attached policies
aws iam list-roles --query 'Roles[*].[RoleName,Arn]' --output table
# Find roles with AdministratorAccess
aws iam list-entities-for-policy \
--policy-arn arn:aws:iam::aws:policy/AdministratorAccess \
--entity-filter Role --output table
# Check who can assume a specific role (trust policy)
aws iam get-role --role-name MyRole \
--query 'Role.AssumeRolePolicyDocument' --output json
Use AWS IAM Access Analyzer to find over-permissive access:
AWS Console → IAM → Access Analyzer → Create analyzer (if not already set up) → Review findings
Access Analyzer identifies: S3 buckets with public access, IAM roles assumable by external accounts, KMS keys with cross-account access, and Lambda functions with public access. Each finding is a potential CC6.1 scope-creep issue.
Part 2: GitHub
Export organisation members, owners, and outside collaborators
10–15 minutes
Via the GitHub web interface:
- Org owners: github.com/orgs/[org]/people?filter=owners
- All members: github.com/orgs/[org]/people
- Outside collaborators: github.com/orgs/[org]/settings/outside_collaborators
Via the GitHub CLI (most efficient for large orgs):
# List all org owners
gh api "/orgs/YOUR-ORG/members?role=admin&per_page=100" \
--jq '.[] | [.login, .html_url] | @csv' > github-owners.csv
# List all org members
gh api "/orgs/YOUR-ORG/members?per_page=100" \
--jq '.[] | [.login, .html_url] | @csv' > github-members.csv
# List outside collaborators (the most commonly missed group)
gh api "/orgs/YOUR-ORG/outside_collaborators?per_page=100" \
--jq '.[] | [.login, .html_url] | @csv' > github-outside-collaborators.csv
Review repository-level admin access
15–20 minutes
Org-level role (Owner vs Member) doesn't tell you everything. Repo-level Admin access — which allows changing branch protection rules, managing deploy keys, and deleting the repo — must also be reviewed.
# List collaborators for a specific repo with their permission level
gh api "/repos/YOUR-ORG/YOUR-REPO/collaborators?affiliation=all&per_page=100" \
--jq '.[] | [.login, .permissions.admin, .permissions.maintain, .permissions.push, .permissions.pull] | @csv'
# Find all repo admins across all repos in the org
for repo in $(gh repo list YOUR-ORG --limit 100 --json name --jq '.[].name'); do
echo "=== $repo ==="
gh api "/repos/YOUR-ORG/$repo/collaborators?permission=admin" \
--jq '.[] | [.login, "admin", "$repo"] | @csv'
done
GitHub Teams — the structured approach:
If your org uses GitHub Teams, review access at the team level rather than per-repo per-user:
# List all teams in the org
gh api "/orgs/YOUR-ORG/teams?per_page=100" --jq '.[] | [.name, .privacy, .members_count] | @csv'
# List members of a specific team
gh api "/orgs/YOUR-ORG/teams/TEAM-SLUG/members?per_page=100" --jq '.[].login'
# List repos a team has admin access to
gh api "/orgs/YOUR-ORG/teams/TEAM-SLUG/repos?per_page=100" \
--jq '.[] | select(.permissions.admin == true) | [.name, "admin"] | @csv'
Review GitHub Actions secrets and deploy keys
10 minutes
GitHub Actions secrets and deploy keys are frequently missed in access reviews. They provide programmatic access to your repositories and connected systems (AWS, Docker Hub, npm, etc.).
Via the web interface:
- Org-level secrets: github.com/orgs/[org]/settings/secrets/actions
- Repo-level secrets: github.com/[org]/[repo]/settings/secrets/actions
- Deploy keys: github.com/[org]/[repo]/settings/keys
For each secret or deploy key, document:
- What it provides access to (AWS account, npm registry, Docker Hub)
- Named owner — a human who is responsible for this key
- Last rotated date — keys not rotated in 90+ days should be reviewed
- Which workflows use it — if a workflow is deleted or changed, is the secret still needed?
Populate and archive the access review
15 minutes + reviewer time
Recommended review structure in the spreadsheet:
- One section per system (AWS IAM, GitHub Org, GitHub Repos — grouped by repo)
- Separate sections for service accounts and deploy keys
- Separate section for outside collaborators (these often need immediate action)
Archive per review cycle:
- iam-credential-report-2025-01-10.csv
- iam-admin-users-2025-01-10.csv (output of list-entities-for-policy)
- github-members-2025-01-10.csv
- github-outside-collaborators-2025-01-10.csv
- access-review-aws-github-2025-Q1.xlsx (completed review with decisions)
- Screenshots/ (for any Revoked rows — IAM user disabled, GitHub member removed)
Evidence quality checklist — AWS IAM & GitHub
Common mistakes that cause audit findings
- ❌ Reviewing IAM users but not IAM roles
→ Lambda functions with AdministratorAccess are the same risk level as admin users. Include application roles in your review.
- ❌ Not checking GitHub outside collaborators
→ This list is invisible in the standard People view. It must be checked separately — it's where most former-employee access is found.
- ❌ IAM access keys that were rotated but old keys not deleted
→ AWS allows two access keys per user. The old key must be deleted, not just disabled. A disabled key can be re-enabled.
- ❌ GitHub Actions secrets not included in the access review
→ Secrets with production AWS access are equivalent to admin access. Include as service account rows.
Track this properly every 90 days
AllowNow gives you a central place to record every AWS and GitHub permission, assign ownership, and run structured access reviews. Add your services, map users to roles, and export audit-ready PDF reports in one click. Every review decision is timestamped.