AWSTemplateFormatVersion: 2010-09-09 Description: IoT Hub portal deployment Metadata: AWS::CloudFormation::Interface: ParameterGroups: - Label: default: Solution Parameters: - UniqueSolutionPrefix - Label: default: Amazon Web Services resources access Parameters: - awsAccess - awsAccessSecretkey - Label: default: PostgreSQL Parameters: - pgsqlAdminLogin - pgsqlAdminPassword - Label: default: Open ID Parameters: - openIdApiClientId - openIdClientId - openIdAuthority - openIdMetadataURL - openIdScopeName 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" ConstraintDescription: Should be less than 20 letters AllowedPattern: "^[a-z]+$" pgsqlAdminLogin: Type: String Description: PostgreSQL user MinLength: "1" MaxLength: "30" pgsqlAdminPassword: Type: String NoEcho: "true" Description: PostgreSQL password MinLength: "8" MaxLength: "41" awsAccess: Type: String Description: AWS Access Secret NoEcho: "true" awsAccessSecretkey: Type: String Description: AWS Access Secret Key NoEcho: "true" openIdApiClientId: Type: String Description: The Open ID API client ID for the B2C tenant openIdClientId: Type: String Description: The Open ID client ID for the B2C tenant openIdAuthority: Type: String Description: The Open ID Authority openIdMetadataURL: Type: String Description: The Open ID metadata Url from the Identity provider openIdScopeName: Type: String Description: The Open ID Scope name Resources: #======== 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: Type: AWS::EC2::VPC Properties: CidrBlock: 10.0.0.0/16 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: 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: 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 ========== PostgreSQLDB: Type: AWS::RDS::DBInstance Properties: 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: Fn::Join: - "_" - - Ref: UniqueSolutionPrefix - "db" VPCSecurityGroups: - Ref: PgSQLSecurityGroup DBSubnetGroupName: Ref: PgSQLSubnetGroup Tags: - Key: Name Value: Fn::Join: - "-" - - Ref: UniqueSolutionPrefix - "database" PgSQLSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Databse security group VpcId: Ref: VPC SecurityGroupIngress: - IpProtocol: tcp FromPort: 5432 ToPort: 5432 SourceSecurityGroupId: Fn::GetAtt: AppRunnerSecurityGroup.GroupId Tags: - Key: Name Value: Fn::Join: - "-" - - Ref: UniqueSolutionPrefix - "database-sg" PgSQLSubnetGroup: Type: AWS::RDS::DBSubnetGroup Properties: DBSubnetGroupName: Fn::Join: - "-" - - Ref: UniqueSolutionPrefix - "database-snetgroup" DBSubnetGroupDescription: Database subnet group SubnetIds: - 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: Fn::Join: - "-" - - Ref: UniqueSolutionPrefix - "AppRunner" AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: - apprunner.amazonaws.com - tasks.apprunner.amazonaws.com - build.apprunner.amazonaws.com - cloudformation.amazonaws.com Action: "sts:AssumeRole" Policies: - PolicyName: SMPolicy PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - "secretsmanager:GetSecretValue" Resource: Fn::Join: - "" - - Fn::Sub: "arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:" - Ref: UniqueSolutionPrefix - "-*" - PolicyName: AmazonElasticContainerRegistryPublicReadOnly PolicyDocument: 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: "*" - PolicyName: AWSAppRunnerFullAccess PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: "iam:CreateServiceLinkedRole" Resource: "arn:aws:iam::*:role/aws-service-role/apprunner.amazonaws.com/AWSServiceRoleForAppRunner" Condition: StringLike: iam:AWSServiceName: "apprunner.amazonaws.com" - Effect: Allow Action: "iam:PassRole" Resource: "*" Condition: StringLike: iam:PassedToService: "apprunner.amazonaws.com" - Sid: AppRunnerAdminAccess Effect: Allow Action: "apprunner:*" Resource: "*" - PolicyName: AWSAppRunnerServicePolicyForECRAccess PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - "ecr:GetDownloadUrlForLayer" - "ecr:BatchGetImage" - "ecr:DescribeImages" - "ecr:GetAuthorizationToken" - "ecr:BatchCheckLayerAvailability" Resource: "*" AppRunnerSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: App Runner Security group VpcId: Ref: VPC Tags: - Key: Name Value: Fn::Join: - "-" - - Ref: UniqueSolutionPrefix - "apprunner-sg" AppRunnerDatabaseOutboundRule: Type: AWS::EC2::SecurityGroupEgress Properties: IpProtocol: tcp FromPort: 5432 ToPort: 5432 DestinationSecurityGroupId: Fn::GetAtt: - PgSQLSecurityGroup - GroupId GroupId: Fn::GetAtt: - AppRunnerSecurityGroup - GroupId AppRunnerToIPV4InternetOutboundRule: Type: AWS::EC2::SecurityGroupEgress Properties: IpProtocol: tcp FromPort: 443 ToPort: 443 CidrIp: 0.0.0.0/0 GroupId: Fn::GetAtt: - AppRunnerSecurityGroup - GroupId AppRunnerService: Type: AWS::AppRunner::Service Properties: ServiceName: Fn::Join: - "-" - - Ref: UniqueSolutionPrefix - "portal" InstanceConfiguration: Cpu: 1024 Memory: 2048 InstanceRoleArn: Fn::GetAtt: InstanceRole.Arn HealthCheckConfiguration: Protocol: "HTTP" Path: "/" Interval: 20 Timeout: 5 HealthyThreshold: 2 UnhealthyThreshold: 5 NetworkConfiguration: EgressConfiguration: EgressType: VPC VpcConnectorArn: Ref: AppRunnerServiceVPCConnector SourceConfiguration: AutoDeploymentsEnabled: false AuthenticationConfiguration: AccessRoleArn: Fn::GetAtt: InstanceRole.Arn ImageRepository: ImageConfiguration: Port: 80 RuntimeEnvironmentSecrets: - Name: AWS__Access Value: Ref: SMAWSKey - Name: AWS__AccessSecret Value: Ref: SMAWSSecretKey - Name: PostgreSQL__ConnectionString Value: Ref: SMPostgreSQLConnectionString - Name: AWS__S3Storage__ConnectionString Value: Ref: SMS3StorageConnectionString RuntimeEnvironmentVariables: - Name: AWS__Region Value: Ref: AWS::Region - Name: AWS__BucketName Value: Fn::Join: - "-" - - Ref: UniqueSolutionPrefix - "bucket" - Name: OIDC__ApiClientId Value: Fn::Sub: "${openIdApiClientId}" - Name: OIDC__ClientId Value: Fn::Sub: "${openIdClientId}" - Name: OIDC__Authority Value: Fn::Sub: "${openIdAuthority}" - Name: OIDC__MetadataUrl Value: Fn::Sub: "${openIdMetadataURL}" - Name: OIDC__Scope Value: Fn::Sub: "${openIdScopeName}" - Name: CloudProvider Value: AWS ImageIdentifier: Fn::Sub: "578920151383.dkr.ecr.eu-west-1.amazonaws.com/iot-hub-portal:5.0.0-rc0" ImageRepositoryType: ECR Tags: - Key: Name Value: Fn::Join: - "-" - - Ref: UniqueSolutionPrefix - "apprunner" AppRunnerServiceVPCConnector: Type: AWS::AppRunner::VpcConnector Properties: Subnets: - Ref: PrivateSubnet1 - Ref: PrivateSubnet2 - Ref: PrivateSubnet3 SecurityGroups: - Ref: AppRunnerSecurityGroup Tags: - Key: Name Value: Fn::Join: - "-" - - Ref: UniqueSolutionPrefix - "apprunner-vpc-connector" #============= IoT Greengrass Role ============== GreenGrasRole: Type: AWS::IAM::Role Properties: RoleName: Fn::Join: - "-" - - Ref: UniqueSolutionPrefix - "GreenGrass" AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: greengrass.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSGreengrassResourceAccessRolePolicy - arn:aws:iam::aws:policy/AWSGreengrassFullAccess Policies: - PolicyName: ECRPermissions PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - ecr:GetAuthorizationToken - ecr:BatchGetImage - ecr:GetDownloadUrlForLayer Resource: "*"