Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@ categories: ["access management", "security"]
primary_category: "security"
---

# Check MFA Is Enabled for AWS IAM Root Accounts
# Check Root Account Access Usage

Ensuring Multi-Factor Authentication (MFA) is enabled for AWS IAM root accounts is critical as it provides an additional layer of security beyond just a password, significantly reducing the risk of unauthorized access. This protection helps prevent potential security breaches and enhances overall account security by requiring a second form of verification.
Monitoring access activity for AWS IAM root accounts is essential for maintaining a secure cloud environment. This policy checks for critical access indicators that could pose security risks if left unmonitored — including MFA status, recent usage of the root password, and access key activity.

This [policy pack](https://turbot.com/guardrails/docs/concepts/policy-packs) can help you configure the following settings for IAM root accounts:
Root accounts are highly privileged and should ideally remain unused. This policy ensures that any recent access patterns — whether via password or keys — are detected and flagged if they fall outside the approved window.

- Check and alarm if root accounts do not have MFA enabled
This [policy pack](https://turbot.com/guardrails/docs/concepts/policy-packs) helps you enforce the following controls on IAM root accounts:
- Check and alert if MFA is not enabled
- Detect if the root password was used in the last 14 days
- Check if Access Key 1 or Access Key 2 was used within the last 14 days
- Identify active access keys that are unused but still present

## Documentation

Expand Down Expand Up @@ -53,7 +57,7 @@ Clone:

```sh
git clone https://github.com/turbot/guardrails-samples.git
cd guardrails-samples/policy_packs/aws/iam/check_mfa_is_enabled_for_root_accounts
cd guardrails-samples/policy_packs/aws/iam/check_root_account_access_usage
```

Run the Terraform to create the policy pack in your workspace:
Expand Down
126 changes: 126 additions & 0 deletions policy_packs/aws/iam/check_root_account_security_usage/policies.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# AWS > IAM > Root > Approved
resource "turbot_policy_setting" "aws_iam_root_approved" {
resource = turbot_policy_pack.main.id
type = "tmod:@turbot/aws-iam#/policy/types/rootApproved"
value = "Check: Approved"
}

# AWS > IAM > Root > Approved > Custom
resource "turbot_policy_setting" "aws_iam_root_approved_custom" {
resource = turbot_policy_pack.main.id
type = "tmod:@turbot/aws-iam#/policy/types/rootApprovedCustom"
template_input = <<-EOT
{
root {
mfaActive: get(path: "mfa_active")
passwordEnabled: get(path: "password_enabled")
passwordLastUsed: get(path: "password_last_used")
accessKey1Active: get(path: "access_key_1_active")
accessKey1LastUsed: get(path: "access_key_1_last_used")
accessKey2Active: get(path: "access_key_2_active")
accessKey2LastUsed: get(path: "access_key_2_last_used")
}
}
EOT
template = <<-EOT
{%- set now = "now" | date("constructor") -%}
{%- set nowMs = now | date("getTime") -%}
{%- set fourteenDaysMs = 14 * 24 * 60 * 60 * 1000 -%}
{%- set fourteenDaysAgo = nowMs - fourteenDaysMs -%}

[
{# MFA Check #}
{%- if $.root.mfaActive == "true" -%}
{
"title": "MFA",
"result": "Approved",
"message": "MFA is enabled"
},
{%- else -%}
{
"title": "MFA",
"result": "Not approved",
"message": "MFA is not enabled"
},
{%- endif -%}

{# Password Usage Check #}
{%- if $.root.passwordEnabled == "true" and $.root.passwordLastUsed -%}
{%- set pwdMs = $.root.passwordLastUsed | date("getTime") -%}
{%- if pwdMs > fourteenDaysAgo -%}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • If a password hasn’t been used for 14 days and the control is in ALARM, will it automatically transition to OK on the fifteenth day, or does it require a manual re-run?
  • Similarly, if the control is in OK and the password gets used again, will it automatically re-trigger and move to ALARM?

If this requires manual execution in either case, controls might stay outdated, showing stale ALARM or OK states.

{
"title": "Password Usage",
"result": "Not approved",
"message": "Root password was used within the last 14 days"
},
{%- else -%}
{
"title": "Password Usage",
"result": "Approved",
"message": "Root password has not been used in the last 14 days"
},
{%- endif -%}
{%- elif $.root.passwordEnabled == "false" -%}
{
"title": "Password Usage",
"result": "Approved",
"message": "Root password is disabled"
},
{%- else -%}
{
"title": "Password Usage",
"result": "Approved",
"message": "Password enabled but never used"
},
{%- endif -%}

{# Access Key 1 Check #}
{%- if $.root.accessKey1Active == "true" and $.root.accessKey1LastUsed -%}
{%- set ak1Ms = $.root.accessKey1LastUsed | date("getTime") -%}
{%- if ak1Ms > fourteenDaysAgo -%}
{
"title": "Access Key 1",
"result": "Not approved",
"message": "Access Key 1 was used within the last 14 days"
},
{%- else -%}
{
"title": "Access Key 1",
"result": "Approved",
"message": "Access Key 1 not used in last 14 days"
},
{%- endif -%}
{%- else -%}
{
"title": "Access Key 1",
"result": "Approved",
"message": "Access Key 1 inactive or never used"
},
{%- endif -%}

{# Access Key 2 Check #}
{%- if $.root.accessKey2Active == "true" and $.root.accessKey2LastUsed -%}
{%- set ak2Ms = $.root.accessKey2LastUsed | date("getTime") -%}
{%- if ak2Ms > fourteenDaysAgo -%}
{
"title": "Access Key 2",
"result": "Not approved",
"message": "Access Key 2 was used within the last 14 days"
}
Copy link
Preview

Copilot AI Jul 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing comma after the JSON object. This will cause a syntax error in the generated JSON array as it's the last item but other blocks above have trailing commas.

Suggested change
}
},

Copilot uses AI. Check for mistakes.

{%- else -%}
{
"title": "Access Key 2",
"result": "Approved",
"message": "Access Key 2 not used in last 14 days"
}
Copy link
Preview

Copilot AI Jul 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing comma after the JSON object. This will cause a syntax error in the generated JSON array as it's the last item but other blocks above have trailing commas.

Suggested change
}
},

Copilot uses AI. Check for mistakes.

{%- endif -%}
{%- else -%}
{
"title": "Access Key 2",
"result": "Approved",
"message": "Access Key 2 inactive or never used"
}
Copy link
Preview

Copilot AI Jul 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing comma after the JSON object. This will cause a syntax error in the generated JSON array as it's the last item but other blocks above have trailing commas.

Suggested change
}
},

Copilot uses AI. Check for mistakes.

{%- endif -%}
]
EOT
}