From e027d60394ef088f8924edaf24f2886fe195caca Mon Sep 17 00:00:00 2001 From: Kevin BEAUGRAND <9513635+kbeaugrand@users.noreply.github.com> Date: Tue, 23 May 2023 12:50:58 +0200 Subject: [PATCH] Reformat template + Add internet gateway to the App Runner and VPC connection (#2094) --- templates/aws/awsdeploy.yml | 679 +++++++++++++++++++++++++++--------- 1 file changed, 507 insertions(+), 172 deletions(-) diff --git a/templates/aws/awsdeploy.yml b/templates/aws/awsdeploy.yml index ae338c2ed..50b4aa707 100644 --- a/templates/aws/awsdeploy.yml +++ b/templates/aws/awsdeploy.yml @@ -1,163 +1,447 @@ AWSTemplateFormatVersion: 2010-09-09 -Description: IoT Hub portal deployment template +Description: IoT Hub portal deployment Metadata: AWS::CloudFormation::Interface: ParameterGroups: - - Label: - default: "Solution" + - Label: + default: Solution Parameters: - UniqueSolutionPrefix - - Label: - default: "Amazon Web Services resources access" + - Label: + default: Amazon Web Services resources access Parameters: - awsAccess - awsAccessSecretkey - - Label: - default: "PostgreSQL" + - Label: + default: PostgreSQL Parameters: - pgsqlAdminLogin - pgsqlAdminPassword - - Label: - default: "Open ID" + - Label: + default: Open ID Parameters: - openIdApiClientId - openIdClientId - openIdAuthority - openIdMetadataURL - openIdScopeName - ParameterLabels: - ParameterLabel + ParameterLabels: ParameterLabel Parameters: UniqueSolutionPrefix: Type: String Description: Prefix used for resource names. Should be unique as this will also be used for bucket and database name. Should not contain uppercase letters - MinLength: '1' - MaxLength: '20' + MinLength: "1" + MaxLength: "20" ConstraintDescription: Should be less than 20 letters - AllowedPattern: '^[a-z]+$' + AllowedPattern: "^[a-z]+$" pgsqlAdminLogin: Type: String Description: PostgreSQL user - MinLength: '1' - MaxLength: '30' + MinLength: "1" + MaxLength: "30" pgsqlAdminPassword: Type: String - NoEcho: 'true' + NoEcho: "true" Description: PostgreSQL password - MinLength: '8' - MaxLength: '41' + MinLength: "8" + MaxLength: "41" awsAccess: Type: String Description: AWS Access Secret - NoEcho: 'true' + NoEcho: "true" awsAccessSecretkey: Type: String Description: AWS Access Secret Key - NoEcho: 'true' + NoEcho: "true" openIdApiClientId: - Type: String + Type: String Description: The Open ID API client ID for the B2C tenant - Default: iot-portal-api openIdClientId: - Type: String + Type: String Description: The Open ID client ID for the B2C tenant - Default: iot-portal openIdAuthority: - Type: String + Type: String Description: The Open ID Authority - Default: https://cgigeiotdemoauth.azurewebsites.net/auth/realms/iot-portal openIdMetadataURL: - Type: String + Type: String Description: The Open ID metadata Url from the Identity provider - Default: https://cgigeiotdemoauth.azurewebsites.net/auth/realms/iot-portal/.well-known/openid-configuration openIdScopeName: - Type: String + Type: String Description: The Open ID Scope name - Default: iot_access Resources: -#======== S3 Storage ========== - s3Bucket: - Type: AWS::S3::Bucket - Properties: - BucketName: !Join [ "-", [!Ref UniqueSolutionPrefix, "bucket"] ] - PublicAccessBlockConfiguration: - BlockPublicAcls: false - IgnorePublicAcls: false - BlockPublicPolicy: false - RestrictPublicBuckets: false - OwnershipControls: - Rules: + #======== S3 Storage ========== + + S3Bucket: + Type: AWS::S3::Bucket + Properties: + BucketName: + Fn::Join: + - "-" + - - Ref: UniqueSolutionPrefix + - "bucket" + PublicAccessBlockConfiguration: + BlockPublicAcls: false + IgnorePublicAcls: false + BlockPublicPolicy: false + RestrictPublicBuckets: false + OwnershipControls: + Rules: - ObjectOwnership: BucketOwnerPreferred - AccessControl: AwsExecRead -#======== Virtual Private Cloud ========== - VPC: + AccessControl: AwsExecRead + + #======== Virtual Private Cloud ========== + + VPC: Type: AWS::EC2::VPC Properties: CidrBlock: 10.0.0.0/16 - EnableDnsSupport: 'true' - EnableDnsHostnames: 'true' - Subnet1: + EnableDnsSupport: "true" + EnableDnsHostnames: "true" + Tags: + - Key: Name + Value: + Fn::Join: + - "-" + - - Ref: UniqueSolutionPrefix + - "vpc" + + PrivateSubnet1: Type: AWS::EC2::Subnet Properties: VpcId: Ref: VPC CidrBlock: 10.0.0.0/27 - AvailabilityZone: !Join [ "", [!Ref AWS::Region, "a"] ] - Subnet2: + AvailabilityZone: + Fn::Select: + - 0 + - Fn::GetAZs: "" + Tags: + - Key: Name + Value: + Fn::Join: + - "-" + - - Ref: UniqueSolutionPrefix + - "priv-snet-1" + + PrivateSubnet2: Type: AWS::EC2::Subnet Properties: VpcId: Ref: VPC CidrBlock: 10.0.0.32/27 - AvailabilityZone: !Join [ "", [!Ref AWS::Region, "b"] ] + AvailabilityZone: + Fn::Select: + - 1 + - Fn::GetAZs: "" + Tags: + - Key: Name + Value: + Fn::Join: + - "-" + - - Ref: UniqueSolutionPrefix + - "priv-snet-2" + + PrivateSubnet3: + Type: AWS::EC2::Subnet + Properties: + VpcId: + Ref: VPC + CidrBlock: 10.0.0.64/27 + AvailabilityZone: + Fn::Select: + - 2 + - Fn::GetAZs: "" + Tags: + - Key: Name + Value: + Fn::Join: + - "-" + - - Ref: UniqueSolutionPrefix + - "priv-snet-3" + + PublicSubnet: + Type: AWS::EC2::Subnet + Properties: + VpcId: + Ref: VPC + CidrBlock: 10.0.0.224/27 + Tags: + - Key: Name + Value: + Fn::Join: + - "-" + - - Ref: UniqueSolutionPrefix + - "pub-snet" + + InternetGateway: + Type: AWS::EC2::InternetGateway + Properties: + Tags: + - Key: Name + Value: + Fn::Join: + - "-" + - - Ref: UniqueSolutionPrefix + - "igw" + + InternetGatewayAttachment: + Type: AWS::EC2::VPCGatewayAttachment + Properties: + VpcId: + Ref: VPC + InternetGatewayId: + Ref: InternetGateway + + PublicRouteTable: + Type: AWS::EC2::RouteTable + Properties: + VpcId: + Ref: VPC + Tags: + - Key: Name + Value: + Fn::Join: + - "-" + - - Ref: UniqueSolutionPrefix + - "pub-rt" + + InternetRoutePublicSubnet: + Type: AWS::EC2::Route + Properties: + RouteTableId: + Ref: PublicRouteTable + GatewayId: + Ref: InternetGateway + DestinationCidrBlock: 0.0.0.0/0 + + NatGwEip: + DependsOn: InternetGatewayAttachment + Type: AWS::EC2::EIP + Properties: + Domain: vpc + Tags: + - Key: Name + Value: + Fn::Join: + - "-" + - - Ref: UniqueSolutionPrefix + - "natgw-eip" + + NatGateway: + Type: AWS::EC2::NatGateway + Properties: + AllocationId: + Fn::GetAtt: NatGwEip.AllocationId + SubnetId: + Ref: PublicSubnet + Tags: + - Key: Name + Value: + Fn::Join: + - "-" + - - Ref: UniqueSolutionPrefix + - "natgw" + + PrivateRouteTable: + Type: AWS::EC2::RouteTable + Properties: + VpcId: + Ref: VPC + Tags: + - Key: Name + Value: + Fn::Join: + - "-" + - - Ref: UniqueSolutionPrefix + - "priv-rt" + + InternetRoutePrivateSubnet: + Type: AWS::EC2::Route + Properties: + RouteTableId: + Ref: PrivateRouteTable + NatGatewayId: + Ref: NatGateway + DestinationCidrBlock: 0.0.0.0/0 + + Subnet1RouteTableAssociation: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: + Ref: PrivateSubnet1 + RouteTableId: + Ref: PrivateRouteTable + + Subnet2RouteTableAssociation: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: + Ref: PrivateSubnet2 + RouteTableId: + Ref: PrivateRouteTable + + Subnet3RouteTableAssociation: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: + Ref: PrivateSubnet3 + RouteTableId: + Ref: PrivateRouteTable + + PublicSubnetRouteTableAssociation: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: + Ref: PublicSubnet + RouteTableId: + Ref: PublicRouteTable + + #======== PostgreSQL database ========== -#======== PostgreSQL database ========== PostgreSQLDB: Type: AWS::RDS::DBInstance Properties: - DBInstanceIdentifier: !Join [ "-", [!Ref UniqueSolutionPrefix, "pgdb"] ] - AllocatedStorage: '20' - DBInstanceClass: 'db.t2.micro' + DBInstanceIdentifier: + Fn::Join: + - "-" + - - Ref: UniqueSolutionPrefix + - "pgdb" + AllocatedStorage: "20" + DBInstanceClass: "db.t2.micro" Engine: postgres EngineVersion: 12 LicenseModel: postgresql-license - MasterUsername: !Ref pgsqlAdminLogin - MasterUserPassword: !Ref pgsqlAdminPassword - DBName: !Join [ "_", [!Ref UniqueSolutionPrefix, "cgigeiotdemo"] ] + MasterUsername: + Ref: pgsqlAdminLogin + MasterUserPassword: + Ref: pgsqlAdminPassword + DBName: + Fn::Join: + - "_" + - - Ref: UniqueSolutionPrefix + - "db" VPCSecurityGroups: - - !Ref PgSQLSecurityGroup - DBSubnetGroupName: !Ref PgSQLSubnetGroup + - Ref: PgSQLSecurityGroup + DBSubnetGroupName: + Ref: PgSQLSubnetGroup + Tags: + - Key: Name + Value: + Fn::Join: + - "-" + - - Ref: UniqueSolutionPrefix + - "database" PgSQLSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: - GroupDescription: Enable access to the database instance - VpcId: !Ref VPC + GroupDescription: Databse security group + VpcId: + Ref: VPC SecurityGroupIngress: - IpProtocol: tcp FromPort: 5432 ToPort: 5432 - CidrIp: 0.0.0.0/0 + SourceSecurityGroupId: + Fn::GetAtt: AppRunnerSecurityGroup.GroupId + Tags: + - Key: Name + Value: + Fn::Join: + - "-" + - - Ref: UniqueSolutionPrefix + - "database-sg" PgSQLSubnetGroup: Type: AWS::RDS::DBSubnetGroup Properties: - DBSubnetGroupName: !Join [ "-", [!Ref UniqueSolutionPrefix, "pgSqlsubnetgroup"] ] - DBSubnetGroupDescription: Subnets available for the database + DBSubnetGroupName: + Fn::Join: + - "-" + - - Ref: UniqueSolutionPrefix + - "database-snetgroup" + DBSubnetGroupDescription: Database subnet group SubnetIds: - - !Ref Subnet1 - - !Ref Subnet2 - -#InstanceRoleArn + - Ref: PrivateSubnet1 + - Ref: PrivateSubnet2 + - Ref: PrivateSubnet3 + Tags: + - Key: Name + Value: + Fn::Join: + - "-" + - - Ref: UniqueSolutionPrefix + - "db-snet-group" + + #======== Secrets ========== + + SMAWSKey: + Type: AWS::SecretsManager::Secret + Properties: + Name: + Fn::Join: + - "-" + - - Ref: UniqueSolutionPrefix + - "AWSKey" + SecretString: + Fn::Sub: "${awsAccess}" + + SMAWSSecretKey: + Type: AWS::SecretsManager::Secret + Properties: + Name: + Fn::Join: + - "-" + - - Ref: UniqueSolutionPrefix + - "AWSSecretKey" + SecretString: + Fn::Sub: "${awsAccessSecretkey}" + + SMPostgreSQLConnectionString: + Type: AWS::SecretsManager::Secret + Properties: + Name: + Fn::Join: + - "-" + - - Ref: UniqueSolutionPrefix + - "PostgreSQLConnectionString" + SecretString: + Fn::Join: + - "" + - - "Server=" + - Fn::GetAtt: PostgreSQLDB.Endpoint.Address + - Fn::Sub: ";Database=${UniqueSolutionPrefix}_db;Port=5432;User Id=${pgsqlAdminLogin};Password=${pgsqlAdminPassword};Pooling=true;Connection Lifetime=0;Command Timeout=0;" + + SMS3StorageConnectionString: + Type: AWS::SecretsManager::Secret + Properties: + Name: + Fn::Join: + - "-" + - - Ref: UniqueSolutionPrefix + - "S3StorageConnectionString" + SecretString: ! + Fn::Join: + - "" + - - Fn::Sub: "https://s3.${AWS::Region}.amazonaws.com/" + - Ref: UniqueSolutionPrefix + - "-bucket" + + #============= App Runner ============== + InstanceRole: Type: AWS::IAM::Role Properties: - RoleName: !Join [ "-", [!Ref UniqueSolutionPrefix, "AppRunner"] ] + RoleName: + Fn::Join: + - "-" + - - Ref: UniqueSolutionPrefix + - "AppRunner" AssumeRolePolicyDocument: - Version: '2012-10-17' + Version: "2012-10-17" Statement: - Effect: Allow Principal: @@ -166,110 +450,125 @@ Resources: - tasks.apprunner.amazonaws.com - build.apprunner.amazonaws.com - cloudformation.amazonaws.com - Action: 'sts:AssumeRole' + Action: "sts:AssumeRole" Policies: - PolicyName: SMPolicy PolicyDocument: - Version: '2012-10-17' + Version: "2012-10-17" Statement: - Effect: Allow Action: - - 'secretsmanager:GetSecretValue' - Resource: !Join - - '' - - - !Sub 'arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:' - - !Ref UniqueSolutionPrefix - - '-*' + - "secretsmanager:GetSecretValue" + Resource: + Fn::Join: + - "" + - - Fn::Sub: "arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:" + - Ref: UniqueSolutionPrefix + - "-*" - PolicyName: AmazonElasticContainerRegistryPublicReadOnly PolicyDocument: - Version: '2012-10-17' + Version: "2012-10-17" Statement: - Effect: Allow Action: - - 'ecr-public:GetAuthorizationToken' - - 'sts:GetServiceBearerToken' - - 'ecr-public:BatchCheckLayerAvailability' - - 'ecr-public:GetRepositoryPolicy' - - 'ecr-public:DescribeRepositories' - - 'ecr-public:DescribeRegistries' - - 'ecr-public:DescribeImages' - - 'ecr-public:DescribeImageTags' - - 'ecr-public:GetRepositoryCatalogData' - - 'ecr-public:GetRegistryCatalogData' - Resource: '*' + - "ecr-public:GetAuthorizationToken" + - "sts:GetServiceBearerToken" + - "ecr-public:BatchCheckLayerAvailability" + - "ecr-public:GetRepositoryPolicy" + - "ecr-public:DescribeRepositories" + - "ecr-public:DescribeRegistries" + - "ecr-public:DescribeImages" + - "ecr-public:DescribeImageTags" + - "ecr-public:GetRepositoryCatalogData" + - "ecr-public:GetRegistryCatalogData" + Resource: "*" - PolicyName: AWSAppRunnerFullAccess PolicyDocument: - Version: '2012-10-17' + Version: "2012-10-17" Statement: - Effect: Allow - Action: 'iam:CreateServiceLinkedRole' - Resource: 'arn:aws:iam::*:role/aws-service-role/apprunner.amazonaws.com/AWSServiceRoleForAppRunner' + Action: "iam:CreateServiceLinkedRole" + Resource: "arn:aws:iam::*:role/aws-service-role/apprunner.amazonaws.com/AWSServiceRoleForAppRunner" Condition: StringLike: - iam:AWSServiceName: 'apprunner.amazonaws.com' + iam:AWSServiceName: "apprunner.amazonaws.com" - Effect: Allow - Action: 'iam:PassRole' - Resource: '*' + Action: "iam:PassRole" + Resource: "*" Condition: StringLike: - iam:PassedToService: 'apprunner.amazonaws.com' + iam:PassedToService: "apprunner.amazonaws.com" - Sid: AppRunnerAdminAccess Effect: Allow - Action: 'apprunner:*' - Resource: '*' + Action: "apprunner:*" + Resource: "*" - PolicyName: AWSAppRunnerServicePolicyForECRAccess PolicyDocument: - Version: '2012-10-17' + Version: "2012-10-17" Statement: - Effect: Allow Action: - - 'ecr:GetDownloadUrlForLayer' - - 'ecr:BatchGetImage' - - 'ecr:DescribeImages' - - 'ecr:GetAuthorizationToken' - - 'ecr:BatchCheckLayerAvailability' - Resource: '*' - -# AWS Secrets Manager - SMAWSKey: - Type: AWS::SecretsManager::Secret - Properties: - Name: !Join [ "-", [!Ref UniqueSolutionPrefix, "AWSKey"] ] - SecretString: !Sub '${awsAccess}' - SMAWSSecretKey: - Type: AWS::SecretsManager::Secret + - "ecr:GetDownloadUrlForLayer" + - "ecr:BatchGetImage" + - "ecr:DescribeImages" + - "ecr:GetAuthorizationToken" + - "ecr:BatchCheckLayerAvailability" + Resource: "*" + + AppRunnerSecurityGroup: + Type: AWS::EC2::SecurityGroup Properties: - Name: !Join [ "-", [!Ref UniqueSolutionPrefix, "AWSSecretKey"] ] - SecretString: !Sub '${awsAccessSecretkey}' - SMPostgreSQLConnectionString: - Type: AWS::SecretsManager::Secret + GroupDescription: App Runner Security group + VpcId: + Ref: VPC + Tags: + - Key: Name + Value: + Fn::Join: + - "-" + - - Ref: UniqueSolutionPrefix + - "apprunner-sg" + + AppRunnerDatabaseOutboundRule: + Type: AWS::EC2::SecurityGroupEgress Properties: - Name: !Join [ "-", [!Ref UniqueSolutionPrefix, "PostgreSQLConnectionString"] ] - SecretString: !Join - - "" - - - 'Server=' - - !GetAtt PostgreSQLDB.Endpoint.Address - - !Sub ';Database=${UniqueSolutionPrefix}_cgigeiotdemo;Port=5432;User Id=${pgsqlAdminLogin};Password=${pgsqlAdminPassword};Pooling=true;Connection Lifetime=0;Command Timeout=0;' - SMS3StorageConnectionString: - Type: AWS::SecretsManager::Secret + IpProtocol: tcp + FromPort: 5432 + ToPort: 5432 + DestinationSecurityGroupId: + Fn::GetAtt: + - PgSQLSecurityGroup + - GroupId + GroupId: + Fn::GetAtt: + - AppRunnerSecurityGroup + - GroupId + + AppRunnerToIPV4InternetOutboundRule: + Type: AWS::EC2::SecurityGroupEgress Properties: - Name: !Join [ "-", [!Ref UniqueSolutionPrefix, "S3StorageConnectionString"] ] - SecretString: !Join - - "" - - - !Sub 'https://s3.${AWS::Region}.amazonaws.com/' - - !Ref UniqueSolutionPrefix - - '-bucket' - + IpProtocol: tcp + FromPort: 443 + ToPort: 443 + CidrIp: 0.0.0.0/0 + GroupId: + Fn::GetAtt: + - AppRunnerSecurityGroup + - GroupId -#============= App Runner - To Launch the App ============== AppRunnerService: Type: AWS::AppRunner::Service Properties: - ServiceName: !Join [ "-", [!Ref UniqueSolutionPrefix, "portal"] ] + ServiceName: + Fn::Join: + - "-" + - - Ref: UniqueSolutionPrefix + - "portal" InstanceConfiguration: - Cpu: 1 vCPU - Memory: 2 GB - InstanceRoleArn: !GetAtt InstanceRole.Arn + Cpu: 1024 + Memory: 2048 + InstanceRoleArn: + Fn::GetAtt: InstanceRole.Arn HealthCheckConfiguration: Protocol: "HTTP" Path: "/" @@ -277,65 +576,101 @@ Resources: Timeout: 5 HealthyThreshold: 2 UnhealthyThreshold: 5 - NetworkConfiguration: - EgressConfiguration: + NetworkConfiguration: + EgressConfiguration: EgressType: VPC - VpcConnectorArn: !Ref AppRunnerServiceVPCConnector + VpcConnectorArn: + Ref: AppRunnerServiceVPCConnector SourceConfiguration: AutoDeploymentsEnabled: false AuthenticationConfiguration: - AccessRoleArn: !GetAtt InstanceRole.Arn - ImageRepository: - ImageConfiguration: - Port: 80 # This is The port that your application listens to in the container. It can be changed (To see) - RuntimeEnvironmentSecrets: + AccessRoleArn: + Fn::GetAtt: InstanceRole.Arn + ImageRepository: + ImageConfiguration: + Port: 80 + RuntimeEnvironmentSecrets: - Name: AWS__Access - Value: !Ref SMAWSKey + Value: + Ref: SMAWSKey - Name: AWS__AccessSecret - Value: !Ref SMAWSSecretKey + Value: + Ref: SMAWSSecretKey - Name: PostgreSQL__ConnectionString - Value: !Ref SMPostgreSQLConnectionString + Value: + Ref: SMPostgreSQLConnectionString - Name: AWS__S3Storage__ConnectionString - Value: !Ref SMS3StorageConnectionString - RuntimeEnvironmentVariables: + Value: + Ref: SMS3StorageConnectionString + RuntimeEnvironmentVariables: - Name: AWS__Region - Value: !Ref AWS::Region + Value: + Ref: AWS::Region - Name: AWS__BucketName - Value: !Join [ "-", [!Ref UniqueSolutionPrefix, "bucket"] ] + Value: + Fn::Join: + - "-" + - - Ref: UniqueSolutionPrefix + - "bucket" - Name: OIDC__ApiClientId - Value: !Sub '${openIdApiClientId}' + Value: + Fn::Sub: "${openIdApiClientId}" - Name: OIDC__ClientId - Value: !Sub '${openIdClientId}' + Value: + Fn::Sub: "${openIdClientId}" - Name: OIDC__Authority - Value: !Sub '${openIdAuthority}' + Value: + Fn::Sub: "${openIdAuthority}" - Name: OIDC__MetadataUrl - Value: !Sub '${openIdMetadataURL}' + Value: + Fn::Sub: "${openIdMetadataURL}" - Name: OIDC__Scope - Value: !Sub '${openIdScopeName}' + Value: + Fn::Sub: "${openIdScopeName}" - Name: CloudProvider Value: AWS - Name: PortalName Value: IoT Portal DEMO - AWS - ImageIdentifier: !Sub '${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/iot-hub-portal:latest' + ImageIdentifier: + Fn::Sub: "${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/iot-hub-portal:latest" ImageRepositoryType: ECR - + Tags: + - Key: Name + Value: + Fn::Join: + - "-" + - - Ref: UniqueSolutionPrefix + - "apprunner" + AppRunnerServiceVPCConnector: Type: AWS::AppRunner::VpcConnector Properties: - VpcConnectorName: !Join [ "-", [!Ref UniqueSolutionPrefix, "portal-vpc-connector"] ] Subnets: - - !Ref Subnet1 - - !Ref Subnet2 + - Ref: PrivateSubnet1 + - Ref: PrivateSubnet2 + - Ref: PrivateSubnet3 SecurityGroups: - - !Ref PgSQLSecurityGroup + - Ref: AppRunnerSecurityGroup + Tags: + - Key: Name + Value: + Fn::Join: + - "-" + - - Ref: UniqueSolutionPrefix + - "apprunner-vpc-connector" + + #============= IoT Greengrass Role ============== -#GreenGras role script GreenGrasRole: Type: AWS::IAM::Role Properties: - RoleName: !Join [ "-", [!Ref UniqueSolutionPrefix, "GreenGrass"] ] + RoleName: + Fn::Join: + - "-" + - - Ref: UniqueSolutionPrefix + - "GreenGrass" AssumeRolePolicyDocument: - Version: '2012-10-17' + Version: "2012-10-17" Statement: - Effect: Allow Principal: @@ -347,11 +682,11 @@ Resources: Policies: - PolicyName: ECRPermissions PolicyDocument: - Version: '2012-10-17' + Version: "2012-10-17" Statement: - Effect: Allow Action: - ecr:GetAuthorizationToken - ecr:BatchGetImage - ecr:GetDownloadUrlForLayer - Resource: "*" \ No newline at end of file + Resource: "*"