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

Template generation produces incorrect template when Policies are specified using an intrinsic. #12401

Open
johncuyle opened this issue Mar 26, 2024 · 0 comments

Comments

@johncuyle
Copy link

Issue description

Error message on CloudFormation deployment:

Properties validation failed for resource FunctionRole with message: [#/ManagedPolicyArns/2: expected type: String, found: JSONArray]

The template:

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: An AWS Serverless Function and Layer.
Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
    - Label:
        default: Lambda Configuration
      Parameters:
      - Runtime
      - Policies
      - FunctionKey
      - LayerKey
      - Export
      - MacroName
    - Label:
        default: Pipeline Information
      Parameters:
      - BuildId
      - ChangeId
Parameters:
  FunctionKey:
    Type: String
    Description: The S3 Key for the packaged function.
  LayerKey:
    Type: String
    Description: The S3 Key for the packaged layer.
  Runtime:
    Type: String
    Default: python3.10
    AllowedValues:
    - python3.10
    - python3.11
    - python3.12
    Description: Python runtime version.
  Policies:
    Type: CommaDelimitedList
    Description: List of Policies to attach to Lambda Function.
  Export:
    Type: String
    Default: ''
    Description: The export name for the Service Token. Leave blank to not export.
  MacroName:
    Type: String
    Default: ''
    Description: The name of the macro. Leave blank if lambda is not used as a CloudFormation
      Macro.
  BuildId:
    Type: Number
    Default: 0
    Description: BuildId number, typically passed from a build pipeline.
  ChangeId:
    Type: String
    Default: 0
    Description: Git change id for this build.
Mappings:
  RegionBucketMap:
    us-east-1:
      Bucket: account-lambda-content-4jnm3cjkchnev-us-east-1
    us-east-2:
      Bucket: account-lambda-content-4jnm3cjkchnev-us-east-2
    us-west-2:
      Bucket: account-lambda-content-4jnm3cjkchnev-us-west-2
Conditions:
  HasExport:
    Fn::Not:
    - Fn::Equals:
      - Ref: Export
      - ''
  IsMacro:
    Fn::Not:
    - Fn::Equals:
      - Ref: MacroName
      - ''
Resources:
  Lib:
    Type: AWS::Serverless::LayerVersion
    Properties:
      LayerName:
        Fn::Sub: ${AWS::StackName}-lib
      Description: Dependencies for the app.
      ContentUri:
        Bucket:
          Fn::FindInMap:
          - RegionBucketMap
          - Ref: AWS::Region
          - Bucket
        Key:
          Ref: LayerKey
      CompatibleRuntimes:
      - Ref: Runtime
  Function:
    Type: AWS::Serverless::Function
    Properties:
      Handler: function.handler
      Runtime:
        Ref: Runtime
      CodeUri:
        Bucket:
          Fn::FindInMap:
          - RegionBucketMap
          - Ref: AWS::Region
          - Bucket
        Key:
          Ref: FunctionKey
      Description: Call the AWS Lambda API
      Timeout: 10
      Policies:
        Ref: Policies
      Tracing: Active
      Layers:
      - Ref: Lib
      Tags:
        StackName:
          Ref: AWS::StackName
        BuildId:
          Ref: BuildId
        ChangeId:
          Ref: ChangeId
  Macro:
    Type: AWS::CloudFormation::Macro
    Condition: IsMacro
    Properties:
      Description:
        Fn::Sub: ${AWS::StackName} Macro
      FunctionName:
        Fn::GetAtt:
        - Function
        - Arn
      Name:
        Ref: MacroName
Outputs:
  ServiceToken:
    Description: The ServiceToken needed to invoke the lambda.
    Value:
      Fn::GetAtt:
      - Function
      - Arn
  ExportName:
    Condition: HasExport
    Description: The Export name for the ServiceToken, for Import into CloudFormation
      templates.
    Value:
      Ref: Export
  ExportToken:
    Condition: HasExport
    Description: The Exported ServiceToken.
    Value:
      Fn::GetAtt:
      - Function
      - Arn
    Export:
      Name:
        Ref: Export

Note that

Resources.Function.Properties.Policies

is

Ref: Policies

and Policies is a template Parameter.

The value of the Policies Parameter:

AWSLambdaBasicExecutionRole,AWSLambda_ReadOnlyAccess,AWSXrayWriteOnlyAccess

Expected Generation:

- AWSLambdaBasicExecutionRole
- AWSLambda_ReadOnlyAccess
- AWSXrayWriteOnlyAccess

Actual processed template:

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "An AWS Serverless Function and Layer.",
  "Parameters": {
    "FunctionKey": {
      "Type": "String",
      "Description": "The S3 Key for the packaged function."
    },
    "LayerKey": {
      "Type": "String",
      "Description": "The S3 Key for the packaged layer."
    },
    "Runtime": {
      "Type": "String",
      "Default": "python3.10",
      "AllowedValues": [
        "python3.10",
        "python3.11",
        "python3.12"
      ],
      "Description": "Python runtime version."
    },
    "Policies": {
      "Type": "CommaDelimitedList",
      "Description": "List of Policies to attach to Lambda Function."
    },
    "Export": {
      "Type": "String",
      "Default": "",
      "Description": "The export name for the Service Token. Leave blank to not export."
    },
    "MacroName": {
      "Type": "String",
      "Default": "",
      "Description": "The name of the macro. Leave blank if lambda is not used as a CloudFormation Macro."
    },
    "BuildId": {
      "Type": "Number",
      "Default": 0,
      "Description": "BuildId number, typically passed from a build pipeline."
    },
    "ChangeId": {
      "Type": "String",
      "Default": 0,
      "Description": "Git change id for this build."
    }
  },
  "Mappings": {
    "RegionBucketMap": {
      "us-east-1": {
        "Bucket": "account-lambda-content-4jnm3cjkchnev-us-east-1"
      },
      "us-east-2": {
        "Bucket": "account-lambda-content-4jnm3cjkchnev-us-east-2"
      },
      "us-west-2": {
        "Bucket": "account-lambda-content-4jnm3cjkchnev-us-west-2"
      }
    }
  },
  "Metadata": {
    "AWS::CloudFormation::Interface": {
      "ParameterGroups": [
        {
          "Label": {
            "default": "Lambda Configuration"
          },
          "Parameters": [
            "Runtime",
            "Policies",
            "FunctionKey",
            "LayerKey",
            "Export",
            "MacroName"
          ]
        },
        {
          "Label": {
            "default": "Pipeline Information"
          },
          "Parameters": [
            "BuildId",
            "ChangeId"
          ]
        }
      ]
    }
  },
  "Outputs": {
    "ServiceToken": {
      "Description": "The ServiceToken needed to invoke the lambda.",
      "Value": {
        "Fn::GetAtt": [
          "Function",
          "Arn"
        ]
      }
    },
    "ExportName": {
      "Condition": "HasExport",
      "Description": "The Export name for the ServiceToken, for Import into CloudFormation templates.",
      "Value": {
        "Ref": "Export"
      }
    },
    "ExportToken": {
      "Condition": "HasExport",
      "Description": "The Exported ServiceToken.",
      "Value": {
        "Fn::GetAtt": [
          "Function",
          "Arn"
        ]
      },
      "Export": {
        "Name": {
          "Ref": "Export"
        }
      }
    }
  },
  "Resources": {
    "Macro": {
      "Type": "AWS::CloudFormation::Macro",
      "Condition": "IsMacro",
      "Properties": {
        "Description": {
          "Fn::Sub": "${AWS::StackName} Macro"
        },
        "FunctionName": {
          "Fn::GetAtt": [
            "Function",
            "Arn"
          ]
        },
        "Name": {
          "Ref": "MacroName"
        }
      }
    },
    "Function": {
      "Type": "AWS::Lambda::Function",
      "Properties": {
        "Code": {
          "S3Bucket": {
            "Fn::FindInMap": [
              "RegionBucketMap",
              {
                "Ref": "AWS::Region"
              },
              "Bucket"
            ]
          },
          "S3Key": {
            "Ref": "FunctionKey"
          }
        },
        "Description": "Call the AWS Lambda API",
        "Handler": "function.handler",
        "Role": {
          "Fn::GetAtt": [
            "FunctionRole",
            "Arn"
          ]
        },
        "Runtime": {
          "Ref": "Runtime"
        },
        "Timeout": 10,
        "Tags": [
          {
            "Key": "lambda:createdBy",
            "Value": "SAM"
          },
          {
            "Key": "StackName",
            "Value": {
              "Ref": "AWS::StackName"
            }
          },
          {
            "Key": "BuildId",
            "Value": {
              "Ref": "BuildId"
            }
          },
          {
            "Key": "ChangeId",
            "Value": {
              "Ref": "ChangeId"
            }
          }
        ],
        "TracingConfig": {
          "Mode": "Active"
        },
        "Layers": [
          {
            "Ref": "Lib0dc7131ac3"
          }
        ]
      }
    },
    "FunctionRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Action": [
                "sts:AssumeRole"
              ],
              "Effect": "Allow",
              "Principal": {
                "Service": [
                  "lambda.amazonaws.com"
                ]
              }
            }
          ]
        },
        "ManagedPolicyArns": [
          "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
          "arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess",
          {
            "Ref": "Policies"
          }
        ],
        "Tags": [
          {
            "Key": "lambda:createdBy",
            "Value": "SAM"
          },
          {
            "Key": "StackName",
            "Value": {
              "Ref": "AWS::StackName"
            }
          },
          {
            "Key": "BuildId",
            "Value": {
              "Ref": "BuildId"
            }
          },
          {
            "Key": "ChangeId",
            "Value": {
              "Ref": "ChangeId"
            }
          }
        ]
      }
    },
    "Lib0dc7131ac3": {
      "Type": "AWS::Lambda::LayerVersion",
      "DeletionPolicy": "Retain",
      "Properties": {
        "Content": {
          "S3Bucket": {
            "Fn::FindInMap": [
              "RegionBucketMap",
              {
                "Ref": "AWS::Region"
              },
              "Bucket"
            ]
          },
          "S3Key": {
            "Ref": "LayerKey"
          }
        },
        "Description": "Dependencies for the app.",
        "LayerName": {
          "Fn::Sub": "${AWS::StackName}-lib"
        },
        "CompatibleRuntimes": [
          {
            "Ref": "Runtime"
          }
        ]
      }
    }
  },
  "Conditions": {
    "HasExport": {
      "Fn::Not": [
        {
          "Fn::Equals": [
            {
              "Ref": "Export"
            },
            ""
          ]
        }
      ]
    },
    "IsMacro": {
      "Fn::Not": [
        {
          "Fn::Equals": [
            {
              "Ref": "MacroName"
            },
            ""
          ]
        }
      ]
    }
  }
}

Note that ManagedPolicyArns has been expanded to:

        "ManagedPolicyArns": [
          "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
          "arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess",
          {
            "Ref": "Policies"
          }

Which is both the source of the error message and incorrect expansion.

Context

N/A

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