Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Transform AWS::LanguageExtensions failed with: Condition layout is incorrect -- But I'm not using Conditions anywhere in the template. #156

Open
johncuyle opened this issue Mar 15, 2024 · 1 comment

Comments

@johncuyle
Copy link

johncuyle commented Mar 15, 2024

Community Note

  • Please vote on this issue by adding a 👍 reaction to the original issue to help the community and maintainers prioritize this request
  • Please do not leave "+1" or "me too" comments, they generate extra noise for issue followers and do not help prioritize the request
  • If you are interested in working on this issue or have submitted a pull request, please leave a comment

Tell us about the bug

What goes wrong? Please describe the issue in as much detail as possible.

NOTE: This has the same title as 147 and I suppose it may have the same underlying cause, but in 147 there's a workaround in that reordering the creation condition allows him to create the stack. I'm not using conditions. At all. There are no conditions in this template, which means I can't reorder them, which means I have no decent workaround.

The following code:

AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::LanguageExtensions'
Description: 'IAM OIDC Provider configuration for kubernetes cluster'

Parameters:
  ClusterName:
    Type: String
    Description: The Cluster Name.  Used to uniquely identify created roles.
  OpenIdConnectIssuerUrl:
    Type: String
    Description: The OpenIdConnect issuer URL.
  RoleMapDocument:
    Type: String
    Description: The Document containing the RoleMap, as json string.

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
    - Label:
        default: "OpenIdConnect"
      Parameters:
      - ClusterName
      - OpenIdConnectIssuerUrl
      - RoleMapDocument

Mappings:
  'Fn::Transform':
    Name: JMESPath
    Parameters:
      Path: "@"
      Document: !Ref RoleMapDocument

Resources:
  OIDCThumbprint:
    Type: Custom::OIDCThumbprint
    Properties:
      ServiceToken: !ImportValue OpenIdConnectThumbprint
      OpenIdConnectIssuerUrl: !Ref OpenIdConnectIssuerUrl

  OIDCProvider:
    Type: AWS::IAM::OIDCProvider
    Properties:
      ClientIdList: 
        - sts.amazonaws.com
      ThumbprintList: 
        - !GetAtt OIDCThumbprint.Thumbprint
      Url: !Ref OpenIdConnectIssuerUrl
      Tags:
      - Key: StackName
        Value: !Ref AWS::StackName

  Fn::ForEach::Role:
  - Name
  - 'Fn::Transform':
      Name: JMESPath
      Parameters:
        Path: "keys(RoleMap)"
        Document: !Ref RoleMapDocument
  - '&{Name}':
      Type: AWS::IAM::Role
      Properties:
        AssumeRolePolicyDocument: 
          Fn::ToJsonString:
            Version: "2012-10-17"
            Statement:
            - Effect: Allow
              Principal:
                Federated: !Ref OIDCProvider
              Action: sts:AssumeRoleWithWebIdentity
              Condition:
                StringEquals:
                  'Fn::Transform':
                    Name: JMESPath
                    Parameters:
                      Path: "@"
                      Document: 
                        Fn::Sub: 
                        - '{ "${Provider}:aud": "sts.amazonaws.com", "${Provider}:sub": { "Fn::FindInMap" : [ "RoleMap", { "Ref" : "Name" }, "sub" ] } }'
                        - Provider: !Select [ 1, !Split [ "//", !Ref OpenIdConnectIssuerUrl ] ]
        Description: !FindInMap [ RoleMap, !Ref Name, Description ]
        ManagedPolicyArns: !FindInMap [ RoleMap, !Ref Name, ManagedPolicyArns ]
        RoleName: !Sub '${ClusterName}-${Name}'
        Path: !Ref Path
        Tags:
        - Key: StackName
          Value: !Ref AWS::StackName

Outputs:
  Arn:
    Description: Arn for OpenIdConnect Provider
    Value: !Ref OIDCProvider

  Fn::ForEach::RoleOutput:
  - Name
  - 'Fn::Transform':
      Name: JMESPath
      Parameters:
        Path: "keys(RoleMap)"
        Document: !Ref RoleMapDocument
  - '&{Name}':
      Description: !FindInMap [ RoleMap, !Ref Name, Description ]
      Value:
        Ref: !Sub '${Name}'

