Parameters: SendAnonymousData: Type: String Default: '' SolutionIdentifier: Type: String Default: '' LambdaCodeUriBucket: Type: String Default: '' LambdaCodeUriKey: Type: String Default: '' CognitoUserPoolArn: Type: String Resources: ChallengeBucket: Type: AWS::S3::Bucket DeletionPolicy: Retain Properties: VersioningConfiguration: Status: Enabled BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 PublicAccessBlockConfiguration: BlockPublicAcls : true BlockPublicPolicy : true IgnorePublicAcls : true RestrictPublicBuckets : true LoggingConfiguration: DestinationBucketName: !Ref LoggingBucket LogFilePrefix: "challenges-bucket/" ChallengeBucketPolicy: Type: AWS::S3::BucketPolicy Properties: Bucket: !Ref ChallengeBucket PolicyDocument: Statement: - Effect: Deny Principal: "*" Action: "*" Resource: - !Sub "arn:aws:s3:::${ChallengeBucket}/*" - !Sub "arn:aws:s3:::${ChallengeBucket}" Condition: Bool: aws:SecureTransport: false LoggingBucket: Type: AWS::S3::Bucket DeletionPolicy: Retain Properties: VersioningConfiguration: Status: Enabled PublicAccessBlockConfiguration: BlockPublicAcls : true BlockPublicPolicy : true IgnorePublicAcls : true RestrictPublicBuckets : true AccessControl: LogDeliveryWrite BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 Metadata: cfn_nag: rules_to_suppress: - id: W35 reason: S3 Bucket access logging not needed here. LoggingBucketPolicy: Type: AWS::S3::BucketPolicy Properties: Bucket: !Ref LoggingBucket PolicyDocument: Statement: - Effect: Deny Principal: "*" Action: "*" Resource: - !Sub "arn:aws:s3:::${LoggingBucket}/*" - !Sub "arn:aws:s3:::${LoggingBucket}" Condition: Bool: aws:SecureTransport: false TrailBucket: Type: AWS::S3::Bucket DeletionPolicy: Retain Properties: VersioningConfiguration: Status: Enabled PublicAccessBlockConfiguration: BlockPublicAcls : true BlockPublicPolicy : true IgnorePublicAcls : true RestrictPublicBuckets : true BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 Metadata: cfn_nag: rules_to_suppress: - id: W35 reason: S3 Bucket access logging not needed here. TrailBucketPolicy: Type: AWS::S3::BucketPolicy Properties: Bucket: !Ref TrailBucket PolicyDocument: Statement: - Effect: Allow Principal: Service: cloudtrail.amazonaws.com Action: s3:GetBucketAcl Resource: !Sub 'arn:aws:s3:::${TrailBucket}' - Effect: Allow Principal: Service: cloudtrail.amazonaws.com Action: s3:PutObject Resource: !Sub 'arn:aws:s3:::${TrailBucket}/AWSLogs/${AWS::AccountId}/*' Condition: StringEquals: s3:x-amz-acl: bucket-owner-full-control - Effect: Deny Principal: "*" Action: "*" Resource: - !Sub "arn:aws:s3:::${TrailBucket}/*" - !Sub "arn:aws:s3:::${TrailBucket}" Condition: Bool: aws:SecureTransport: false Trail: Type: AWS::CloudTrail::Trail DependsOn: TrailBucketPolicy Properties: TrailName: !Ref AWS::StackName S3BucketName: !Ref TrailBucket IsLogging: true ChallengeTable: Type: AWS::DynamoDB::Table DeletionPolicy: Retain Properties: AttributeDefinitions: - AttributeName: id AttributeType: S KeySchema: - AttributeName: id KeyType: HASH PointInTimeRecoverySpecification: PointInTimeRecoveryEnabled: true BillingMode: PAY_PER_REQUEST Metadata: cfn_nag: rules_to_suppress: - id: W74 reason: Server-side encryption is done using an AWS owned key TokenSecret: Type: AWS::SecretsManager::Secret Properties: GenerateSecretString: {} Metadata: cfn_nag: rules_to_suppress: - id: W77 reason: "Neither key control nor cross-account sharing are required" APIGatewayAccount: Type: AWS::ApiGateway::Account Properties: CloudWatchRoleArn: !GetAtt APIGatewayLogRole.Arn APIGatewayLogRole: Type: AWS::IAM::Role Properties: Policies: - PolicyName: AmazonAPIGatewayPushToCloudWatchLogs PolicyDocument: Statement: - Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:DescribeLogGroups - logs:DescribeLogStreams - logs:PutLogEvents - logs:GetLogEvents - logs:FilterLogEvents Effect: Allow Resource: arn:*:logs:*:*:* AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - apigateway.amazonaws.com Action: sts:AssumeRole Path: / RestAPILogGroup: Type: AWS::Logs::LogGroup Properties: RetentionInDays: 30 Metadata: cfn_nag: rules_to_suppress: - id: W84 reason: "Not using AWS KMS customer managed key." RestAPI: Type: AWS::Serverless::Api Properties: MethodSettings: - ResourcePath: '/*' HttpMethod: '*' LoggingLevel: INFO MetricsEnabled: true DataTraceEnabled: false AccessLogSetting: DestinationArn: !GetAtt RestAPILogGroup.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' DefinitionBody: swagger: 2.0 definitions: CreateChallenge: type: object required: - imageWidth - imageHeight properties: imageWidth: type: integer imageHeight: type: integer additionalProperties: type: string PutChallengeFrame: type: object required: - token - timestamp - frameBase64 properties: token: type: string timestamp: type: integer frameBase64: type: string additionalProperties: false VerifyChallengeResponse: type: object required: - token properties: token: type: string additionalProperties: false x-amazon-apigateway-request-validators: all: validateRequestBody: true validateRequestParameters: true x-amazon-apigateway-request-validator: all paths: /challenge: post: x-amazon-apigateway-request-validator: all parameters: - required: true in: body name: CreateChallenge schema: $ref: '#/definitions/CreateChallenge' /challenge/{challenge_id}/frame: put: x-amazon-apigateway-request-validator: all parameters: - in: path name: challenge_id required: true type: string - required: true in: body name: PutChallengeFrame schema: $ref: '#/definitions/PutChallengeFrame' /challenge/{challenge_id}/verify: post: x-amazon-apigateway-request-validator: all parameters: - in: path name: challenge_id required: true type: string - required: true in: body name: VerifyChallengeResponse schema: $ref: '#/definitions/VerifyChallengeResponse' APIHandler: Properties: Environment: Variables: CLIENT_CHALLENGE_SELECTION: True ACCOUNT_ID: Ref: AWS::AccountId REGION_NAME: Ref: AWS::Region BUCKET_NAME: Ref: ChallengeBucket TABLE_NAME: Ref: ChallengeTable TOKEN_SECRET: Ref: TokenSecret SOLUTION_IDENTIFIER: Ref: SolutionIdentifier SEND_ANONYMOUS_USAGE_DATA: Ref: SendAnonymousData COGNITO_USER_POOL_ARN: Ref: CognitoUserPoolArn Metadata: cfn_nag: rules_to_suppress: - id: W89 reason: "This function does not need to access any resource provisioned within a VPC." - id: W92 reason: "This function does not need performance optimization, so the default limit suffice." DefaultRole: Properties: Policies: - PolicyDocument: Statement: - Action: - s3:PutObject - s3:GetObject Effect: Allow Resource: - !Sub "arn:aws:s3:::${ChallengeBucket}/*" - Action: - dynamodb:GetItem - dynamodb:PutItem - dynamodb:UpdateItem Effect: Allow Resource: - !Sub "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${ChallengeTable}" - Action: - rekognition:DetectFaces Effect: Allow Resource: - "*" - Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents Effect: Allow Resource: arn:*:logs:*:*:* - Action: - secretsmanager:GetResourcePolicy - secretsmanager:GetSecretValue - secretsmanager:DescribeSecret - secretsmanager:ListSecretVersionIds - secretsmanager:ListSecrets Effect: Allow Resource: Ref: TokenSecret Version: "2012-10-17" PolicyName: DefaultRolePolicy Metadata: cfn_nag: rules_to_suppress: - id: W11 reason: "Rekognition action must be applied to all resources" Outputs: TableName: Value: !Ref ChallengeTable TokenSecretArn: Value: !Ref TokenSecret