AWSTemplateFormatVersion: "2010-09-09" Description: Media2Cloud WebApp Stack - create frontend resources such as Amazon API Gateway, Amazon Cognito User Pool, demo portal, and etc. Version %%VERSION%% Mappings: Workflow: WebApp: Package: "%%PKG_WEBAPP%%" Api: Name: api Package: "%%PKG_API%%" APIGW: Stage: Name: demo Node: Runtime: Version: nodejs14.x Cognito: Group: Viewer: viewer Creator: creator Admin: admin Parameters: S3Bucket: Type: String Description: S3Bucket KeyPrefix: Type: String Description: KeyPrefix SolutionId: Type: String Description: SolutionId SolutionLowerCaseId: Type: String Description: SolutionLowerCaseId ResourcePrefix: Type: String Description: ResourcePrefix CustomUserAgent: Type: String Description: CustomUserAgent AnonymousUsage: Type: String Description: AnonymousUsage CustomResourcesLambdaArn: Type: String Description: CustomResourcesLambdaArn CustomResourcesRoleName: Type: String Description: CustomResourcesRoleName AwsSdkLayer: Type: String Description: AwsSdkLayer CoreLibLayer: Type: String Description: CoreLibLayer SolutionUuid: Type: String Description: SolutionUuid IotHost: Type: String Description: IotHost IotTopic: Type: String Description: IotTopic IotThingPolicy: Type: String Description: IotThingPolicy IngestBucket: Type: String Description: IngestBucket ProxyBucket: Type: String Description: ProxyBucket WebBucket: Type: String Description: WebBucket CloudFrontDistributionId: Type: String Description: CloudFrontDistributionId CloudFrontDistributionDomainName: Type: String Description: CloudFrontDistributionDomainName OpenSearchDomainName: Type: String Description: OpenSearchDomainName OpenSearchDomainEndpoint: Type: String Description: OpenSearchDomainEndpoint DefaultAIOptions: Type: String Description: DefaultAIOptions DefaultMinConfidence: Type: String Description: DefaultMinConfidence AIOptionsS3Key: Type: String Description: AIOptionsS3Key Resources: ################################################################################ # # APIGW CloudWatch Logs # ################################################################################ APIGWLogRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: apigateway.amazonaws.com Action: sts:AssumeRole Path: / ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs APIGWAccount: Type: AWS::ApiGateway::Account Properties: CloudWatchRoleArn: !GetAtt APIGWLogRole.Arn ################################################################################ # # REST API resources # ################################################################################ ApiLogGroup: Metadata: cfn_nag: rules_to_suppress: - id: W84 reason: Use default encryption. Disable additional KMS encryption requirement. https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html Type: AWS::Logs::LogGroup Properties: LogGroupName: !Sub - /aws/lambda/${ResourcePrefix}-${name} - name: !FindInMap - Workflow - Api - Name RetentionInDays: 7 ApiRole: Type: AWS::IAM::Role Metadata: cfn_nag: rules_to_suppress: - id: W11 reason: Resources require wildcard Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: sts:AssumeRole Principal: Service: lambda.amazonaws.com Path: !Sub /${ResourcePrefix}/ ManagedPolicyArns: - arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess Policies: - PolicyName: !Sub ${ResourcePrefix}-api-logs PolicyDocument: Version: "2012-10-17" Statement: # CloudWatch Logs - Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents Resource: !GetAtt ApiLogGroup.Arn # IoT - allows API to attach policy to IoT core - Effect: Allow Action: iot:AttachPolicy # Attaching Cognito Identity Id requires wildcard character # cert/* and thinggroup/* resources won't work. # See details on https://docs.aws.amazon.com/IAM/latest/UserGuide/list_awsiot.html Resource: "*" # Rekognition - allows access to Face Collection - Effect: Allow Action: - rekognition:ListCollections - rekognition:DescribeCollection - rekognition:CreateCollection - rekognition:DeleteCollection - rekognition:ListFaces - rekognition:IndexFaces - rekognition:DeleteFaces Resource: !Sub arn:aws:rekognition:${AWS::Region}:${AWS::AccountId}:collection/* # Rekognition - allows access to Custom Labels projects - Effect: Allow Action: rekognition:DescribeProjects Resource: "*" - Effect: Allow Action: rekognition:DescribeProjectVersions Resource: !Sub arn:aws:rekognition:${AWS::Region}:${AWS::AccountId}:project/*/* # Comprehend - list custom entity recognizers - Effect: Allow Action: comprehend:ListEntityRecognizers Resource: "*" # Transcribe - list custom language models and vocabularies - Effect: Allow Action: - transcribe:ListLanguageModels - transcribe:ListVocabularies Resource: "*" # S3 - check object exists on ingest bucket - Effect: Allow Action: s3:GetObject Resource: !Sub arn:aws:s3:::${IngestBucket}/* # S3 - list object on proxy bucket - Effect: Allow Action: s3:ListBucket Resource: !Sub arn:aws:s3:::${ProxyBucket} # S3 - read/write/delete on proxy bucket - Effect: Allow Action: - s3:GetObject - s3:PutObject - s3:DeleteObject Resource: !Sub arn:aws:s3:::${ProxyBucket}/* # OpenSearch - Effect: Allow Action: - es:ESHttpGet - es:ESHttpHead - es:ESHttpPost - es:ESHttpPut - es:ESHttpDelete Resource: - !Sub arn:aws:es:${AWS::Region}:${AWS::AccountId}:domain/${OpenSearchDomainName} - !Sub arn:aws:es:${AWS::Region}:${AWS::AccountId}:domain/${OpenSearchDomainName}/* # Note: Attach Backend resources such as Step Functions and DynamoDB in the main stack ApiLambda: Type: AWS::Lambda::Function Metadata: cfn_nag: rules_to_suppress: - id: W89 reason: Workflow not using VPC - id: W92 reason: Workflow not limiting simultaneous executions Properties: FunctionName: !Sub - ${ResourcePrefix}-${name} - name: !FindInMap - Workflow - Api - Name Description: !Sub (${SolutionLowerCaseId}) Handle GET, POST, OPTIONS requests Runtime: !FindInMap - Node - Runtime - Version MemorySize: 128 Timeout: 300 Handler: index.handler Role: !GetAtt ApiRole.Arn Code: S3Bucket: !Ref S3Bucket S3Key: !Sub - ${KeyPrefix}/${package} - package: !FindInMap - Workflow - Api - Package Layers: - !Ref AwsSdkLayer - !Ref CoreLibLayer TracingConfig: Mode: Active Environment: Variables: ENV_EXPECTED_BUCKET_OWNER: !Ref AWS::AccountId ENV_CUSTOM_USER_AGENT: !Ref CustomUserAgent ENV_SOLUTION_ID: !Ref SolutionId ENV_RESOURCE_PREFIX: !Ref ResourcePrefix ENV_ANONYMOUS_USAGE: !Ref AnonymousUsage ENV_SOLUTION_UUID: !Ref SolutionUuid ENV_IOT_HOST: !Ref IotHost ENV_IOT_TOPIC: !Ref IotTopic ENV_IOT_THING_POLICY_NAME: !Ref IotThingPolicy ENV_INGEST_BUCKET: !Ref IngestBucket ENV_PROXY_BUCKET: !Ref ProxyBucket ENV_ES_DOMAIN_ENDPOINT: !Ref OpenSearchDomainEndpoint ENV_DEFAULT_AI_OPTIONS: !Ref DefaultAIOptions ENV_DEFAULT_MINCONFIDENCE: !Ref DefaultMinConfidence ENV_USER_POOL_ID: !Ref CognitoUserPool ENV_AI_OPTIONS_S3KEY: !Ref AIOptionsS3Key Tags: - Key: SolutionId Value: !Ref SolutionId RestApi: Type: AWS::ApiGateway::RestApi Properties: Description: !Sub (${SolutionLowerCaseId}) Rest API for webapp Body: swagger: 2.0 info: version: "2018-08-03T20:13:00Z" title: !Sub ${ResourcePrefix}-api basePath: !Sub - /${stageName} - stageName: !FindInMap - APIGW - Stage - Name schemes: - https paths: /{operation}: get: produces: - application/json parameters: - name: operation in: path required: true type: string response: "200": description: 200 response schema: $ref: "#/definitions/Empty" security: - sigv4: [] x-amazon-apigateway-integration: uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${ApiLambda.Arn}/invocations responses: default: statusCode: "200" passthroughBehavior: when_no_match httpMethod: POST contentHandling: CONVERT_TO_TEXT type: aws_proxy delete: produces: - application/json parameters: - name: operation in: path required: true type: string response: "200": description: 200 response schema: $ref: "#/definitions/Empty" security: - sigv4: [] x-amazon-apigateway-integration: uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${ApiLambda.Arn}/invocations responses: default: statusCode: "200" passthroughBehavior: when_no_match httpMethod: POST contentHandling: CONVERT_TO_TEXT type: aws_proxy post: produces: - application/json parameters: - name: operation in: path required: true type: string response: "200": description: 200 response schema: $ref: "#/definitions/Empty" security: - sigv4: [] x-amazon-apigateway-integration: uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${ApiLambda.Arn}/invocations responses: default: statusCode: "200" passthroughBehavior: when_no_match httpMethod: POST contentHandling: CONVERT_TO_TEXT type: aws_proxy options: produces: - application/json parameters: - name: operation in: path required: true type: string response: "200": description: 200 response schema: $ref: "#/definitions/Empty" headers: Access-Control-Allow-Origin: type: string Access-Control-Allow-Methods: type: string Access-Control-Allow-Headers: type: string x-amazon-apigateway-integration: uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${ApiLambda.Arn}/invocations responses: default: statusCode: "200" passthroughBehavior: when_no_match httpMethod: POST contentHandling: CONVERT_TO_TEXT type: aws_proxy /{operation}/{uuid+}: get: produces: - application/json parameters: - name: uuid in: path required: true type: string - name: operation in: path required: true type: string response: "200": description: 200 response schema: $ref: "#/definitions/Empty" security: - sigv4: [] x-amazon-apigateway-integration: uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${ApiLambda.Arn}/invocations responses: default: statusCode: "200" passthroughBehavior: when_no_match httpMethod: POST contentHandling: CONVERT_TO_TEXT type: aws_proxy delete: produces: - application/json parameters: - name: uuid in: path required: true type: string - name: operation in: path required: true type: string response: "200": description: 200 response schema: $ref: "#/definitions/Empty" security: - sigv4: [] x-amazon-apigateway-integration: uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${ApiLambda.Arn}/invocations responses: default: statusCode: "200" passthroughBehavior: when_no_match httpMethod: POST contentHandling: CONVERT_TO_TEXT type: aws_proxy post: produces: - application/json parameters: - name: uuid in: path required: true type: string - name: operation in: path required: true type: string response: "200": description: 200 response schema: $ref: "#/definitions/Empty" security: - sigv4: [] x-amazon-apigateway-integration: uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${ApiLambda.Arn}/invocations responses: default: statusCode: "200" passthroughBehavior: when_no_match httpMethod: POST contentHandling: CONVERT_TO_TEXT type: aws_proxy options: produces: - application/json parameters: - name: uuid in: path required: true type: string - name: operation in: path required: true type: string response: "200": description: 200 response schema: $ref: "#/definitions/Empty" headers: Access-Control-Allow-Origin: type: string Access-Control-Allow-Methods: type: string Access-Control-Allow-Headers: type: string x-amazon-apigateway-integration: uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${ApiLambda.Arn}/invocations responses: default: statusCode: "200" passthroughBehavior: when_no_match httpMethod: POST contentHandling: CONVERT_TO_TEXT type: aws_proxy securityDefinitions: sigv4: type: apiKey name: Authorization in: header x-amazon-apigateway-authtype: awsSigv4 definitions: Empty: type: object title: Empty Schema APIGWLogGroup: Metadata: cfn_nag: rules_to_suppress: - id: W84 reason: Use default encryption. Disable additional KMS encryption requirement. https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html Type: AWS::Logs::LogGroup Properties: LogGroupName: !Sub /apigateway/media2cloud/${ResourcePrefix} RetentionInDays: 7 Deployment: Type: AWS::ApiGateway::Deployment Metadata: cfn_nag: rules_to_suppress: - id: W68 reason: Supress UsagePlan requirement Properties: Description: !Sub (${SolutionLowerCaseId}) created by ${ResourcePrefix} RestApiId: !Ref RestApi StageName: !FindInMap - APIGW - Stage - Name StageDescription: Description: !Sub (${SolutionLowerCaseId}) created by ${ResourcePrefix} LoggingLevel: ERROR AccessLogSetting: DestinationArn: !GetAtt APIGWLogGroup.Arn Format: $context.identity.sourceIp $context.identity.caller $context.identity.user [$context.requestTime] \"$context.httpMethod $context.resourcePath $context.protocol\" $context.status $context.responseLength $context.requestId MethodSettings: - ResourcePath: /~1{operation} HttpMethod: GET DataTraceEnabled: false LoggingLevel: ERROR - ResourcePath: /~1{operation} HttpMethod: POST DataTraceEnabled: false LoggingLevel: ERROR - ResourcePath: /~1{operation} HttpMethod: DELETE DataTraceEnabled: false LoggingLevel: ERROR - ResourcePath: /~1{operation}~1{uuid+} HttpMethod: GET DataTraceEnabled: false LoggingLevel: ERROR - ResourcePath: /~1{operation}~1{uuid+} HttpMethod: POST DataTraceEnabled: false LoggingLevel: ERROR - ResourcePath: /~1{operation}~1{uuid+} HttpMethod: DELETE DataTraceEnabled: false LoggingLevel: ERROR DELETEOperation: Type: AWS::Lambda::Permission Properties: Action: lambda:InvokeFunction FunctionName: !GetAtt ApiLambda.Arn Principal: apigateway.amazonaws.com SourceArn: !Sub - arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${RestApi}/${stageName}/DELETE/* - stageName: !FindInMap - APIGW - Stage - Name GETOperation: Type: AWS::Lambda::Permission Properties: Action: lambda:InvokeFunction FunctionName: !GetAtt ApiLambda.Arn Principal: apigateway.amazonaws.com SourceArn: !Sub - arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${RestApi}/${stageName}/GET/* - stageName: !FindInMap - APIGW - Stage - Name OPTIONSOperation: Type: AWS::Lambda::Permission Properties: Action: lambda:InvokeFunction FunctionName: !GetAtt ApiLambda.Arn Principal: apigateway.amazonaws.com SourceArn: !Sub - arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${RestApi}/${stageName}/OPTIONS/* - stageName: !FindInMap - APIGW - Stage - Name POSTOperation: Type: AWS::Lambda::Permission Properties: Action: lambda:InvokeFunction FunctionName: !GetAtt ApiLambda.Arn Principal: apigateway.amazonaws.com SourceArn: !Sub - arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${RestApi}/${stageName}/POST/* - stageName: !FindInMap - APIGW - Stage - Name DELETEUuid: Type: AWS::Lambda::Permission Properties: Action: lambda:InvokeFunction FunctionName: !GetAtt ApiLambda.Arn Principal: apigateway.amazonaws.com SourceArn: !Sub - arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${RestApi}/${stageName}/DELETE/*/* - stageName: !FindInMap - APIGW - Stage - Name GETUuid: Type: AWS::Lambda::Permission Properties: Action: lambda:InvokeFunction FunctionName: !GetAtt ApiLambda.Arn Principal: apigateway.amazonaws.com SourceArn: !Sub - arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${RestApi}/${stageName}/GET/*/* - stageName: !FindInMap - APIGW - Stage - Name OPTIONSUuid: Type: AWS::Lambda::Permission Properties: Action: lambda:InvokeFunction FunctionName: !GetAtt ApiLambda.Arn Principal: apigateway.amazonaws.com SourceArn: !Sub - arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${RestApi}/${stageName}/OPTIONS/*/* - stageName: !FindInMap - APIGW - Stage - Name POSTUuid: Type: AWS::Lambda::Permission Properties: Action: lambda:InvokeFunction FunctionName: !GetAtt ApiLambda.Arn Principal: apigateway.amazonaws.com SourceArn: !Sub - arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${RestApi}/${stageName}/POST/*/* - stageName: !FindInMap - APIGW - Stage - Name ################################################################################ # # Cognito resources # * UserPool, IdentityPool, AppClient, & Authenticated role # * UserGroups (viewer, creator, & admin) & IAM roles and policies mapped to UserGroup # * Add Cognito Admin APIs permission to ApiRole # ################################################################################ CognitoUserPool: Type: AWS::Cognito::UserPool Metadata: cfn_nag: rules_to_suppress: - id: F78 reason: Disable MFA check Properties: AdminCreateUserConfig: AllowAdminCreateUserOnly: true InviteMessageTemplate: EmailSubject: You are invited to AWS Media2Cloud demo portal EmailMessage: !Sub - |-

