Cloud Security

AWS IAM Security Best Practices: Policies, Roles, and Audit

A comprehensive guide to locking down AWS IAM: root account protection, MFA enforcement, role design, SCPs, IAM Access Analyzer, and CloudTrail auditing.

August 5, 20256 min readShipSafer Team

Identity and Access Management (IAM) is the foundational security layer of every AWS environment. Misconfigured IAM is the root cause behind a significant share of cloud breaches — from accidental data exposure to full account takeovers. This guide walks through the most important IAM hardening measures, from eliminating root account risk to detecting over-permissive roles with automated tooling.

Lock Down the Root Account

The AWS root account has unrestricted access to every resource, billing setting, and account configuration. It cannot be restricted by IAM policies. This makes it the highest-value target in your AWS environment.

Steps to secure the root account:

  1. Remove all root access keys immediately. In the IAM console, under "My Security Credentials," delete any active access keys for root. Root should never make programmatic API calls.
  2. Enable MFA on root using a hardware TOTP device or a U2F security key — not a phone that could be SIM-swapped.
  3. Store the root credentials in a physical safe or a privileged access management (PAM) system. Only two or three people in the organization should know them.
  4. Set a root account email on a shared security alias (e.g., aws-root@yourdomain.com) that multiple team members can monitor.
  5. Create a CloudWatch alarm for any root account activity using this filter pattern:
{
  "source": ["aws.signin"],
  "detail-type": ["AWS Console Sign In via CloudTrail"],
  "detail": {
    "userIdentity": {
      "type": ["Root"]
    }
  }
}

Any root login should trigger an immediate alert to your security team.

Enforce MFA for All Human Users

Every IAM user who can access the AWS console should have MFA enabled. You can enforce this with an IAM policy that denies all actions except MFA enrollment until the user has authenticated with MFA:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyAllExceptMFASetup",
      "Effect": "Deny",
      "NotAction": [
        "iam:CreateVirtualMFADevice",
        "iam:EnableMFADevice",
        "iam:GetUser",
        "iam:ListMFADevices",
        "iam:ListVirtualMFADevices",
        "iam:ResyncMFADevice",
        "sts:GetSessionToken"
      ],
      "Resource": "*",
      "Condition": {
        "BoolIfExists": {
          "aws:MultiFactorAuthPresent": "false"
        }
      }
    }
  ]
}

Attach this policy to a group called RequireMFA and add every new user to that group on creation.

IAM Roles vs. IAM Users

A foundational IAM best practice is to minimize the use of long-lived IAM user credentials (access key ID + secret access key). These credentials do not expire unless you explicitly rotate them, and if leaked, they grant persistent access until revoked.

Use IAM roles instead:

  • EC2, Lambda, ECS, EKS: Assign instance profiles and execution roles. Applications retrieve short-lived credentials automatically from the instance metadata service (IMDS). Always use IMDSv2, which requires a PUT token exchange and prevents SSRF-based metadata theft.
  • CI/CD pipelines: Use OIDC federation (covered in a separate guide) to let GitHub Actions, GitLab, or CircleCI assume roles without storing any static credentials.
  • Cross-account access: Create roles in target accounts with trust policies scoped to your management or tooling account. Require sts:ExternalId when third-party vendors need access to prevent confused deputy attacks.

When you must use IAM users (legacy systems, some CLI tooling), enforce 90-day key rotation with an AWS Config rule:

aws configservice put-config-rule \
  --config-rule '{"ConfigRuleName":"access-keys-rotated","Source":{"Owner":"AWS","SourceIdentifier":"ACCESS_KEYS_ROTATED"},"InputParameters":"{\"maxAccessKeyAge\":\"90\"}"}'

Writing Least-Privilege IAM Policies

Every policy should grant only the permissions required for the specific task. Avoid wildcard actions (*) and wildcard resources (*) except where functionally unavoidable.

Bad (overly permissive):

{
  "Effect": "Allow",
  "Action": "s3:*",
  "Resource": "*"
}

Good (least privilege):

{
  "Effect": "Allow",
  "Action": [
    "s3:GetObject",
    "s3:PutObject"
  ],
  "Resource": "arn:aws:s3:::my-app-bucket/uploads/*"
}

