aws-cloudformation / aws-cloudformation/cloudformation-guard

[BUG] Multiple resource template falsely fails on changing unrelated resource metadata

Open
#647 0 comments 0 reactions 0 assignees View on GitHub
bug
Dominant language
Rust
Stars
1.4k
Forks
196
Avg merge
3d 6h
Merged PRs (30d)
5

Description

**Describe the bug**
I have a simple CloudFormation template that has more than one resource. For some resources, I would like to adhere to the [aws-guard-rules-registry/CFN_NO_EXPLICIT_RESOURCE_NAMES](https://github.com/aws-cloudformation/aws-guard-rules-registry/blob/main/rules/aws/aws_cloudformation/cfn_no_explicit_resource_names.guard) rule, and for others I want to use explicit names with good reason. The problem is that when I apply suppression via `Metadata.guard.SuppressedRules` to a resource that does not conform to the rule, a `FAIL` would be reported when I do not also include the metadata for Resources that do NOT need it.

I do apologise for submitting a new issue as this relates to #636 but this is a sanitised submission stripped down to the least amount of data to reproduce the issue using `cfn-guard test`.

**To Reproduce**
Please supply:

1. Example rules and template that results in the error:

**Resources:**

**Resource 1**: Guard data: `file: cfn_no_explicit_resource_names-modified.guard`
Notes:
- the types have been reduced to `AWS::EC2::SecurityGroup` and `AWS::IAM::Role` in both `applicable_types` and Rule/When evaluation
- `Metadata.cfn_nag` portions have been removed
```
# Rule Identifier:
# CFN_NO_EXPLICIT_RESOURCE_NAMES
# NOTE: Reduced for test-case sake to prove inconsistent failure

let applicable_types = [
"AWS::EC2::SecurityGroup",
"AWS::IAM::Role"
]

# Select applicable resources less suppressed resources
let cloudformation_no_static_name_resources = Resources.*[Type in %applicable_types
Metadata.guard.SuppressedRules not exists or
Metadata.guard.SuppressedRules.* != "CFN_NO_EXPLICIT_RESOURCE_NAMES"
]

rule CFN_NO_EXPLICIT_RESOURCE_NAMES
when %cloudformation_no_static_name_resources !empty {
AWS::EC2::SecurityGroup {
Properties{
GroupName empty
}
}
AWS::IAM::Role {
Properties{
RoleName empty
}
}
%cloudformation_no_static_name_resources not empty
<<
Violation: Resource found with an explicit name, this disallows updates that require replacement of this resource.
Fix: Remove static name from the resource
>>
}

```

**Resource 2**: Test data: `file: test.yaml`

Notes:
- 5 cases are declared, progressing from single-resource to multiple resources and the point of unexpected outcome in 5th case.

```yaml
---
- name: CFN_NO_EXPLICIT_RESOURCE_NAMES Single Resource IAM Role, CONFORM/Expect Pass
input:
Resources:
ElasticContainerTaskExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Action:
- sts:AssumeRole
Effect: Allow
Principal:
Service: ecs-tasks.amazonaws.com
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
expectations:
rules:
CFN_NO_EXPLICIT_RESOURCE_NAMES: PASS

- name: CFN_NO_EXPLICIT_RESOURCE_NAMES Single Resource IAM Role, FLOUT/Expect Fail
input:
Resources:
ElasticContainerTaskExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Action:
- sts:AssumeRole
Effect: Allow
Principal:
Service: ecs-tasks.amazonaws.com
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
RoleName: task-execution-role
expectations:
rules:
CFN_NO_EXPLICIT_RESOURCE_NAMES: FAIL

- name: CFN_NO_EXPLICIT_RESOURCE_NAMES Single Resource IAM Role, FLOUT+SUPPRESS/Expect Skip
input:
Resources:
ElasticContainerTaskExecutionRole:
Type: AWS::IAM::Role
Metadata:
guard:
SuppressedRules:
- CFN_NO_EXPLICIT_RESOURCE_NAMES
Properties:
AssumeRolePolicyDocument:
Statement:
- Action:
- sts:AssumeRole
Effect: Allow
Principal:
Service: ecs-tasks.amazonaws.com
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
RoleName: task-execution-role
expectations:
rules:
CFN_NO_EXPLICIT_RESOURCE_NAMES: SKIP

- name: CFN_NO_EXPLICIT_RESOURCE_NAMES Multiple Resource, Suppression in Both (SecurityGroup suppression not needed)/Expect Skip
input:
AWSTemplateFormatVersion: '2010-09-09'
Description: Creates an Elastic Container Service HTTP service and task.
Parameters:
EcsClusterName:
Type: String
Description: The name of the ECS Cluster
StackNameTag:
Type: String
VpcId:
Type: AWS::EC2::VPC::Id
Description: Specify the VPC ID.
Resources:
ElasticContainerSecurityGroup:
Type: AWS::EC2::SecurityGroup
Metadata:
guard:
SuppressedRules:
- EC2_SECURITY_GROUP_EGRESS_OPEN_TO_WORLD_RULE
- SECURITY_GROUP_EGRESS_PORT_RANGE_RULE
- CFN_NO_EXPLICIT_RESOURCE_NAMES ## NOT NEEDED because Properties.GroupName not defined
Properties:
GroupDescription: VPC Security Group
SecurityGroupEgress:
- Description: ALLOW all TO all
CidrIp: 0.0.0.0/0
IpProtocol: tcp
FromPort: 0
ToPort: 65535
SecurityGroupIngress:
- Description: !Sub ALLOW http FROM ${EcsClusterName}-external-alb
FromPort: 80
IpProtocol: tcp
SourceSecurityGroupId: !Sub "{{resolve:ssm:/${VpcId}/security/security-groups/security-group-id/${EcsClusterName}-external-alb}}"
ToPort: 80
Tags:
- Key: Name
Value: !Sub ${StackNameTag}-instance-security-group
VpcId: !Ref VpcId
ElasticContainerTaskExecutionRole:
Type: AWS::IAM::Role
Metadata:
guard:
SuppressedRules:
- CFN_NO_EXPLICIT_RESOURCE_NAMES
Properties:
AssumeRolePolicyDocument:
Statement:
- Action:
- sts:AssumeRole
Effect: Allow
Principal:
Service: ecs-tasks.amazonaws.com
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
RoleName: explicit-name
expectations:
rules:
CFN_NO_EXPLICIT_RESOURCE_NAMES: SKIP

- name: CFN_NO_EXPLICIT_RESOURCE_NAMES Multiple Resource, Suppression in Role Only (SecurityGroup suppression Removed)/Expect Skip
input:
AWSTemplateFormatVersion: '2010-09-09'
Description: Creates an Elastic Container Service HTTP service and task.
Parameters:
EcsClusterName:
Type: String
Description: The name of the ECS Cluster
StackNameTag:
Type: String
VpcId:
Type: AWS::EC2::VPC::Id
Description: Specify the VPC ID.
Resources:
ElasticContainerSecurityGroup:
Type: AWS::EC2::SecurityGroup
Metadata:
guard:
SuppressedRules:
- EC2_SECURITY_GROUP_EGRESS_OPEN_TO_WORLD_RULE
- SECURITY_GROUP_EGRESS_PORT_RANGE_RULE
# - CFN_NO_EXPLICIT_RESOURCE_NAMES ## Removed because NOT NEEDED
Properties:
GroupDescription: VPC Security Group
SecurityGroupEgress:
- Description: ALLOW all TO all
CidrIp: 0.0.0.0/0
IpProtocol: tcp
FromPort: 0
ToPort: 65535
SecurityGroupIngress:
- Description: !Sub ALLOW http FROM ${EcsClusterName}-external-alb
FromPort: 80
IpProtocol: tcp
SourceSecurityGroupId: !Sub "{{resolve:ssm:/${VpcId}/security/security-groups/security-group-id/${EcsClusterName}-external-alb}}"
ToPort: 80
Tags:
- Key: Name
Value: !Sub ${StackNameTag}-instance-security-group
VpcId: !Ref VpcId
ElasticContainerTaskExecutionRole:
Type: AWS::IAM::Role
Metadata:
guard:
SuppressedRules:
- CFN_NO_EXPLICIT_RESOURCE_NAMES
Properties:
AssumeRolePolicyDocument:
Statement:
- Action:
- sts:AssumeRole
Effect: Allow
Principal:
Service: ecs-tasks.amazonaws.com
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
RoleName: explicit-name
expectations:
rules:
CFN_NO_EXPLICIT_RESOURCE_NAMES: SKIP
```

2. The commands you used to invoke the tool

```shell
cfn-guard test -r cfn_no_explicit_resource_names-modified.guard -t test.yaml
```

Output:

```text
Test Case #1
Name: CFN_NO_EXPLICIT_RESOURCE_NAMES Single Resource IAM Role, CONFORM/Expect Pass
PASS Rules:
CFN_NO_EXPLICIT_RESOURCE_NAMES: Expected = PASS

Test Case #2
Name: CFN_NO_EXPLICIT_RESOURCE_NAMES Single Resource IAM Role, FLOUT/Expect Fail
PASS Rules:
CFN_NO_EXPLICIT_RESOURCE_NAMES: Expected = FAIL

Test Case #3
Name: CFN_NO_EXPLICIT_RESOURCE_NAMES Single Resource IAM Role, FLOUT+SUPPRESS/Expect Skip
PASS Rules:
CFN_NO_EXPLICIT_RESOURCE_NAMES: Expected = SKIP

Test Case #4
Name: CFN_NO_EXPLICIT_RESOURCE_NAMES Multiple Resource, Suppression in Both (SecurityGroup suppression not needed)/Expect Skip
PASS Rules:
CFN_NO_EXPLICIT_RESOURCE_NAMES: Expected = SKIP

Test Case #5
Name: CFN_NO_EXPLICIT_RESOURCE_NAMES Multiple Resource, Suppression in Role Only (SecurityGroup suppression Removed)/Expect Skip
FAIL Rules:
CFN_NO_EXPLICIT_RESOURCE_NAMES: Expected = SKIP, Evaluated = [FAIL]
```

With `-v` output:

```text
Test Case #1
Name: CFN_NO_EXPLICIT_RESOURCE_NAMES Single Resource IAM Role, CONFORM/Expect Pass
`- File(, Status=PASS)[Context=File(rules=1)]
`- Rule(CFN_NO_EXPLICIT_RESOURCE_NAMES, Status=PASS)[Context=CFN_NO_EXPLICIT_RESOURCE_NAMES]
|- Rule/When(Status=PASS)[Context=Rule#CFN_NO_EXPLICIT_RESOURCE_NAMES/When]
| `- GuardClauseBlock(Status = PASS)[Context=GuardAccessClause#block %cloudformation_no_static_name_resources not EMPTY ]
| |- Filter/ConjunctionsBlock(Status=PASS)[Context=Filter/Map#2]
| | |- GuardClauseBlock(Status = PASS)[Context=GuardAccessClause#block Type IN %applicable_types]
| | | `- GuardClauseValueCheck(Status=PASS)[Context= Type IN %applicable_types]
| | `- Disjunction(Status = PASS)[Context=cfn_guard::rules::exprs::GuardClause#disjunction]
| | `- GuardClauseBlock(Status = PASS)[Context=GuardAccessClause#block Metadata.guard.SuppressedRules not EXISTS ]
| | `- GuardClauseValueCheck(Status=PASS)[Context= Metadata.guard.SuppressedRules not EXISTS ]
| `- GuardClauseValueCheck(Status=PASS)[Context= %cloudformation_no_static_name_resources not EMPTY ]
|- Type(AWS::EC2::SecurityGroup, Status=SKIP)[Context=TypeBlock#AWS::EC2::SecurityGroup]
| `- Filter/ConjunctionsBlock(Status=FAIL)[Context=Filter/Map#1]
| `- GuardClauseBlock(Status = FAIL)[Context=GuardAccessClause#block Type EQUALS "AWS::EC2::SecurityGroup"]
| `- GuardClauseBinaryCheck(Status=FAIL, Comparison= EQUALS, from=(resolved, Path=/Resources/ElasticContainerTaskExecutionRole/Type[L:0,C:0] Value="AWS::IAM::Role"), to=(resolved, Path=[L:0,C:0] Value="AWS::EC2::SecurityGroup"))[Context= Type EQUALS "AWS::EC2::SecurityGroup"]
|- Type(AWS::IAM::Role, Status=PASS)[Context=TypeBlock#AWS::IAM::Role]
| |- Filter/ConjunctionsBlock(Status=PASS)[Context=Filter/Map#1]
| | `- GuardClauseBlock(Status = PASS)[Context=GuardAccessClause#block Type EQUALS "AWS::IAM::Role"]
| | `- GuardClauseValueCheck(Status=PASS)[Context= Type EQUALS "AWS::IAM::Role"]
| `- TypeBlock/Block Status=PASS)[Context=TypeBlock#AWS::IAM::Role/0]
| `- GuardValueBlockCheck(Status = PASS)[Context=BlockGuardClause#Location[file:cfn_no_explicit_resource_names-modified.guard, line:24, column:7]]
| `- GuardClauseBlock(Status = PASS)[Context=GuardAccessClause#block RoleName EMPTY ]
| `- GuardClauseValueCheck(Status=PASS)[Context= RoleName EMPTY ]
`- GuardClauseBlock(Status = PASS)[Context=GuardAccessClause#block %cloudformation_no_static_name_resources not EMPTY ]
`- GuardClauseValueCheck(Status=PASS)[Context= %cloudformation_no_static_name_resources not EMPTY ]
PASS Rules:
CFN_NO_EXPLICIT_RESOURCE_NAMES: Expected = PASS

Test Case #2
Name: CFN_NO_EXPLICIT_RESOURCE_NAMES Single Resource IAM Role, FLOUT/Expect Fail
`- File(, Status=FAIL)[Context=File(rules=1)]
`- Rule(CFN_NO_EXPLICIT_RESOURCE_NAMES, Status=FAIL)[Context=CFN_NO_EXPLICIT_RESOURCE_NAMES]
|- Rule/When(Status=PASS)[Context=Rule#CFN_NO_EXPLICIT_RESOURCE_NAMES/When]
| `- GuardClauseBlock(Status = PASS)[Context=GuardAccessClause#block %cloudformation_no_static_name_resources not EMPTY ]
| |- Filter/ConjunctionsBlock(Status=PASS)[Context=Filter/Map#2]
| | |- GuardClauseBlock(Status = PASS)[Context=GuardAccessClause#block Type IN %applicable_types]
| | | `- GuardClauseValueCheck(Status=PASS)[Context= Type IN %applicable_types]
| | `- Disjunction(Status = PASS)[Context=cfn_guard::rules::exprs::GuardClause#disjunction]
| | `- GuardClauseBlock(Status = PASS)[Context=GuardAccessClause#block Metadata.guard.SuppressedRules not EXISTS ]
| | `- GuardClauseValueCheck(Status=PASS)[Context= Metadata.guard.SuppressedRules not EXISTS ]
| `- GuardClauseValueCheck(Status=PASS)[Context= %cloudformation_no_static_name_resources not EMPTY ]
|- Type(AWS::EC2::SecurityGroup, Status=SKIP)[Context=TypeBlock#AWS::EC2::SecurityGroup]
| `- Filter/ConjunctionsBlock(Status=FAIL)[Context=Filter/Map#1]
| `- GuardClauseBlock(Status = FAIL)[Context=GuardAccessClause#block Type EQUALS "AWS::EC2::SecurityGroup"]
| `- GuardClauseBinaryCheck(Status=FAIL, Comparison= EQUALS, from=(resolved, Path=/Resources/ElasticContainerTaskExecutionRole/Type[L:0,C:0] Value="AWS::IAM::Role"), to=(resolved, Path=[L:0,C:0] Value="AWS::EC2::SecurityGroup"))[Context= Type EQUALS "AWS::EC2::SecurityGroup"]
|- Type(AWS::IAM::Role, Status=FAIL)[Context=TypeBlock#AWS::IAM::Role]
| |- Filter/ConjunctionsBlock(Status=PASS)[Context=Filter/Map#1]
| | `- GuardClauseBlock(Status = PASS)[Context=GuardAccessClause#block Type EQUALS "AWS::IAM::Role"]
| | `- GuardClauseValueCheck(Status=PASS)[Context= Type EQUALS "AWS::IAM::Role"]
| `- TypeBlock/Block Status=FAIL)[Context=TypeBlock#AWS::IAM::Role/0]
| `- GuardValueBlockCheck(Status = FAIL)[Context=BlockGuardClause#Location[file:cfn_no_explicit_resource_names-modified.guard, line:24, column:7]]
| `- GuardClauseBlock(Status = FAIL)[Context=GuardAccessClause#block RoleName EMPTY ]
| `- GuardClauseUnaryCheck(Status=FAIL, Comparison= EMPTY, Value-At=(resolved, Path=/Resources/ElasticContainerTaskExecutionRole/Properties/RoleName[L:0,C:0] Value="task-execution-role"))[Context= RoleName EMPTY ]
`- GuardClauseBlock(Status = PASS)[Context=GuardAccessClause#block %cloudformation_no_static_name_resources not EMPTY ]
`- GuardClauseValueCheck(Status=PASS)[Context= %cloudformation_no_static_name_resources not EMPTY ]
PASS Rules:
CFN_NO_EXPLICIT_RESOURCE_NAMES: Expected = FAIL

Test Case #3
Name: CFN_NO_EXPLICIT_RESOURCE_NAMES Single Resource IAM Role, FLOUT+SUPPRESS/Expect Skip
`- File(, Status=SKIP)[Context=File(rules=1)]
`- Rule(CFN_NO_EXPLICIT_RESOURCE_NAMES, Status=SKIP)[Context=CFN_NO_EXPLICIT_RESOURCE_NAMES]
`- Rule/When(Status=FAIL)[Context=Rule#CFN_NO_EXPLICIT_RESOURCE_NAMES/When]
`- GuardClauseBlock(Status = FAIL)[Context=GuardAccessClause#block %cloudformation_no_static_name_resources not EMPTY ]
|- Filter/ConjunctionsBlock(Status=FAIL)[Context=Filter/Map#2]
| |- GuardClauseBlock(Status = PASS)[Context=GuardAccessClause#block Type IN %applicable_types]
| | `- GuardClauseValueCheck(Status=PASS)[Context= Type IN %applicable_types]
| `- Disjunction(Status = FAIL)[Context=cfn_guard::rules::exprs::GuardClause#disjunction]
| |- GuardClauseBlock(Status = FAIL)[Context=GuardAccessClause#block Metadata.guard.SuppressedRules not EXISTS ]
| | `- GuardClauseUnaryCheck(Status=FAIL, Comparison=not EXISTS, Value-At=(resolved, Path=/Resources/ElasticContainerTaskExecutionRole/Metadata/guard/SuppressedRules[L:0,C:0] Value=["CFN_NO_EXPLICIT_RESOURCE_NAMES"]))[Context= Metadata.guard.SuppressedRules not EXISTS ]
| `- GuardClauseBlock(Status = FAIL)[Context=GuardAccessClause#block Metadata.guard.SuppressedRules.* not EQUALS "CFN_NO_EXPLICIT_RESOURCE_NAMES"]
| `- GuardClauseBinaryCheck(Status=FAIL, Comparison=not EQUALS, from=(resolved, Path=/Resources/ElasticContainerTaskExecutionRole/Metadata/guard/SuppressedRules/0[L:0,C:0] Value="CFN_NO_EXPLICIT_RESOURCE_NAMES"), to=(resolved, Path=[L:0,C:0] Value="CFN_NO_EXPLICIT_RESOURCE_NAMES"))[Context= Metadata.guard.SuppressedRules.* not EQUALS "CFN_NO_EXPLICIT_RESOURCE_NAMES"]
`- GuardClause(Status=FAIL, Empty, )[Context= %cloudformation_no_static_name_resources not EMPTY ]
PASS Rules:
CFN_NO_EXPLICIT_RESOURCE_NAMES: Expected = SKIP

Test Case #4
Name: CFN_NO_EXPLICIT_RESOURCE_NAMES Multiple Resource, Suppression in Both (SecurityGroup suppression not needed)/Expect Skip
`- File(, Status=SKIP)[Context=File(rules=1)]
`- Rule(CFN_NO_EXPLICIT_RESOURCE_NAMES, Status=SKIP)[Context=CFN_NO_EXPLICIT_RESOURCE_NAMES]
`- Rule/When(Status=FAIL)[Context=Rule#CFN_NO_EXPLICIT_RESOURCE_NAMES/When]
`- GuardClauseBlock(Status = FAIL)[Context=GuardAccessClause#block %cloudformation_no_static_name_resources not EMPTY ]
|- Filter/ConjunctionsBlock(Status=FAIL)[Context=Filter/Map#2]
| |- GuardClauseBlock(Status = PASS)[Context=GuardAccessClause#block Type IN %applicable_types]
| | `- GuardClauseValueCheck(Status=PASS)[Context= Type IN %applicable_types]
| `- Disjunction(Status = FAIL)[Context=cfn_guard::rules::exprs::GuardClause#disjunction]
| |- GuardClauseBlock(Status = FAIL)[Context=GuardAccessClause#block Metadata.guard.SuppressedRules not EXISTS ]
| | `- GuardClauseUnaryCheck(Status=FAIL, Comparison=not EXISTS, Value-At=(resolved, Path=/Resources/ElasticContainerSecurityGroup/Metadata/guard/SuppressedRules[L:0,C:0] Value=["EC2_SECURITY_GROUP_EGRESS_OPEN_TO_WORLD_RULE","SECURITY_GROUP_EGRESS_PORT_RANGE_RULE","CFN_NO_EXPLICIT_RESOURCE_NAMES"]))[Context= Metadata.guard.SuppressedRules not EXISTS ]
| `- GuardClauseBlock(Status = FAIL)[Context=GuardAccessClause#block Metadata.guard.SuppressedRules.* not EQUALS "CFN_NO_EXPLICIT_RESOURCE_NAMES"]
| |- GuardClauseValueCheck(Status=PASS)[Context= Metadata.guard.SuppressedRules.* not EQUALS "CFN_NO_EXPLICIT_RESOURCE_NAMES"]
| |- GuardClauseValueCheck(Status=PASS)[Context= Metadata.guard.SuppressedRules.* not EQUALS "CFN_NO_EXPLICIT_RESOURCE_NAMES"]
| `- GuardClauseBinaryCheck(Status=FAIL, Comparison=not EQUALS, from=(resolved, Path=/Resources/ElasticContainerSecurityGroup/Metadata/guard/SuppressedRules/2[L:0,C:0] Value="CFN_NO_EXPLICIT_RESOURCE_NAMES"), to=(resolved, Path=[L:0,C:0] Value="CFN_NO_EXPLICIT_RESOURCE_NAMES"))[Context= Metadata.guard.SuppressedRules.* not EQUALS "CFN_NO_EXPLICIT_RESOURCE_NAMES"]
|- Filter/ConjunctionsBlock(Status=FAIL)[Context=Filter/Map#2]
| |- GuardClauseBlock(Status = PASS)[Context=GuardAccessClause#block Type IN %applicable_types]
| | `- GuardClauseValueCheck(Status=PASS)[Context= Type IN %applicable_types]
| `- Disjunction(Status = FAIL)[Context=cfn_guard::rules::exprs::GuardClause#disjunction]
| |- GuardClauseBlock(Status = FAIL)[Context=GuardAccessClause#block Metadata.guard.SuppressedRules not EXISTS ]
| | `- GuardClauseUnaryCheck(Status=FAIL, Comparison=not EXISTS, Value-At=(resolved, Path=/Resources/ElasticContainerTaskExecutionRole/Metadata/guard/SuppressedRules[L:0,C:0] Value=["CFN_NO_EXPLICIT_RESOURCE_NAMES"]))[Context= Metadata.guard.SuppressedRules not EXISTS ]
| `- GuardClauseBlock(Status = FAIL)[Context=GuardAccessClause#block Metadata.guard.SuppressedRules.* not EQUALS "CFN_NO_EXPLICIT_RESOURCE_NAMES"]
| `- GuardClauseBinaryCheck(Status=FAIL, Comparison=not EQUALS, from=(resolved, Path=/Resources/ElasticContainerTaskExecutionRole/Metadata/guard/SuppressedRules/0[L:0,C:0] Value="CFN_NO_EXPLICIT_RESOURCE_NAMES"), to=(resolved, Path=[L:0,C:0] Value="CFN_NO_EXPLICIT_RESOURCE_NAMES"))[Context= Metadata.guard.SuppressedRules.* not EQUALS "CFN_NO_EXPLICIT_RESOURCE_NAMES"]
`- GuardClause(Status=FAIL, Empty, )[Context= %cloudformation_no_static_name_resources not EMPTY ]
PASS Rules:
CFN_NO_EXPLICIT_RESOURCE_NAMES: Expected = SKIP

Test Case #5
Name: CFN_NO_EXPLICIT_RESOURCE_NAMES Multiple Resource, Suppression in Role Only (SecurityGroup suppression Removed)/Expect Skip
`- File(, Status=FAIL)[Context=File(rules=1)]
`- Rule(CFN_NO_EXPLICIT_RESOURCE_NAMES, Status=FAIL)[Context=CFN_NO_EXPLICIT_RESOURCE_NAMES]
|- Rule/When(Status=PASS)[Context=Rule#CFN_NO_EXPLICIT_RESOURCE_NAMES/When]
| `- GuardClauseBlock(Status = PASS)[Context=GuardAccessClause#block %cloudformation_no_static_name_resources not EMPTY ]
| |- Filter/ConjunctionsBlock(Status=PASS)[Context=Filter/Map#2]
| | |- GuardClauseBlock(Status = PASS)[Context=GuardAccessClause#block Type IN %applicable_types]
| | | `- GuardClauseValueCheck(Status=PASS)[Context= Type IN %applicable_types]
| | `- Disjunction(Status = PASS)[Context=cfn_guard::rules::exprs::GuardClause#disjunction]
| | |- GuardClauseBlock(Status = FAIL)[Context=GuardAccessClause#block Metadata.guard.SuppressedRules not EXISTS ]
| | | `- GuardClauseUnaryCheck(Status=FAIL, Comparison=not EXISTS, Value-At=(resolved, Path=/Resources/ElasticContainerSecurityGroup/Metadata/guard/SuppressedRules[L:0,C:0] Value=["EC2_SECURITY_GROUP_EGRESS_OPEN_TO_WORLD_RULE","SECURITY_GROUP_EGRESS_PORT_RANGE_RULE"]))[Context= Metadata.guard.SuppressedRules not EXISTS ]
| | `- GuardClauseBlock(Status = PASS)[Context=GuardAccessClause#block Metadata.guard.SuppressedRules.* not EQUALS "CFN_NO_EXPLICIT_RESOURCE_NAMES"]
| | |- GuardClauseValueCheck(Status=PASS)[Context= Metadata.guard.SuppressedRules.* not EQUALS "CFN_NO_EXPLICIT_RESOURCE_NAMES"]
| | `- GuardClauseValueCheck(Status=PASS)[Context= Metadata.guard.SuppressedRules.* not EQUALS "CFN_NO_EXPLICIT_RESOURCE_NAMES"]
| |- Filter/ConjunctionsBlock(Status=FAIL)[Context=Filter/Map#2]
| | |- GuardClauseBlock(Status = PASS)[Context=GuardAccessClause#block Type IN %applicable_types]
| | | `- GuardClauseValueCheck(Status=PASS)[Context= Type IN %applicable_types]
| | `- Disjunction(Status = FAIL)[Context=cfn_guard::rules::exprs::GuardClause#disjunction]
| | |- GuardClauseBlock(Status = FAIL)[Context=GuardAccessClause#block Metadata.guard.SuppressedRules not EXISTS ]
| | | `- GuardClauseUnaryCheck(Status=FAIL, Comparison=not EXISTS, Value-At=(resolved, Path=/Resources/ElasticContainerTaskExecutionRole/Metadata/guard/SuppressedRules[L:0,C:0] Value=["CFN_NO_EXPLICIT_RESOURCE_NAMES"]))[Context= Metadata.guard.SuppressedRules not EXISTS ]
| | `- GuardClauseBlock(Status = FAIL)[Context=GuardAccessClause#block Metadata.guard.SuppressedRules.* not EQUALS "CFN_NO_EXPLICIT_RESOURCE_NAMES"]
| | `- GuardClauseBinaryCheck(Status=FAIL, Comparison=not EQUALS, from=(resolved, Path=/Resources/ElasticContainerTaskExecutionRole/Metadata/guard/SuppressedRules/0[L:0,C:0] Value="CFN_NO_EXPLICIT_RESOURCE_NAMES"), to=(resolved, Path=[L:0,C:0] Value="CFN_NO_EXPLICIT_RESOURCE_NAMES"))[Context= Metadata.guard.SuppressedRules.* not EQUALS "CFN_NO_EXPLICIT_RESOURCE_NAMES"]
| `- GuardClauseValueCheck(Status=PASS)[Context= %cloudformation_no_static_name_resources not EMPTY ]
|- Type(AWS::EC2::SecurityGroup, Status=PASS)[Context=TypeBlock#AWS::EC2::SecurityGroup]
| |- Filter/ConjunctionsBlock(Status=PASS)[Context=Filter/Map#1]
| | `- GuardClauseBlock(Status = PASS)[Context=GuardAccessClause#block Type EQUALS "AWS::EC2::SecurityGroup"]
| | `- GuardClauseValueCheck(Status=PASS)[Context= Type EQUALS "AWS::EC2::SecurityGroup"]
| |- Filter/ConjunctionsBlock(Status=FAIL)[Context=Filter/Map#1]
| | `- GuardClauseBlock(Status = FAIL)[Context=GuardAccessClause#block Type EQUALS "AWS::EC2::SecurityGroup"]
| | `- GuardClauseBinaryCheck(Status=FAIL, Comparison= EQUALS, from=(resolved, Path=/Resources/ElasticContainerTaskExecutionRole/Type[L:0,C:0] Value="AWS::IAM::Role"), to=(resolved, Path=[L:0,C:0] Value="AWS::EC2::SecurityGroup"))[Context= Type EQUALS "AWS::EC2::SecurityGroup"]
| `- TypeBlock/Block Status=PASS)[Context=TypeBlock#AWS::EC2::SecurityGroup/0]
| `- GuardValueBlockCheck(Status = PASS)[Context=BlockGuardClause#Location[file:cfn_no_explicit_resource_names-modified.guard, line:19, column:7]]
| `- GuardClauseBlock(Status = PASS)[Context=GuardAccessClause#block GroupName EMPTY ]
| `- GuardClauseValueCheck(Status=PASS)[Context= GroupName EMPTY ]
|- Type(AWS::IAM::Role, Status=FAIL)[Context=TypeBlock#AWS::IAM::Role]
| |- Filter/ConjunctionsBlock(Status=FAIL)[Context=Filter/Map#1]
| | `- GuardClauseBlock(Status = FAIL)[Context=GuardAccessClause#block Type EQUALS "AWS::IAM::Role"]
| | `- GuardClauseBinaryCheck(Status=FAIL, Comparison= EQUALS, from=(resolved, Path=/Resources/ElasticContainerSecurityGroup/Type[L:0,C:0] Value="AWS::EC2::SecurityGroup"), to=(resolved, Path=[L:0,C:0] Value="AWS::IAM::Role"))[Context= Type EQUALS "AWS::IAM::Role"]
| |- Filter/ConjunctionsBlock(Status=PASS)[Context=Filter/Map#1]
| | `- GuardClauseBlock(Status = PASS)[Context=GuardAccessClause#block Type EQUALS "AWS::IAM::Role"]
| | `- GuardClauseValueCheck(Status=PASS)[Context= Type EQUALS "AWS::IAM::Role"]
| `- TypeBlock/Block Status=FAIL)[Context=TypeBlock#AWS::IAM::Role/0]
| `- GuardValueBlockCheck(Status = FAIL)[Context=BlockGuardClause#Location[file:cfn_no_explicit_resource_names-modified.guard, line:24, column:7]]
| `- GuardClauseBlock(Status = FAIL)[Context=GuardAccessClause#block RoleName EMPTY ]
| `- GuardClauseUnaryCheck(Status=FAIL, Comparison= EMPTY, Value-At=(resolved, Path=/Resources/ElasticContainerTaskExecutionRole/Properties/RoleName[L:0,C:0] Value="explicit-name"))[Context= RoleName EMPTY ]
`- GuardClauseBlock(Status = PASS)[Context=GuardAccessClause#block %cloudformation_no_static_name_resources not EMPTY ]
`- GuardClauseValueCheck(Status=PASS)[Context= %cloudformation_no_static_name_resources not EMPTY ]
FAIL Rules:
CFN_NO_EXPLICIT_RESOURCE_NAMES: Expected = SKIP, Evaluated = [FAIL]
```

**NOTE: Please be sure that the templates, rules and logs you provide as part of your bug report do not contain any sensitive information.**

**Expected behaviour**
Having a template with multiple resources that is eligible to be evaluated against the [aws-guard-rules-registry/CFN_NO_EXPLICIT_RESOURCE_NAMES](https://github.com/aws-cloudformation/aws-guard-rules-registry/blob/main/rules/aws/aws_cloudformation/cfn_no_explicit_resource_names.guard) rule should not affect the rule evaluation result of a different resource to the one which has its `Metadata` altered.

**Operating System:**
Windows, running WSL2

**OS Version**
Ubuntu 24.04.2 LTS / `Linux HOSTNAME_REDACTED 5.15.167.4-microsoft-standard-WSL2 #1 SMP Tue Nov 5 00:21:55 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux`

**Additional context**
At this stage of the journey I don't know whether the problem is with the Guard DSL or within `cfn-guard` and the Rust code. I am down the rabbit hole learning Rust in an attempt to troubleshoot this problem and I don't see light at the end of the tunnel yet.

Contributor guide

Open the contributing guide

Research direction

Run `cfn-guard test -r cfn_no_explicit_resource_names-modified.guard -t test.yaml` and compare test cases 4 and 5, using `-v` to inspect rule evaluation. Trace how `Resources.*`, `Metadata.guard.SuppressedRules`, and the `CFN_NO_EXPLICIT_RESOURCE_NAMES` rule interact; done means case 5 evaluates as SKIP without suppressing the unrelated security group.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
testing, tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.