Skip to content
Merged
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
4 changes: 2 additions & 2 deletions .github/workflows/security-report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ jobs:
);

if (botComment) {
const disclaimer = '⚠️ **Experimental Feature**: This security report is currently in experimental phase. Results may include false positives and the rules are being actively refined. \n**This security report is NOT a review blocker.** Please try `merge from main` to avoid findings unrelated to the PR.\n\n---\n\n';
const disclaimer = '⚠️ **Experimental Feature**: This security report is currently in experimental phase. Results may include false positives and the rules are being actively refined. \n**This security report is NOT a review blocker.** Please try `merge from main` to avoid findings unrelated to the PR.\nTo suppress a specific rule, see [Suppressing Rules](https://github.com/aws/aws-cdk/blob/main/tools/%40aws-cdk/security-guardian/README.md#suppressing-rules).\n\n---\n\n';
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
Expand Down Expand Up @@ -126,7 +126,7 @@ jobs:
);

if (botComment) {
const disclaimer = '⚠️ **Experimental Feature**: This security report is currently in experimental phase. Results may include false positives and the rules are being actively refined. \n**This security report is NOT a review blocker.** Please try `merge from main` to avoid findings unrelated to the PR.\n\n---\n\n';
const disclaimer = '⚠️ **Experimental Feature**: This security report is currently in experimental phase. Results may include false positives and the rules are being actively refined. \n**This security report is NOT a review blocker.** Please try `merge from main` to avoid findings unrelated to the PR.\nTo suppress a specific rule, see [Suppressing Rules](https://github.com/aws/aws-cdk/blob/main/tools/%40aws-cdk/security-guardian/README.md#suppressing-rules).\n\n---\n\n';
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
Expand Down
29 changes: 16 additions & 13 deletions tools/@aws-cdk/security-guardian/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,27 +160,30 @@ Use `mikepenz/action-junit-report@e08919a3b1fb83a78393dfb775a9c37f17d8eea6` (v6.

### Enhanced Failure Formatting

The tool automatically enhances CFN Guard failure messages by:
When `enhance_xml` is enabled, the tool enhances CFN Guard failure messages by:

- Splitting concatenated failure messages into individual violations
- Extracting exact line numbers and column positions
- Identifying specific CloudFormation resources and properties
- Formatting output for better readability in CI/CD reports
- Extracting human-readable error descriptions from custom `<<##ERROR:...##>>` annotations in guard rules
- Replacing the raw rule name with an actionable message that explains what's wrong and how to fix it
- Splitting concatenated failure details into individual violations
- Prefixing each failure with the validation type (e.g., `[Type: Static]` or `[Type: Resolved]`)

Each guard rule includes a custom error annotation using cfn-guard's `<<...>>` syntax:

```
<<##ERROR:EBS volume must have encryption enabled. Set 'Encrypted' to true.##>>
```

**Before (Raw CFN Guard Output):**

```text
IAM_NO_WILDCARD_ACTIONS_INLINE for Type: ResolvedCheck was not compliant as property [Policies[*].PolicyDocument.Statement[*]] is missing. Value traversed to [Path=/Resources/Role1/Properties[L:324,C:20]]Check was not compliant as property [Policies[*].PolicyDocument.Statement[*]] is missing. Value traversed to [Path=/Resources/Role2/Properties[L:485,C:20]]
```xml
<failure message="EBS_ENCRYPTION_ENABLED for Type: Static">Check was not compliant as property [Properties.Encrypted] is missing.</failure>
```

**After (Enhanced Format):**

```text
Rule: IAM_NO_WILDCARD_ACTIONS_INLINE (Type: Resolved)
==================================================

- Check was not compliant as property [Policies[*].PolicyDocument.Statement[*]] is missing. Value traversed to [Path=/Resources/Role1/Properties[L:324,C:20]]
- Check was not compliant as property [Policies[*].PolicyDocument.Statement[*]] is missing. Value traversed to [Path=/Resources/Role2/Properties[L:485,C:20]]
```xml
<failure message="[Type: Static] EBS volume must have encryption enabled. Set 'Encrypted' to true.">
Check was not compliant as property [Properties.Encrypted] is missing.</failure>
```

---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ rule DOCUMENTDB_ENCRYPTION_ENABLED when %documentdb_clusters !empty {
%documentdb_clusters {
Properties.StorageEncrypted exists
Properties.StorageEncrypted == true
<<##ERROR:DocumentDB cluster must have encryption enabled. Set 'StorageEncrypted' to true.##>>
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ rule EBS_ENCRYPTION_ENABLED when %ebs_volumes !empty {
%ebs_volumes {
Properties.Encrypted exists
Properties.Encrypted == true
<<##ERROR:EBS volume must have encryption enabled. Set 'Encrypted' to true.##>>
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ rule EC2_NO_OPEN_SECURITY_GROUPS when %security_groups !empty {
Properties.SecurityGroupIngress[*] {
when CidrIp exists {
CidrIp != "0.0.0.0/0"
<<##ERROR:Security group must not allow unrestricted ingress. Remove '0.0.0.0/0' from CidrIp and scope to specific IP ranges.##>>
}
when CidrIpv6 exists {
CidrIpv6 != "::/0"
<<##ERROR:Security group must not allow unrestricted ingress. Remove '::/0' from CidrIpv6 and scope to specific IP ranges.##>>
}
}
}
Expand All @@ -33,4 +35,4 @@ rule EC2_NO_OPEN_SECURITY_GROUPS when %security_groups !empty {
# 3. Most applications legitimately need unrestricted outbound access
# 4. Restricting egress is an advanced security practice, not a default requirement
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@ rule IAM_NO_OVERLY_PERMISSIVE_PASSROLE when %iam_passrole_resources !empty {
when Resource exists {
when Resource is_string {
Resource != "*"
<<##ERROR:iam:PassRole must not use wildcard resources. Replace '*' in Resource with specific role ARNs that can be passed.##>>
}
when Resource is_list {
Resource[*] != "*"
<<##ERROR:iam:PassRole must not use wildcard resources. Replace '*' in Resource with specific role ARNs that can be passed.##>>
}
}
}
Expand All @@ -37,9 +39,11 @@ rule IAM_NO_OVERLY_PERMISSIVE_PASSROLE when %iam_passrole_resources !empty {
when Resource exists {
when Resource is_string {
Resource != "*"
<<##ERROR:iam:PassRole must not use wildcard resources. Replace '*' in Resource with specific role ARNs that can be passed.##>>
}
when Resource is_list {
Resource[*] != "*"
<<##ERROR:iam:PassRole must not use wildcard resources. Replace '*' in Resource with specific role ARNs that can be passed.##>>
}
}
}
Expand All @@ -59,9 +63,11 @@ rule IAM_NO_OVERLY_PERMISSIVE_PASSROLE when %iam_passrole_resources !empty {
when Resource exists {
when Resource is_string {
Resource != "*"
<<##ERROR:iam:PassRole must not use wildcard resources. Replace '*' in Resource with specific role ARNs that can be passed.##>>
}
when Resource is_list {
Resource[*] != "*"
<<##ERROR:iam:PassRole must not use wildcard resources. Replace '*' in Resource with specific role ARNs that can be passed.##>>
}
}
}
Expand All @@ -71,9 +77,11 @@ rule IAM_NO_OVERLY_PERMISSIVE_PASSROLE when %iam_passrole_resources !empty {
when Resource exists {
when Resource is_string {
Resource != "*"
<<##ERROR:iam:PassRole must not use wildcard resources. Replace '*' in Resource with specific role ARNs that can be passed.##>>
}
when Resource is_list {
Resource[*] != "*"
<<##ERROR:iam:PassRole must not use wildcard resources. Replace '*' in Resource with specific role ARNs that can be passed.##>>
}
}
}
Expand All @@ -83,4 +91,4 @@ rule IAM_NO_OVERLY_PERMISSIVE_PASSROLE when %iam_passrole_resources !empty {
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,15 @@ rule IAM_NO_WILDCARD_ACTIONS_INLINE when %iam_roles !empty {
when Action exists {
when Action is_string {
Action != "*"
<<##ERROR:IAM role inline policy must not use wildcard actions. Replace '*' in Action with specific actions needed.##>>
Action != /\w+:\*$/
<<##ERROR:IAM role inline policy must not use wildcard actions. Replace 'service:*' in Action with specific actions needed.##>>
}
when Action is_list {
Action[*] != "*"
<<##ERROR:IAM role inline policy must not use wildcard actions. Replace '*' in Action with specific actions needed.##>>
Action[*] != /\w+:\*$/
<<##ERROR:IAM role inline policy must not use wildcard actions. Replace 'service:*' in Action with specific actions needed.##>>
}
}
}
Expand All @@ -38,15 +42,19 @@ rule IAM_NO_WILDCARD_ACTIONS_INLINE when %iam_roles !empty {
when Action exists {
when Action is_string {
Action != "*"
<<##ERROR:IAM role inline policy must not use wildcard actions. Replace '*' in Action with specific actions needed.##>>
Action != /\w+:\*$/
<<##ERROR:IAM role inline policy must not use wildcard actions. Replace 'service:*' in Action with specific actions needed.##>>
}
when Action is_list {
Action[*] != "*"
<<##ERROR:IAM role inline policy must not use wildcard actions. Replace '*' in Action with specific actions needed.##>>
Action[*] != /\w+:\*$/
<<##ERROR:IAM role inline policy must not use wildcard actions. Replace 'service:*' in Action with specific actions needed.##>>
}
}
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,18 @@ rule IAM_NO_WILDCARD_ACTIONS when %iam_policies !empty {
when Action exists {
when Action is_string {
Action != "*"
<<##ERROR:IAM policy must not use wildcard actions. Replace '*' in Action with specific actions needed.##>>
Action != /\w+:\*$/
<<##ERROR:IAM policy must not use wildcard actions. Replace 'service:*' in Action with specific actions needed.##>>
}
when Action is_list {
Action[*] != "*"
<<##ERROR:IAM policy must not use wildcard actions. Replace '*' in Action with specific actions needed.##>>
Action[*] != /\w+:\*$/
<<##ERROR:IAM policy must not use wildcard actions. Replace 'service:*' in Action with specific actions needed.##>>
}
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,14 @@ let iam_roles = Resources.*[
# Note: Only string values are checked - intrinsic functions (maps like Fn::GetAtt) are skipped
rule IAM_NO_WORLD_ACCESSIBLE_TRUST_POLICY when %iam_roles !empty {
%iam_roles.Properties.AssumeRolePolicyDocument.Statement[ Effect == "Allow" ] {
when Principal is_string { Principal != "*" }
when Principal.AWS is_string { Principal.AWS != "*" }
when Principal.AWS is_list { Principal.AWS[*][ this is_string ] != "*" }
when Principal is_string { Principal != "*"
<<##ERROR:IAM role trust policy must not be world-accessible. Remove '*' from Principal and scope to specific accounts, roles, or services.##>>
}
when Principal.AWS is_string { Principal.AWS != "*"
<<##ERROR:IAM role trust policy must not be world-accessible. Remove '*' from Principal and scope to specific accounts, roles, or services.##>>
}
when Principal.AWS is_list { Principal.AWS[*][ this is_string ] != "*"
<<##ERROR:IAM role trust policy must not be world-accessible. Remove '*' from Principal and scope to specific accounts, roles, or services.##>>
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,32 @@ rule IAM_POLICY_NO_BROAD_PRINCIPALS when %iam_policies_no_broad_principals !empt
# Check for wildcard as entire principal
when Principal is_string {
Principal != "*"
<<##ERROR:IAM policy must not use broadly scoped principals. Remove '*', root principals, or wildcard account ARNs from Principal and scope to specific accounts or roles.##>>
}
# Check if AWS principal exists
when Principal.AWS exists {
# Check if AWS is a string
when Principal.AWS is_string {
Principal.AWS != "*"
<<##ERROR:IAM policy must not use broadly scoped principals. Remove '*', root principals, or wildcard account ARNs from Principal and scope to specific accounts or roles.##>>
Principal.AWS != /(?i):root/
<<##ERROR:IAM policy must not use broadly scoped principals. Remove '*', root principals, or wildcard account ARNs from Principal and scope to specific accounts or roles.##>>
Principal.AWS != /arn:aws:iam::\*:/
<<##ERROR:IAM policy must not use broadly scoped principals. Remove '*', root principals, or wildcard account ARNs from Principal and scope to specific accounts or roles.##>>
}
# Check if AWS is an array - only check string items
when Principal.AWS is_list {
when Principal.AWS[*] is_string {
Principal.AWS[*] != "*"
<<##ERROR:IAM policy must not use broadly scoped principals. Remove '*', root principals, or wildcard account ARNs from Principal and scope to specific accounts or roles.##>>
Principal.AWS[*] != /(?i):root/
<<##ERROR:IAM policy must not use broadly scoped principals. Remove '*', root principals, or wildcard account ARNs from Principal and scope to specific accounts or roles.##>>
Principal.AWS[*] != /arn:aws:iam::\*:/
<<##ERROR:IAM policy must not use broadly scoped principals. Remove '*', root principals, or wildcard account ARNs from Principal and scope to specific accounts or roles.##>>
}
}
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,27 @@ rule IAM_ROLE_NO_BROAD_PRINCIPALS when %iam_roles_no_broad_principals !empty {
# Check for wildcard as entire principal
when Principal is_string {
Principal != "*"
<<##ERROR:IAM role trust policy must not use broadly scoped principals. Remove '*' from Principal and scope to specific accounts or roles.##>>
}
# Check if AWS principal exists
when Principal.AWS exists {
# Check if AWS is a string
when Principal.AWS is_string {
Principal.AWS != "*"
<<##ERROR:IAM role trust policy must not use broadly scoped principals. Remove '*' from Principal and scope to specific accounts or roles.##>>
# Wildcard account - matches any AWS account
Principal.AWS != /arn:aws:iam::\*:/
<<##ERROR:IAM role trust policy must not use broadly scoped principals. Remove wildcard account ARNs (arn:aws:iam::*:) from Principal and scope to specific accounts or roles.##>>
}
# Check if AWS is an array - iterate each item and check only string items
# This handles mixed arrays with strings and intrinsic functions (objects)
when Principal.AWS is_list {
Principal.AWS[*] {
when this is_string {
this != "*"
<<##ERROR:IAM role trust policy must not use broadly scoped principals. Remove '*' from Principal and scope to specific accounts or roles.##>>
this != /arn:aws:iam::\*:/
<<##ERROR:IAM role trust policy must not use broadly scoped principals. Remove wildcard account ARNs (arn:aws:iam::*:) from Principal and scope to specific accounts or roles.##>>
}
}
}
Expand Down
Loading
Loading