Welcome to AWS Media2Cloud demo portal

You will need this user name and temporary password to log in the first time.

User name: {username}

Temporary password: {####}
(After you log in with your temporary password, you will be prompted to create a new one.)

Open the link to log in:
${url}

Information you need to know

S3 Bucket where content is ingested:
${ingest}

S3 Bucket where proxies and metadata are stored:
${proxy}

Team AWS Media2Cloud

 

- { url: !Sub "https://${CloudFrontDistributionDomainName}", ingest: !Ref IngestBucket, proxy: !Ref ProxyBucket } AliasAttributes: - email AutoVerifiedAttributes: - email MfaConfiguration: 'OFF' Policies: PasswordPolicy: MinimumLength: 8 RequireLowercase: true RequireNumbers: true RequireSymbols: true RequireUppercase: true UserPoolName: !Sub ${ResourcePrefix}-userpool # Different usergroup(s) UserPoolGroupAdmin: Type: AWS::Cognito::UserPoolGroup Properties: Description: Administrator access. Allow view, upload, process assets and manage users. GroupName: !FindInMap - Cognito - Group - Admin Precedence: 1 UserPoolId: !Ref CognitoUserPool UserPoolGroupCreator: Type: AWS::Cognito::UserPoolGroup Properties: Description: Creator group allows users to view, create, and process assets. GroupName: !FindInMap - Cognito - Group - Creator Precedence: 98 UserPoolId: !Ref CognitoUserPool UserPoolGroupViewer: Type: AWS::Cognito::UserPoolGroup Properties: Description: Viewer group allows users to view assets. GroupName: !FindInMap - Cognito - Group - Viewer Precedence: 99 UserPoolId: !Ref CognitoUserPool # Usergroup IAM role(s) UserPoolGroupViewerRole: Type: AWS::IAM::Role Properties: 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: authenticated Path: !Sub /${ResourcePrefix}/ UserPoolGroupCreatorRole: Type: AWS::IAM::Role Properties: 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: authenticated Path: !Sub /${ResourcePrefix}/ UserPoolGroupAdminRole: Type: AWS::IAM::Role Properties: 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: authenticated Path: !Sub /${ResourcePrefix}/ # Usergroup role polices UserPoolGroupNonAdminDenyPolicy: Type: AWS::IAM::Policy Properties: PolicyName: !Sub ${ResourcePrefix}-UserPoolGroupNonAdminDenyPolicy PolicyDocument: Version: "2012-10-17" Statement: # Deny all HTTP methods on /users and /users/* - Effect: Deny Action: execute-api:Invoke Resource: - !Sub - arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${RestApi}/${stageName}/*/users - stageName: !FindInMap - APIGW - Stage - Name - !Sub - arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${RestApi}/${stageName}/*/users/* - stageName: !FindInMap - APIGW - Stage - Name # Deny POST and DELETE methods on /settings/* - Effect: Deny Action: execute-api:Invoke Resource: - !Sub - arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${RestApi}/${stageName}/POST/settings/* - stageName: !FindInMap - APIGW - Stage - Name - !Sub - arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${RestApi}/${stageName}/DELETE/settings/* - stageName: !FindInMap - APIGW - Stage - Name Roles: - !Ref UserPoolGroupViewerRole - !Ref UserPoolGroupCreatorRole UserPoolGroupViewerAccessPolicy: Type: AWS::IAM::Policy Properties: PolicyName: !Sub ${ResourcePrefix}-UserPoolGroupViewerAccessPolicy PolicyDocument: Version: "2012-10-17" Statement: # Cognito - allow to get identity id - Effect: Allow Action: cognito-identity:GetId Resource: !Sub arn:aws:cognito-identity:${AWS::Region}:${AWS::AccountId}:identitypool/${CognitoIdentityPool} # S3 - Read/List on ingest bucket - Effect: Allow Action: s3:ListBucket Resource: !Sub arn:aws:s3:::${IngestBucket} - Effect: Allow Action: s3:GetObject Resource: !Sub arn:aws:s3:::${IngestBucket}/* # S3 - Read/List on proxy bucket - Effect: Allow Action: s3:ListBucket Resource: !Sub arn:aws:s3:::${ProxyBucket} - Effect: Allow Action: s3:GetObject Resource: !Sub arn:aws:s3:::${ProxyBucket}/* # API Gateway - Effect: Allow Action: execute-api:Invoke Resource: !Sub - arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${RestApi}/${stageName}/*/* - stageName: !FindInMap - APIGW - Stage - Name # IoT - Effect: Allow Action: iot:Connect Resource: !Sub arn:aws:iot:${AWS::Region}:${AWS::AccountId}:client/* - Effect: Allow Action: iot:Subscribe Resource: !Sub arn:aws:iot:${AWS::Region}:${AWS::AccountId}:topicfilter/${IotTopic} - Effect: Allow Action: iot:Receive Resource: !Sub arn:aws:iot:${AWS::Region}:${AWS::AccountId}:topic/${IotTopic} Roles: - !Ref UserPoolGroupViewerRole - !Ref UserPoolGroupCreatorRole - !Ref UserPoolGroupAdminRole UserPoolGroupCreatorAccessPolicy: Type: AWS::IAM::Policy Properties: PolicyName: !Sub ${ResourcePrefix}-UserPoolGroupCreatorAccessPolicy PolicyDocument: Version: "2012-10-17" Statement: # S3 - Write on ingest bucket - Effect: Allow Action: s3:PutObject Resource: !Sub arn:aws:s3:::${IngestBucket}/* # S3 - Write on proxy bucket - Effect: Allow Action: s3:PutObject Resource: !Sub arn:aws:s3:::${ProxyBucket}/* Roles: - !Ref UserPoolGroupCreatorRole - !Ref UserPoolGroupAdminRole CognitoAppClient: Type: AWS::Cognito::UserPoolClient Properties: ClientName: !Sub ${ResourcePrefix}-app RefreshTokenValidity: 30 UserPoolId: !Ref CognitoUserPool CognitoIdentityPool: Type: AWS::Cognito::IdentityPool Properties: IdentityPoolName: !Sub ${ResourcePrefix}-identity AllowUnauthenticatedIdentities: false CognitoIdentityProviders: - ClientId: !Ref CognitoAppClient ProviderName: !GetAtt CognitoUserPool.ProviderName ServerSideTokenCheck: false CognitoAuthenticatedRole: Type: AWS::IAM::Role Metadata: cfn_nag: rules_to_suppress: - id: W11 reason: This wildcard is present as the authenticated cognito role needs to be able to access contents within the bucket! Properties: 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: authenticated Path: !Sub /${ResourcePrefix}/ Policies: - PolicyName: !Sub ${ResourcePrefix}-CognitoAuthUser PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: iam:PassRole Resource: - !GetAtt UserPoolGroupAdminRole.Arn - !GetAtt UserPoolGroupCreatorRole.Arn - !GetAtt UserPoolGroupViewerRole.Arn CognitoIdentityPoolRoleAttachment: Type: AWS::Cognito::IdentityPoolRoleAttachment Properties: IdentityPoolId: !Ref CognitoIdentityPool Roles: authenticated: !GetAtt CognitoAuthenticatedRole.Arn RoleMappings: userpool: AmbiguousRoleResolution: Deny IdentityProvider: !Sub ${CognitoUserPool.ProviderName}:${CognitoAppClient} Type: Rules RulesConfiguration: Rules: - Claim: cognito:groups MatchType: Contains Value: !FindInMap - Cognito - Group - Admin RoleARN: !GetAtt UserPoolGroupAdminRole.Arn - Claim: cognito:groups MatchType: Contains Value: !FindInMap - Cognito - Group - Creator RoleARN: !GetAtt UserPoolGroupCreatorRole.Arn - Claim: cognito:groups MatchType: Contains Value: !FindInMap - Cognito - Group - Viewer RoleARN: !GetAtt UserPoolGroupViewerRole.Arn # Attach cognito admin access to ApiRole ApiRolePolicyUserManagement: Type: AWS::IAM::Policy Properties: PolicyName: !Sub ${ResourcePrefix}-ApiRolePolicyUserManagement PolicyDocument: Version: "2012-10-17" Statement: # Cognito management - Effect: Allow Action: - cognito-idp:AdminCreateUser - cognito-idp:AdminDeleteUser - cognito-idp:AdminGetUser - cognito-idp:AdminListGroupsForUser - cognito-idp:AdminAddUserToGroup - cognito-idp:ListUsers Resource: !GetAtt CognitoUserPool.Arn Roles: - !Ref ApiRole ################################################################################ # # PostProcess (Custom Resources) # * Copy web content to web bucket # * Invalidate CloudFront distribution cache when updating stack # * Update CORS rules on both ingest and proxy buckets # ################################################################################ CustomResourcesPolicyWebapp: Type: AWS::IAM::Policy Properties: Roles: - !Ref CustomResourcesRoleName PolicyName: !Sub ${ResourcePrefix}-custom-resources-webapp-stack PolicyDocument: Version: "2012-10-17" Statement: # CloudFront - Invalidate Cache - Effect: Allow Action: cloudfront:CreateInvalidation Resource: !Sub arn:aws:cloudfront::${AWS::AccountId}:distribution/* # S3 - GetObject from S3Bucket - Effect: Allow Action: s3:GetObject Resource: !Sub - arn:aws:s3:::${S3Bucket}/${KeyPrefix}/${package} - package: !FindInMap - Workflow - WebApp - Package # S3 - PutObject to WebBucket - Effect: Allow Action: s3:PutObject Resource: !Sub arn:aws:s3:::${WebBucket}/* # S3 - PutBucketCORS for Ingest and Proxy buckets - Effect: Allow Action: s3:PutBucketCORS Resource: - !Sub arn:aws:s3:::${IngestBucket} - !Sub arn:aws:s3:::${ProxyBucket} CopyWebContent: DependsOn: CustomResourcesPolicyWebapp Type: Custom::CopyWebContent Properties: ServiceToken: !Ref CustomResourcesLambdaArn Data: SolutionId: !Ref SolutionId Source: Bucket: !Ref S3Bucket Key: !Sub - ${KeyPrefix}/${package} - package: !FindInMap - Workflow - WebApp - Package Destination: Bucket: !Ref WebBucket InvalidateCache: DependsOn: CustomResourcesPolicyWebapp Type: Custom::InvalidateCache Properties: ServiceToken: !Ref CustomResourcesLambdaArn Data: DistributionId: !Ref CloudFrontDistributionId Paths: - /index.html - /app.min.js - /solution-manifest.js - /css* - /src* - /third_party* LastUpdated: !GetAtt CopyWebContent.LastUpdated UpdateIngestCorsRule: DependsOn: CustomResourcesPolicyWebapp Type: Custom::SetCORS Properties: ServiceToken: !Ref CustomResourcesLambdaArn Data: Bucket: !Ref IngestBucket AllowedOrigins: - !Sub https://${CloudFrontDistributionDomainName} AllowedMethods: - HEAD - GET - PUT - POST AllowedHeaders: - "*" ExposeHeaders: - Content-Length - ETag - x-amz-meta-uuid - x-amz-meta-md5 MaxAgeSeconds: 3000 UpdateProxyCorsRule: DependsOn: CustomResourcesPolicyWebapp Type: Custom::SetCORS Properties: ServiceToken: !Ref CustomResourcesLambdaArn Data: Bucket: !Ref ProxyBucket AllowedOrigins: - !Sub https://${CloudFrontDistributionDomainName} AllowedMethods: - HEAD - GET - PUT - POST - DELETE AllowedHeaders: - "*" ExposeHeaders: - Content-Length - ETag - x-amz-meta-uuid - x-amz-meta-md5 MaxAgeSeconds: 3000 Outputs: RestApi: Value: !Ref RestApi Description: RestApi ApiEndpoint: Value: !Sub - https://${RestApi}.execute-api.${AWS::Region}.amazonaws.com/${stageName} - stageName: !FindInMap - APIGW - Stage - Name Description: ApiEndpoint ApiRole: Value: !Ref ApiRole Description: ApiRole ApiRoleArn: Value: !GetAtt ApiRole.Arn Description: ApiRoleArn CognitoUserPool: Value: !Ref CognitoUserPool Description: CognitoUserPool CognitoUserPoolArn: Value: !GetAtt CognitoUserPool.Arn Description: CognitoUserPoolArn CognitoUserPoolProviderName: Value: !GetAtt CognitoUserPool.ProviderName Description: CognitoUserPoolProviderName CognitoUserPoolProviderURL: Value: !GetAtt CognitoUserPool.ProviderURL Description: CognitoUserPoolProviderURL CognitoAppClient: Value: !Ref CognitoAppClient Description: CognitoAppClient CognitoIdentityPool: Value: !Ref CognitoIdentityPool Description: CognitoIdentityPool CognitoIdentityPoolName: Value: !GetAtt CognitoIdentityPool.Name Description: CognitoIdentityPoolName LastUpdated: Value: !GetAtt CopyWebContent.LastUpdated Description: LastUpdated HomePage: Value: !Sub https://${CloudFrontDistributionDomainName} Description: HomePage