# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: MIT-0 --- AWSTemplateFormatVersion: '2010-09-09' Description: > Deep Learning Suggestion Blog Demo **WARNING** This template creates resources which incur charges. You will be billed for the AWS resources used if you create a stack from this template. Metadata: AWS::ServerlessRepo::Application: Name: deep-learning-suggestions Description: A sample web application where citizens can report damage to government property by taking a picture, and selecting suggestions based on the image content. For example, reporting a damaged road sign, or graffiti. Author: Caesar Kabalan SpdxLicenseId: MIT-0 LicenseUrl: ../LICENSE ReadmeUrl: ../README.md Labels: [ 'public-sector', 'government', 'rekognition' ] HomePageUrl: https://github.com/ckabalan/my-app-project SemanticVersion: 0.9.5 SourceCodeUrl: https://github.com/ckabalan/my-app-project Parameters: LogLevel: Type: String Description: AWS Lambda function logging level AllowedValues: - CRITICAL - ERROR - WARNING - INFO - DEBUG - TRACE Default: DEBUG AllowOriginMode: Type: String Description: Sets the Access-Control-Allow-Origin header value AllowedValues: - Strict / Amazon CloudFront Only - Allow All Origins Default: Strict / Amazon CloudFront Only Conditions: StrictOriginOn: !Equals - !Ref 'AllowOriginMode' - Strict / Amazon CloudFront Only Transform: AWS::Serverless-2016-10-31 Resources: OriginAccessIdentity: Type: AWS::CloudFront::CloudFrontOriginAccessIdentity Properties: CloudFrontOriginAccessIdentityConfig: Comment: Deep Learning Suggestion Blog Static Website Amazon CloudFront Identity StaticWebsite: Type: AWS::S3::Bucket Properties: BucketName: !Sub - dl-suggest-blog-static-website-${Unique} - Unique: !Select [ 4, !Split [ '-', !Select [ 2, !Split [ '/', !Ref 'AWS::StackId' ] ] ] ] AccessControl: Private LoggingConfiguration: DestinationBucketName: !Ref LoggingBucket LogFilePrefix: s3-static-website/ PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true IgnorePublicAcls: true RestrictPublicBuckets: true BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 VersioningConfiguration: Status: Enabled LifecycleConfiguration: Rules: - Id: DeleteOldVersionAfter90Days Status: Enabled NoncurrentVersionExpiration: NoncurrentDays: 90 UploadedImages: Type: AWS::S3::Bucket Properties: BucketName: !Sub - dl-suggest-blog-uploaded-images-${Unique} - Unique: !Select [ 4, !Split [ '-', !Select [ 2, !Split [ '/', !Ref 'AWS::StackId' ] ] ] ] AccessControl: Private LoggingConfiguration: DestinationBucketName: !Ref LoggingBucket LogFilePrefix: s3-uploaded-images/ PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true IgnorePublicAcls: true RestrictPublicBuckets: true BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 VersioningConfiguration: Status: Enabled LifecycleConfiguration: Rules: - Id: DeleteOldVersionAfter90Days Status: Enabled NoncurrentVersionExpiration: NoncurrentDays: 90 CorsConfiguration: CorsRules: - AllowedHeaders: - '*' AllowedMethods: - HEAD - GET - PUT - POST - DELETE AllowedOrigins: - !If [ StrictOriginOn, !Sub 'https://${CloudFront.DomainName}', '*' ] ExposedHeaders: - ETag Id: CORSRule MaxAge: 3600 StaticWebsiteBucketPolicy: Type: AWS::S3::BucketPolicy Properties: Bucket: !Sub - dl-suggest-blog-static-website-${Unique} - Unique: !Select [ 4, !Split [ '-', !Select [ 2, !Split [ '/', !Ref 'AWS::StackId' ] ] ] ] PolicyDocument: Version: '2012-10-17' Id: WebAccess Statement: - Sid: CloudFrontReadForGetBucketObjects Principal: AWS: !Sub 'arn:${AWS::Partition}:iam::cloudfront:user/CloudFront Origin Access Identity ${OriginAccessIdentity}' Effect: Allow Action: - s3:GetObject - s3:GetObjectVersion Resource: !Sub - arn:${AWS::Partition}:s3:::dl-suggest-blog-static-website-${Unique}/* - Unique: !Select [ 4, !Split [ '-', !Select [ 2, !Split [ '/', !Ref 'AWS::StackId' ] ] ] ] - Sid: DenyPlaintextAccess Principal: '*' Effect: Deny Action: s3:* Resource: - !Sub - arn:${AWS::Partition}:s3:::dl-suggest-blog-static-website-${Unique} - Unique: !Select [ 4, !Split [ '-', !Select [ 2, !Split [ '/', !Ref 'AWS::StackId' ] ] ] ] - !Sub - arn:${AWS::Partition}:s3:::dl-suggest-blog-static-website-${Unique}/* - Unique: !Select [ 4, !Split [ '-', !Select [ 2, !Split [ '/', !Ref 'AWS::StackId' ] ] ] ] Condition: Bool: aws:SecureTransport: 'false' UploadedImagesBucketPolicy: Type: AWS::S3::BucketPolicy DependsOn: UploadedImages Properties: Bucket: !Sub - dl-suggest-blog-uploaded-images-${Unique} - Unique: !Select [ 4, !Split [ '-', !Select [ 2, !Split [ '/', !Ref 'AWS::StackId' ] ] ] ] PolicyDocument: Version: '2012-10-17' Id: WebAccess Statement: - Sid: CloudFrontReadForGetBucketObjects Principal: AWS: !Sub 'arn:${AWS::Partition}:iam::cloudfront:user/CloudFront Origin Access Identity ${OriginAccessIdentity}' Effect: Allow Action: - s3:GetObject - s3:GetObjectVersion Resource: !Sub - arn:${AWS::Partition}:s3:::dl-suggest-blog-uploaded-images-${Unique}/* - Unique: !Select [ 4, !Split [ '-', !Select [ 2, !Split [ '/', !Ref 'AWS::StackId' ] ] ] ] - Sid: DenyPlaintextAccess Principal: '*' Effect: Deny Action: s3:* Resource: - !Sub - arn:${AWS::Partition}:s3:::dl-suggest-blog-uploaded-images-${Unique} - Unique: !Select [ 4, !Split [ '-', !Select [ 2, !Split [ '/', !Ref 'AWS::StackId' ] ] ] ] - !Sub - arn:${AWS::Partition}:s3:::dl-suggest-blog-uploaded-images-${Unique}/* - Unique: !Select [ 4, !Split [ '-', !Select [ 2, !Split [ '/', !Ref 'AWS::StackId' ] ] ] ] Condition: Bool: aws:SecureTransport: 'false' LoggingBucket: Type: AWS::S3::Bucket DeletionPolicy: Retain Metadata: cfn_nag: rules_to_suppress: - id: W35 reason: Unnecessary to log access to the logging bucket. Properties: BucketName: !Sub - dl-suggest-blog-logging-${Unique} - Unique: !Select [ 4, !Split [ '-', !Select [ 2, !Split [ '/', !Ref 'AWS::StackId' ] ] ] ] AccessControl: Private PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true IgnorePublicAcls: true RestrictPublicBuckets: true OwnershipControls: Rules: - ObjectOwnership: BucketOwnerPreferred BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 VersioningConfiguration: Status: Enabled LifecycleConfiguration: Rules: - Id: DeleteOldVersionAfter90Days Status: Enabled NoncurrentVersionExpiration: NoncurrentDays: 90 LoggingBucketBucketPolicy: Type: AWS::S3::BucketPolicy DependsOn: LoggingBucket Properties: Bucket: !Sub - dl-suggest-blog-logging-${Unique} - Unique: !Select [ 4, !Split [ '-', !Select [ 2, !Split [ '/', !Ref 'AWS::StackId' ] ] ] ] PolicyDocument: Version: '2012-10-17' Id: WebAccess Statement: - Sid: S3ServerAccessLogsPolicy Effect: Allow Principal: Service: logging.s3.amazonaws.com Action: - s3:PutObject Resource: !Sub - arn:${AWS::Partition}:s3:::dl-suggest-blog-logging-${Unique}/* - Unique: !Select [ 4, !Split [ '-', !Select [ 2, !Split [ '/', !Ref 'AWS::StackId' ] ] ] ] Condition: ArnEquals: aws:SourceArn: - !Sub - arn:${AWS::Partition}:s3:::dl-suggest-blog-static-website-${Unique} - Unique: !Select [ 4, !Split [ '-', !Select [ 2, !Split [ '/', !Ref 'AWS::StackId' ] ] ] ] - !Sub - arn:${AWS::Partition}:s3:::dl-suggest-blog-uploaded-images-${Unique} - Unique: !Select [ 4, !Split [ '-', !Select [ 2, !Split [ '/', !Ref 'AWS::StackId' ] ] ] ] StringEquals: aws:SourceAccount: !Ref 'AWS::AccountId' - Sid: DenyPlaintextAccess Principal: '*' Effect: Deny Action: s3:* Resource: - !Sub - arn:${AWS::Partition}:s3:::dl-suggest-blog-logging-${Unique} - Unique: !Select [ 4, !Split [ '-', !Select [ 2, !Split [ '/', !Ref 'AWS::StackId' ] ] ] ] - !Sub - arn:${AWS::Partition}:s3:::dl-suggest-blog-logging-${Unique}/* - Unique: !Select [ 4, !Split [ '-', !Select [ 2, !Split [ '/', !Ref 'AWS::StackId' ] ] ] ] Condition: Bool: aws:SecureTransport: 'false' APIGatewayLogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: !Sub - /aws/apigateway/DLSuggestBlog-${Unique} - Unique: !Select [ 4, !Split [ '-', !Select [ 2, !Split [ '/', !Ref 'AWS::StackId' ] ] ] ] RetentionInDays: 30 CloudFront: Type: AWS::CloudFront::Distribution Metadata: cfn_nag: rules_to_suppress: - id: W70 reason: When using the default CloudFront distribution domain you cannot specify the TLS version options. Changing from the default CloudFront distribution would significantly increase the scope and cost of the project by requiring the customer to select a custom domain, deploy a Route53 Hosted Zone, and generate an ACM Certificate. Properties: DistributionConfig: Comment: Deep Learning Suggestion Blog Static Website # Uncomment the following if you want to restrict access to the US. # Also consider changing the PriceClass option #Restrictions: # GeoRestriction: # Locations: # - US # RestrictionType: whitelist Logging: Bucket: !GetAtt LoggingBucket.DomainName Prefix: cloudfront IncludeCookies: true DefaultCacheBehavior: ForwardedValues: QueryString: true TargetOriginId: !Sub 'S3-Static-Website' ViewerProtocolPolicy: redirect-to-https Compress: true DefaultRootObject: index.html CacheBehaviors: - ForwardedValues: QueryString: true TargetOriginId: !Sub 'S3-Uploaded-Images' ViewerProtocolPolicy: redirect-to-https Compress: true PathPattern: /maint-img/* Enabled: true HttpVersion: http2 Origins: - DomainName: !Sub - dl-suggest-blog-static-website-${Unique}.s3.${AWS::Region}.amazonaws.com - Unique: !Select [ 4, !Split [ '-', !Select [ 2, !Split [ '/', !Ref 'AWS::StackId' ] ] ] ] Id: !Sub 'S3-Static-Website' S3OriginConfig: OriginAccessIdentity: !Sub 'origin-access-identity/cloudfront/${OriginAccessIdentity}' - DomainName: !Sub - dl-suggest-blog-uploaded-images-${Unique}.s3.${AWS::Region}.amazonaws.com - Unique: !Select [ 4, !Split [ '-', !Select [ 2, !Split [ '/', !Ref 'AWS::StackId' ] ] ] ] Id: !Sub 'S3-Uploaded-Images' S3OriginConfig: OriginAccessIdentity: !Sub 'origin-access-identity/cloudfront/${OriginAccessIdentity}' PriceClass: PriceClass_100 IPV6Enabled: false ReportTable: Type: AWS::DynamoDB::Table Properties: AttributeDefinitions: - AttributeName: pk AttributeType: S - AttributeName: sk AttributeType: S - AttributeName: gsi1pk AttributeType: S - AttributeName: gsi1sk AttributeType: S GlobalSecondaryIndexes: - IndexName: GSI1 KeySchema: - AttributeName: gsi1pk KeyType: HASH - AttributeName: gsi1sk KeyType: RANGE Projection: ProjectionType: ALL KeySchema: - AttributeName: pk KeyType: HASH - AttributeName: sk KeyType: RANGE BillingMode: PAY_PER_REQUEST PointInTimeRecoverySpecification: PointInTimeRecoveryEnabled: true SSESpecification: SSEEnabled: true API: Type: AWS::Serverless::Api DependsOn: APILogsRoleAttachment Properties: StageName: v1 Auth: UsagePlan: CreateUsagePlan: PER_API Description: Deep Learning Suggestion Blog API Usage Plan Quota: # Roughly 5 submissions per minute 24/7 # (10 API calls * 5 submissions * 60 minutes * 24 hours) # Max ~$0.90/day for the API Gateway and Lambda fees # Additional processing (DynamoDB / Rekognition / S3) would depend # on how many requests are legitimate. Limit: 72000 Period: DAY AccessLogSetting: DestinationArn: !GetAtt APIGatewayLogGroup.Arn Format: '{"requestId":"$context.requestId","extendedRequestId":"$context.extendedRequestId","ip":"$context.identity.sourceIp","caller":"$context.identity.caller","user":"$context.identity.user","requestTime":"$context.requestTime","httpMethod":"$context.httpMethod","resourcePath":"$context.resourcePath","status":"$context.status","protocol":"$context.protocol","responseLength":"$context.responseLength"}' Cors: AllowMethods: '''DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT''' AllowHeaders: '''Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token''' AllowOrigin: !If [ StrictOriginOn, !Sub '''https://${CloudFront.DomainName}''', '''*''' ] APILogsRoleAttachment: Type: AWS::ApiGateway::Account Properties: CloudWatchRoleArn: !GetAtt APILogsRole.Arn APILogsRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: Action: 'sts:AssumeRole' Effect: Allow Principal: Service: apigateway.amazonaws.com Path: / ManagedPolicyArns: - 'arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs' GetSubmissionsLogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: !Sub /aws/lambda/${GetSubmissions} RetentionInDays: 7 GetSubmissions: Type: AWS::Serverless::Function Metadata: cfn_nag: rules_to_suppress: - id: W89 reason: This does not increase the security of the solutions and greatly increases the cost and scope of the deployment. - id: W92 reason: This is not necessary for this project. Customers can enable this once they understand their usage patterns. Properties: CodeUri: get_submissions/ Handler: app.lambda_handler Runtime: python3.9 Timeout: 5 Events: ApiEvent: Type: Api Properties: Path: /submissions Method: get RestApiId: !Ref 'API' Auth: ApiKeyRequired: true Architectures: - arm64 Environment: Variables: LOGURU_LEVEL: !Ref 'LogLevel' REPORT_TABLE: !Ref 'ReportTable' ALLOW_ORIGIN_HEADER_VALUE: !If [ StrictOriginOn, !Sub 'https://${CloudFront.DomainName}', '*' ] Policies: - AWSLambdaBasicExecutionRole - DynamoDBReadPolicy: TableName: !Ref 'ReportTable' GetSubmissionLogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: !Sub /aws/lambda/${GetSubmission} RetentionInDays: 7 GetSubmission: Type: AWS::Serverless::Function Metadata: cfn_nag: rules_to_suppress: - id: W89 reason: This does not increase the security of the solutions and greatly increases the cost and scope of the deployment. - id: W92 reason: This is not necessary for this project. Customers can enable this once they understand their usage patterns. Properties: CodeUri: get_submission/ Handler: app.lambda_handler Runtime: python3.9 Timeout: 5 Events: ApiEvent: Type: Api Properties: Path: /submission/{submission_id} Method: get RestApiId: !Ref 'API' Auth: ApiKeyRequired: true Architectures: - arm64 Environment: Variables: LOGURU_LEVEL: !Ref 'LogLevel' REPORT_TABLE: !Ref 'ReportTable' ALLOW_ORIGIN_HEADER_VALUE: !If [ StrictOriginOn, !Sub 'https://${CloudFront.DomainName}', '*' ] Policies: - AWSLambdaBasicExecutionRole - DynamoDBReadPolicy: TableName: !Ref 'ReportTable' PatchSubmissionLogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: !Sub /aws/lambda/${PatchSubmission} RetentionInDays: 7 PatchSubmission: Type: AWS::Serverless::Function Metadata: cfn_nag: rules_to_suppress: - id: W89 reason: This does not increase the security of the solutions and greatly increases the cost and scope of the deployment. - id: W92 reason: This is not necessary for this project. Customers can enable this once they understand their usage patterns. Properties: CodeUri: patch_submission/ Handler: app.lambda_handler Runtime: python3.9 Timeout: 5 Events: ApiEvent: Type: Api Properties: Path: /submission/{submission_id} Method: patch RestApiId: !Ref 'API' Auth: ApiKeyRequired: true Architectures: - arm64 Environment: Variables: LOGURU_LEVEL: !Ref 'LogLevel' REPORT_TABLE: !Ref 'ReportTable' ALLOW_ORIGIN_HEADER_VALUE: !If [ StrictOriginOn, !Sub 'https://${CloudFront.DomainName}', '*' ] Policies: - AWSLambdaBasicExecutionRole - DynamoDBWritePolicy: TableName: !Ref 'ReportTable' GetReportsLogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: !Sub /aws/lambda/${GetReports} RetentionInDays: 7 GetReports: Type: AWS::Serverless::Function Metadata: cfn_nag: rules_to_suppress: - id: W89 reason: This does not increase the security of the solutions and greatly increases the cost and scope of the deployment. - id: W92 reason: This is not necessary for this project. Customers can enable this once they understand their usage patterns. Properties: CodeUri: get_reports/ Handler: app.lambda_handler Runtime: python3.9 Timeout: 5 Events: ApiEvent: Type: Api Properties: Path: /reports Method: get RestApiId: !Ref 'API' Auth: ApiKeyRequired: true Architectures: - arm64 Environment: Variables: LOGURU_LEVEL: !Ref 'LogLevel' REPORT_TABLE: !Ref 'ReportTable' ALLOW_ORIGIN_HEADER_VALUE: !If [ StrictOriginOn, !Sub 'https://${CloudFront.DomainName}', '*' ] Policies: - AWSLambdaBasicExecutionRole - DynamoDBReadPolicy: TableName: !Ref 'ReportTable' ProcessUploadLogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: !Sub /aws/lambda/${ProcessUpload} RetentionInDays: 7 ProcessUpload: Type: AWS::Serverless::Function Metadata: cfn_nag: rules_to_suppress: - id: W89 reason: This does not increase the security of the solutions and greatly increases the cost and scope of the deployment. - id: W92 reason: This is not necessary for this project. Customers can enable this once they understand their usage patterns. Properties: CodeUri: process_upload/ Handler: app.lambda_handler Runtime: python3.9 Timeout: 30 Events: ApiEvent: Type: S3 Properties: Bucket: !Ref 'UploadedImages' Events: - s3:ObjectCreated:Put - s3:ObjectCreated:Post - s3:ObjectCreated:CompleteMultipartUpload Architectures: - arm64 Environment: Variables: LOGURU_LEVEL: !Ref 'LogLevel' REPORT_TABLE: !Ref 'ReportTable' ALLOW_ORIGIN_HEADER_VALUE: !If [ StrictOriginOn, !Sub 'https://${CloudFront.DomainName}', '*' ] DeadLetterQueue: Type: SQS TargetArn: !Sub - arn:${AWS::Partition}:sqs:${AWS::Region}:${AWS::AccountId}:DL-Suggest-Blog-Process-Image-DLQ-${Unique} - Unique: !Select [ 4, !Split [ '-', !Select [ 2, !Split [ '/', !Ref 'AWS::StackId' ] ] ] ] Policies: - AWSLambdaBasicExecutionRole - DynamoDBReadPolicy: TableName: !Ref 'ReportTable' - DynamoDBWritePolicy: TableName: !Ref 'ReportTable' - SQSSendMessagePolicy: QueueName: !Sub - DL-Suggest-Blog-Process-Image-DLQ-${Unique} - Unique: !Select [ 4, !Split [ '-', !Select [ 2, !Split [ '/', !Ref 'AWS::StackId' ] ] ] ] - S3CrudPolicy: BucketName: !Sub - dl-suggest-blog-uploaded-images-${Unique}/maint-img/* - Unique: !Select [ 4, !Split [ '-', !Select [ 2, !Split [ '/', !Ref 'AWS::StackId' ] ] ] ] - RekognitionDetectOnlyPolicy: {} CustomSeedDDBData: # Logging disabled on this function Type: AWS::Serverless::Function Metadata: cfn_nag: rules_to_suppress: - id: W89 reason: This does not increase the security of the solutions and greatly increases the cost and scope of the deployment. - id: W92 reason: This is not necessary for this project. Customers can enable this once they understand their usage patterns. Properties: CodeUri: seed_ddb_data/ Handler: app.handler Runtime: python3.9 Timeout: 30 Architectures: - arm64 Environment: Variables: LOGURU_LEVEL: !Ref 'LogLevel' Policies: # AWSLambdaBasicExecutionRole intentionally omitted. If allowed, it will create # a new log group during the deletion of the stack which cannot be cleaned up # automatically. No way to prevent that or specify an order, so we choose to not # allow logging at all. - DynamoDBReadPolicy: TableName: !Ref 'ReportTable' - DynamoDBWritePolicy: TableName: !Ref 'ReportTable' CustomSeedS3Data: # Logging disabled on this function Type: AWS::Serverless::Function Metadata: cfn_nag: rules_to_suppress: - id: W89 reason: This does not increase the security of the solutions and greatly increases the cost and scope of the deployment. - id: W92 reason: This is not necessary for this project. Customers can enable this once they understand their usage patterns. Properties: CodeUri: seed_s3_data/ Handler: app.handler Runtime: python3.9 Timeout: 30 Architectures: - arm64 Environment: Variables: LOGURU_LEVEL: !Ref 'LogLevel' Policies: # AWSLambdaBasicExecutionRole intentionally omitted. If allowed, it will create # a new log group during the deletion of the stack which cannot be cleaned up # automatically. No way to prevent that or specify an order, so we choose to not # allow logging at all. - S3CrudPolicy: BucketName: !Sub - dl-suggest-blog-static-website-${Unique} - Unique: !Select [ 4, !Split [ '-', !Select [ 2, !Split [ '/', !Ref 'AWS::StackId' ] ] ] ] - Version: '2012-10-17' Statement: - Effect: Allow Action: - apigateway:GET Resource: - !Sub - arn:${AWS::Partition}:apigateway:${AWS::Region}::/apikeys/${ApiKeyId} - ApiKeyId: !Ref APIApiKey SeedDDBData: Type: Custom::SeedDDBData Properties: ServiceToken: !GetAtt 'CustomSeedDDBData.Arn' TableName: !Ref 'ReportTable' SeedS3Data: Type: Custom::SeedS3Data Properties: ServiceToken: !GetAtt 'CustomSeedS3Data.Arn' UniqueSuffix: !Select [ 4, !Split [ '-', !Select [ 2, !Split [ '/', !Ref 'AWS::StackId' ] ] ] ] IdentityPoolId: !Ref 'CognitoIdentityPool' ApiBaseURL: !Sub 'https://${API}.execute-api.${AWS::Region}.amazonaws.com/v1' StaticWebsiteBucket: !Ref StaticWebsite UploadedImagesBucket: !Ref UploadedImages APIKeyId: !Ref APIApiKey ImageProcessingDLQ: Type: AWS::SQS::Queue Properties: QueueName: !Sub - DL-Suggest-Blog-Process-Image-DLQ-${Unique} - Unique: !Select [ 4, !Split [ '-', !Select [ 2, !Split [ '/', !Ref 'AWS::StackId' ] ] ] ] KmsMasterKeyId: alias/aws/sqs Map: Type: AWS::Location::Map Properties: Configuration: Style: RasterEsriImagery MapName: !Sub - DL-Suggest-Blog-Map-${Unique} - Unique: !Select [ 4, !Split [ '-', !Select [ 2, !Split [ '/', !Ref 'AWS::StackId' ] ] ] ] CognitoIdentityPool: Type: AWS::Cognito::IdentityPool Metadata: cfn_nag: rules_to_suppress: - id: W57 reason: Unauthenticated identies are preferred for this application so users can anonymously but securely upload images to the S3 bucket and render a map from the Amazon Location Service. Properties: IdentityPoolName: !Sub - DL-Suggest-Blog-Identity-Pool-${Unique} - Unique: !Select [ 4, !Split [ '-', !Select [ 2, !Split [ '/', !Ref 'AWS::StackId' ] ] ] ] AllowUnauthenticatedIdentities: true CognitoIdentityUnauthenticatedRole: Type: AWS::IAM::Role Metadata: cfn_nag: rules_to_suppress: - id: W28 reason: While not ideal, this is necessary to prevent a circular dependency. The resource uses the stack-id as part of the resource name, and the only properties of an IAM Role requiring replacement are Path and RoleName. Properties: RoleName: !Sub - DL-Suggest-Blog-Cognito-Unauth-${Unique} - Unique: !Select [ 4, !Split [ '-', !Select [ 2, !Split [ '/', !Ref 'AWS::StackId' ] ] ] ] AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Federated: cognito-identity.amazonaws.com Action: sts:AssumeRoleWithWebIdentity Condition: StringEquals: cognito-identity.amazonaws.com:aud: !Ref 'CognitoIdentityPool' ForAnyValue:StringLike: cognito-identity.amazonaws.com:amr: unauthenticated Policies: - PolicyName: Image-Upload-Access PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - s3:PutObject Resource: - !Sub - arn:${AWS::Partition}:s3:::dl-suggest-blog-uploaded-images-${Unique}/maint-img/* - Unique: !Select [ 4, !Split [ '-', !Select [ 2, !Split [ '/', !Ref 'AWS::StackId' ] ] ] ] - PolicyName: Maps-Access PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - geo:GetMap* Resource: !Sub - arn:${AWS::Partition}:geo:${AWS::Region}:${AWS::AccountId}:map/DL-Suggest-Blog-Map-${Unique} - Unique: !Select [ 4, !Split [ '-', !Select [ 2, !Split [ '/', !Ref 'AWS::StackId' ] ] ] ] # Warning: This is just to provide cross-site abuse, it can be spoofed with a modified browser Condition: StringLike: aws:referer: - !If [ StrictOriginOn, !Sub 'https://${CloudFront.DomainName}/*', '*' ] CognitoIdentityPoolRoleMapping: Type: AWS::Cognito::IdentityPoolRoleAttachment Properties: IdentityPoolId: !Ref 'CognitoIdentityPool' Roles: unauthenticated: !GetAtt 'CognitoIdentityUnauthenticatedRole.Arn' Outputs: SubmissionURL: Description: Submission/Citizen UI - CloudFront Static Website Value: !Sub 'https://${CloudFront.DomainName}/index.html' MapViewURL: Description: Viewing/Government UI - CloudFront Static Website Value: !Sub 'https://${CloudFront.DomainName}/reports.html' #WebsiteBucketName: # Description: S3 Bucket Name for Static Content Upload # Value: !Ref 'StaticWebsite' #APIBaseURL: # Description: API Gateway endpoint URL for v1 stage # Value: !Sub 'https://${API}.execute-api.${AWS::Region}.amazonaws.com/v1' #IdentityPoolId: # Description: Identity Pool Id for temporary credentials from Cognito # Value: !Ref 'CognitoIdentityPool' #UniqueSuffix: # Description: A suffix appended to resources to make their names unique # Value: !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref 'AWS::StackId']]]]