cloudformation/appstack.yaml (1,223 lines of code) (raw):

AWSTemplateFormatVersion: '2010-09-09' Description: Indexing, search, proxying and retrieval for glacier-backed S3 Parameters: App: Type: String Description: Application identifier for RiffRaff Default: archivehunter Stack: Type: String Description: Stack identifier for RiffRaff Default: multimedia Stage: Type: String AllowedValues: - CODE - DEV - PROD Description: Deployment stage SentryDSN: Type: String Description: DSN for connecting to Sentry SentryPublicDSN: Type: String Description: Sentry public DSN for javascript LoggingStreamName: Description: Name of a Kinesis stream to send logs to Type: String AmiId: Type: String Description: ID of the base image to build instances from. Build this with Amigo. DeploymentDomain: Type: String Description: DNS domain that this is going to be deployed into. This is needed for pan-domain authentication to work (see readme). Assumption is that the access URL is at https://${App}.${DeploymentDomain} OAuthConfigBucket: Type: String Description: S3 bucket that contains the signing cert, which must be called `oauth-signing.pem` OAuthClientId: Type: String Description: Client ID provided by the OAuth IdP OAuthClientName: Type: String Description: Client Name provided by the OAuth IdP OAuthLoginUri: Type: String Description: Login endpoint provided by the OAuth IdP OAuthSigningCertPath: Type: String Description: Path to the signing cert OAuthTokenUri: Type: String Description: Token endpoint provided by the OAuth IdP OAuthAdminClaimName: Type: String Description: Name of a claim which, if present and a string 'true' will indicate that the user is an administrator Default: multimedia_admin OAuthValidAudiences: Type: String Description: JSON style list of audience names, one of which must be the current "aud" field in a token in order for it to be considered valid Default: "[\"archivehunter\"]" OAuthScope: Type: String Description: OAuth scope to request Origin: Type: String Description: Origin to allow CORS requests from AvatarBucket: Type: String Description: Name of a bucket which will hold user avatars. Public access is not required; the primary proxy bucket usually makes a good choice. OfficeIpRange: Type: String Description: CIDR block of ip addresses to be allowed SSH access InstanceType: Type: String Description: What type of instance to launch AllowedValues: - t3.nano - t4g.nano - t3.micro - t4g.micro - t3.small - t4g.small - t3.medium - t4g.medium - t3.large - t4g.large - t3.xlarge - t4g.xlarge - t4g.2xlarge - c6g.4xlarge - c6g.8xlarge Default: t3.medium KeyPair: Type: AWS::EC2::KeyPair::KeyName Description: Root access keypair VPCID: Description: Virtual Private Cloud to deploy into Type: AWS::EC2::VPC::Id DeploySubnets: Description: Subnets to deploy into. Type: List<AWS::EC2::Subnet::Id> ESVolumeSize: Description: Size of storage volume to provision, for each node in the elasticsearch cluster Type: Number ESVolumeType: Description: Type of storage to provision for elasticsearch. Choose sc1 for cheap dev, or gp2 for prod Type: String AllowedValues: - standard - gp2 - io1 Default: gp2 ESSubnet: Description: Subnet within which to deploy elasticsearch. Currently only one is supported. Type: AWS::EC2::Subnet::Id ESInstanceCount: Description: Number of instances to provision for elasticsearch Type: Number ESInstanceType: Description: Type of instance to deploy for elasticsearch Type: String AllowedValues: - t2.small.elasticsearch - t2.medium.elasticsearch - r4.large.elasticsearch - r4.xlarge.elasticsearch Default: t2.small.elasticsearch LoadBalancerCert: Description: ARN of an SSL certificate to allow https access to loadbalancer Type: String AppSecretString: Description: Long random string used as an app secret to secure cookies etc. Type: String NoEcho: True ProxyingTaskImageRef: Description: Docker path to the image used for proxying. Type: String Default: guardianmultimedia/archivehunter-proxying:90 VideoTranscodingPresetId: Description: ETS Preset ID to use when transcoding video proxies Type: String Default: 1387374611767-d52fja AudioTranscodingPresetId: Description: ETS Preset ID to use when transcoding video proxies Type: String Default: 1351620000001-300040 ServerSharedSecret: Description: Shared secret for script->server communication Type: String NoEcho: true BucketNotificationLambda: Description: Optional ARN of the bucket notifications lambda. Specify this so that it can be configured automatically by the app Type: String AllowedPattern: ^arn:[^:\n]*:[^:\n]*:[^:\n]*:[^:\n]*:[^:\/\n]*[:\/]?.*$ Resources: AccessorSG: #this group is exported and used by bucketmonitor yaml. defined here so that it can be marked for access to ES Type: AWS::EC2::SecurityGroup Properties: GroupDescription: SG for lambdas to access ArchiveHunter search index VpcId: !Ref VPCID ESSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: SG for access to the ArchiveHunter search index SecurityGroupIngress: - IpProtocol: tcp SourceSecurityGroupId: !GetAtt AccessorSG.GroupId FromPort: 443 ToPort: 443 - IpProtocol: tcp SourceSecurityGroupId: !GetAtt InstanceSecurityGroup.GroupId FromPort: 443 ToPort: 443 VpcId: !Ref VPCID SearchDomain: Type: AWS::Elasticsearch::Domain Properties: AccessPolicies: Version: '2012-10-17' Statement: - Effect: Allow Principal: #security is provided via firewalling the security groups AWS: "*" Action: - es:* Resource: "*" EBSOptions: EBSEnabled: true VolumeSize: !Ref ESVolumeSize VolumeType: !Ref ESVolumeType ElasticsearchClusterConfig: DedicatedMasterEnabled: false InstanceCount: !Ref ESInstanceCount InstanceType: !Ref ESInstanceType ZoneAwarenessEnabled: false #FIXME: defaulting to this for dev, update with appropriate safeguards for prod ElasticsearchVersion: 6.3 Tags: - Key: App Value: !Ref App - Key: Stack Value: !Ref Stack - Key: Stage Value: !Ref Stage VPCOptions: SecurityGroupIds: - !GetAtt ESSecurityGroup.GroupId SubnetIds: - !Ref ESSubnet ScanTargetsTable: Type: AWS::DynamoDB::Table Properties: AttributeDefinitions: - AttributeName: bucketName AttributeType: S KeySchema: - AttributeName: bucketName KeyType: HASH ProvisionedThroughput: WriteCapacityUnits: 1 ReadCapacityUnits: 1 Tags: - Key: App Value: !Sub ${App}-webapp - Key: Stack Value: !Ref Stack - Key: Stage Value: !Ref Stage UserProfileTable: Type: AWS::DynamoDB::Table Properties: AttributeDefinitions: - AttributeName: userEmail AttributeType: S KeySchema: - AttributeName: userEmail KeyType: HASH ProvisionedThroughput: WriteCapacityUnits: 1 ReadCapacityUnits: 4 Tags: - Key: App Value: !Sub ${App}-webapp - Key: Stack Value: !Ref Stack - Key: Stage Value: !Ref Stage LightboxTable: Type: AWS::DynamoDB::Table Properties: AttributeDefinitions: - AttributeName: userEmail AttributeType: S - AttributeName: fileId AttributeType: S - AttributeName: restoreStatus AttributeType: S - AttributeName: memberOfBulk AttributeType: S KeySchema: - AttributeName: userEmail KeyType: HASH - AttributeName: fileId KeyType: RANGE BillingMode: PAY_PER_REQUEST GlobalSecondaryIndexes: - IndexName: statusIndex KeySchema: - AttributeName: restoreStatus KeyType: HASH - AttributeName: fileId KeyType: RANGE Projection: ProjectionType: ALL - IndexName: memberOfBulkIndex KeySchema: - AttributeName: memberOfBulk KeyType: HASH - AttributeName: userEmail KeyType: RANGE Projection: ProjectionType: ALL - IndexName: fileIdIndex KeySchema: - AttributeName: fileId KeyType: HASH - AttributeName: userEmail KeyType: RANGE Projection: ProjectionType: ALL LightboxBulkEntryTable: Type: AWS::DynamoDB::Table Properties: AttributeDefinitions: - AttributeName: id AttributeType: S - AttributeName: userEmail AttributeType: S - AttributeName: description AttributeType: S KeySchema: - AttributeName: id KeyType: HASH ProvisionedThroughput: WriteCapacityUnits: 1 ReadCapacityUnits: 4 GlobalSecondaryIndexes: - IndexName: DescIndex KeySchema: - AttributeName: description KeyType: HASH - AttributeName: userEmail KeyType: RANGE Projection: ProjectionType: ALL ProvisionedThroughput: WriteCapacityUnits: 1 ReadCapacityUnits: 2 JobHistoryTable: Type: AWS::DynamoDB::Table Properties: AttributeDefinitions: - AttributeName: jobId AttributeType: S - AttributeName: jobType AttributeType: S - AttributeName: sourceId AttributeType: S - AttributeName: sourceType AttributeType: S - AttributeName: jobStatus AttributeType: S - AttributeName: startedAt AttributeType: S KeySchema: - AttributeName: jobId KeyType: HASH ProvisionedThroughput: WriteCapacityUnits: 2 ReadCapacityUnits: 10 GlobalSecondaryIndexes: - IndexName: sourcesIndex KeySchema: - AttributeName: sourceId KeyType: HASH - AttributeName: sourceType KeyType: RANGE Projection: ProjectionType: ALL ProvisionedThroughput: WriteCapacityUnits: 2 ReadCapacityUnits: 10 - IndexName: jobStatusIndex KeySchema: - AttributeName: jobStatus KeyType: HASH - AttributeName: startedAt KeyType: RANGE Projection: ProjectionType: ALL ProvisionedThroughput: WriteCapacityUnits: 2 ReadCapacityUnits: 10 - IndexName: jobTypeIndex KeySchema: - AttributeName: jobType KeyType: HASH - AttributeName: startedAt KeyType: RANGE Projection: ProjectionType: ALL ProvisionedThroughput: WriteCapacityUnits: 2 ReadCapacityUnits: 10 Tags: - Key: App Value: !Sub ${App}-webapp - Key: Stack Value: !Ref Stack - Key: Stage Value: !Ref Stage ServerTokensTable: Type: AWS::DynamoDB::Table Properties: AttributeDefinitions: - AttributeName: value AttributeType: S KeySchema: - AttributeName: value KeyType: HASH ProvisionedThroughput: WriteCapacityUnits: 1 ReadCapacityUnits: 3 Tags: - Key: App Value: !Sub ${App}-webapp - Key: Stack Value: !Ref Stack - Key: Stage Value: !Ref Stage OAuthTokensTable: Type: AWS::DynamoDB::Table Properties: AttributeDefinitions: - AttributeName: userEmail AttributeType: S - AttributeName: issued AttributeType: N KeySchema: - AttributeName: userEmail KeyType: HASH - AttributeName: issued KeyType: RANGE ProvisionedThroughput: WriteCapacityUnits: 1 ReadCapacityUnits: 4 Tags: - Key: App Value: !Sub ${App}-webapp - Key: Stack Value: !Ref Stack - Key: Stage Value: !Ref Stage ProxyLocationTable: Type: AWS::DynamoDB::Table Properties: AttributeDefinitions: - AttributeName: fileId AttributeType: S - AttributeName: proxyId AttributeType: S - AttributeName: proxyType AttributeType: S KeySchema: - AttributeName: fileId KeyType: HASH - AttributeName: proxyType KeyType: RANGE GlobalSecondaryIndexes: - IndexName: proxyIdIndex KeySchema: - AttributeName: proxyId KeyType: HASH Projection: ProjectionType: ALL BillingMode: PAY_PER_REQUEST Tags: - Key: App Value: !Sub ${App}-webapp - Key: Stack Value: !Ref Stack - Key: Stage Value: !Ref Stage ProxyFrameworkTable: Type: AWS::DynamoDB::Table Properties: AttributeDefinitions: - AttributeName: region AttributeType: S KeySchema: - AttributeName: region KeyType: HASH ProvisionedThroughput: WriteCapacityUnits: 2 ReadCapacityUnits: 2 Tags: - Key: App Value : !Sub ${App}-webapp - Key: Stack Value: !Ref Stack - Key: Stage Value: !Ref Stage LoadBalancerSG: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Load-balancer security group for launchdetector SecurityGroupIngress: - CidrIp: !Ref OfficeIpRange FromPort: 9000 ToPort: 9000 IpProtocol: tcp - CidrIp: !Ref OfficeIpRange FromPort: 443 ToPort: 443 IpProtocol: tcp - SourceSecurityGroupId: !GetAtt AutodowningLambdaSG.GroupId FromPort: 8558 ToPort: 8558 IpProtocol: tcp VpcId: !Ref VPCID LoadBalancer: Type: AWS::ElasticLoadBalancing::LoadBalancer Properties: CrossZone: true HealthCheck: HealthyThreshold: "3" Interval: "10" Target: "HTTP:9000/is-online" Timeout: "3" UnhealthyThreshold: "2" Listeners: - InstancePort: "9000" InstanceProtocol: "http" LoadBalancerPort: "443" Protocol: "https" SSLCertificateId: !Ref LoadBalancerCert - InstancePort: "2552" InstanceProtocol: "tcp" LoadBalancerPort: "2552" Protocol: "tcp" - InstancePort: "8558" InstanceProtocol: "http" LoadBalancerPort: "8558" Protocol: "http" Scheme: internal SecurityGroups: - !GetAtt LoadBalancerSG.GroupId Subnets: !Ref DeploySubnets Tags: - Key: App Value: !Sub ${App}-webapp - Key: Stack Value: !Ref Stack - Key: Stage Value: !Ref Stage #see https://docs.aws.amazon.com/elastictranscoder/latest/developerguide/access-control.html TranscodingRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: elastictranscoder.amazonaws.com Action: sts:AssumeRole Path: "/" Policies: - PolicyName: BucketRead PolicyDocument: Version: 2012-10-17 Statement: Sid: '1' Effect: Allow Action: - s3:Get* - s3:ListBucket - s3:Put* - s3:*MultipartUpload* Resource: "*" - PolicyName: SNSPublish PolicyDocument: Version: 2012-10-17 Statement: Sid: '2' Effect: Allow Action: sns:Publish Resource: "*" - PolicyName: BlockBucketOps PolicyDocument: Version: 2012-10-17 Statement: Sid: '3' Effect: Deny Action: - sns:*Permission* - sns:*Delete* - sns:*Remove* - s3:*Policy* - s3:*Delete* Resource: "*" TranscodeUpdateTopic: Type: AWS::SNS::Topic Properties: Subscription: - Endpoint: !GetAtt TranscodeUpdateMsg.Arn Protocol: sqs TranscodeUpdateMsg: Type: AWS::SQS::Queue IngestTranscodeMsg: Type: AWS::SQS::Queue ProxyFrameworkMsg: Type: AWS::SQS::Queue Properties: RedrivePolicy: deadLetterTargetArn: !GetAtt ProxyFrameworkDLQ.Arn maxReceiveCount: 10 ProxyFrameworkDLQ: Type: AWS::SQS::Queue FileMoveQueue: Type: AWS::SQS::Queue Properties: RedrivePolicy: deadLetterTargetArn: !GetAtt FileMoveDLQ.Arn maxReceiveCount: 10 VisibilityTimeout: 3600 #keep message hidden for 1 hour after successful delivery - should be long enough for a move to complete FileMoveDLQ: Type: AWS::SQS::Queue ProxyFrameworkMsgSubs: Type: AWS::SQS::QueuePolicy Properties: PolicyDocument: Version: '2012-10-17' Id: MyQueuePolicy Statement: - Sid: Allow-SendMessage-From-SNS-Topic Effect: Allow Principal: "*" Action: - sqs:SendMessage #arn:aws:sns:region:account-id:topicname Resource: !Sub arn:aws:sns:*:${AWS::AccountId}:* Queues: - !GetAtt ProxyFrameworkMsg.QueueName #Autodowning lambda - receive events from autoscaling to tell us when nodes go away. ScalingRule: Type: AWS::Events::Rule Properties: Description: Notify on EC2 events EventPattern: source: - aws.ec2 detail-type: - "EC2 Instance State-change Notification" State: ENABLED Targets: - Arn: !GetAtt AutodowningLambda.Arn Id: !Sub ${Stack}-${App}Autodowning-${Stage} #this SG is allowed to access 8558 via the (internal) loadbalancer AutodowningInstancesTable: Type: AWS::DynamoDB::Table Properties: AttributeDefinitions: - AttributeName: instanceId AttributeType: S KeySchema: - AttributeName: instanceId KeyType: HASH ProvisionedThroughput: WriteCapacityUnits: 1 ReadCapacityUnits: 1 Tags: - Key: App Value: !Sub ${App}-autodowning - Key: Stack Value: !Ref Stack - Key: Stage Value: !Ref Stage AutodowningLambdaSG: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Security group for archivehunter autodowning lambda VpcId: !Ref VPCID AutodowningInvokePermission: Type: AWS::Lambda::Permission Properties: Action: lambda:InvokeFunction FunctionName: !GetAtt AutodowningLambda.Arn Principal: events.amazonaws.com SourceArn: !GetAtt ScalingRule.Arn AutodowningLambdaRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: Effect: Allow Principal: Service: lambda.amazonaws.com Action: sts:AssumeRole Path: / Policies: - PolicyName: AccessBucket PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - ec2:CreateNetworkInterface - ec2:DescribeNetworkInterfaces - ec2:DeleteNetworkInterface - ec2:DescribeInstances - ec2:DescribeInstanceStatus - ec2:DescribeTags Resource: "*" - Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents Resource: "*" - PolicyName: AccessInstancesTable PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - dynamodb:* Resource: - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${AutodowningInstancesTable} #main lambda function AutodowningLambda: Type: AWS::Lambda::Function Properties: Code: S3Bucket: gnm-multimedia-rr-deployables S3Key: !Sub ${Stack}/${Stage}/archivehunter-autodowning-lambda/autoDowningLambda.jar Environment: Variables: LOADBALANCER: !GetAtt LoadBalancer.DNSName INSTANCES_TABLE: !Ref AutodowningInstancesTable #only act on instances that match these tags APP_TAG: !Sub ${App}-webapp STACK_TAG: !Ref Stack STAGE_TAG: !Ref Stage Handler: AutoDowningLambdaMain FunctionName: !Sub archivehunter-autodown-${Stage} MemorySize: 768 Role: !GetAtt AutodowningLambdaRole.Arn Runtime: java11 Timeout: 60 VpcConfig: SecurityGroupIds: - !GetAtt AutodowningLambdaSG.GroupId SubnetIds: !Ref DeploySubnets Tags: - Key: App Value: !Ref App - Key: Stack Value: !Ref Stack - Key: Stage Value: !Ref Stage InstanceRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: ec2.amazonaws.com Action: sts:AssumeRole Path: "/" ManagedPolicyArns: - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore Policies: - PolicyName: KinesisLogging PolicyDocument: Version: 2012-10-17 Statement: Effect: Allow Action: - kinesis:Describe* - kinesis:List* - kinesis:PutRecord - kinesis:PutRecords Resource: - !Sub arn:aws:kinesis:${AWS::Region}:${AWS::AccountId}:stream/${LoggingStreamName} - PolicyName: DataAccess PolicyDocument: Version: 2012-10-17 Statement: Effect: Allow Action: - dynamodb:BatchWriteItem - dynamodb:PutItem - dynamodb:UpdateTable #required for updating provisioned capacity - dynamodb:BatchWriteItem - dynamodb:DescribeTable - dynamodb:DeleteItem - dynamodb:RestoreTableFromBackup - dynamodb:GetItem - dynamodb:Scan - dynamodb:Query - dynamodb:UpdateItem - dynamodb:CreateBackup - dynamodb:GetRecords Resource: - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${ScanTargetsTable} - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${ProxyLocationTable} - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${ProxyLocationTable}/index/* - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${JobHistoryTable} - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${JobHistoryTable}/index/* - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${UserProfileTable} - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${UserProfileTable}/index/* - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${LightboxTable} - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${LightboxTable}/index/* - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${ProxyFrameworkTable} - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${ProxyFrameworkTable}/index/* - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${LightboxBulkEntryTable} - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${LightboxBulkEntryTable}/index/* - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${ServerTokensTable} - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${ServerTokensTable}/index/* - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${OAuthTokensTable} - PolicyName: TranscoderAccess PolicyDocument: Version: 2012-10-17 Statement: Effect: Allow Action: - "elastictranscoder:*" Resource: - !Sub arn:aws:elastictranscoder:*:${AWS::AccountId}:pipeline/* - !Sub arn:aws:elastictranscoder:*:${AWS::AccountId}:job/* - !Sub arn:aws:elastictranscoder:*:${AWS::AccountId}:preset/${VideoTranscodingPresetId} - !Sub arn:aws:elastictranscoder:*:${AWS::AccountId}:preset/${AudioTranscodingPresetId} - PolicyName: TranscoderPipeline PolicyDocument: Version: 2012-10-17 Statement: Effect: Allow Action: - elastictranscoder:CreatePipeline Resource: - "*" - PolicyName: TranscodeRole PolicyDocument: Statement: Effect: Allow Action: - iam:PassRole Resource: - !GetAtt TranscodingRole.Arn - PolicyName: TranscoderAccessList PolicyDocument: Version: 2012-10-17 Statement: Effect: Allow Action: - elastictranscoder:ListPipelines Resource: - "*" - PolicyName: PandaAccess PolicyDocument: Version: 2012-10-17 Statement: Effect: Allow Action: - s3:GetObject - s3:ListObjects Resource: - !Sub arn:aws:s3:::${OAuthConfigBucket}/* - PolicyName: MessageQueueRead PolicyDocument: Version: 2012-10-17 Statement: Effect: Allow Action: - sqs:* Resource: - !GetAtt TranscodeUpdateMsg.Arn - !GetAtt IngestTranscodeMsg.Arn - !GetAtt ProxyFrameworkMsg.Arn - !GetAtt FileMoveQueue.Arn - PolicyName: EC2InstanceDiscovery PolicyDocument: Version: 2012-10-17 Statement: Effect: Allow Action: - ec2:Describe* Resource: "*" - PolicyName: BucketScanAccess PolicyDocument: Version: 2012-10-17 Statement: Effect: Allow Action: - s3:ListBucket - s3:GetObject - s3:GetObjectVersion - s3:ListBucketVersions - s3:RestoreObject - s3:GetBucketNotification - s3:PutBucketNotification Resource: - arn:aws:s3:::* - arn:aws:s3:::*/* - PolicyName: ProxiesManagementAccess PolicyDocument: Version: 2012-10-17 Statement: Effect: Allow Action: - s3:ListBucket - s3:GetObject - s3:PutObject - s3:DeleteObject - s3:*Multipart* Resource: - arn:aws:s3:::*proxies* - arn:aws:s3:::*proxies*/* - arn:aws:s3:::*proxy* - arn:aws:s3:::*proxy*/* - PolicyName: DataMigration PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - s3:DeleteObject Resource: - arn:aws:s3:::*holding-pen*/* - Effect: Allow Action: - s3:PutObject Resource: - arn:aws:s3:::*/* - PolicyName: DeployablesAccess PolicyDocument: Version: 2012-10-17 Statement: Effect: Allow Action: - s3:ListBucket - s3:GetObject Resource: - arn:aws:s3:::gnm-multimedia-rr-deployables - arn:aws:s3:::gnm-multimedia-rr-deployables/* - PolicyName: ElasticSearchAccess PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: es:* Resource: !Sub arn:aws:es:${AWS::Region}:${AWS::AccountId}:domain/${SearchDomain} - Effect: Deny Action: es:DeleteElasticSearchDomain Resource: !Sub arn:aws:es:${AWS::Region}:${AWS::AccountId}:domain/${SearchDomain} - PolicyName: ClouformationList PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - cloudformation:ListStacks - cloudformation:DescribeStacks Resource: "*" InstanceProfile: Type: AWS::IAM::InstanceProfile Properties: Roles: - !Ref InstanceRole #Akka clustering - let all instances within `InstanceSecurityGroup` talk to each other InstanceSGIngressRemoting: Type: AWS::EC2::SecurityGroupIngress Properties: SourceSecurityGroupId: !GetAtt InstanceSecurityGroup.GroupId FromPort: 2552 ToPort: 2552 IpProtocol: tcp GroupId: !GetAtt InstanceSecurityGroup.GroupId #Akka clustering - let all instances within `InstanceSecurityGroup` talk to each other InstanceSGIngressAkkaMgt: Type: AWS::EC2::SecurityGroupIngress Properties: SourceSecurityGroupId: !GetAtt InstanceSecurityGroup.GroupId FromPort: 8558 ToPort: 8558 IpProtocol: tcp GroupId: !GetAtt InstanceSecurityGroup.GroupId InstanceSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Instance security group for archive hunter VpcId: !Ref VPCID SecurityGroupIngress: - SourceSecurityGroupId: !GetAtt LoadBalancerSG.GroupId FromPort: 9000 ToPort: 9000 IpProtocol: tcp - SourceSecurityGroupId: !GetAtt LoadBalancerSG.GroupId FromPort: 8558 ToPort: 8558 IpProtocol: tcp - CidrIp: !Ref OfficeIpRange FromPort: 22 ToPort: 22 IpProtocol: tcp LaunchConfig: Type: AWS::AutoScaling::LaunchConfiguration Properties: IamInstanceProfile: !Ref InstanceProfile ImageId: !Ref AmiId InstanceType: !Ref InstanceType KeyName: !Ref KeyPair SecurityGroups: - !Ref InstanceSecurityGroup UserData: !Base64 "Fn::Sub": | #!/bin/bash -e mkdir -p /tmp/install aws s3 cp s3://gnm-multimedia-rr-deployables/${Stack}/${Stage}/archivehunter-webapp/archivehunter.deb /tmp/install dpkg --install /tmp/install/archivehunter.deb mkdir -p /usr/share/archivehunter/conf chown archivehunter /usr/share/archivehunter mkdir -p /var/log/archivehunter chown archivehunter /var/log/archivehunter declare -x HOSTNAME=`hostname` declare -x LOCAL_IP=`curl http://169.254.169.254/latest/meta-data/local-ipv4` echo "$LOCAL_IP $HOSTNAME" >> /etc/hosts aws s3 cp s3://${OAuthConfigBucket}/oauth-signing.pem /usr/share/archivehunter/conf/oauth-signing.pem cat > /usr/share/archivehunter/conf/application.conf << EOF play.http.secret.key = "${AppSecretString}" play.filters { hosts { allowed = [".${AWS::Region}.elb.amazonaws.com","localhost:9000"] } # Disabled filters remove elements from the enabled list. disabled += play.filters.headers.SecurityHeadersFilter disabled += play.filters.hosts.AllowedHostsFilter disabled += play.filters.csrf.CSRFFilter } application.langs="en" externalData { awsRegion="${AWS::Region}" scanTargets="${ScanTargetsTable}" jobTable="${JobHistoryTable}" //ddbHost="localhost" ddbHost="dynamodb.${AWS::Region}.amazonaws.com" indexName = "archivehunter" problemItemsIndex="proxy-issues" problemSummaryIndex="proxy-issues-summary" avatarBucket="${AvatarBucket}" bucketMonitorLambdaARN="${BucketNotificationLambda}" } scanner { masterSchedule = 300 #in seconds } proxies { tableName = "${ProxyLocationTable}" ecsTaskDefinitionName = "nodefinition" #this should match the container name in ProxyingTaskDefinition taskContainerName = "${Stack}-${App}Proxy-${Stage}" appServerUrl = "https://${LoadBalancer.DNSName}" completionNotification = "${TranscodeUpdateTopic}" errorNotification = "${TranscodeUpdateTopic}" warningNotification = "${TranscodeUpdateTopic}" transcodingRole = "${TranscodingRole.Arn}" notificationsQueue = "${TranscodeUpdateMsg}" videoPresetId = "${VideoTranscodingPresetId}" audioPresetId = "${AudioTranscodingPresetId}" } lightbox { tableName = "${LightboxTable}" bulkTableName = "${LightboxBulkEntryTable}" } elasticsearch { hostname = "${SearchDomain.DomainEndpoint}" port = 443 ssl = true } ingest { notificationsQueue = "${IngestTranscodeMsg.QueueName}" } proxyFramework { notificationsQueue = "${ProxyFrameworkMsg}" notificationsQueueArn = "${ProxyFrameworkMsg.Arn}" trackingTable = "${ProxyFrameworkTable}" } filemover.notificationsQueue = "${FileMoveQueue}" filemover.concurrency = 1 auth { deployedUrl = "https://${App}.${DeploymentDomain}" domain = "${DeploymentDomain}" userProfileTable = "${UserProfileTable}" tokenSigningCertPath: "${OAuthSigningCertPath}" panDomainBucket = "${OAuthConfigBucket}" } oAuth { clientId: "${OAuthClientId}" resource: "${OAuthClientName}" scope: "${OAuthScope}" oAuthUri: "${OAuthLoginUri}" tokenUrl: "${OAuthTokenUri}" adminClaimName: "${OAuthAdminClaimName}" authCookieName: "archivehunterAuth" refreshCookieName: "archivehunterRefresh" enforceSecure: true //You can turn this off to make development simpler but always turn it on for production! tokenSigningCertPath: "${OAuthSigningCertPath}" validAudiences: ${OAuthValidAudiences} oAuthTokensTable: "${OAuthTokensTable}" origin: "${Origin}" type: "Azure" keyTimeOut = 86400 } serverToken { serverTokenTable = "${ServerTokensTable}" } serverAuth { sharedSecret = "${ServerSharedSecret}" } archive { restoresExpireAfter = 10 } akka.http.host-connection-pool.response-entity-subscription-timeout = 60.seconds akka.actor { provider = "cluster" serializers { akka-cluster-client = "akka.cluster.client.protobuf.ClusterClientMessageSerializer" akka-singleton = "akka.cluster.singleton.protobuf.ClusterSingletonMessageSerializer" } serialization-bindings { "akka.cluster.client.ClusterClientMessage" = akka-cluster-client "akka.cluster.singleton.ClusterSingletonMessage" = akka-singleton } serialization-identifiers { "akka.cluster.client.protobuf.ClusterClientMessageSerializer" = 15 "akka.cluster.singleton.protobuf.ClusterSingletonMessageSerializer" = 14 } } akka.remote { log-remote-lifecycle-events = on artery { transport = tcp hostname = "${!LOCAL_IP}" canonical.port = 2552 } } akka.management { http { hostname = "127.0.0.1" hostname = "${!LOCAL_IP}" bind-hostname = "0.0.0.0" port = 8558 bind-port = 8558 route-providers-read-only = false } cluster { auto-down-unreachable-after = 30s bootstrap { contact-point-discovery { service-name = "${Stack}-${App}-${Stage}" discovery-method = akka.discovery.aws-api-ec2-tag-based } } } } akka.discovery { method = aws-api-ec2-tag-based aws-api-ec2-tag-based { class = akka.discovery.awsapi.ec2.Ec2TagBasedServiceDiscovery tag-key = "service" } } akka.discovery.akka.discovery.aws-api-ec2-tag-based.class = akka.discovery.awsapi.ec2.Ec2TagBasedServiceDiscovery # Enable metrics extension in akka-cluster-metrics. akka.extensions=["akka.cluster.metrics.ClusterMetricsExtension"] EOF cat > /etc/default/archivehunter << EOF #exiting on out of memory error should cause either systemd or autoscaling group to restart us JAVA_OPTS="-XX:+ExitOnOutOfMemoryError -Dconfig.file=/usr/share/archivehunter/conf/application.conf -Dsoftware.amazon.awssdk.http.async.service.impl=software.amazon.awssdk.http.nio.netty.NettySdkAsyncHttpService" EOF sudo mv /lib/systemd/system/archivehunter.service /tmp sudo bash -c "cat /tmp/archivehunter.service | sed s/LimitNOFILE=1024/LimitNOFILE=16384/ > /lib/systemd/system/archivehunter.service" sudo systemctl daemon-reload cat > /etc/security/limits.d/archivehunter.conf << EOF archivehunter hard nofiles unlimited archivehunter soft nofiles unlimited EOF declare -x BUILD_NUMBER=$(grep buildNumber /usr/share/archivehunter/conf/version.properties | awk -F= '{print $2}') cat > /usr/share/archivehunter/conf/sentry.properties << EOF dsn=${SentryDSN} public-dsn=${SentryPublicDSN} stacktrace.app.packages=auth,controllers,drivers,exceptions,helpers,models,postrun,services,views environment=${Stage} release=${!BUILD_NUMBER} EOF cat /usr/share/archivehunter/conf/logback-deployment.xml | sed s/{region}/${AWS::Region}/ > /tmp/logback.xml cat /tmp/logback.xml | sed s/{app}/${App}/ > /tmp/logback.xml.2 cat /tmp/logback.xml.2 | sed s/{stack}/${Stack}/ > /tmp/logback.xml.3 cat /tmp/logback.xml.3 | sed s/{stage}/${Stage}/ > /tmp/logback.xml.4 cat /tmp/logback.xml.4 | sed s/{logging-stream}/${LoggingStreamName}/ > /usr/share/archivehunter/conf/logback.xml rm -f /tmp/logback.xml* systemctl restart archivehunter systemctl enable archivehunter AutoScaleGroup: Type: AWS::AutoScaling::AutoScalingGroup Properties: DesiredCapacity: "2" HealthCheckGracePeriod: 300 HealthCheckType: ELB LaunchConfigurationName: !Ref LaunchConfig LoadBalancerNames: - !Ref LoadBalancer MaxSize: "8" MinSize: "2" Tags: - Key: App Value: !Sub ${App}-webapp PropagateAtLaunch: true - Key: Stack Value: !Ref Stack PropagateAtLaunch: true - Key: Stage Value: !Ref Stage PropagateAtLaunch: true - Key: service Value: !Sub ${Stack}-${App}-${Stage} PropagateAtLaunch: true VPCZoneIdentifier: !Ref DeploySubnets Outputs: ElasticSearchEndpoint: Description: ES endpoint for ArchiveHunter lambdas Value: !GetAtt SearchDomain.DomainEndpoint Export: Name: !Sub ${AWS::StackName}-ESEndpoint Loadbalancer: Description: Loadbalancer address to access service Value: !GetAtt LoadBalancer.DNSName AccessorSG: Description: Security group for accessing the index Value: !GetAtt AccessorSG.GroupId Export: Name: !Sub ${AWS::StackName}-AccessorSG IngestMessageQueueUrl: Description: URL of the message queue for notifications from the ingest lambda Value: !Ref IngestTranscodeMsg Export: Name: !Sub ${AWS::StackName}-IngestTranscodeMsg ProxyFrameworkMsgQueue: Description: ARN of the message use for notifications from the Proxying Framework Value: !GetAtt ProxyFrameworkMsg.Arn IngestMessageQueueArn: Description: URL of the message queue for notifications from the ingest lambda Value: !GetAtt IngestTranscodeMsg.Arn Export: Name: !Sub ${AWS::StackName}-IngestTranscodeMsgArn JobHistoryTable: Description: ARN of the JobHistoryTable Value: !GetAtt JobHistoryTable.Arn Export: Name: !Sub ${AWS::StackName}-JobHistoryTable LightboxTable: Description: ARN of the LightboxTable Value: !GetAtt LightboxTable.Arn Export: Name: !Sub ${AWS::StackName}-LightboxTable