AWSTemplateFormatVersion: 2010-09-09 Transform: AWS::Serverless-2016-10-31 Description: Handling Redirections@Edge using CloudFront and Lambda@Edge Parameters: UserName: Type: String Description: The username to be used to login. Default: adminuser DeployUserInterface: Description: Deploy the user interface to assist in rule definition and maintenance. Default: yes Type: String AllowedValues: - yes - no ConstraintDescription: must specify yes or no. UIPrefix: Type: String Description: The folder where the user interface would be deployed in the bucket. Default: ui Metadata: AWS::CloudFormation::Interface: ParameterGroups: - Label: default: "User Interface (optional)" Parameters: - DeployUserInterface - UserName - UIPrefix ParameterLabels: DeployUserInterface: default: "Deploy User Interface?" Conditions: DeployUICondition: !Equals [ !Ref DeployUserInterface, yes ] Mappings: SourceCode: General: S3Bucket: 'CODE_BUCKET' LambdaPrefix: 'redirection/lambda/latest' UIPrefix: 'redirection/ui/latest' Resources: RuleBucket: Type: 'AWS::S3::Bucket' Properties: VersioningConfiguration: Status: Enabled CorsConfiguration: CorsRules: - AllowedHeaders: - '*' AllowedMethods: - 'GET' - 'PUT' - 'POST' - 'DELETE' AllowedOrigins: - '*' MaxAge: 3000 RuleBucketPolicy: Type: 'AWS::S3::BucketPolicy' Properties: PolicyDocument: Statement: - Action: - s3:GetObject Effect: Allow Principal: AWS: !GetAtt OriginRequestFunctionRole.Arn Resource: !Sub arn:aws:s3:::${RuleBucket}/* Bucket: !Ref RuleBucket OriginRequestFunctionRole: Type: 'AWS::IAM::Role' Properties: Path: /service-role/ ManagedPolicyArns: - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole' AssumeRolePolicyDocument: Version: 2012-10-17 Statement: Action: - 'sts:AssumeRole' Effect: Allow Principal: Service: - lambda.amazonaws.com - edgelambda.amazonaws.com OriginRequestFunction: Type: 'AWS::Lambda::Function' Properties: Code: S3Bucket: !Join ["-", [!FindInMap ["SourceCode", "General", "S3Bucket"], !Ref "AWS::Region"]] S3Key: !Join ["/", [!FindInMap ["SourceCode", "General", "LambdaPrefix"], "origin-request-function.zip"]] MemorySize: 128 Handler: index.handler Role: !GetAtt OriginRequestFunctionRole.Arn Timeout: 5 Runtime: nodejs12.x OriginRequestFunctionVersion: Type: "AWS::Lambda::Version" Properties: FunctionName: !Ref OriginRequestFunction Description: "A version of OriginRequestFunction" DeployFunctionRole: Type: 'AWS::IAM::Role' Condition: DeployUICondition Properties: Path: /service-role/ ManagedPolicyArns: - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole' - 'arn:aws:iam::aws:policy/AmazonCognitoPowerUser' Policies: - PolicyName: CustomPolicy PolicyDocument: Version: 2012-10-17 Statement: - Action: - 's3:*' Resource: !Sub 'arn:aws:s3:::${RuleBucket}/*' Effect: Allow - Action: - 's3:*' Resource: !Join ["/",[!Join [":",['arn','aws','s3','','',!Join ["-", [!FindInMap ["SourceCode", "General", "S3Bucket"], !Ref "AWS::Region"]]]],'*']] Effect: Allow AssumeRolePolicyDocument: Version: 2012-10-17 Statement: Action: - 'sts:AssumeRole' Effect: Allow Principal: Service: - lambda.amazonaws.com - s3.amazonaws.com DeployFunction: Type: 'AWS::Lambda::Function' Condition: DeployUICondition Properties: Code: S3Bucket: !Join ["-", [!FindInMap ["SourceCode", "General", "S3Bucket"], !Ref "AWS::Region"]] S3Key: !Join ["/", [!FindInMap ["SourceCode", "General", "LambdaPrefix"], "deploy-function.zip"]] MemorySize: 512 Environment: Variables: RuleBucket: !Ref RuleBucket UIPrefix: !Ref UIPrefix SourceUIFileBucket: !Join ["-", [!FindInMap ["SourceCode", "General", "S3Bucket"], !Ref "AWS::Region"]] SourceUIFilePath: !Join ["/", [!FindInMap ["SourceCode", "General", "UIPrefix"], "ui.zip"]] Handler: index.handler Role: !GetAtt DeployFunctionRole.Arn Timeout: 300 Runtime: nodejs12.x DeployArtifacts: Type: 'Custom::LambdaCallout' Condition: DeployUICondition Properties: UserPoolClient: !Ref UserPoolClient UserPool: !Ref UserPool IdentityPool: !Ref IdentityPool UserName: !Ref UserName ServiceToken: !GetAtt - DeployFunction - Arn UserPool: Type: 'AWS::Cognito::UserPool' Condition: DeployUICondition Properties: AutoVerifiedAttributes: - email UserPoolName: Rule_Manager_UserPool MfaConfiguration: 'OFF' Schema: - AttributeDataType: String Required: true Name: name Mutable: true CognitoUnAuthorizedRole: Type: 'AWS::IAM::Role' Condition: DeployUICondition Properties: Policies: - PolicyName: CognitoUnauthorizedPolicy PolicyDocument: Version: 2012-10-17 Statement: - Action: - 'mobileanalytics:GetEvents' - 'cognito-sync:*' Resource: '*' Effect: Allow AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Action: - 'sts:AssumeRoleWithWebIdentity' Effect: Allow Condition: StringEquals: 'cognito-identity.amazonaws.com:aud': !Ref IdentityPool 'ForAnyValue:StringLike': 'cognito-identity.amazonaws.com:amr': unauthenticated Principal: Federated: cognito-identity.amazonaws.com UserPoolClient: Type: 'AWS::Cognito::UserPoolClient' Condition: DeployUICondition Properties: GenerateSecret: false ClientName: Rule_Manager_ApplicationClient UserPoolId: !Ref UserPool CognitoAuthorizedRole: Type: 'AWS::IAM::Role' Condition: DeployUICondition Properties: Policies: - PolicyName: CognitoAuthorizedPolicy PolicyDocument: Version: 2012-10-17 Statement: - Action: - 'mobileanalytics:PutEvents' - 'cognito-sync:*' - 'cognito-identity:*' - 's3:*' Resource: '*' Effect: Allow - Action: - 'lambda:InvokeFunction' Resource: '*' Effect: Allow AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Action: - 'sts:AssumeRoleWithWebIdentity' Effect: Allow Condition: StringEquals: 'cognito-identity.amazonaws.com:aud': !Ref IdentityPool 'ForAnyValue:StringLike': 'cognito-identity.amazonaws.com:amr': authenticated Principal: Federated: cognito-identity.amazonaws.com IdentityPool: Type: 'AWS::Cognito::IdentityPool' Condition: DeployUICondition Properties: IdentityPoolName: Rule_Manager_IdentityPool AllowUnauthenticatedIdentities: false CognitoIdentityProviders: - ClientId: !Ref UserPoolClient ProviderName: !GetAtt - UserPool - ProviderName IdentityPoolRoleMapping: Type: 'AWS::Cognito::IdentityPoolRoleAttachment' Condition: DeployUICondition Properties: IdentityPoolId: !Ref IdentityPool Roles: unauthenticated: !GetAtt CognitoUnAuthorizedRole.Arn authenticated: !GetAtt CognitoAuthorizedRole.Arn StaticBucket: Type: AWS::S3::Bucket StaticBucketPolicy: Type: AWS::S3::BucketPolicy Properties: Bucket: !Ref StaticBucket PolicyDocument: Statement: - Sid: PolicyForCloudFrontPrivateContent Effect: Allow Principal: CanonicalUser: !GetAtt OriginAccessIdentity.S3CanonicalUserId Action: - 's3:GetObject' Resource: !Sub arn:aws:s3:::${StaticBucket}/* OriginAccessIdentity: Type: 'AWS::CloudFront::CloudFrontOriginAccessIdentity' Metadata: Comment: !Sub "${AWS::StackName} - Access private S3 bucket content only through CloudFront" Properties: CloudFrontOriginAccessIdentityConfig: Comment: !Sub "${AWS::StackName} - Access private S3 bucket content only through CloudFront" SiteContentDistribution: Type: AWS::CloudFront::Distribution Properties: DistributionConfig: Origins: - DomainName: !Sub ${StaticBucket}.s3.amazonaws.com Id: myS3Origin S3OriginConfig: OriginAccessIdentity: !Sub "origin-access-identity/cloudfront/${OriginAccessIdentity}" OriginCustomHeaders: - HeaderName: rules_bucket HeaderValue: !Ref RuleBucket - HeaderName: rules_file HeaderValue: redirector.json Enabled: 'true' Comment: !Sub "${AWS::StackName} - distribution for vod content delivery" DefaultRootObject: index.html DefaultCacheBehavior: TargetOriginId: myS3Origin LambdaFunctionAssociations: - EventType: 'origin-request' LambdaFunctionARN: !Ref OriginRequestFunctionVersion ViewerProtocolPolicy: allow-all DefaultTTL: '300' SmoothStreaming: 'false' Compress: 'true' ForwardedValues: QueryString: 'true' Cookies: Forward: 'none' PriceClass: PriceClass_All ViewerCertificate: CloudFrontDefaultCertificate: 'true' Outputs: UserName: Condition: DeployUICondition Value: !GetAtt DeployArtifacts.UserName TemporaryPassword: Condition: DeployUICondition Value: !GetAtt DeployArtifacts.Password UserInterface: Condition: DeployUICondition Value: !Join ["/",["https://s3.amazonaws.com",!Ref RuleBucket, !Ref UIPrefix,'index.html']] SiteContentDistribution: Value: !Join ["/",['https:/',!GetAtt SiteContentDistribution.DomainName]]