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