Skip to content

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

Description

@johanmynhardt

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 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.
---
- 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
  1. The commands you used to invoke the tool
cfn-guard test -r cfn_no_explicit_resource_names-modified.guard -t test.yaml

Output:

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:

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 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions