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
