# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: MIT-0 AWSTemplateFormatVersion: 2010-09-09 Description: AWS CloudFormation Template to setup a cloud optimised Personal Gaming server. Metadata: 'AWS::CloudFormation::Interface': ParameterGroups: - Label: default: Personal Gaming Server Configuration Parameters: - GameServer - InstanceType - DiskSize - DiskType - GamingTCPTrafficPortStart - GamingTCPTrafficPortEnd - GamingUDPTrafficPortStart - GamingUDPTrafficPortEnd - Label: default: AWS Configuration Parameters: - KeyName - HostedZoneId - Domain - ShutdownTimeHours - ShutdownTimeMins - IdTagValue - Label: default: You should not need to change anything in this section. Parameters: - IdTagName - LatestAMIIDx86 ParameterLabels: GameServer: default: Choose what game server you wish to install InstanceType: default: Select the server size (we recommend selecting a larger instance for initial install and then changing this to a smaller size later). DiskSize: default: How many Gibibytes would you like? DiskType: default: What do you want your hard-drive made from (GP3 is recommended)? KeyName: default: Select the key pair you will use to access server OS Domain: default: Enter the domain name you will use. Note this should be controlled by the Hosted Zone above (see setup instructions earlier) HostedZoneId: default: Enter the ARN for the Hosted Zone that contains your DNS records (see earlier step) IdTagName: default: Tag name that will be used to identify your gaming server(s) IdTagValue: default: Tag value that will be used to identify your gaming server(s) ShutdownTimeHours: default: The time of day to shutdown the server automatically in hours (NOTE GMT/UTC TIME!! - not local time zone) ShutdownTimeMins: default: The minutes past the hours specfied above to automatically shut down your server LatestAMIIDx86: default: The AMI to use. This is the machine image and includes at a minimum the OS for the server GamingTCPTrafficPortStart: default: Start of the port range that should be accessible from the internet for TCP traffic. Leave blank if you only want to allow UDP traffic to your server. GamingTCPTrafficPortEnd: default: End of the port range that should be accessible from the internet for TCP traffic. GamingUDPTrafficPortStart: default: Start of the port range that should be accessible from the internet for UDP traffic. Leave blank if you only want to allow TCP traffic to your server. GamingUDPTrafficPortEnd: default: End of the port range that should be accessible from the internet for UDP traffic. Parameters: GameServer: Description: Please enter the URL for the shell script to install the game server of your choice - we call this the game cartridge URL. Please note this must be an uncompressed shell script to run on selected AMI (Ubuntu by default). Type: String AllowedPattern: https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*) ConstraintDescription: 'Must be a valid URL.' Default: https://raw.githubusercontent.com/aws-samples/personal-game-server-manager/main/Bash/valheim.sh KeyName: Description: >- This is required so you can access the server. If there is no option to select one, please go back and create one before trying to run this template again AllowedPattern: .+ ConstraintDescription: >- You must choose a keypair otherwise you won't be able to login to your server's OS Type: 'AWS::EC2::KeyPair::KeyName' DiskSize: Description: >- Size of disk - you must choose a disk size of at least 8GiB (because this is the size of the Linux AMI) AllowedPattern: '^([8-9]|([1-9]\d{1,}))$' ConstraintDescription: You must choose a disk size of at least 8 GiB Default: '8' Type: String DiskType: Description: Choose disk type. GP3 is recommended AllowedPattern: .+ AllowedValues: - gp3 - gp2 - standard - IO2 Default: gp3 ConstraintDescription: You must choose a disk type Type: String InstanceType: Description: 'Select the AWS EC2 instance type you want. You can change avaialble options later via the AWS Console - see blog entry for more details' AllowedPattern: .+ ConstraintDescription: You must choose a server size Type: String AllowedValues: - t3a.micro - t3a.small - t3a.medium - t3a.large - t3a.xlarge - t3a.2xlarge LatestAMIIDx86: Description: Defaults to the latest Ubuntu AMI ID. You can replace this if you would like to use a different AMI / Operating System. Type: 'AWS::SSM::Parameter::Value' Default: /aws/service/canonical/ubuntu/server/20.04/stable/current/amd64/hvm/ebs-gp2/ami-id GamingTCPTrafficPortStart: Description: Enter port number for your server ingress security group. This will open your gaming server to TCP traffic to a range of ports with this as the lower boundary. Type: String AllowedPattern: ^(^$|[0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$ ConstraintDescription: Must be a valid port number. GamingTCPTrafficPortEnd: Description: Enter port number for your server ingress security group. This will open your gaming server to TCP traffic to a range of ports with this as the upper boundary. Type: String AllowedPattern: ^(^$|[0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$ ConstraintDescription: Must be a valid port number. GamingUDPTrafficPortStart: Description: Enter port number for your server ingress security group. This will open your gaming server to UDP traffic to a range of ports with this as the lower boundary. Type: String AllowedPattern: ^(^$|[0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$ ConstraintDescription: Must be a valid port number. Default: '2456' GamingUDPTrafficPortEnd: Description: Enter port number for your server ingress security group. This will open your gaming server to UDP traffic to a range of ports with this as the upper boundary. Type: String AllowedPattern: ^(^$|[0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$ ConstraintDescription: Must be a valid port number. Default: '2458' Domain: Description: Domain name you will use to access your server (e.g. mygameserver.link). This is optional however without it you will need to configure a new IP address each stop/start Type: String HostedZoneId: Description: Select the ID of your hosted zone - if there isn't one please go back to the section that describes setting up a domain name and hosted zone Type: 'AWS::Route53::HostedZone::Id' ConstraintDescription: >- Although optional, it's required if you are using a custom domain (recommended) so the DNS record for your domain can be updated IdTagName: Description: The name of the tag that will be used to identify your gaming server(s). DO NOT CHANGE THIS VALUE AllowedPattern: mcServerFinder Default: mcServerFinder Type: String ConstraintDescription: Please set this to "mcserverfinder" this is required so we can find your server(s) to control them IdTagValue: Description: The value of the tag that will be used to identify your gaming server(s). This can be useful if you want to control different servers independently - such as if you wanted to spin up a server to test out some features or mods without risking your main server - in this case you may want to change the tag value to something like mcDev or mcTest for example. AllowedPattern: .+ Type: String Default: mcServer ConstraintDescription: This is a mandatory field - you must pust something in. We recommend not changing the default value. ShutdownTimeHours: Description: Setting a time here will automatically stop your server at the same time every day. You adjust the rule, add rules, changes days etc. later directly in the AWS EventBridge Console. IMPORTANT NOTE - the time must be set in GMT not your local time zone (also note therefore this time will appear to change when going from summer time to winter for relevant time zones). Leaving blank will not set an auto server shut down rule. Type: String AllowedValues: - 'None - do not auto shut-down' - '23' - '22' - '21' - '20' - '19' - '18' - '17' - '16' - '15' - '14' - '13' - '12' - '11' - '10' - '9' - '8' - '7' - '6' - '5' - '4' - '3' - '2' - '1' - '0' ShutdownTimeMins: Description: Minutes past the hours to auto shut down your server each day Type: String AllowedValues: - 'Not Applicable' - '0' - '5' - '10' - '15' - '20' - '25' - '30' - '35' - '40' - '45' - '50' - '55' Conditions: ShutdownTimeHoursIsNull: !Equals - !Ref ShutdownTimeHours - 'None - do not auto shut-down' ShutdownTimeMinsIsNull: !Equals - !Ref ShutdownTimeMins - 'None' noSecIngress: !And - !Condition hasTCP - !Condition hasUDP hasTCP: !Not - !Equals - !Ref GamingTCPTrafficPortStart - '' hasUDP: !Not - !Equals - !Ref GamingUDPTrafficPortStart - '' Resources: mcEC2Instance: Type: 'AWS::EC2::Instance' DependsOn: mcIgAttachment Properties: SecurityGroupIds: - !Ref mcSecurityGroup InstanceType: !Ref InstanceType CreditSpecification: CPUCredits: Standard SubnetId: !Ref mcSubnet IamInstanceProfile: !Ref mcInstanceProfile KeyName: !Ref KeyName Tags: - Key: Name Value: Cloudformation MC server - Key: !Ref IdTagName Value: !Ref IdTagValue - Key: domain Value: !Ref Domain - Key: hostedZoneId Value: !Ref HostedZoneId BlockDeviceMappings: - DeviceName: /dev/sda1 Ebs: DeleteOnTermination: 'true' Encrypted: 'false' VolumeSize: !Ref DiskSize VolumeType: !Ref DiskType ImageId: !Ref LatestAMIIDx86 UserData: Fn::Base64: !Sub - | #!/bin/bash -xe apt update apt upgrade -y apt install -y python-setuptools mkdir -p /opt/aws/bin wget https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-py3-latest.tar.gz python3 -m easy_install --script-dir /opt/aws/bin aws-cfn-bootstrap-py3-latest.tar.gz ln -s /root/aws-cfn-bootstrap-latest/init/ubuntu/cfn-hup /etc/init.d/cfn-hup echo ${AWS::StackName} > /tmp/mcParamName.txt mkdir -p /usr/games/install cd /usr/games/install wget -O gameserverinstall.sh ${GameServer} chmod +x gameserverinstall.sh ./gameserverinstall.sh /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource mcC2Instance --region ${AWS::Region} - GameServer: !Ref GameServer mcPwordParameter: Type: AWS::SSM::Parameter Properties: DataType: text Description: Parameter to store temp password randomly generated on initial stack creation Name: !Sub mcValheimPW-${AWS::StackName} Tier: Standard Type: String Value: tempValue mcInstanceProfile: Type: AWS::IAM::InstanceProfile Properties: Path: /service-role/ Roles: - !Ref mcGamingServerIamRole mcGamingServerIamRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: ec2.amazonaws.com Action: 'sts:AssumeRole' Description: IAM role to allow gaming EC2 server to update parameter with generated app password Path: /service-role/ Policies: - PolicyName: mcPersonalGamingServerIamRole PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: "ssm:PutParameter" Resource: !Sub - arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${paramName} - paramName: !Ref mcPwordParameter mcControlApi: Type: 'AWS::ApiGateway::RestApi' Properties: Description: "Primary API to control gaming server" Name: !Sub mcControlApi-${mcVPC} EndpointConfiguration: Types: - REGIONAL FailOnWarnings: true mcCognitoAuthoriser: Type: 'AWS::ApiGateway::Authorizer' Properties: IdentitySource: method.request.header.Authorization Name: mcCognitoAuthoriser ProviderARNs: - !GetAtt mcCognitoUserPool.Arn RestApiId: !Ref mcControlApi Type: COGNITO_USER_POOLS mcControlApiDeployment: DependsOn: mcApiGetInfoGetMethod Type: 'AWS::ApiGateway::Deployment' Properties: RestApiId: !Ref mcControlApi Description: Deployment of gaming control REST API StageName: prod mcControlApiResGetInfo: Type: 'AWS::ApiGateway::Resource' Properties: ParentId: !GetAtt - mcControlApi - RootResourceId PathPart: getinfo RestApiId: !Ref mcControlApi mcControlApiResResize: Type: 'AWS::ApiGateway::Resource' Properties: ParentId: !GetAtt - mcControlApi - RootResourceId PathPart: resize RestApiId: !Ref mcControlApi mcControlApiResStart: Type: 'AWS::ApiGateway::Resource' Properties: ParentId: !GetAtt - mcControlApi - RootResourceId PathPart: start RestApiId: !Ref mcControlApi mcControlApiResStop: Type: 'AWS::ApiGateway::Resource' Properties: ParentId: !GetAtt - mcControlApi - RootResourceId PathPart: stop RestApiId: !Ref mcControlApi mcApiGetInfoGetMethod: Type: 'AWS::ApiGateway::Method' Properties: ApiKeyRequired: false AuthorizationType: COGNITO_USER_POOLS AuthorizerId: !Ref mcCognitoAuthoriser HttpMethod: GET Integration: IntegrationHttpMethod: POST IntegrationResponses: - StatusCode: 200 ResponseParameters: method.response.header.Access-Control-Allow-Origin : "'*'" ResponseTemplates: application/json : '' PassthroughBehavior: WHEN_NO_TEMPLATES RequestTemplates: application/json : "{\r\n \"command\": \"getInfo\",\r\n \"mcTagName\": \"$input.params('mctagname')\",\r\n \"mcTagValue\": \"$input.params('mctagvalue')\"\r\n}" TimeoutInMillis: 15000 Type: AWS Uri: !Sub - arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${lambdaArn}/invocations - lambdaArn: !GetAtt mcStartStopLambda.Arn MethodResponses: - StatusCode: 200 ResponseModels: application/json : 'Empty' ResponseParameters: method.response.header.Access-Control-Allow-Origin : false OperationName: GetInfo RequestParameters: method.request.querystring.mctagname : false method.request.querystring.mctagvalue : false ResourceId: !Ref mcControlApiResGetInfo RestApiId: !Ref mcControlApi mcApiGetInfoOptionsMethod: Type: 'AWS::ApiGateway::Method' Properties: ApiKeyRequired: false AuthorizationType: NONE HttpMethod: OPTIONS Integration: IntegrationResponses: - StatusCode: 200 ResponseParameters: method.response.header.Access-Control-Allow-Headers : "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'" method.response.header.Access-Control-Allow-Origin : "'*'" method.response.header.Access-Control-Allow-Credentials : "'true'" method.response.header.Access-Control-Allow-Methods : "'GET,OPTIONS'" ResponseTemplates: application/json : '' PassthroughBehavior: WHEN_NO_MATCH RequestTemplates: application/json : '{"statusCode": 200}' TimeoutInMillis: 15000 Type: MOCK MethodResponses: - StatusCode: 200 ResponseModels: application/json : 'Empty' ResponseParameters: method.response.header.Access-Control-Allow-Headers : false method.response.header.Access-Control-Allow-Origin : false method.response.header.Access-Control-Allow-Credentials : false method.response.header.Access-Control-Allow-Methods : false OperationName: GetInfoOPTIONS ResourceId: !Ref mcControlApiResGetInfo RestApiId: !Ref mcControlApi mcApiStartGetMethod: Type: 'AWS::ApiGateway::Method' Properties: ApiKeyRequired: false AuthorizationType: COGNITO_USER_POOLS AuthorizerId: !Ref mcCognitoAuthoriser HttpMethod: GET Integration: IntegrationHttpMethod: POST IntegrationResponses: - StatusCode: 200 ResponseParameters: method.response.header.Access-Control-Allow-Origin : "'*'" ResponseTemplates: application/json : '' PassthroughBehavior: WHEN_NO_TEMPLATES RequestTemplates: application/json : "{\r\n \"command\": \"start\",\r\n \"mcTagName\": \"$input.params('mctagname')\",\r\n \"mcTagValue\": \"$input.params('mctagvalue')\"\r\n}" TimeoutInMillis: 15000 Type: AWS Uri: !Sub - arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${lambdaArn}/invocations - lambdaArn: !GetAtt mcStartStopLambda.Arn MethodResponses: - StatusCode: 200 ResponseModels: application/json : 'Empty' ResponseParameters: method.response.header.Access-Control-Allow-Origin : false OperationName: Start RequestParameters: method.request.querystring.mctagname : false method.request.querystring.mctagvalue : false ResourceId: !Ref mcControlApiResStart RestApiId: !Ref mcControlApi mcApiStartOptionsMethod: Type: 'AWS::ApiGateway::Method' Properties: ApiKeyRequired: false AuthorizationType: NONE HttpMethod: OPTIONS Integration: IntegrationResponses: - StatusCode: 200 ResponseParameters: method.response.header.Access-Control-Allow-Headers : "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'" method.response.header.Access-Control-Allow-Origin : "'*'" method.response.header.Access-Control-Allow-Credentials : "'true'" method.response.header.Access-Control-Allow-Methods : "'GET,OPTIONS'" ResponseTemplates: application/json : '' PassthroughBehavior: WHEN_NO_MATCH RequestTemplates: application/json : '{"statusCode": 200}' TimeoutInMillis: 15000 Type: MOCK MethodResponses: - StatusCode: 200 ResponseModels: application/json : 'Empty' ResponseParameters: method.response.header.Access-Control-Allow-Headers : false method.response.header.Access-Control-Allow-Origin : false method.response.header.Access-Control-Allow-Credentials : false method.response.header.Access-Control-Allow-Methods : false OperationName: StartOPTIONS ResourceId: !Ref mcControlApiResStart RestApiId: !Ref mcControlApi mcApiStopGetMethod: Type: 'AWS::ApiGateway::Method' Properties: ApiKeyRequired: false AuthorizationType: COGNITO_USER_POOLS AuthorizerId: !Ref mcCognitoAuthoriser HttpMethod: GET Integration: IntegrationHttpMethod: POST IntegrationResponses: - StatusCode: 200 ResponseParameters: method.response.header.Access-Control-Allow-Origin : "'*'" ResponseTemplates: application/json : '' PassthroughBehavior: WHEN_NO_TEMPLATES RequestTemplates: application/json : "{\r\n \"command\": \"stop\",\r\n \"mcTagName\": \"$input.params('mctagname')\",\r\n \"mcTagValue\": \"$input.params('mctagvalue')\"\r\n}" TimeoutInMillis: 15000 Type: AWS Uri: !Sub - arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${lambdaArn}/invocations - lambdaArn: !GetAtt mcStartStopLambda.Arn MethodResponses: - StatusCode: 200 ResponseModels: application/json : 'Empty' ResponseParameters: method.response.header.Access-Control-Allow-Origin : false OperationName: Stop RequestParameters: method.request.querystring.mctagname : false method.request.querystring.mctagvalue : false ResourceId: !Ref mcControlApiResStop RestApiId: !Ref mcControlApi mcApiStopOptionsMethod: Type: 'AWS::ApiGateway::Method' Properties: ApiKeyRequired: false AuthorizationType: NONE HttpMethod: OPTIONS Integration: IntegrationResponses: - StatusCode: 200 ResponseParameters: method.response.header.Access-Control-Allow-Headers : "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'" method.response.header.Access-Control-Allow-Origin : "'*'" method.response.header.Access-Control-Allow-Credentials : "'true'" method.response.header.Access-Control-Allow-Methods : "'GET,OPTIONS'" ResponseTemplates: application/json : '' PassthroughBehavior: WHEN_NO_MATCH RequestTemplates: application/json : '{"statusCode": 200}' TimeoutInMillis: 15000 Type: MOCK MethodResponses: - StatusCode: 200 ResponseModels: application/json : 'Empty' ResponseParameters: method.response.header.Access-Control-Allow-Headers : false method.response.header.Access-Control-Allow-Origin : false method.response.header.Access-Control-Allow-Credentials : false method.response.header.Access-Control-Allow-Methods : false OperationName: StopOPTIONS ResourceId: !Ref mcControlApiResStop RestApiId: !Ref mcControlApi mcApiResizeGetMethod: Type: 'AWS::ApiGateway::Method' Properties: ApiKeyRequired: false AuthorizationType: COGNITO_USER_POOLS AuthorizerId: !Ref mcCognitoAuthoriser HttpMethod: GET Integration: IntegrationHttpMethod: POST IntegrationResponses: - StatusCode: 200 ResponseParameters: method.response.header.Access-Control-Allow-Origin : "'*'" ResponseTemplates: application/json : '' PassthroughBehavior: WHEN_NO_TEMPLATES RequestTemplates: application/json : "{\r\n \"command\": \"reSize\",\r\n \"mcTagName\": \"$input.params('mctagname')\",\r\n \"mcTagValue\": \"$input.params('mctagvalue')\",\r\n \"reSizeType\": \"$input.params('resize')\"\r\n}" TimeoutInMillis: 15000 Type: AWS Uri: !Sub - arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${lambdaArn}/invocations - lambdaArn: !GetAtt mcStartStopLambda.Arn MethodResponses: - StatusCode: 200 ResponseModels: application/json : 'Empty' ResponseParameters: method.response.header.Access-Control-Allow-Origin : false OperationName: Resize RequestParameters: method.request.querystring.mctagname : false method.request.querystring.mctagvalue : false method.request.querystring.resize : false ResourceId: !Ref mcControlApiResResize RestApiId: !Ref mcControlApi mcApiResizeOptionsMethod: Type: 'AWS::ApiGateway::Method' Properties: ApiKeyRequired: false AuthorizationType: NONE HttpMethod: OPTIONS Integration: IntegrationResponses: - StatusCode: 200 ResponseParameters: method.response.header.Access-Control-Allow-Headers : "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'" method.response.header.Access-Control-Allow-Origin : "'*'" method.response.header.Access-Control-Allow-Credentials : "'true'" method.response.header.Access-Control-Allow-Methods : "'GET,OPTIONS'" ResponseTemplates: application/json : '' PassthroughBehavior: WHEN_NO_MATCH RequestTemplates: application/json : '{"statusCode": 200}' TimeoutInMillis: 15000 Type: MOCK MethodResponses: - StatusCode: 200 ResponseModels: application/json : 'Empty' ResponseParameters: method.response.header.Access-Control-Allow-Headers : false method.response.header.Access-Control-Allow-Origin : false method.response.header.Access-Control-Allow-Credentials : false method.response.header.Access-Control-Allow-Methods : false OperationName: ResizeOPTIONS ResourceId: !Ref mcControlApiResResize RestApiId: !Ref mcControlApi mcStateMachineDNS: Type: 'AWS::StepFunctions::StateMachine' Properties: Definition: Comment: Step machine to update DNS while minimising Lambda execution runtime StartAt: getIpUpdateDNS States: getIpUpdateDNS: Type: Task Resource: '${mcUpdateDns}' Retry: - ErrorEquals: - noIpFound IntervalSeconds: 3 MaxAttempts: 10 BackoffRate: 1 - ErrorEquals: - States.TaskFailed IntervalSeconds: 30 MaxAttempts: 2 BackoffRate: 2 - ErrorEquals: - States.ALL IntervalSeconds: 5 MaxAttempts: 5 BackoffRate: 2 End: true DefinitionSubstitutions: mcUpdateDns: !GetAtt mcUpdateDnsLambda.Arn RoleArn: !GetAtt mcStateMachineIamRole.Arn StateMachineName: !Sub ${AWS::StackName}-mcStateMachine StateMachineType: STANDARD mcCognitoUserPool: Type: AWS::Cognito::UserPool Properties: AdminCreateUserConfig: AllowAdminCreateUserOnly: True AutoVerifiedAttributes: - email DeviceConfiguration: ChallengeRequiredOnNewDevice: True DeviceOnlyRememberedOnUserPrompt: True Policies: PasswordPolicy: MinimumLength: 8 RequireLowercase: True RequireNumbers: True RequireSymbols: True RequireUppercase: True TemporaryPasswordValidityDays: 3 Schema: - Name: email Required: true UsernameAttributes: - email UsernameConfiguration: CaseSensitive: false mcUserPoolDomain: Type: AWS::Cognito::UserPoolDomain Properties: Domain: !Join - '' - !Split - '.cloudfront.net' - !GetAtt mcCloudFront.DomainName UserPoolId: !Ref mcCognitoUserPool mcCognitoAppClient: Type: AWS::Cognito::UserPoolClient Properties: AccessTokenValidity: 1 IdTokenValidity: 1 AllowedOAuthFlows: - code AllowedOAuthFlowsUserPoolClient: true AllowedOAuthScopes: - openid - aws.cognito.signin.user.admin CallbackURLs: - !Sub https://${mcCloudFront.DomainName}/signed_in.html DefaultRedirectURI: !Sub https://${mcCloudFront.DomainName}/signed_in.html ExplicitAuthFlows: - ALLOW_REFRESH_TOKEN_AUTH - ALLOW_USER_SRP_AUTH - ALLOW_CUSTOM_AUTH GenerateSecret: false LogoutURLs: - !Sub https://${mcCloudFront.DomainName}/logout.html PreventUserExistenceErrors: ENABLED SupportedIdentityProviders: - COGNITO UserPoolId: !Ref mcCognitoUserPool mcStateMachineIamRole: Type: 'AWS::IAM::Role' Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: states.amazonaws.com Action: 'sts:AssumeRole' Description: State machine execution role Path: /service-role/ Policies: - PolicyName: mcStateMachineInline PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - 'lambda:InvokeFunction' Resource: - !GetAtt mcUpdateDnsLambda.Arn mcCustomResourceLambdaIamRole: Type: 'AWS::IAM::Role' Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: 'sts:AssumeRole' Description: Role for custom resource to allow lambda function to execute Path: /service-role/ Policies: - PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - 'logs:CreateLogGroup' - 'logs:CreateLogStream' - 'logs:PutLogEvents' Resource: 'arn:aws:logs:*:*:*' - Effect: Allow Action: - 's3:ListBucket' Resource: - !GetAtt mcLambdaSourceFilesS3.Arn - !GetAtt mcS3Bucket.Arn - !GetAtt mcLoggingBucket.Arn - Effect: Allow Action: - 's3:PutObject' - 's3:DeleteObject' - 's3:GetObject' Resource: - !Sub - ${bucket}/* - bucket: !GetAtt mcLambdaSourceFilesS3.Arn - !Sub - ${bucket}/* - bucket: !GetAtt mcS3Bucket.Arn - !Sub - ${bucket}/* - bucket: !GetAtt mcLoggingBucket.Arn PolicyName: mcCustomResourceInline mcUpdateDnsLambdaIamRole: Type: 'AWS::IAM::Role' Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: 'sts:AssumeRole' Description: Execution role for mcStartStop Lambda function Path: /service-role/ Policies: - PolicyName: mcUpdateDnsInline PolicyDocument: !Sub - | { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action":[ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": "*" }, { "Effect": "Allow", "Action": "route53:ChangeResourceRecordSets", "Resource": "arn:aws:route53:::hostedzone/${hostedZone}" }, { "Effect": "Allow", "Action":[ "ec2:DescribeInstances", "ec2:DescribeTags" ], "Resource": "*" } ] } - hostedZone: !Ref HostedZoneId mcStartStopLambdaIamRole: Type: 'AWS::IAM::Role' Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: 'sts:AssumeRole' Description: Execution role for mcStartStop Lambda function Path: /service-role/ Policies: - PolicyName: mcStartStopInline PolicyDocument: !Sub - | { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action":[ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": "*" }, { "Effect": "Allow", "Action": "states:StartExecution", "Resource": "arn:aws:states:${AWS::Region}:${AWS::AccountId}:stateMachine:${AWS::StackName}-mcStateMachine" }, { "Effect": "Allow", "Action":[ "ec2:DescribeInstances", "ec2:DescribeTags", "ec2:DescribeInstanceTypes" ], "Resource": "*" }, { "Effect": "Allow", "Action":[ "ec2:StartInstances", "ec2:ModifyInstanceAttribute", "ec2:StopInstances" ], "Resource": "arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:instance/*", "Condition": { "StringEquals": { "ec2:ResourceTag/${tagname}": "${tagvalue}" } } } ] } - tagname: !Ref IdTagName tagvalue: !Ref IdTagValue mcBackupIamRole: Type: 'AWS::IAM::Role' Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: backup.amazonaws.com Action: 'sts:AssumeRole' Description: Role for custom resource to allow lambda function to execute Path: /service-role/ Policies: - PolicyName: mcBackupInline PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - ec2:CreateTags - ec2:DeleteSnapshot Resource: arn:aws:ec2:*::snapshot/* - Effect: Allow Action: - ec2:CreateImage - ec2:DeregisterImage Resource: "*" - Effect: Allow Action: - ec2:CopyImage - ec2:CopySnapshot Resource: "*" - Effect: Allow Action: - ec2:CreateTags Resource: arn:aws:ec2:*:*:image/* - Effect: Allow Action: - ec2:DescribeSnapshots - ec2:DescribeTags - ec2:DescribeImages - ec2:DescribeInstances - ec2:DescribeInstanceAttribute - ec2:DescribeInstanceCreditSpecifications - ec2:DescribeNetworkInterfaces - ec2:DescribeElasticGpus - ec2:DescribeSpotInstanceRequests Resource: "*" - Effect: Allow Action: - ec2:CreateSnapshot - ec2:DeleteSnapshot - ec2:DescribeVolumes - ec2:DescribeSnapshots Resource: - arn:aws:ec2:*::snapshot/* - arn:aws:ec2:*:*:volume/* - Action: - tag:GetResources Resource: "*" Effect: Allow - Effect: Allow Action: - backup:DescribeBackupVault - backup:CopyIntoBackupVault Resource: arn:aws:backup:*:*:backup-vault:* mcKmsKey: Type: AWS::KMS::Key Properties: Description: "Encryption key gaming server backups" EnableKeyRotation: True Enabled: True KeyPolicy: Version: "2012-10-17" Statement: - Effect: Allow Principal: "AWS": { "Fn::Sub": "arn:${AWS::Partition}:iam::${AWS::AccountId}:root" } Action: - kms:* Resource: "*" mcBackupVault: Type: "AWS::Backup::BackupVault" Properties: BackupVaultName: !Sub "${AWS::StackName}-mcBackupVaultWithDailyBackups" EncryptionKeyArn: !GetAtt mcKmsKey.Arn mcBackupPlan: Type: "AWS::Backup::BackupPlan" Properties: BackupPlan: BackupPlanName: "mcBackupPlan" BackupPlanRule: - RuleName: "mcDailyBackups" TargetBackupVault: !Ref mcBackupVault ScheduleExpression: "cron(0 5 ? * * *)" Lifecycle: DeleteAfterDays: 7 TagBasedBackupSelection: Type: "AWS::Backup::BackupSelection" Properties: BackupSelection: SelectionName: "TagBasedBackupSelection" IamRoleArn: !GetAtt mcBackupIamRole.Arn ListOfTags: - ConditionType: "STRINGEQUALS" ConditionKey: !Ref IdTagName ConditionValue: !Ref IdTagValue BackupPlanId: !Ref mcBackupPlan mcCopyToS3Lambda: Type: 'AWS::Lambda::Function' Properties: Code: ZipFile: | import os import urllib.request from urllib.parse import urlparse import boto3 import zipfile import cfnresponse def lambda_handler(event, context): s3 = boto3.resource('s3') properties = event['ResourceProperties'] urls = properties['sourceUrls'] bucket = properties['targetS3Bucket'] if event['RequestType'] == 'Create': try: for i in urls: url = i.get('url') urlPath = urlparse(url).path fileName = os.path.basename(urlPath) filePath = '/tmp/' + fileName urllib.request.urlretrieve(url, filePath) fileName = os.path.basename(filePath) if i.get('zip'): zipFileName = os.path.splitext(fileName)[0]+'.zip' zipFilePath = '/tmp/' + zipFileName arcName = i.get('filename') zf = zipfile.ZipFile(zipFilePath, mode ='w') zf.write(filePath, arcname=arcName) zf.close() fileName = zipFileName filePath = zipFilePath contentType = i.get('content-type') if i.get('folder'): fileName = i.get('folder')+ '/' + fileName s3.Object(bucket, fileName).put(Body=open(filePath, 'rb'),ContentType=contentType) cfnresponse.send(event, context, cfnresponse.SUCCESS, {'Response': 'Success - files copied'}) except Exception as e: print(e) cfnresponse.send(event, context, cfnresponse.FAILED, {'Response': 'Failed to copy files'}) if event['RequestType'] == 'Delete': try: s3bucket = s3.Bucket(bucket) s3bucket.objects.delete() cfnresponse.send(event, context, cfnresponse.SUCCESS, {'Response': 'Success- bucket emptied'}) except Exception as e: print(e) cfnresponse.send(event, context, cfnresponse.FAILED, {'Response': 'Failed to empty bucket please clean up manually'}) cfnresponse.send(event, context, cfnresponse.SUCCESS, {'Response': 'Update action not Supported.'}) Description: Custom resource lambda to copy required code files from github to S3 Role: !GetAtt mcCustomResourceLambdaIamRole.Arn Handler: index.lambda_handler Runtime: python3.8 Timeout: 10 mcUpdateConfigLambda: Type: AWS::Lambda::Function Properties: Code: ZipFile: | import boto3 import cfnresponse def lambda_handler(event, context): s3 = boto3.client('s3') properties = event['ResourceProperties'] configfile = properties['configfile'] bucket = properties['S3Bucket'] if event['RequestType'] == 'Create': try: with open('/tmp/configuration.js', 'wb') as f: s3.download_fileobj(bucket, configfile, f) f.close() f = open('/tmp/configuration.js','rt') configdata = f.read() configdata = configdata.replace('REPLACE-WITH-CFURL', properties['cloudfrontURL']) configdata = configdata.replace('REPLACE-WITH-COGNITO', properties['cognitoclientid']) configdata = configdata.replace('REPLACE-WITH-COGDOMAIN', properties['cognitodomain']) configdata = configdata.replace('REPLACE-WITH-APIURL', properties['apiurl']) configdata = configdata.replace('REPLACE-WITH-IDTAGNAME', properties['idtagname']) configdata = configdata.replace('REPLACE-WITH-IDTAGVALUE', properties['idtagvalue']) configdata = configdata.replace('REPLACE-WITH-STACKNAME', properties['stackname']) configdata = configdata.replace('REPLACE-WITH-POOLS-ID', properties['coguserpool']) f.close() f = open('/tmp/configuration.js','wt') f.write(configdata) f.close() s3.put_object(Bucket=bucket,Key=configfile,Body=open('/tmp/configuration.js','rb'),ContentType="application/javascript") cfnresponse.send(event, context, cfnresponse.SUCCESS, {'Response': 'Updated config file.'}) except Exception as e: print(e) cfnresponse.send(event, context, cfnresponse.FAILED, {'Response': 'Failed to copy files'}) cfnresponse.send(event, context, cfnresponse.SUCCESS, {'Response': 'Update or Delete action not Supported.'}) Description: Custom resource lambda to update config files on stack build complete Role: !GetAtt mcCustomResourceLambdaIamRole.Arn Handler: index.lambda_handler Runtime: python3.8 Timeout: 10 mcgetSrcIpsLambda: Type: AWS::Lambda::Function Properties: Code: ZipFile: | import json from urllib.request import urlopen import cfnresponse def lambda_handler(event, context): if event['RequestType'] == 'Create': try: ip_ranges = urlopen('https://ip-ranges.amazonaws.com/ip-ranges.json') ip_ranges = json.loads(ip_ranges.read())['prefixes'] ec2instanceconnect_ips = [item['ip_prefix'] for item in ip_ranges if item["service"] == "EC2_INSTANCE_CONNECT" and item["region"] == event['ResourceProperties']['region']] ec2instanceconnect_ips = ec2instanceconnect_ips[0] responseData = {'srcip': ec2instanceconnect_ips} cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData) except Exception as e: print(e) cfnresponse.send(event, context, cfnresponse.FAILED, {'Response': 'Failed to get source IP for EC2 instance connect though I am perplexed as to why','srcip':'0.0.0.0/0'}) else: cfnresponse.send(event, context, cfnresponse.SUCCESS, {'Response': 'Update or Delete action not Supported.'}) Description: Custom resource lambda to obtain source IP address range for EC2 Instance Connect in current region Role: !GetAtt mcCustomResourceLambdaIamRole.Arn Handler: index.lambda_handler Runtime: python3.8 Timeout: 10 mcStartStopLambda: Type: 'AWS::Lambda::Function' DependsOn: mcCopyLambdaFiles Properties: Code: S3Bucket: !Ref mcLambdaSourceFilesS3 S3Key: gaming_server_start_stop-v1_0.zip Description: Lambda function to control gaming server instances Environment: Variables: micro: t3a.micro small: t3a.small medium: t3a.medium large: t3a.large stepfunctionarn: !Sub 'arn:aws:states:${AWS::Region}:${AWS::AccountId}:stateMachine:${AWS::StackName}-mcStateMachine' Handler: lambda_function.lambda_handler MemorySize: 128 PackageType: Zip Role: !GetAtt mcStartStopLambdaIamRole.Arn Runtime: python3.8 Timeout: 10 mcUpdateDnsLambda: Type: 'AWS::Lambda::Function' DependsOn: mcCopyLambdaFiles Properties: Code: S3Bucket: !Ref mcLambdaSourceFilesS3 S3Key: mcUpdateDNS-v1_0.zip Description: Lambda to update DNS Handler: lambda_function.lambda_handler MemorySize: 128 PackageType: Zip Role: !GetAtt mcUpdateDnsLambdaIamRole.Arn Runtime: python3.8 Timeout: 10 mcLambdaSourceFilesS3: Type: AWS::S3::Bucket Properties: AccessControl: Private PublicAccessBlockConfiguration: BlockPublicAcls: True BlockPublicPolicy: True IgnorePublicAcls: True RestrictPublicBuckets: True LoggingConfiguration: DestinationBucketName: !Ref mcLoggingBucket LogFilePrefix: mcS3BucketLogs/ mcS3Bucket: Type: AWS::S3::Bucket Properties: AccessControl: Private PublicAccessBlockConfiguration: BlockPublicAcls: True BlockPublicPolicy: True IgnorePublicAcls: True RestrictPublicBuckets: True LoggingConfiguration: DestinationBucketName: !Ref mcLoggingBucket LogFilePrefix: mcS3BucketLogs/ mcS3BucketPolicy: Type: AWS::S3::BucketPolicy Properties: Bucket: !Ref mcS3Bucket PolicyDocument: Statement: - Action: - 's3:GetObject' Effect: Allow Resource: !Sub - 'arn:aws:s3:::${mcS3Bucket}/*' - mcS3Bucket: !Ref mcS3Bucket Principal: AWS: !Sub - 'arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ${OriginAccessiId}' - OriginAccessiId: !Ref mcOriginAccessId mcLoggingBucket: Type: AWS::S3::Bucket Properties: BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: 'aws:kms' KMSMasterKeyID: KMS-KEY-ARN AccessControl: LogDeliveryWrite OwnershipControls: Rules: - ObjectOwnership: BucketOwnerPreferred mcOriginAccessId: Type: AWS::CloudFront::CloudFrontOriginAccessIdentity Properties: CloudFrontOriginAccessIdentityConfig: Comment: Origin Access ID for front end control site mcCloudFront: Type: AWS::CloudFront::Distribution DependsOn: mcCopyFrontEndFiles Properties: DistributionConfig: Comment: Gaming server control centre DefaultCacheBehavior: AllowedMethods: - GET - HEAD - OPTIONS CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6 TargetOriginId: mcCloudFrontS3OriginID ViewerProtocolPolicy: redirect-to-https DefaultRootObject: index.html Enabled: True HttpVersion: http2 IPV6Enabled: true Logging: Bucket: !GetAtt mcLoggingBucket.DomainName IncludeCookies: True Prefix: mcCloudFrontLogs/ Origins: - DomainName: !Sub ${mcS3Bucket}.s3.${AWS::Region}.amazonaws.com Id: mcCloudFrontS3OriginID S3OriginConfig: OriginAccessIdentity: !Sub origin-access-identity/cloudfront/${mcOriginAccessId} PriceClass: PriceClass_100 ViewerCertificate: CloudFrontDefaultCertificate: True mcStartStopLambdaPermissionGetInfo: Type: AWS::Lambda::Permission Properties: Action: lambda:InvokeFunction FunctionName: !GetAtt mcStartStopLambda.Arn Principal: apigateway.amazonaws.com SourceArn: !Sub arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${mcControlApi}/*/GET/getinfo mcStartStopLambdaPermissionStart: Type: AWS::Lambda::Permission Properties: Action: lambda:InvokeFunction FunctionName: !GetAtt mcStartStopLambda.Arn Principal: apigateway.amazonaws.com SourceArn: !Sub arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${mcControlApi}/*/GET/start mcStartStopLambdaPermissionStop: Type: AWS::Lambda::Permission Properties: Action: lambda:InvokeFunction FunctionName: !GetAtt mcStartStopLambda.Arn Principal: apigateway.amazonaws.com SourceArn: !Sub arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${mcControlApi}/*/GET/stop mcStartStopLambdaPermissionResize: Type: AWS::Lambda::Permission Properties: Action: lambda:InvokeFunction FunctionName: !GetAtt mcStartStopLambda.Arn Principal: apigateway.amazonaws.com SourceArn: !Sub arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${mcControlApi}/*/GET/resize mcSecurityGroup: Type: 'AWS::EC2::SecurityGroup' Properties: Tags: - Key: Name Value: CF Gaming server Security Group GroupDescription: Enable HTTP access via port 80 locked down to the load balancer VpcId: !Ref mcVPC mcSecurityGroupIngressTCP: Type: AWS::EC2::SecurityGroupIngress Condition: hasTCP Properties: Description: Rule to allow gaming clients connect to your game server over TCP IPv4 GroupId: !Ref mcSecurityGroup IpProtocol: TCP FromPort: !Ref GamingTCPTrafficPortStart ToPort: !Ref GamingTCPTrafficPortEnd CidrIp: 0.0.0.0/0 mcSecurityGroupIngressUDP: Type: AWS::EC2::SecurityGroupIngress Condition: hasUDP Properties: Description: Rule to allow gaming clients connect to your game server over UDP IPv4 GroupId: !Ref mcSecurityGroup IpProtocol: UDP FromPort: !Ref GamingUDPTrafficPortStart ToPort: !Ref GamingUDPTrafficPortEnd CidrIp: 0.0.0.0/0 mcSecurityGroupIngressEC2Connect: Type: AWS::EC2::SecurityGroupIngress Properties: Description: Rule to allow EC2 Instance Connect to SSH into instance GroupId: !Ref mcSecurityGroup IpProtocol: tcp FromPort: 22 ToPort: 22 CidrIp: !GetAtt mcGetInstanceConnectIps.srcip mcInternetGateway: Type: 'AWS::EC2::InternetGateway' Properties: Tags: - Key: Name Value: CF Gamine Server IG mcRouteTable: Type: 'AWS::EC2::RouteTable' Properties: Tags: - Key: Name Value: CF Gaming Server Route Table VpcId: !Ref mcVPC mcRoute: Type: 'AWS::EC2::Route' Properties: DestinationCidrBlock: 0.0.0.0/0 GatewayId: !Ref mcInternetGateway RouteTableId: !Ref mcRouteTable mcSubnet: Type: 'AWS::EC2::Subnet' Properties: Tags: - Key: Name Value: CF Gaming Server Subnet CidrBlock: 10.0.0.0/24 MapPublicIpOnLaunch: 'true' VpcId: !Ref mcVPC mcVPC: Type: 'AWS::EC2::VPC' Properties: Tags: - Key: Name Value: Personal Gaming Server VPC CidrBlock: 10.0.0.0/16 EnableDnsHostnames: 'true' EnableDnsSupport: 'true' mcIgAttachment: Type: 'AWS::EC2::VPCGatewayAttachment' Properties: VpcId: !Ref mcVPC InternetGatewayId: !Ref mcInternetGateway mcRouteTblAttach: Type: 'AWS::EC2::SubnetRouteTableAssociation' Properties: RouteTableId: !Ref mcRouteTable SubnetId: !Ref mcSubnet mcBedTime: Type: AWS::Events::Rule Properties: Description: Rule to shutdown server at certain time every day ScheduleExpression: !Join - '' - - 'cron(' - !If - ShutdownTimeHoursIsNull - '0' - !Ref ShutdownTimeMins - ' ' - !If - ShutdownTimeHoursIsNull - '0' - !Ref ShutdownTimeHours - ' * * ? *)' State: !If - ShutdownTimeHoursIsNull - DISABLED - ENABLED Targets: - Arn: !GetAtt mcStartStopLambda.Arn Id: mcStartStopLambdaFunction Input: !Sub - '{ "command": "stop", "mcTagName": "${idtagname}", "mcTagValue": "${idtagvalue}" }' - idtagname: !Ref IdTagName idtagvalue: !Ref IdTagValue mcBedTimeLambdaPermission: Type: AWS::Lambda::Permission Properties: FunctionName: !Ref mcStartStopLambda Action: lambda:InvokeFunction Principal: events.amazonaws.com SourceArn: !GetAtt mcBedTime.Arn mcCopyFrontEndFiles: Type: Custom::WebFileCopy Properties: ServiceToken: !GetAtt mcCopyToS3Lambda.Arn targetS3Bucket: !Ref mcS3Bucket sourceUrls: - url: https://raw.githubusercontent.com/aws-samples/personal-game-server-manager/main/FrontEnd/styles.css content-type: text/css - url: https://raw.githubusercontent.com/aws-samples/personal-game-server-manager/main/FrontEnd/index.html content-type: text/html - url: https://raw.githubusercontent.com/aws-samples/personal-game-server-manager/main/FrontEnd/signed_in.html content-type: text/html - url: https://raw.githubusercontent.com/aws-samples/personal-game-server-manager/main/FrontEnd/logout.html content-type: text/html - url: https://raw.githubusercontent.com/aws-samples/personal-game-server-manager/main/FrontEnd/js/config.js content-type: application/javascript folder: js - url: https://raw.githubusercontent.com/aws-samples/personal-game-server-manager/main/FrontEnd/js/index.js content-type: application/javascript folder: js mcCopyLambdaFiles: Type: Custom::WebFileCopy Properties: ServiceToken: !GetAtt mcCopyToS3Lambda.Arn targetS3Bucket: !Ref mcLambdaSourceFilesS3 sourceUrls: - url: https://raw.githubusercontent.com/aws-samples/personal-game-server-manager/main/Lambda/mcUpdateDNS-v1_0.py content-type: application/zip zip: True filename: lambda_function.py - url: https://raw.githubusercontent.com/aws-samples/personal-game-server-manager/main/Lambda/gaming_server_start_stop-v1_0.py content-type: application/zip zip: True filename: lambda_function.py mcUpdateConfig: Type: Custom::UpdateFileInS3 Properties: ServiceToken: !GetAtt mcUpdateConfigLambda.Arn configfile: js/config.js S3Bucket: !Ref mcS3Bucket cloudfrontURL: !Sub https://${mcCloudFront.DomainName} cognitoclientid: !Ref mcCognitoAppClient cognitodomain: !Sub ${mcUserPoolDomain}.auth.${AWS::Region}.amazoncognito.com apiurl: !Sub https://${mcControlApi}.execute-api.${AWS::Region}.amazonaws.com/prod/ idtagname: !Ref IdTagName idtagvalue: !Ref IdTagValue stackname: !Ref AWS::StackName coguserpool: !Ref mcCognitoUserPool mcGetInstanceConnectIps: Type: Custom::GetSourceIps Properties: ServiceToken: !GetAtt mcgetSrcIpsLambda.Arn region: !Ref AWS::Region Outputs: mcControlApiUrl: Description: This is the URL for the API to control your gaming server instances Value: !Sub https://${mcControlApi}.execute-api.${AWS::Region}.amazonaws.com/prod/ mcPublicIp: Description: This is the public IP for your gaming server, you will need to run a "start" command to associate your domain name with it (if you selected one) Value: !GetAtt mcEC2Instance.PublicIp mcCognitoUserPoolId: Description: Unique user pool ID for the people you want to be able to control your server Value: !Ref mcCognitoUserPool mcCloudfrontUrl: Description: The default domain of your control website. Note you can update this to be a CNAME of your custom domain. Value: !Sub https://${mcCloudFront.DomainName} mcS3Bucket: Description: The name of the created S3 bucket Value: !GetAtt mcS3Bucket.Arn mcTagName: Description: Tag name used. This should not have been changed, and should be mcServerFinder Value: !Ref IdTagName mcTagValue: Description: Tag value selected to identify servers to be controlled by web app Value: !Ref IdTagValue mcCognitoDomainName: Description: The domain name for the Cognito hosted UI Value: !Sub ${mcUserPoolDomain}.auth.${AWS::Region}.amazoncognito.com mcCognitoClientID: Description: The clientID for the Cognito user pool Value: !Ref mcCognitoAppClient