# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: MIT-0 AWSTemplateFormatVersion: "2010-09-09" Description: > This CloudFormation template sets up a scenario where Amazon GameLift queues are used in conjunction with a custom matchmaker. For simplicity sake, this custom matchmaker form matches by taking the oldest players in the waiting pool and not considering any other factors such as skills or latency. Once the group of players to form matches is identified, a lambda function calls GameLift:StartGameSessionPlacement to start a queue placement. Parameters: ApiGatewayStageNameParameter: Type: String Default: v1 Description: Name of the Api Gateway stage BuildNameParameter: Type: String Default: Sample GameLift Build Description: Name of the build BuildOperatingSystemParameter: Type: String Description: Operating system of the build BuildS3BucketParameter: Type: String Description: Bucket that stores the server build BuildS3KeyParameter: Type: String Description: Key of the server build in the S3 bucket BuildVersionParameter: Type: String Description: Version number of the build FleetDescriptionParameter: Type: String Default: Deployed by the Amazon GameLift Plug-in for Unity. Description: Description of the fleet FleetNameParameter: Type: String Default: Sample GameLift Fleet Description: Name of the fleet FleetTcpFromPortParameter: Type: Number Default: 33430 Description: Starting port number for TCP ports to be opened FleetTcpToPortParameter: Type: Number Default: 33440 Description: Ending port number for TCP ports to be opened FleetUdpFromPortParameter: Type: Number Default: 33430 Description: Starting port number for UDP ports to be opened FleetUdpToPortParameter: Type: Number Default: 33440 Description: Ending port number for UDP ports to be opened GameNameParameter: Type: String Default: MyGame Description: Game name to prepend before resource names MaxLength: 12 LambdaZipS3BucketParameter: Type: String Description: S3 bucket that stores the lambda function zip LambdaZipS3KeyParameter: Type: String Description: S3 key that stores the lambda function zip LaunchParametersParameter: Type: String Description: Parameters used to launch the game server process LaunchPathParameter: Type: String Description: Location of the game server executable in the build MatchmakingTimeoutInSecondsParamter: Type: Number Default: 60 Description: Time in seconds before matchmaker times out to wait for enough players to create game session placement MaxTransactionsPerFiveMinutesPerIpParameter: Type: Number Default: 100 MaxValue: 20000000 MinValue: 100 NumPlayersPerGameParameter: Type: Number Default: 2 Description: Number of players per game session QueueTimeoutInSecondsParameter: Type: Number Default: 60 Description: Time in seconds before game session placement times out to place players on a server Resources: ApiGatewayCloudWatchRole: Type: "AWS::IAM::Role" Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: - apigateway.amazonaws.com Action: "sts:AssumeRole" ManagedPolicyArns: - "arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs" Account: Type: "AWS::ApiGateway::Account" Properties: CloudWatchRoleArn: !GetAtt ApiGatewayCloudWatchRole.Arn GameRequestLambdaFunctionExecutionRole: Type: "AWS::IAM::Role" Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - "sts:AssumeRole" ManagedPolicyArns: - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" Policies: - PolicyName: !Sub ${GameNameParameter}GameRequestLambdaFunctionPolicies PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - "dynamodb:PutItem" - "dynamodb:UpdateItem" - "dynamodb:GetItem" - "dynamodb:DeleteItem" - "dynamodb:Query" - "sqs:SendMessage" Resource: "*" GameSessionEventHandlerLambdaFunctionExecutionRole: Type: "AWS::IAM::Role" Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - "sts:AssumeRole" ManagedPolicyArns: - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" Policies: - PolicyName: !Sub ${GameNameParameter}GameSessionEventHandlerLambdaFunctionPolicies PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - "dynamodb:PutItem" Resource: "*" GameSessionPlacementTable: Type: "AWS::DynamoDB::Table" Properties: AttributeDefinitions: - AttributeName: PlacementId AttributeType: S KeySchema: - AttributeName: PlacementId KeyType: HASH ProvisionedThroughput: ReadCapacityUnits: 5 WriteCapacityUnits: 5 TableName: !Sub ${GameNameParameter}GameSessionPlacementTable TimeToLiveSpecification: AttributeName: ExpirationTime Enabled: true MatchmakingRequestTable: Type: "AWS::DynamoDB::Table" Properties: AttributeDefinitions: - AttributeName: PlayerId AttributeType: S - AttributeName: StartTime AttributeType: "N" KeySchema: - AttributeName: PlayerId KeyType: HASH - AttributeName: StartTime KeyType: RANGE ProvisionedThroughput: ReadCapacityUnits: 5 WriteCapacityUnits: 5 TableName: !Sub ${GameNameParameter}MatchmakingRequestTable TimeToLiveSpecification: AttributeName: ExpirationTime Enabled: true RestApi: Type: "AWS::ApiGateway::RestApi" Properties: Name: !Sub ${GameNameParameter}RestApi ResultsRequestLambdaFunctionExecutionRole: Type: "AWS::IAM::Role" Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - "sts:AssumeRole" ManagedPolicyArns: - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" Policies: - PolicyName: !Sub ${GameNameParameter}ResultsRequestLambdaFunctionPolicies PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - "dynamodb:GetItem" - "dynamodb:Query" Resource: "*" SimpleMatchmakerLambdaFunctionExecutionRole: Type: "AWS::IAM::Role" Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - "sts:AssumeRole" ManagedPolicyArns: - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" Policies: - PolicyName: !Sub ${GameNameParameter}SimpleMatchmakerLambdaFunctionPolicies PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - "dynamodb:UpdateItem" - "gamelift:StartGameSessionPlacement" - "sqs:ReceiveMessage" - "sqs:DeleteMessage" - "sqs:GetQueueAttributes" Resource: "*" UserPool: Type: "AWS::Cognito::UserPool" Properties: AutoVerifiedAttributes: - email EmailConfiguration: EmailSendingAccount: COGNITO_DEFAULT EmailVerificationMessage: "Please verify your email to complete account registration for GameLift Sample Game. Confirmation Code {####}." EmailVerificationSubject: GameLift Sample Game Account Verification Policies: PasswordPolicy: MinimumLength: 8 RequireLowercase: true RequireNumbers: true RequireSymbols: true RequireUppercase: true Schema: - Name: email AttributeDataType: String Mutable: false Required: true UserPoolName: !Sub ${GameNameParameter}UserPool UsernameAttributes: - email BuildAccessRole: Type: "AWS::IAM::Role" Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: - cloudformation.amazonaws.com - gamelift.amazonaws.com Action: "sts:AssumeRole" Policies: - PolicyName: !Sub ${GameNameParameter}BuildS3AccessPolicy PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - "s3:GetObject" - "s3:GetObjectVersion" Resource: - "Fn::Sub": "arn:aws:s3:::${BuildS3BucketParameter}/${BuildS3KeyParameter}" RoleName: !Sub ${GameNameParameter}BuildIAMRole GameRequestApiResource: Type: "AWS::ApiGateway::Resource" Properties: ParentId: !GetAtt RestApi.RootResourceId PathPart: start_game RestApiId: !Ref RestApi ResultsRequestApiResource: Type: "AWS::ApiGateway::Resource" Properties: ParentId: !GetAtt RestApi.RootResourceId PathPart: get_game_connection RestApiId: !Ref RestApi SimpleMatchMakerTicketQueue: Type: "AWS::SQS::Queue" Properties: FifoQueue: true MessageRetentionPeriod: !Ref MatchmakingTimeoutInSecondsParamter QueueName: !Sub ${GameNameParameter}SimpleMatchMakerTicketQueue.fifo UserPoolClient: Type: "AWS::Cognito::UserPoolClient" Properties: AccessTokenValidity: 1 ClientName: !Sub ${GameNameParameter}UserPoolClient ExplicitAuthFlows: - ALLOW_USER_PASSWORD_AUTH - ALLOW_REFRESH_TOKEN_AUTH GenerateSecret: false IdTokenValidity: 1 PreventUserExistenceErrors: ENABLED ReadAttributes: - email - preferred_username RefreshTokenValidity: 30 SupportedIdentityProviders: - COGNITO UserPoolId: !Ref UserPool WebACL: Type: "AWS::WAFv2::WebACL" DependsOn: - ApiDeployment Properties: DefaultAction: Allow: {} Description: !Sub "WebACL for game: ${GameNameParameter}" Name: !Sub ${GameNameParameter}WebACL Rules: - Name: !Sub ${GameNameParameter}WebACLPerIpThrottleRule Action: Block: {} Priority: 0 Statement: RateBasedStatement: AggregateKeyType: IP Limit: !Ref MaxTransactionsPerFiveMinutesPerIpParameter VisibilityConfig: CloudWatchMetricsEnabled: true MetricName: !Sub ${GameNameParameter}WebACLPerIpThrottleRuleMetrics SampledRequestsEnabled: true Scope: REGIONAL VisibilityConfig: CloudWatchMetricsEnabled: true MetricName: !Sub ${GameNameParameter}WebACLMetrics SampledRequestsEnabled: true ApiDeployment: Type: "AWS::ApiGateway::Deployment" DependsOn: - GameRequestApiMethod - ResultsRequestApiMethod Properties: RestApiId: !Ref RestApi StageDescription: DataTraceEnabled: true LoggingLevel: INFO MetricsEnabled: true StageName: !Ref ApiGatewayStageNameParameter Authorizer: Type: "AWS::ApiGateway::Authorizer" Properties: IdentitySource: method.request.header.Auth Name: CognitoAuthorizer ProviderARNs: - "Fn::GetAtt": - UserPool - Arn RestApiId: !Ref RestApi Type: COGNITO_USER_POOLS GameSessionEventHandlerLambdaFunction: Type: "AWS::Lambda::Function" Properties: Code: S3Bucket: !Ref LambdaZipS3BucketParameter S3Key: !Ref LambdaZipS3KeyParameter Description: Lambda function to handle game requests Environment: Variables: GameSessionPlacementTableName: !Ref GameSessionPlacementTable FunctionName: !Sub ${GameNameParameter}GameSessionEventHandlerLambda Handler: game_session_event_handler.handler MemorySize: 128 Role: !GetAtt GameSessionEventHandlerLambdaFunctionExecutionRole.Arn Runtime: python3.8 GameRequestApiMethod: Type: "AWS::ApiGateway::Method" Properties: AuthorizationType: COGNITO_USER_POOLS AuthorizerId: !Ref Authorizer HttpMethod: POST Integration: Type: AWS_PROXY IntegrationHttpMethod: POST Uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GameRequestLambdaFunction.Arn}/invocations" OperationName: GameRequest ResourceId: !Ref GameRequestApiResource RestApiId: !Ref RestApi GameSessionEventTopic: Type: "AWS::SNS::Topic" Properties: Subscription: - Endpoint: !GetAtt GameSessionEventHandlerLambdaFunction.Arn Protocol: lambda TopicName: !Sub ${GameNameParameter}GameSessionEventTopic ResultsRequestApiMethod: Type: "AWS::ApiGateway::Method" Properties: AuthorizationType: COGNITO_USER_POOLS AuthorizerId: !Ref Authorizer HttpMethod: POST Integration: Type: AWS_PROXY IntegrationHttpMethod: POST Uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${ResultsRequestLambdaFunction.Arn}/invocations" OperationName: ResultsRequest ResourceId: !Ref ResultsRequestApiResource RestApiId: !Ref RestApi ResultsRequestLambdaFunction: Type: "AWS::Lambda::Function" Properties: Code: S3Bucket: !Ref LambdaZipS3BucketParameter S3Key: !Ref LambdaZipS3KeyParameter Description: Lambda function to handle game requests Environment: Variables: GameSessionPlacementTableName: !Ref GameSessionPlacementTable MatchmakingRequestTableName: !Ref MatchmakingRequestTable FunctionName: !Sub ${GameNameParameter}ResultsRequestLambda Handler: results_request.handler MemorySize: 128 Role: !GetAtt ResultsRequestLambdaFunctionExecutionRole.Arn Runtime: python3.8 WebACLAssociation: Type: "AWS::WAFv2::WebACLAssociation" DependsOn: - ApiDeployment - WebACL Properties: ResourceArn: !Sub - "arn:aws:apigateway:${REGION}::/restapis/${REST_API_ID}/stages/${STAGE_NAME}" - REGION: !Ref "AWS::Region" REST_API_ID: !Ref RestApi STAGE_NAME: !Ref ApiGatewayStageNameParameter WebACLArn: !GetAtt WebACL.Arn GameSessionEventHandlerLambdaPermission: Type: "AWS::Lambda::Permission" Properties: Action: "lambda:InvokeFunction" FunctionName: !Ref GameSessionEventHandlerLambdaFunction Principal: sns.amazonaws.com SourceArn: !Ref GameSessionEventTopic ServerBuild: Type: "AWS::GameLift::Build" Properties: Name: !Ref BuildNameParameter OperatingSystem: !Ref BuildOperatingSystemParameter StorageLocation: Bucket: !Ref BuildS3BucketParameter Key: !Ref BuildS3KeyParameter RoleArn: !GetAtt BuildAccessRole.Arn Version: !Ref BuildVersionParameter GameRequestLambdaFunction: Type: "AWS::Lambda::Function" Properties: Code: S3Bucket: !Ref LambdaZipS3BucketParameter S3Key: !Ref LambdaZipS3KeyParameter Description: Lambda function to handle game requests Environment: Variables: GameSessionPlacementTableName: !Ref GameSessionPlacementTable MatchmakingRequestTableName: !Ref MatchmakingRequestTable SimpleMatchMakerTicketQueueUrl: !Ref SimpleMatchMakerTicketQueue FunctionName: !Sub ${GameNameParameter}GameRequestLambda Handler: game_request.handler MemorySize: 128 Role: !GetAtt GameRequestLambdaFunctionExecutionRole.Arn Runtime: python3.8 ResultsRequestLambdaFunctionApiGatewayPermission: Type: "AWS::Lambda::Permission" Properties: Action: "lambda:InvokeFunction" FunctionName: !GetAtt ResultsRequestLambdaFunction.Arn Principal: apigateway.amazonaws.com SourceArn: !Sub "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${RestApi}/*/*/*" GameSessionQueue: Type: "AWS::GameLift::GameSessionQueue" Properties: Destinations: - DestinationArn: !Sub "arn:aws:gamelift:${AWS::Region}:${AWS::AccountId}:fleet/${FleetResource}" Name: !Sub ${GameNameParameter}GameSessionQueue NotificationTarget: !Ref GameSessionEventTopic TimeoutInSeconds: !Ref QueueTimeoutInSecondsParameter GameRequestLambdaFunctionApiGatewayPermission: Type: "AWS::Lambda::Permission" Properties: Action: "lambda:InvokeFunction" FunctionName: !GetAtt GameRequestLambdaFunction.Arn Principal: apigateway.amazonaws.com SourceArn: !Sub "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${RestApi}/*/*/*" GameSessionEventTopicPolicy: Type: "AWS::SNS::TopicPolicy" DependsOn: GameSessionEventTopic Properties: PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: gamelift.amazonaws.com Action: - "sns:Publish" Resource: !Ref GameSessionEventTopic Condition: ArnLike: "aws:SourceArn": !GetAtt GameSessionQueue.Arn Topics: - Ref: GameSessionEventTopic SimpleMatchmakerLambdaFunction: Type: "AWS::Lambda::Function" Properties: Code: S3Bucket: !Ref LambdaZipS3BucketParameter S3Key: !Ref LambdaZipS3KeyParameter Description: Lambda function to handle game requests Environment: Variables: MatchmakingRequestTableName: !Ref MatchmakingRequestTable NumPlayersPerGame: !Ref NumPlayersPerGameParameter QueueName: !Ref GameSessionQueue FunctionName: !Sub ${GameNameParameter}SimpleMatchmakerLambda Handler: simple_matchmaker.handler MemorySize: 128 Role: !GetAtt SimpleMatchmakerLambdaFunctionExecutionRole.Arn Runtime: python3.8 FleetResource: Type: "AWS::GameLift::Fleet" Properties: BuildId: !Ref ServerBuild CertificateConfiguration: CertificateType: GENERATED Description: !Ref FleetDescriptionParameter DesiredEC2Instances: 1 EC2InboundPermissions: - FromPort: !Ref FleetTcpFromPortParameter IpRange: "0.0.0.0/0" Protocol: TCP ToPort: !Ref FleetTcpToPortParameter - FromPort: !Ref FleetUdpFromPortParameter IpRange: "0.0.0.0/0" Protocol: UDP ToPort: !Ref FleetUdpToPortParameter EC2InstanceType: c5.large FleetType: ON_DEMAND Locations: - Location: us-west-2 - Location: us-east-1 - Location: eu-west-1 Name: !Ref FleetNameParameter NewGameSessionProtectionPolicy: FullProtection ResourceCreationLimitPolicy: NewGameSessionsPerCreator: 5 PolicyPeriodInMinutes: 2 RuntimeConfiguration: GameSessionActivationTimeoutSeconds: 300 MaxConcurrentGameSessionActivations: 1 ServerProcesses: - ConcurrentExecutions: 1 LaunchPath: !Ref LaunchPathParameter Parameters: !Ref LaunchParametersParameter SimpleMatchMakerEventSource: Type: "AWS::Lambda::EventSourceMapping" Properties: BatchSize: !Ref NumPlayersPerGameParameter Enabled: true EventSourceArn: !GetAtt SimpleMatchMakerTicketQueue.Arn FunctionName: !GetAtt SimpleMatchmakerLambdaFunction.Arn Outputs: ApiGatewayEndpoint: Description: Url of ApiGateway Endpoint Value: !Sub "https://${RestApi}.execute-api.${AWS::Region}.amazonaws.com/${ApiGatewayStageNameParameter}/" UserPoolClientId: Description: Id of UserPoolClient Value: !Ref UserPoolClient