Use condition keys to add context-aware restrictions:

{
  "Effect": "Allow",
  "Action": "s3:GetObject",
  "Resource": "arn:aws:s3:::my-bucket/*",
  "Condition": {
    "StringEquals": {
      "aws:RequestedRegion": "us-east-1"
    },
    "Bool": {
      "aws:SecureTransport": "true"
    }
  }
}

Service Control Policies (SCPs) in AWS Organizations

If you use AWS Organizations, SCPs let you create guardrails that apply across all accounts in an organizational unit (OU) regardless of what individual account IAM policies allow. SCPs are allow-lists or deny-lists evaluated before account-level policies.

Useful SCPs to implement:

Prevent disabling of CloudTrail:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyCloudTrailChanges",
      "Effect": "Deny",
      "Action": [
        "cloudtrail:DeleteTrail",
        "cloudtrail:StopLogging",
        "cloudtrail:UpdateTrail"
      ],
      "Resource": "*"
    }
  ]
}

Restrict to approved regions only:

{
  "Sid": "DenyUnapprovedRegions",
  "Effect": "Deny",
  "Action": "*",
  "Resource": "*",
  "Condition": {
    "StringNotEquals": {
      "aws:RequestedRegion": ["us-east-1", "us-west-2", "eu-west-1"]
    }
  }
}

SCPs do not grant permissions — they only restrict. An effective FullAWSAccess SCP must always be attached at the root to allow policies at the account level to take effect.

IAM Access Analyzer

AWS IAM Access Analyzer identifies resources in your account that are accessible from outside your AWS organization or account. It analyzes resource-based policies on S3 buckets, IAM roles, KMS keys, Lambda functions, and SQS queues.

Enable it in every region:

aws accessanalyzer create-analyzer \
  --analyzer-name organization-analyzer \
  --type ORGANIZATION \
  --region us-east-1

Access Analyzer also offers policy validation — it flags policies that grant more permissions than necessary and suggests least-privilege alternatives. Run it as part of your IaC pipeline:

aws accessanalyzer validate-policy \
  --policy-document file://my-policy.json \
  --policy-type IDENTITY_POLICY

The findings API integrates with Security Hub, so you can route Access Analyzer alerts to the same workflow as your other security findings.

Auditing with CloudTrail

CloudTrail records every API call made in your account — who made it, when, from where, and whether it succeeded. A properly configured CloudTrail setup is your primary forensic resource after a security incident.

Required CloudTrail configuration:

  1. Enable a multi-region trail that captures management events in all regions.
  2. Enable log file validation (CloudTrail signs each log file with SHA-256 so you can detect tampering).
  3. Deliver logs to a dedicated S3 bucket in a separate security account with a bucket policy that prevents deletion.
  4. Enable CloudTrail Insights to detect unusual API activity patterns automatically.
aws cloudtrail create-trail \
  --name org-trail \
  --s3-bucket-name my-cloudtrail-logs \
  --is-multi-region-trail \
  --enable-log-file-validation \
  --include-global-service-events

Critical CloudWatch alarms to configure on CloudTrail logs:

  • Root account API calls
  • IAM policy changes (CreatePolicy, AttachUserPolicy, PutRolePolicy)
  • Security group changes
  • CloudTrail configuration changes
  • AWS Config changes
  • Console logins without MFA

Detecting Over-Permissive Roles

IAM Access Advisor shows the last time each permission was used for an IAM entity. Use this to right-size permissions over time.

For programmatic analysis, the AWS IAM Policy Simulator and open-source tools like Parliament (policy linter) and Cloudsplaining (generates least-privilege reports) are invaluable:

pip install cloudsplaining
cloudsplaining download --profile default
cloudsplaining scan --input-file default.json --output report/

Cloudsplaining produces an HTML report highlighting every policy that allows data exfiltration, privilege escalation, resource exposure, or credentials exposure — with remediation suggestions per finding.

Set a recurring job to run this monthly and track the number of over-permissive findings over time. Pair it with AWS Security Hub's IAM findings for a complete picture of your IAM security posture.

aws
iam
cloud security
aws security
access control

Check Your Security Score — Free

See exactly how your domain scores on DMARC, TLS, HTTP headers, and 25+ other automated security checks in under 60 seconds.