Repository: aws-cloudformation/cfn-lint

Issue:
E3057 does not check if a TargetGroupID value is a valid OriginGroup.
### CloudFormation Lint Version

1.10.2

### What operating system are you using?

Mac

### Describe the bug

E3057 is a false positive if a valid OriginGroup is specified as the TargetOriginID. The new rule only checks if the TargetOriginID is a valid TargetOrigin, but doesn't also check if the value is a valid OriginGroup. 
https://github.com/aws-cloudformation/cfn-lint/pull/3561

### Expected behavior

The lint rule should also check if a TargetOriginID value is a valid OriginGroup.

### Reproduction template

```
AWSTemplateFormatVersion: '2010-09-09'
Resources:
  MyS3BucketPrimary:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: my-primary-bucket

  MyS3BucketSecondary:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: my-secondary-bucket

  MyCloudFrontDistribution:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        Origins:
          - Id: PrimaryS3Origin
            DomainName: !GetAtt MyS3BucketPrimary.DomainName
            S3OriginConfig:
              OriginAccessIdentity: ""
          - Id: SecondaryS3Origin
            DomainName: !GetAtt MyS3BucketSecondary.DomainName
            S3OriginConfig:
              OriginAccessIdentity: ""
        OriginGroups:
          Quantity: 1
          Items:
            - Id: MyOriginGroup
              FailoverCriteria:
                StatusCodes:
                  Quantity: 1
                  Items:
                    - 403
              Members:
                Quantity: 2
                Items:
                  - OriginId: PrimaryS3Origin
                  - OriginId: SecondaryS3Origin
        DefaultCacheBehavior:
          TargetOriginId: MyOriginGroup
          ViewerProtocolPolicy: redirect-to-https
          AllowedMethods: ['GET', 'HEAD', 'OPTIONS']
          CachedMethods: ['GET', 'HEAD', 'OPTIONS']
          ForwardedValues:
            QueryString: false
            Cookies:
              Forward: none
        Enabled: true

Outputs:
  CloudFrontDistributionId:
    Description: "CloudFront Distribution ID"
    Value: !Ref MyCloudFrontDistribution
  CloudFrontDomainName:
    Description: "CloudFront Domain Name"
    Value: !GetAtt MyCloudFrontDistribution.DomainName
```


Discussion:
Thanks @jmh2012 Working on a fix.



Patch:
diff --git a/src/cfnlint/rules/resources/cloudfront/DistributionTargetOriginId.py b/src/cfnlint/rules/resources/cloudfront/DistributionTargetOriginId.py
index 39f0d963c5..cf4bf4385b 100644
--- a/src/cfnlint/rules/resources/cloudfront/DistributionTargetOriginId.py
+++ b/src/cfnlint/rules/resources/cloudfront/DistributionTargetOriginId.py
@@ -50,9 +50,22 @@ def validate(
                     break
                 origin_ids.append(origin_id)
             else:
-                yield ValidationError(
-                    message=f"{cache_origin_id!r} is not one of {origin_ids!r}",
-                    rule=self,
-                    path_override=cache_validator.context.path.path,
-                    validator="enum",
-                )
+                for origin_id, _ in get_value_from_path(
+                    cache_validator,
+                    instance,
+                    path=deque(["OriginGroups", "Items", "*", "Id"]),
+                ):
+                    if origin_id is None:
+                        continue
+                    if not validator.is_type(origin_id, "string"):
+                        break
+                    if origin_id == cache_origin_id:
+                        break
+                    origin_ids.append(origin_id)
+                else:
+                    yield ValidationError(
+                        message=f"{cache_origin_id!r} is not one of {origin_ids!r}",
+                        rule=self,
+                        path_override=cache_validator.context.path.path,
+                        validator="enum",
+                    )


Test patch:
diff --git a/test/unit/rules/resources/cloudfront/test_distribution_target_origin_id.py b/test/unit/rules/resources/cloudfront/test_distribution_target_origin_id.py
index 3b27e1cc8a..eeb2e3cb72 100644
--- a/test/unit/rules/resources/cloudfront/test_distribution_target_origin_id.py
+++ b/test/unit/rules/resources/cloudfront/test_distribution_target_origin_id.py
@@ -74,7 +74,7 @@ def rule():
             },
             [
                 ValidationError(
-                    ("'origin-id' is not one of " "['foo', 'bar']"),
+                    ("'origin-id' is not one of ['foo', 'bar']"),
                     rule=DistributionTargetOriginId(),
                     path=deque([]),
                     validator="enum",
@@ -117,6 +117,95 @@ def rule():
             },
             [],
         ),
+        (
+            {
+                "DefaultCacheBehavior": {
+                    "TargetOriginId": "origin-id",
+                },
+                "Origins": [
+                    {
+                        "Id": "foo",
+                    },
+                    {
+                        "Id": "bar",
+                    },
+                ],
+                "OriginGroups": {
+                    "Items": [
+                        {
+                            "Id": "group-1",
+                        },
+                        {
+                            "Id": "group-2",
+                        },
+                    ]
+                },
+            },
+            [
+                ValidationError(
+                    (
+                        "'origin-id' is not one of "
+                        "['foo', 'bar', 'group-1', 'group-2']"
+                    ),
+                    rule=DistributionTargetOriginId(),
+                    path=deque([]),
+                    validator="enum",
+                    path_override=deque(["DefaultCacheBehavior", "TargetOriginId"]),
+                )
+            ],
+        ),
+        (
+            {
+                "DefaultCacheBehavior": {
+                    "TargetOriginId": "origin-id",
+                },
+                "Origins": [
+                    {
+                        "Id": "foo",
+                    },
+                    {
+                        "Id": "bar",
+                    },
+                ],
+                "OriginGroups": {
+                    "Items": [
+                        {
+                            "Id": "group-1",
+                        },
+                        {
+                            "Id": "origin-id",
+                        },
+                    ]
+                },
+            },
+            [],
+        ),
+        (
+            {
+                "DefaultCacheBehavior": {
+                    "TargetOriginId": "origin-id",
+                },
+                "Origins": [
+                    {
+                        "Id": "foo",
+                    },
+                    {
+                        "Id": "bar",
+                    },
+                ],
+                "OriginGroups": {
+                    "Items": [
+                        {
+                            "Id": "group-1",
+                        },
+                        {
+                            "Id": {"Ref": "MyParameter"},
+                        },
+                    ]
+                },
+            },
+            [],
+        ),
     ],
 )
 def test_validate(instance, expected, rule, validator):