Gives the following error when deployed via the command line:

Failed to create the changeset: Waiter ChangeSetCreateComplete failed: Waiter encountered a terminal failure state: For expression "Status" we matched expected path: "FAILED" Status: FAILED. Reason: Transform AWS::LanguageExtensions failed with: Condition layout is incorrect

Note that the transform JMESPath is a super simple macro that just does a JMESPath search (Path parameter) on a document (Document parameter). The value passed in for RoleMapDocument looks like this:

{"RoleMap":{"ALBControllerRole":{"ManagedPolicyArns":["arn:aws:iam::99999999999:policy/AWSLoadBalancerControllerIAMPolicy"],"sub":"system:serviceaccount:kube-system:aws-load-balancer-controller","Description":"Allows Ingresses to allocate EC2 Load Balancers."},"ExternalDNSRole":{"ManagedPolicyArns":["arn:aws:iam::99999999999:policy/AWSExternalDNSUpdatePolicy"],"sub":"system:serviceaccount:kube-system:external-dns","Description":"Allows the External Dns service to update Route53."}}}

Which, since the search path is identity, produces the following Mappings entry:

RoleMap: ALBControllerRole: ManagedPolicyArns: ['arn:aws:iam::99999999999:policy/AWSLoadBalancerControllerIAMPolicy'] sub: system:serviceaccount:kube-system:aws-load-balancer-controller Description: Allows Ingresses to allocate EC2 Load Balancers. ExternalDNSRole: ManagedPolicyArns: ['arn:aws:iam::99999999999:policy/AWSExternalDNSUpdatePolicy'] sub: system:serviceaccount:kube-system:external-dns Description: Allows the External Dns service to update Route53.

Within the scope of the ForEach:

`

  • 'Fn::Transform':
    Name: JMESPath
    Parameters:
    Path: "keys(RoleMap)"
    Document: !Ref RoleMapDocument
    `

Produces [ "ALBControllerRole", "ExternalDNSRole" ], which should result in the creation of two resources, and the second call essentially just reconstitutes an object from a string (because I can sub into a key name in an embedded string version of an object but not an object directly.

Expected behavior

Create the stack with the OIDC Provider and two roles configured to allow cluster services to assume them.

Observed behavior

Failed to create the changeset: Waiter ChangeSetCreateComplete failed: Waiter encountered a terminal failure state: For expression "Status" we matched expected path: "FAILED" Status: FAILED. Reason: Transform AWS::LanguageExtensions failed with: Condition layout is incorrect

Test cases

Please include a minimal CloudFormation template here that reproduces the issue

I'll see if there's a more minimal repro, perhaps one that allows me to omit the macro, and will update if I can come up with one. Given that the error message is pretty obviously unrelated to whatever the problem is, it may take some time.

Additional context

Anything else we should know?

@johncuyle
Copy link
Author

I found a much, much more concise repro. First: This works fine (simply add this output to any valid stack that uses LanguageExtensions):

Outputs:
  Fn::ForEach::DebugOutput:
  - Name
  - [ A, B ]
  - '&{Name}Policies':
      Description: Debug. 
      Value:
        Fn::ToJsonString:
          Cond_ition: Irrelevant

You get two outputs:

APolicies | {"Cond_ition":"Irrelevant"} | Debug. | -
BPolicies | {"Cond_ition":"Irrelevant"} | Debug. | -

This doesn't work, but gives a different error that my previous repro:

  Fn::ForEach::DebugOutput:
  - Name
  - [ A, B ]
  - '&{Name}Policies':
      Description: Debug. 
      Value:
        Fn::ToJsonString:
          Condition: Irrelevant

Error:

Waiter encountered a terminal failure state: For expression "Status" we matched expected path: "FAILED" Status: FAILED. Reason: Transform AWS::LanguageExtensions failed with: Fn::ToJsonString does not support Condition intrinsic function for resource Fn::ForEach::DebugOutput

This isn't a Condition, though, "Condition" just happens to be the name of a key that's shoved into a Json string for output.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant