Skip to content

Instantly share code, notes, and snippets.

@Frans
Created May 9, 2023 14:47
Show Gist options
  • Save Frans/bfe186f74be3f169f2159be9d05068c8 to your computer and use it in GitHub Desktop.
Save Frans/bfe186f74be3f169f2159be9d05068c8 to your computer and use it in GitHub Desktop.
Mozilla Hubs 1.1.5 Template
AWSTemplateFormatVersion: '2010-09-09'
Description: |
Hubs Cloud: Private Social VR in your web browser. Your own self-hosted hub powered by Hubs by Mozilla. Full documentation: https://github.com/mozilla/hubs-cloud
Transform: AWS::Serverless-2016-10-31
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: Account Configuration
Parameters:
- AdminEmailAddress
- Label:
default: Domain Configuration
Parameters:
- DomainName
- IsDomainOnRoute53
- InternalZone
- ShortlinkZone
- Label:
default: Email Configuration
Parameters:
- EmailZone
- EmailSubdomain
- Label:
default: Server Configuration
Parameters:
- SpecifyInboundCidr
- KeyPair
- SpecifyInboundSSHCidr
- AppInstanceType
- StreamInstanceType
- Label:
default: Cost Management
Parameters:
- DatabaseMonthlyBudget
- MaxStorage
- Label:
default: Offline Mode
Parameters:
- StackOffline
- StackOfflineRedirectUrl
- Label:
default: Database Configuration
Parameters:
- AutoPauseDb
- DbBackupRetentionPeriod
- DbMaxCapacity
- Label:
default: Restore from Backup
Parameters:
- RestoreStackName
- RestoreDbSnapshotIdentifier
- RestoreAppDbSecretArn
- RestoreBackupVaultName
- RestoreRecoveryPointArn
- Label:
default: SSL Certificates
Parameters:
- UnmanagedDomainCertArn
- UnmanagedDomainEastCertArn
- LetsEncryptEmailAddress
- Label:
default: Advanced - Leave these alone unless you know what you're doing!
Parameters:
- InboundCidrOverride
- InboundSSHCidrOverride
- LoadBalancingMethod
- NewCMKForDiskEncryption
- SubnetAZs
- ClassB
- AppPlacementGroupStrategy
ParameterLabels:
DomainName:
default: Domain Name
IsDomainOnRoute53:
default: Is your domain set up on Route 53?
ShortlinkZone:
default: Short link domain name
InternalZone:
default: Internal domain name
EmailZone:
default: Outgoing Email Domain
EmailSubdomain:
default: Outgoing Email Subdomain Prefix
AdminEmailAddress:
default: Administrator Email Address
LetsEncryptEmailAddress:
default: Let's Encrypt Contact Email Address (Optional)
UnmanagedDomainCertArn:
default: Non-Route 53 Domain SSL Certificate ARN
UnmanagedDomainEastCertArn:
default: Non-Route 53 Domain US-East-1 (N. Virginia) SSL Certificate ARN
AppInstanceType:
default: App server EC2 instance type
ClassB:
default: Class B IP Block
NewCMKForDiskEncryption:
default: Create a dedicated CMK for database and backup encryption?
AppPlacementGroupStrategy:
default: App server placement group strategy
DbBackupRetentionPeriod:
default: Database backup retention period (in days)
DbMaxCapacity:
default: Max database capacity (in ACUs)
RestoreDbSnapshotIdentifier:
default: Restore From Database Snapshot Identifier
RestoreAppDbSecretArn:
default: Restore From Database Secret ARN
RestoreStackName:
default: Restore From Stack Name
RestoreBackupVaultName:
default: Restore From Vault Name
RestoreRecoveryPointArn:
default: Restore From Recovery Point ARN
LoadBalancingMethod:
default: Load Balancing Method
AutoPauseDb:
default: Auto-Pause Database
SubnetAZs:
default: Subnet Availability Zones
StackOffline:
default: Stack Mode
StackOfflineRedirectUrl:
default: Offline Redirect URL (Optional)
DatabaseMonthlyBudget:
default: Account Monthly Database Budget (US dollars, no $ and no cents)
MaxStorage:
default: Storage Limit (in GB)
InboundCidrOverride:
default: Inbound Site Access CIDR
InboundSSHCidrOverride:
default: Inbound SSH CIDR
SpecifyInboundCidr:
default: Restrict Site Access
SpecifyInboundSSHCidr:
default: Restrict SSH Access
Parameters:
DomainName:
Type: String
Description: Domain name your hub will be hosted on. (eg myhub.com or hub.mydomain.com.)
This should be a domain you already own, either on a third party provider (eg
GoDaddy or Namecheap) or on AWS Route 53.
AllowedPattern: ^((?:([a-z0-9]\.|[a-z0-9][a-z0-9\-]{0,61}[a-z0-9])\.)+)([a-z0-9]{2,63}|(?:[a-z0-9][a-z0-9\-]{0,61}[a-z0-9]))\.?$
ConstraintDescription: Must be a valid domain name (eg myhub.com or hub.mycompany.com)
IsDomainOnRoute53:
Type: String
Description: We recommended you use Route 53 to manage your domain. Set to Yes
if your domain is set up on Route 53, and you can move onto the next item. Set
to No if your domain is on a third party service such as GoDaddy or Namecheap.
If you choose 'No', you'll need to set the fields in 'Custom SSL Certificates'
below. Also, if you choose 'No', once your stack is created you'll need to update
your DNS provider using the 'AddressForRootDomain' information in the 'Outputs'
tab. See the 'Outputs' tab once the stack is created for more information on
what to do next.
AllowedValues:
- Yes - My domain is set up on Route 53
- No - My domain is not on Route 53 and I will set up my SSL certificate below
in the 'SSL Certificates' section
ShortlinkZone:
Type: AWS::Route53::HostedZone::Id
Description: You'll need a Route 53 domain name for short room permalinks, entry
codes, and device linking. (eg myhub.link) It should *not* be the same domain
as the any of the domain names that you specified above. This domain will be
used by visitors to share room links, and ideally should be short and easy to
type since it will be typed by users on mobile devices and in VR.
AllowedPattern: .+
ConstraintDescription: You didn't specify a short link Route 53 Zone. Please choose
one or create a new domain name on AWS on Route 53, ideally one short and easy
to type.
InternalZone:
Type: AWS::Route53::HostedZone::Id
Description: Your hub needs a domain name to use internally for server management.
If your primary domain is on Route 53 and you're not using it for anything else,
you can choose it here. Otherwise, you'll need to register a new domain name
on Route 53 for this (for example, myhub-internal.com). This domain name will
not be seen by users.
AllowedPattern: .+
ConstraintDescription: You didn't specify an internal Route 53 Zone. Please choose
one or create a new domain name on AWS on Route 53. It can be anything you want
and is used internally. It will not be seen by your visitors.
EmailZone:
Type: AWS::Route53::HostedZone::Id
Description: To verify email addresses, your hub needs to be able to send email.
If you do not have an existing email SMTP provider, choose a Route 53 domain
you'd like to send email from. It will be set up automatically using AWS Simple
Email Service. If you have an existing email provider you'd like to send email
from (eg Mailchimp) or already have a verified domain in AWS Simple Email Service
select the same zone you chose above for 'Internal Route 53 Zone' and you'll
be able to enter the login credentials from your email provider later.
AllowedPattern: .+
ConstraintDescription: You didn't specify an Route 53 Zone to send email from.
Please choose one. If you already have email set up, just choose your Internal
Route 53 Zone.
EmailSubdomain:
Type: String
Description: The Subdomain prefix to use for outgoing emails. For example, if
your Outgoing Email Route 53 Zone is set to myhub.com, setting this field to
"mail" will send email from the domain mail.myhub.com. If you have a third party
email provider (eg Mailchimp) you can ignore this field.
Default: mail
AdminEmailAddress:
Type: String
Description: Email address for your administrator account. Individuals with access
to this email address will have full administrative control of your hub. Before
you can log in, you will need to verify this email address - you will get a
verification email from AWS after the stack is ready.
AllowedPattern: .+
ConstraintDescription: You must enter a contact email address.
LetsEncryptEmailAddress:
Type: String
Description: Your servers will use Let's Encrypt to automatically manage SSL certificates.
To receive urgent email notifications regarding your certificates from Let's
Encrypt, specify a contact email here. This field is optional. By deploying
this stack you agree to the LetsEncrypt TOS. (https://letsencrypt.org/repository/)
Default: None
UnmanagedDomainCertArn:
Type: String
Description: You can skip this if your domain is on Route 53 and you chose 'Yes'
above to 'Is your domain set up on Route 53?'. Otherwise, if you chose 'No'
and you are using a domain name managed by another provider like GoDaddy or
Namecheap you will need to create an SSL certificate in the same region as your
stack in AWS Certificate Manager before completing this form. Instructions can
be found at https://docs.aws.amazon.com/acm/latest/userguide/gs-acm-request-public.html.
*Note* - make sure you create your SSL certificate in the AWS region you'd like
to use for your hub's stack. (The region can be found in the top right corner
of the AWS console.) Once you've created the certificate, paste the ARN for
the certificate here (eg arn:aws:acm:<region>:<account>:certificate/<id>). You
can find the ARN by clicking on the certificate in the AWS Certificate Manager
console.
ConstraintDescription: You must point to an ARN in AWS Certificate Manager for
your domain.
UnmanagedDomainEastCertArn:
Type: String
Description: You can skip this if your domain is on Route 53 and you chose 'Yes'
above to 'Is your domain set up on Route 53?'. Otherwise, you'll need to create
a SSL certificate for your external domain in US-East-1 (N. Virginia). Instructions
can be found at https://docs.aws.amazon.com/acm/latest/userguide/gs-acm-request-public.html.
*Note* - make sure you provide a SSL certificate in US-East-1 regardless of
what region you are creating the stack in. If you are creating your stack in
US-East-1 (N. Virginia) and so already pasted a certificate ARN in US-East-1
for the above field, you can paste the same certificate ARN here. Otherwise,
you'll need to create a new SSL certificate in US-East-1 and paste its ARN here.
(eg arn:aws:acm:us-east-1:<account>:certificate/<id>) Be sure to change the
region in the top right of the console to 'US East (N. Virginia) before creating
the certificate.
ConstraintDescription: You must point to an ARN in US-East-1 (N. Virginia) AWS
Certificate Manager for your domain.
AppInstanceType:
Type: String
Description: The EC2 instance type for your app server(s). Choose a "t" instance
type if you expect bursty use of the service, and the "c" types if you expect
steady usage.
Default: t3.micro
AllowedValues:
- t3.micro
- t3.small
- t3.medium
- t3.large
- t3.xlarge
- t3.2xlarge
- c4.large
- c5.large
- c5.xlarge
- c5.2xlarge
- c5.4xlarge
- c5.9xlarge
- c5.12xlarge
- c5.18xlarge
- c5.24xlarge
KeyPair:
Type: AWS::EC2::KeyPair::KeyName
Description: 'SSH Keypair for server SSH access. If there no keypairs in this
list, you''ll need to add a SSH keypair via the EC2 console. See: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-key-pairs.html#having-ec2-create-your-key-pair.'
AllowedPattern: .+
ConstraintDescription: 'You must choose an SSH keypair. To create one, see: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-key-pairs.html#having-ec2-create-your-key-pair.'
ClassB:
Type: String
Description: VPC ClassB (10.XX.0.0/16) block to use for internal server IPs.
Default: 0
AppPlacementGroupStrategy:
Type: String
Description: Placement group strategy for app servers. If you only have one app
server, this setting has no effect. If you have more than one app server, choose
'cluster' to increase connectivity between servers at the cost of reliability.
When using 'cluster', you will have lower latency between users but your service
will go down if there is an AZ-wide outage. Choose 'spread' if you would like
to ensure higher availability at the cost of latency or maximum throughput between
app servers. Choose 'none' if you do not have a preference, will be running
more than 7 app servers, or if AWS has been failing to provision servers for
you in previous attempts to create the stack. App servers communicate with one
another extensively, so 'cluster' is the preferred setting if you can tolerate
some risk of downtime if there are AZ-wide outages, which are rare.
Default: cluster
AllowedValues:
- cluster
- spread
- none
DbBackupRetentionPeriod:
Type: Number
Description: Number of days your AWS Aurora database backups will be retained
for.
Default: 14
DbMaxCapacity:
Type: Number
Description: Maximum ACU Capacity for the database. If you are unsure, leave this
at the default. The database will be a serverless Aurora PostgreSQL-compatible
cluster. Increasing the Maximum ACUs will increase the available resources to
your hub for database queries, but will cost more. This needs to be set to at
least 2. Pricing info for ACUs can be found at https://aws.amazon.com/rds/aurora/serverless/
MinValue: 2
MaxValue: 384
Default: 2
RestoreDbSnapshotIdentifier:
Type: String
Description: 'Leave blank if you are not restoring from a backup. To restore from
an existing backup, specify the AWS Aurora database snapshot identifier to restore
from. (eg mystack-snapshot-mystack-app-db-xxxxx) WARNING: do *not* change or
blank out this value after your stack is created, otherwise you will replace
your existing database!'
RestoreAppDbSecretArn:
Type: String
Description: 'Leave blank if you are not restoring from a backup. To restore from
an existing backup, specify the ARN for the database secret from the hub being
restored from. (eg arn:aws:secretsmanager:<region>:<account-id>:secret:AppDbSecret-xxxxxxxxx)
WARNING: do *not* change or blank out this value after your stack is created,
otherwise you will replace your existing database!'
RestoreStackName:
Type: String
Description: 'Leave blank if you are not restoring from a backup. To restore from
an existing backup, specify the stack name being restored from. To restore,
the secrets stored in Parameter Store must still be available from the stack
being restored from. These parameters should still exist even if you deleted
the old stack, unless you manually removed them. WARNING: do *not* change or
blank out this value after your stack is created, otherwise you will replace
your existing database!'
RestoreBackupVaultName:
Type: String
Description: 'Leave blank if you are not restoring from a backup. To restore from
an existing backup, specify the AWS Backup Vault Name to restore storage from.
(eg mystack-daily-backup-xxxxxxx) WARNING: do *not* change or blank out this
value after your stack is created, otherwise you will replace your existing
database!'
RestoreRecoveryPointArn:
Type: String
Description: 'Leave blank if you are not restoring from a backup. To restore from
an existing backup, specify the AWS Backup Vault''s Recovery Point ARN to restore
storage from. (eg arn:aws:backup:<region>:<account id>:recovery-point:xxxxxxxx)
WARNING: do *not* change or blank out this value after your stack is created,
otherwise you will replace your existing database!'
NewCMKForDiskEncryption:
Type: String
Description: Select 'Yes' if you'd like to create a dedicated CMK in KMS for database
and backup encryption. KMS charges for a single managed key will apply.
Default: No - Use the AWS Managed Key
AllowedValues:
- No - Use the AWS Managed Key
- Yes - Create a new CMK
SubnetAZs:
Type: String
Description: 'If your stack is failing to create, and the ''Events'' tab shows
the error ''Value (xxx) for parameter availabilityZone is invalid. Subnets can
currently only be created in the following availability zones: X, Y'', you will
need to select an alternative subnet configuration here. This is an issue with
AWS: https://github.com/widdix/aws-cf-templates/issues/36'
Default: Use zone a and b
AllowedValues:
- Use zone a and b
- Use zone b and c
- Use zone a and c
- Use zone b and d
- Use zone c and d
LoadBalancingMethod:
Type: String
Description: Method for load balancing requests to app servers. If you only have
one or a few app servers, and would like to save on AWS costs, choose DNS Round
Robin. If you have several app servers or would like improved load balancing,
you can also use an Application Load Balancer, which will incur additional costs.
If you are using a domain not on Route 53, be sure to note the "AddressForRootDomain"
in the Outputs tab after the stack update to set your DNS to point to the ALB.
Default: DNS Round Robin
AllowedValues:
- DNS Round Robin
- Application Load Balancer
AutoPauseDb:
Type: String
Description: Your hub uses the Aurora Serverless database, which can greatly save
costs by pausing the database when your hub is not in use. However, when the
database is paused, it can take up to 20 seconds for your site to come back
up when you visit it. You can turn off database pausing if you'd like to avoid
any delay in loading your site. Turning off pausing will incur continuous hourly
database costs. Pricing info can be found at https://aws.amazon.com/rds/aurora/serverless/
Default: Yes - Pause database when not in use
AllowedValues:
- Yes - Pause database when not in use
- No - Pay for database continuously to prevent loading delays
StackOffline:
Type: String
Description: To save costs, you can switch your stack from being Online to Offline
when it is not needed. This will shut down the servers while keeping all of
your data. Once your stack is switched back to Online, it will come back up
just how you left it.
AllowedValues:
- Online
- Offline - Temporarily shut off servers
Default: Online
StackOfflineRedirectUrl:
Type: String
Description: (Optional) When your stack is set to Offline, visitors will be redirected
to this URL.
DatabaseMonthlyBudget:
Type: Number
Description: If you do not want a monthly database budget, set this to zero. To
save costs, your AWS Aurora Serverless database will be shut down when nobody
is hitting your site. When visitors are using your site, you'll incur database
costs. To avoid unexpected charges, you can set a monthly budget. If you are
running multiple hubs, this budget should reflect your maximum allowed cost
across *all* your hubs in this region. This hub will automatically switch into
Offline mode if the Aurora Serverless database charges across all your hubs
in this region exceed this budget. A minimum $20 budget is recommended and will
provide more than 40 hours a week of connectivity for a hub. You only pay for
what you use. Leave this field blank to fall back to your "Max database capacity"
setting in "Database Configuration" to limit your database cost. Pricing info
can be found at https://aws.amazon.com/rds/aurora/serverless/
Default: 0
MaxStorage:
Type: Number
Description: When users upload scenes, avatars, or files, they will use disk storage.
You only pay for the storage you use. You can set a maximum storage amount here
(in GB) to ensure there are no unexpected charges. Pricing info can be found
at https://aws.amazon.com/efs/pricing/
MinValue: 5
MaxValue: 1048576
Default: 128
InboundCidrOverride:
Type: String
Description: IP CDIR which will be granted HTTPS and WebRTC access to your site.
Set to 0.0.0.0/0 to allow anyone on the Internet to access your site.
InboundSSHCidrOverride:
Type: String
Description: IP CDIR which will be granted SSH port access to your servers. Connecting
users will still need valid SSH keys to access your server. Set to 0.0.0.0/0
to allow any IP with the SSH key + 2FA token to access your servers via SSH.
SpecifyInboundCidr:
Type: String
Description: You can restrict which IP addresses can access your site. To allow
anyone on the Internet to load your site, choose 'Allow anyone to access my
site.'
AllowedValues:
- Allow anyone to access my site
- I will specify the 'Inbound Site Access CDIR' in 'Advanced' below to restrict
IP addresses to my site
Default: Allow anyone to access my site
SpecifyInboundSSHCidr:
Type: String
Description: You can restrict the IP addresses that can access your servers over
SSH. SSH access will always require the SSH private key file specified above
and the 2FA token regardless of IP.
AllowedValues:
- Allow anyone with the SSH secret key and 2FA token to access my servers
- I will specify the 'Inbound SSH CDIR' in 'Advanced' below to restrict SSH IP
addresses to my servers
AllowedPattern: .+
ConstraintDescription: You must select if you'd like any IP SSH restrictions.
Mappings:
Regions:
us-east-1:
Abbreviation: USE1
ImageId: ami-0a81bac87065ce464
us-west-2:
Abbreviation: USW2
ImageId: ami-0bd077dc2cb632f73
us-east-2:
Abbreviation: USE2
ImageId: ami-0168e6c7a32422283
ap-northeast-1:
Abbreviation: APN1
ImageId: ami-08fb6cdf9bdf950d0
eu-west-1:
Abbreviation: EU
ImageId: ami-0c16e27df6da08592
ServicesMeta:
RetExternal:
Port: 443
RetInternal:
Port: 4000
RetTurnExternal:
Port: 5349
DialogInternal:
Port: 8443
DialogExternal:
AppPort: 8443
StreamPort: 443
DialogTurnExternal:
Port: 5349
DialogAdmin:
Port: 7000
DialogWebRTCFrom:
Port: 51610
DialogWebRTCTo:
Port: 60999
YTDL:
Port: 8080
BioCensus:
Port: 9631
Ssh:
Port: 22
PostgreSQL:
Port: 5432
InstanceTypeMeta:
t3.micro:
PlacementForCluster: spread
t3.small:
PlacementForCluster: spread
t3.medium:
PlacementForCluster: spread
t3.large:
PlacementForCluster: spread
t3.xlarge:
PlacementForCluster: spread
t3.2xlarge:
PlacementForCluster: spread
c4.large:
PlacementForCluster: cluster
c5.large:
PlacementForCluster: cluster
c5.xlarge:
PlacementForCluster: cluster
c5.2xlarge:
PlacementForCluster: cluster
c5.4xlarge:
PlacementForCluster: cluster
c5.9xlarge:
PlacementForCluster: cluster
c5.12xlarge:
PlacementForCluster: cluster
c5.18xlarge:
PlacementForCluster: cluster
c5.24xlarge:
PlacementForCluster: cluster
AWSMPServerlessResourcesRegionalizedMappings:
us-east-1:
photomnemonic5f452d36bfff4c02a4175c663b64fd08: arn:aws:serverlessrepo:us-east-1:865964754613:applications/photomnemonic-5f452d36-bfff-4c02-a417-5c663b64fd08
speelycaptor5f452d36bfff4c02a4175c663b64fd08: arn:aws:serverlessrepo:us-east-1:865964754613:applications/speelycaptor-5f452d36-bfff-4c02-a417-5c663b64fd08
nearspark5f452d36bfff4c02a4175c663b64fd08: arn:aws:serverlessrepo:us-east-1:865964754613:applications/nearspark-5f452d36-bfff-4c02-a417-5c663b64fd08
awscfnsesdomain5f452d36bfff4c02a4175c663b64fd08: arn:aws:serverlessrepo:us-east-1:865964754613:applications/aws-cfn-ses-domain-5f452d36-bfff-4c02-a417-5c663b64fd08
keymasterpublic5f452d36bfff4c02a4175c663b64fd08: arn:aws:serverlessrepo:us-east-1:771996922080:applications/keymaster-public-5f452d36-bfff-4c02-a417-5c663b64fd08
eu-west-1:
photomnemonic5f452d36bfff4c02a4175c663b64fd08: arn:aws:serverlessrepo:eu-west-1:118096126957:applications/photomnemonic-5f452d36-bfff-4c02-a417-5c663b64fd08
speelycaptor5f452d36bfff4c02a4175c663b64fd08: arn:aws:serverlessrepo:eu-west-1:118096126957:applications/speelycaptor-5f452d36-bfff-4c02-a417-5c663b64fd08
nearspark5f452d36bfff4c02a4175c663b64fd08: arn:aws:serverlessrepo:eu-west-1:118096126957:applications/nearspark-5f452d36-bfff-4c02-a417-5c663b64fd08
awscfnsesdomain5f452d36bfff4c02a4175c663b64fd08: arn:aws:serverlessrepo:eu-west-1:118096126957:applications/aws-cfn-ses-domain-5f452d36-bfff-4c02-a417-5c663b64fd08
keymasterpublic5f452d36bfff4c02a4175c663b64fd08: arn:aws:serverlessrepo:eu-west-1:753951066699:applications/keymaster-public-5f452d36-bfff-4c02-a417-5c663b64fd08
ap-northeast-1:
photomnemonic5f452d36bfff4c02a4175c663b64fd08: arn:aws:serverlessrepo:ap-northeast-1:006377987546:applications/photomnemonic-5f452d36-bfff-4c02-a417-5c663b64fd08
speelycaptor5f452d36bfff4c02a4175c663b64fd08: arn:aws:serverlessrepo:ap-northeast-1:006377987546:applications/speelycaptor-5f452d36-bfff-4c02-a417-5c663b64fd08
nearspark5f452d36bfff4c02a4175c663b64fd08: arn:aws:serverlessrepo:ap-northeast-1:006377987546:applications/nearspark-5f452d36-bfff-4c02-a417-5c663b64fd08
awscfnsesdomain5f452d36bfff4c02a4175c663b64fd08: arn:aws:serverlessrepo:ap-northeast-1:006377987546:applications/aws-cfn-ses-domain-5f452d36-bfff-4c02-a417-5c663b64fd08
keymasterpublic5f452d36bfff4c02a4175c663b64fd08: arn:aws:serverlessrepo:ap-northeast-1:954919168245:applications/keymaster-public-5f452d36-bfff-4c02-a417-5c663b64fd08
us-east-2:
photomnemonic5f452d36bfff4c02a4175c663b64fd08: arn:aws:serverlessrepo:us-east-2:093084136110:applications/photomnemonic-5f452d36-bfff-4c02-a417-5c663b64fd08
speelycaptor5f452d36bfff4c02a4175c663b64fd08: arn:aws:serverlessrepo:us-east-2:093084136110:applications/speelycaptor-5f452d36-bfff-4c02-a417-5c663b64fd08
nearspark5f452d36bfff4c02a4175c663b64fd08: arn:aws:serverlessrepo:us-east-2:093084136110:applications/nearspark-5f452d36-bfff-4c02-a417-5c663b64fd08
awscfnsesdomain5f452d36bfff4c02a4175c663b64fd08: arn:aws:serverlessrepo:us-east-2:093084136110:applications/aws-cfn-ses-domain-5f452d36-bfff-4c02-a417-5c663b64fd08
keymasterpublic5f452d36bfff4c02a4175c663b64fd08: arn:aws:serverlessrepo:us-east-2:024540230436:applications/keymaster-public-5f452d36-bfff-4c02-a417-5c663b64fd08
us-west-2:
photomnemonic5f452d36bfff4c02a4175c663b64fd08: arn:aws:serverlessrepo:us-west-2:382795170440:applications/photomnemonic-5f452d36-bfff-4c02-a417-5c663b64fd08
speelycaptor5f452d36bfff4c02a4175c663b64fd08: arn:aws:serverlessrepo:us-west-2:382795170440:applications/speelycaptor-5f452d36-bfff-4c02-a417-5c663b64fd08
nearspark5f452d36bfff4c02a4175c663b64fd08: arn:aws:serverlessrepo:us-west-2:382795170440:applications/nearspark-5f452d36-bfff-4c02-a417-5c663b64fd08
awscfnsesdomain5f452d36bfff4c02a4175c663b64fd08: arn:aws:serverlessrepo:us-west-2:382795170440:applications/aws-cfn-ses-domain-5f452d36-bfff-4c02-a417-5c663b64fd08
keymasterpublic5f452d36bfff4c02a4175c663b64fd08: arn:aws:serverlessrepo:us-west-2:623131709813:applications/keymaster-public-5f452d36-bfff-4c02-a417-5c663b64fd08
Conditions:
HasManagedDomain:
Fn::Equals:
- Ref: IsDomainOnRoute53
- Yes - My domain is set up on Route 53
HasInboundCidrOverride:
Fn::Equals:
- Ref: SpecifyInboundCidr
- I will specify the 'Inbound Site Access CDIR' in 'Advanced' below to restrict
IP addresses to my site
HasInboundSSHCidrOverride:
Fn::Equals:
- Ref: SpecifyInboundSSHCidr
- I will specify the 'Inbound SSH CDIR' in 'Advanced' below to restrict SSH IP
addresses to my servers
HasUnmangedDomain:
Fn::Not:
- Condition: HasManagedDomain
HasLetsEncryptEmail:
Fn::Not:
- Fn::Equals:
- Ref: LetsEncryptEmailAddress
- None
HasStreamingServers:
Fn::And:
- Fn::Equals:
- Ref: StackOffline
- Online
- Fn::Not:
- Fn::Equals:
- 0
- 0
AppIsNoPlacement:
Fn::Equals:
- Ref: AppPlacementGroupStrategy
- none
AppIsClusterPlacement:
Fn::Equals:
- Ref: AppPlacementGroupStrategy
- cluster
HasEmailSubdomain:
Fn::Not:
- Fn::Equals:
- Ref: EmailSubdomain
- ''
CreateDiskEncryptionKey:
Fn::Equals:
- Ref: NewCMKForDiskEncryption
- Yes - Create a new CMK
SubnetChoiceA:
Fn::Equals:
- Ref: SubnetAZs
- Use zone a and b
SubnetChoiceB:
Fn::Equals:
- Ref: SubnetAZs
- Use zone b and c
SubnetChoiceC:
Fn::Equals:
- Ref: SubnetAZs
- Use zone a and c
SubnetChoiceD:
Fn::Equals:
- Ref: SubnetAZs
- Use zone b and d
IsOnline:
Fn::Equals:
- Ref: StackOffline
- Online
IsOffline:
Fn::Not:
- Condition: IsOnline
HasOfflineRedirectUrl:
Fn::Not:
- Fn::Equals:
- Ref: StackOfflineRedirectUrl
- ''
PerformOfflineRedirect:
Fn::And:
- Condition: IsOffline
- Condition: HasOfflineRedirectUrl
PerformOfflineRedirectWithUnmanagedDomain:
Fn::And:
- Condition: PerformOfflineRedirect
- Condition: HasUnmangedDomain
EnableDbAutoPause:
Fn::Equals:
- Ref: AutoPauseDb
- Yes - Pause database when not in use
HasDbMonthlyBudget:
Fn::Not:
- Fn::Equals:
- Ref: DatabaseMonthlyBudget
- 0
IsNotRestore:
Fn::Equals:
- Ref: RestoreDbSnapshotIdentifier
- ''
IsRestore:
Fn::Not:
- Condition: IsNotRestore
IsEast:
Fn::Equals:
- Fn::Sub: ${AWS::Region}
- us-east-1
IsNonEast:
Fn::Not:
- Condition: IsEast
HasManagedDomainNonEast:
Fn::And:
- Condition: IsNonEast
- Condition: HasManagedDomain
HasManagedDomainEast:
Fn::And:
- Condition: IsEast
- Condition: HasManagedDomain
RequestedALB:
Fn::Equals:
- Ref: LoadBalancingMethod
- Application Load Balancer
HasALB:
Fn::And:
- Fn::Or:
- Condition: IsOnline
- Condition: PerformOfflineRedirectWithUnmanagedDomain
- Condition: RequestedALB
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock:
Fn::Sub: 10.${ClassB}.0.0/16
EnableDnsSupport: true
EnableDnsHostnames: true
InstanceTenancy: default
Tags:
- Key: Name
Value:
Fn::Sub: ${AWS::StackName} 10.${ClassB}.0.0/16
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value:
Fn::Sub: ${AWS::StackName} 10.${ClassB}.0.0/16
VPCGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId:
Ref: VPC
InternetGatewayId:
Ref: InternetGateway
SubnetAPublic:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone:
Fn::Select:
- Fn::If:
- SubnetChoiceA
- 0
- Fn::If:
- SubnetChoiceB
- 1
- Fn::If:
- SubnetChoiceC
- 0
- Fn::If:
- SubnetChoiceD
- 1
- 2
- Fn::GetAZs: ''
CidrBlock:
Fn::Sub: 10.${ClassB}.0.0/20
MapPublicIpOnLaunch: true
VpcId:
Ref: VPC
Tags:
- Key: Name
Value:
Fn::Sub: ${AWS::StackName} A public
- Key: Reach
Value: public
SubnetAPrivate:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone:
Fn::Select:
- Fn::If:
- SubnetChoiceA
- 0
- Fn::If:
- SubnetChoiceB
- 1
- Fn::If:
- SubnetChoiceC
- 0
- Fn::If:
- SubnetChoiceD
- 1
- 2
- Fn::GetAZs: ''
CidrBlock:
Fn::Sub: 10.${ClassB}.16.0/20
VpcId:
Ref: VPC
Tags:
- Key: Name
Value:
Fn::Sub: ${AWS::StackName} A private
- Key: Reach
Value: private
SubnetBPublic:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone:
Fn::Select:
- Fn::If:
- SubnetChoiceA
- 1
- Fn::If:
- SubnetChoiceB
- 2
- Fn::If:
- SubnetChoiceC
- 2
- Fn::If:
- SubnetChoiceD
- 3
- 3
- Fn::GetAZs: ''
CidrBlock:
Fn::Sub: 10.${ClassB}.32.0/20
MapPublicIpOnLaunch: true
VpcId:
Ref: VPC
Tags:
- Key: Name
Value:
Fn::Sub: ${AWS::StackName} B public
- Key: Reach
Value: public
SubnetBPrivate:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone:
Fn::Select:
- Fn::If:
- SubnetChoiceA
- 1
- Fn::If:
- SubnetChoiceB
- 2
- Fn::If:
- SubnetChoiceC
- 2
- Fn::If:
- SubnetChoiceD
- 3
- 3
- Fn::GetAZs: ''
CidrBlock:
Fn::Sub: 10.${ClassB}.48.0/20
VpcId:
Ref: VPC
Tags:
- Key: Name
Value:
Fn::Sub: ${AWS::StackName} B private
- Key: Reach
Value: private
RouteTableAPublic:
Type: AWS::EC2::RouteTable
Properties:
VpcId:
Ref: VPC
Tags:
- Key: Name
Value:
Fn::Sub: ${AWS::StackName} A public
RouteTableAPrivate:
Type: AWS::EC2::RouteTable
Properties:
VpcId:
Ref: VPC
Tags:
- Key: Name
Value:
Fn::Sub: ${AWS::StackName} A private
RouteTableBPublic:
Type: AWS::EC2::RouteTable
Properties:
VpcId:
Ref: VPC
Tags:
- Key: Name
Value:
Fn::Sub: ${AWS::StackName} B public
RouteTableBPrivate:
Type: AWS::EC2::RouteTable
Properties:
VpcId:
Ref: VPC
Tags:
- Key: Name
Value:
Fn::Sub: ${AWS::StackName} B private
RouteTableAssociationAPublic:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId:
Ref: SubnetAPublic
RouteTableId:
Ref: RouteTableAPublic
RouteTableAssociationAPrivate:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId:
Ref: SubnetAPrivate
RouteTableId:
Ref: RouteTableAPrivate
RouteTableAssociationBPublic:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId:
Ref: SubnetBPublic
RouteTableId:
Ref: RouteTableBPublic
RouteTableAssociationBPrivate:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId:
Ref: SubnetBPrivate
RouteTableId:
Ref: RouteTableBPrivate
RouteTablePublicAInternetRoute:
Type: AWS::EC2::Route
DependsOn: VPCGatewayAttachment
Properties:
RouteTableId:
Ref: RouteTableAPublic
DestinationCidrBlock: 0.0.0.0/0
GatewayId:
Ref: InternetGateway
RouteTablePublicBInternetRoute:
Type: AWS::EC2::Route
DependsOn: VPCGatewayAttachment
Properties:
RouteTableId:
Ref: RouteTableBPublic
DestinationCidrBlock: 0.0.0.0/0
GatewayId:
Ref: InternetGateway
NetworkAclPublic:
Type: AWS::EC2::NetworkAcl
Properties:
VpcId:
Ref: VPC
Tags:
- Key: Name
Value:
Fn::Sub: ${AWS::StackName} Public
NetworkAclPrivate:
Type: AWS::EC2::NetworkAcl
Properties:
VpcId:
Ref: VPC
Tags:
- Key: Name
Value:
Fn::Sub: ${AWS::StackName} Private
SubnetNetworkAclAssociationAPublic:
Type: AWS::EC2::SubnetNetworkAclAssociation
Properties:
SubnetId:
Ref: SubnetAPublic
NetworkAclId:
Ref: NetworkAclPublic
SubnetNetworkAclAssociationAPrivate:
Type: AWS::EC2::SubnetNetworkAclAssociation
Properties:
SubnetId:
Ref: SubnetAPrivate
NetworkAclId:
Ref: NetworkAclPrivate
SubnetNetworkAclAssociationBPublic:
Type: AWS::EC2::SubnetNetworkAclAssociation
Properties:
SubnetId:
Ref: SubnetBPublic
NetworkAclId:
Ref: NetworkAclPublic
SubnetNetworkAclAssociationBPrivate:
Type: AWS::EC2::SubnetNetworkAclAssociation
Properties:
SubnetId:
Ref: SubnetBPrivate
NetworkAclId:
Ref: NetworkAclPrivate
NetworkAclEntryInPublicAllowAll:
Type: AWS::EC2::NetworkAclEntry
Properties:
NetworkAclId:
Ref: NetworkAclPublic
RuleNumber: 99
Protocol: -1
RuleAction: allow
Egress: false
CidrBlock: 0.0.0.0/0
NetworkAclEntryOutPublicAllowAll:
Type: AWS::EC2::NetworkAclEntry
Properties:
NetworkAclId:
Ref: NetworkAclPublic
RuleNumber: 99
Protocol: -1
RuleAction: allow
Egress: true
CidrBlock: 0.0.0.0/0
NetworkAclEntryInPrivateAllowVPC:
Type: AWS::EC2::NetworkAclEntry
Properties:
NetworkAclId:
Ref: NetworkAclPrivate
RuleNumber: 99
Protocol: -1
RuleAction: allow
Egress: false
CidrBlock: 0.0.0.0/0
NetworkAclEntryOutPrivateAllowVPC:
Type: AWS::EC2::NetworkAclEntry
Properties:
NetworkAclId:
Ref: NetworkAclPrivate
RuleNumber: 99
Protocol: -1
RuleAction: allow
Egress: true
CidrBlock: 0.0.0.0/0
Route53DependencyLambda:
Metadata:
Source: https://github.com/sonyxperiadev/amazon-custom-resources/tree/master/route53-dependency
Version: 1.0.0
Type: AWS::Lambda::Function
Properties:
FunctionName:
Fn::Sub: ${AWS::StackName}-route53-dependency
Description: Lookup Route 53 info
Handler: index.handler
Role:
Fn::GetAtt: Route53DependencyRole.Arn
Runtime: nodejs14.x
Timeout: 900
Code:
ZipFile: |
'use strict';
function route53Dependency(properties, callback) {
if (!properties.Id && !properties.Domain)
callback("Zone id or domain not specified");
var aws = require("aws-sdk");
var route53 = new aws.Route53();
var responseData = {};
console.log('route53Dependency', properties);
route53.listHostedZones({}, function(err, data) {
console.log('listHostedZones', err, data);
if (err)
return callback(err);
var zones = data.HostedZones;
var matching = zones.filter(function(zone) {
if (properties.Id) {
return zone.Id === "/hostedzone/" + properties.Id;
} else {
var tldParts = properties.Domain.split(".");
var tld = tldParts[tldParts.length - 2] + "." + tldParts[tldParts.length - 1];
return zone.Name === tld + ".";
}
});
if (matching.length != 1)
return callback('Exactly one matching zone is allowed ' + zones);
var match = matching[0];
delete match.Config;
delete match.CallerReference;
match.Id = match.Id.split('/')[2];
match.Name = match.Name.substring(0, match.Name.length-1);
return callback(null, match);
});
}
route53Dependency.handler = function(event, context) {
console.log(JSON.stringify(event, null, ' '));
if (event.RequestType == 'Delete') {
return sendResponse(event, context, "SUCCESS");
}
route53Dependency(event.ResourceProperties, function(err, result) {
var status = err ? 'FAILED' : 'SUCCESS';
return sendResponse(event, context, status, result, err);
});
};
function getReason(err) {
if (err)
return err.message;
else
return '';
}
function sendResponse(event, context, status, data, err) {
var responseBody = {
StackId: event.StackId,
RequestId: event.RequestId,
LogicalResourceId: event.LogicalResourceId,
PhysicalResourceId: 'route53Dependency-' + (event.ResourceProperties.Domain || event.ResourceProperties.Id),
Status: status,
Reason: getReason(err) + " See details in CloudWatch Log: " + context.logStreamName,
Data: data
};
console.log("RESPONSE:\n", responseBody);
var json = JSON.stringify(responseBody);
var https = require("https");
var url = require("url");
var parsedUrl = url.parse(event.ResponseURL);
var options = {
hostname: parsedUrl.hostname,
port: 443,
path: parsedUrl.path,
method: "PUT",
headers: {
"content-type": "",
"content-length": json.length
}
};
var request = https.request(options, function(response) {
console.log("STATUS: " + response.statusCode);
console.log("HEADERS: " + JSON.stringify(response.headers));
context.done(null, data);
});
request.on("error", function(error) {
console.log("sendResponse Error:\n", error);
context.done(error);
});
request.on("end", function() {
console.log("end");
});
request.write(json);
request.end();
}
module.exports = route53Dependency;
Route53DependencyRole:
Properties:
RoleName:
Fn::Sub: ${AWS::StackName}-Route53Dependency
AssumeRolePolicyDocument:
Statement:
- Action:
- sts:AssumeRole
Effect: Allow
Principal:
Service: lambda.amazonaws.com
Version: '2012-10-17'
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
- arn:aws:iam::aws:policy/service-role/AWSLambdaRole
Policies:
- PolicyDocument:
Statement:
- Action:
- route53:ListHostedZones
Effect: Allow
Resource: '*'
Version: '2012-10-17'
PolicyName:
Fn::Sub: ${AWS::StackName}Route53DependencyCustomPolicy
Type: AWS::IAM::Role
ParseURL:
Type: AWS::Lambda::Function
Properties:
FunctionName:
Fn::Sub: ${AWS::StackName}-parse-url
Description: Parse a URL into its constituent parts for redirector
Handler: index.handler
Role:
Fn::GetAtt: ParseURLRole.Arn
Runtime: nodejs14.x
Timeout: 900
Code:
ZipFile: |
const URL = require('url');
const parseURL = {};
function btoa(s) {
return Buffer.from(s).toString('base64');
}
parseURL.handler = function(event, context) {
const url = event.ResourceProperties.URL || "https://en.wikipedia.org/wiki/Rubber_duck";
try {
const parsed = URL.parse(url);
const pathname = parsed.pathname;
return sendResponse(event, context, "SUCCESS", {
S3ReplaceKeyPrefixWith: `${parsed.pathname.substring(1)}${parsed.search || ""}${parsed.hash || ""}`,
S3Protocol: parsed.protocol.replace(":", ""),
S3Hostname: parsed.host,
ALBProtocol: parsed.protocol.replace(":", "").toUpperCase(),
ALBPort: parsed.port,
ALBHost: parsed.host,
ALBPath: parsed.pathname,
ALBQuery: `${parsed.search || ""}${parsed.hash || ""}`.replace(/^\?/, "")
});
} catch (e) {
return sendResponse(event, context, "FAILED", null, `Invalid URL specified: ${url}`);
}
};
function getReason(err) {
if (err)
return err.message;
else
return '';
}
function sendResponse(event, context, status, data, err) {
var responseBody = {
StackId: event.StackId,
RequestId: event.RequestId,
LogicalResourceId: event.LogicalResourceId,
PhysicalResourceId: 'parseUrl-' + btoa(event.ResourceProperties.URL),
Status: status,
Reason: getReason(err) + " See details in CloudWatch Log: " + context.logStreamName,
Data: data
};
console.log("RESPONSE:\n", responseBody);
var json = JSON.stringify(responseBody);
var https = require("https");
var url = require("url");
var parsedUrl = url.parse(event.ResponseURL);
var options = {
hostname: parsedUrl.hostname,
port: 443,
path: parsedUrl.path,
method: "PUT",
headers: {
"content-type": "",
"content-length": json.length
}
};
var request = https.request(options, function(response) {
console.log("STATUS: " + response.statusCode);
console.log("HEADERS: " + JSON.stringify(response.headers));
context.done(null, data);
});
request.on("error", function(error) {
console.log("sendResponse Error:\n", error);
context.done(error);
});
request.on("end", function() {
console.log("end");
});
request.write(json);
request.end();
}
module.exports = parseURL;
ParseURLRole:
Properties:
RoleName:
Fn::Sub: ${AWS::StackName}-ParseURLRole
AssumeRolePolicyDocument:
Statement:
- Action:
- sts:AssumeRole
Effect: Allow
Principal:
Service: lambda.amazonaws.com
Version: '2012-10-17'
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
- arn:aws:iam::aws:policy/service-role/AWSLambdaRole
Type: AWS::IAM::Role
ToLower:
Type: AWS::Lambda::Function
Properties:
FunctionName:
Fn::Sub: ${AWS::StackName}-to-lower
Description: Convert a string to lower
Handler: index.handler
Role:
Fn::GetAtt: ToLowerRole.Arn
Runtime: nodejs14.x
Timeout: 900
Code:
ZipFile: |
const toLower = {};
function btoa(s) {
return Buffer.from(s).toString('base64');
};
function getReason(err) {
if (err)
return err.message;
else
return '';
};
toLower.handler = function(event, context) {
return sendResponse(event, context, "SUCCESS", { Value: event.ResourceProperties.String.toLowerCase() });
};
function sendResponse(event, context, status, data, err) {
var responseBody = {
StackId: event.StackId,
RequestId: event.RequestId,
LogicalResourceId: event.LogicalResourceId,
PhysicalResourceId: 'toLower-' + btoa(event.ResourceProperties.String),
Status: status,
Reason: getReason(err) + " See details in CloudWatch Log: " + context.logStreamName,
Data: data
};
console.log("RESPONSE:\n", responseBody);
var json = JSON.stringify(responseBody);
var https = require("https");
var url = require("url");
var parsedUrl = url.parse(event.ResponseURL);
var options = {
hostname: parsedUrl.hostname,
port: 443,
path: parsedUrl.path,
method: "PUT",
headers: {
"content-type": "",
"content-length": json.length
}
};
var request = https.request(options, function(response) {
console.log("STATUS: " + response.statusCode);
console.log("HEADERS: " + JSON.stringify(response.headers));
context.done(null, data);
});
request.on("error", function(error) {
console.log("sendResponse Error:\n", error);
context.done(error);
});
request.on("end", function() {
console.log("end");
});
request.write(json);
request.end();
}
module.exports = toLower;
ToLowerRole:
Properties:
RoleName:
Fn::Sub: ${AWS::StackName}-ToLowerRole
AssumeRolePolicyDocument:
Statement:
- Action:
- sts:AssumeRole
Effect: Allow
Principal:
Service: lambda.amazonaws.com
Version: '2012-10-17'
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
- arn:aws:iam::aws:policy/service-role/AWSLambdaRole
Type: AWS::IAM::Role
CustomAcmCertificateLambda:
Metadata:
Source: https://github.com/dflook/cloudformation-dns-certificate
Version: 1.7.2
Properties:
Code:
ZipFile: "y=Exception\nU=RuntimeError\nM=True\nimport copy,hashlib as t,json,logging\
\ as B,time\nfrom boto3 import client as J\nfrom botocore.exceptions import\
\ ClientError as u,ParamValidationError as v\nfrom urllib.request import\
\ Request as w,urlopen as x\nA=B.getLogger()\nA.setLevel(B.INFO)\nC=A.info\n\
R=A.exception\nL=copy.copy\nS=time.sleep\nT=lambda j:json.dumps(j,sort_keys=M).encode()\n\
def handler(e,c):\n\tAB='OldResourceProperties';AA='Update';A9='Delete';A8='None';A7='acm';A6='FAILED';A5='properties';A4='stack-id';A3='logical-id';A2='DNS';s='Old';r='Certificate';q='LogicalResourceId';p='DomainName';o='ValidationMethod';n='Route53RoleArn';m='Region';d='RequestType';b='Reinvoked';a='StackId';Z=None;Q='Status';P='Key';O='';N='DomainValidationOptions';K=False;I='ResourceProperties';H='cloudformation:';G='Value';F='CertificateArn';E='Tags';B='PhysicalResourceId';f=c.get_remaining_time_in_millis;C(e)\n\
\tdef g():\n\t\tC=L(A)\n\t\tfor G in ['ServiceToken',m,E,n]:C.pop(G,Z)\n\
\t\tif o in A:\n\t\t\tif A[o]==A2:\n\t\t\t\tfor H in set([A[p]]+A.get('SubjectAlternativeNames',[])):k(H)\n\
\t\t\t\tdel C[N]\n\t\te[B]=D.request_certificate(IdempotencyToken=A0,**C)[F];l()\n\
\tdef V(a):\n\t\twhile M:\n\t\t\ttry:D.delete_certificate(**{F:a});return\n\
\t\t\texcept u as B:\n\t\t\t\tR(O);A=B.response['Error']['Code']\n\t\t\t\
\tif A=='ResourceInUseException':\n\t\t\t\t\tif f()/1000<30:raise\n\t\t\t\
\t\tS(5);continue\n\t\t\t\tif A in['ResourceNotFoundException','ValidationException']:return\n\
\t\t\t\traise\n\t\t\texcept v:return\n\tdef W(p):\n\t\tfor I in D.get_paginator('list_certificates').paginate():\n\
\t\t\tfor A in I['CertificateSummaryList']:\n\t\t\t\tC(A);B={B[P]:B[G]for\
\ B in D.list_tags_for_certificate(**{F:A[F]})[E]}\n\t\t\t\tif B.get(H+A3)==e[q]and\
\ B.get(H+A4)==e[a]and B.get(H+A5)==X(p):return A[F]\n\tdef h():\n\t\tif\
\ e.get(b,K):raise U('Certificate not issued in time')\n\t\te[b]=M;C(e);J('lambda').invoke(FunctionName=c.invoked_function_arn,InvocationType='Event',Payload=T(e))\n\
\tdef i():\n\t\twhile f()/1000>30:\n\t\t\tA=D.describe_certificate(**{F:e[B]})[r];C(A)\n\
\t\t\tif A[Q]=='ISSUED':return M\n\t\t\telif A[Q]==A6:raise U(A.get('FailureReason',O))\n\
\t\t\tS(5)\n\t\treturn K\n\tdef z():A=L(e[s+I]);A.pop(E,Z);B=L(e[I]);B.pop(E,Z);return\
\ A!=B\n\tdef j():\n\t\tW='Type';V='Name';U='HostedZoneId';T='ValidationStatus';R='PENDING_VALIDATION';K='ResourceRecord'\n\
\t\tif A.get(o)!=A2:return\n\t\twhile M:\n\t\t\tH=D.describe_certificate(**{F:e[B]})[r];C(H)\n\
\t\t\tif H[Q]!=R:return\n\t\t\tif not[A for A in H.get(N,[{}])if T not in\
\ A or K not in A]:break\n\t\t\tS(1)\n\t\tfor E in H[N]:\n\t\t\tif E[T]==R:L=k(E[p]);O=L.get(n,A.get(n));I=J('sts').assume_role(RoleArn=O,RoleSessionName=(r+e[q])[:64],DurationSeconds=900)['Credentials']if\
\ O is not Z else{};P=J('route53',aws_access_key_id=I.get('AccessKeyId'),aws_secret_access_key=I.get('SecretAccessKey'),aws_session_token=I.get('SessionToken')).change_resource_record_sets(**{U:L[U],'ChangeBatch':{'Comment':'Domain\
\ validation for '+e[B],'Changes':[{'Action':'UPSERT','ResourceRecordSet':{V:E[K][V],W:E[K][W],'TTL':60,'ResourceRecords':[{G:E[K][G]}]}}]}});C(P)\n\
\tdef k(n):\n\t\tC='.';n=n.rstrip(C);D={B[p].rstrip(C):B for B in A[N]};B=n.split(C)\n\
\t\twhile len(B):\n\t\t\tif C.join(B)in D:return D[C.join(B)]\n\t\t\tB=B[1:]\n\
\t\traise U(N+' missing for '+n)\n\tX=lambda v:t.new('md5',T(v)).hexdigest()\n\
\tdef l():A=L(e[I].get(E,[]));A+=[{P:H+A3,G:e[q]},{P:H+A4,G:e[a]},{P:H+'stack-name',G:e[a].split('/')[1]},{P:H+A5,G:X(e[I])}];D.add_tags_to_certificate(**{F:e[B],E:A})\n\
\tdef Y():\n\t\tC(e);A=x(w(e['ResponseURL'],T(e),{'content-type':O},method='PUT'))\n\
\t\tif A.status!=200:raise y(A)\n\ttry:\n\t\tA0=X(e['RequestId']+e[a]);A=e[I];D=J(A7,region_name=A.get(m));e[Q]='SUCCESS'\n\
\t\tif e[d]=='Create':\n\t\t\tif e.get(b,K)is K:e[B]=A8;g()\n\t\t\tj()\n\
\t\t\tif not i():return h()\n\t\telif e[d]==A9:\n\t\t\tif e[B]!=A8:\n\t\t\
\t\tif e[B].startswith('arn:'):V(e[B])\n\t\t\t\telse:V(W(A))\n\t\telif e[d]==AA:\n\
\t\t\tif z():\n\t\t\t\tC(AA)\n\t\t\t\tif W(A)==e[B]:\n\t\t\t\t\ttry:D=J(A7,region_name=e[AB].get(m));C(A9);V(W(e[AB]))\n\
\t\t\t\t\texcept:R(O)\n\t\t\t\t\treturn Y()\n\t\t\t\tif e.get(b,K)is K:g()\n\
\t\t\t\tj()\n\t\t\t\tif not i():return h()\n\t\t\telse:\n\t\t\t\tif E in\
\ e[s+I]:D.remove_tags_from_certificate(**{F:e[B],E:e[s+I][E]})\n\t\t\t\t\
l()\n\t\telse:raise U(e[d])\n\t\treturn Y()\n\texcept y as A1:R(O);e[Q]=A6;e['Reason']=str(A1);return\
\ Y()"
Description: Cloudformation custom resource for DNS validated certificates
Handler: index.handler
Role:
Fn::GetAtt: CustomAcmCertificateLambdaExecutionRole.Arn
Runtime: python3.9
Timeout: 900
Type: AWS::Lambda::Function
CustomAcmCertificateLambdaExecutionRole:
Properties:
RoleName:
Fn::Sub: ${AWS::StackName}-CustomAcmCertificateLambdaExecution
AssumeRolePolicyDocument:
Statement:
- Action:
- sts:AssumeRole
Effect: Allow
Principal:
Service: lambda.amazonaws.com
Version: '2012-10-17'
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
- arn:aws:iam::aws:policy/service-role/AWSLambdaRole
Policies:
- PolicyDocument:
Statement:
- Action:
- acm:AddTagsToCertificate
- acm:DeleteCertificate
- acm:DescribeCertificate
- acm:RemoveTagsFromCertificate
Effect: Allow
Resource:
- Fn::Sub: arn:aws:acm:*:${AWS::AccountId}:certificate/*
- Action:
- acm:RequestCertificate
- acm:ListTagsForCertificate
- acm:ListCertificates
Effect: Allow
Resource:
- '*'
- Action:
- route53:ChangeResourceRecordSets
Effect: Allow
Resource:
- arn:aws:route53:::hostedzone/*
Version: '2012-10-17'
PolicyName:
Fn::Sub: ${AWS::StackName}CustomAcmCertificateLambdaExecutionPolicy
Type: AWS::IAM::Role
ExternalZoneInfo:
Type: Custom::Route53
Condition: HasManagedDomain
Properties:
Domain:
Ref: DomainName
ServiceToken:
Fn::GetAtt: Route53DependencyLambda.Arn
ShortlinkZoneInfo:
Type: Custom::Route53
Properties:
Id:
Ref: ShortlinkZone
ServiceToken:
Fn::GetAtt: Route53DependencyLambda.Arn
InternalZoneInfo:
Type: Custom::Route53
Properties:
Id:
Ref: InternalZone
ServiceToken:
Fn::GetAtt: Route53DependencyLambda.Arn
EmailZoneInfo:
Type: Custom::Route53
Properties:
Id:
Ref: EmailZone
ServiceToken:
Fn::GetAtt: Route53DependencyLambda.Arn
ExternalZoneSSLCertLocalIfNonEast:
Type: Custom::DNSCertificate
Condition: HasManagedDomainNonEast
Properties:
DomainName:
Ref: DomainName
DomainValidationOptions:
- DomainName:
Ref: DomainName
HostedZoneId:
Fn::GetAtt: ExternalZoneInfo.Id
ValidationMethod: DNS
Region:
Fn::Sub: ${AWS::Region}
ServiceToken:
Fn::GetAtt: CustomAcmCertificateLambda.Arn
DependsOn: ExternalZoneSSLCertEast
ExternalZoneSSLCertLocalIfEast:
Type: Custom::DNSCertificate
Condition: HasManagedDomainEast
Properties:
DomainName:
Ref: DomainName
DomainValidationOptions:
- DomainName:
Ref: DomainName
HostedZoneId:
Fn::GetAtt: ExternalZoneInfo.Id
ValidationMethod: DNS
Region:
Fn::Sub: ${AWS::Region}
ServiceToken:
Fn::GetAtt: CustomAcmCertificateLambda.Arn
DependsOn: ShortlinkZoneSSLCertEast
ExternalZoneSSLCertEast:
Type: Custom::DNSCertificate
Condition: HasManagedDomainNonEast
Properties:
DomainName:
Ref: DomainName
DomainValidationOptions:
- DomainName:
Ref: DomainName
HostedZoneId:
Fn::GetAtt: ExternalZoneInfo.Id
ValidationMethod: DNS
Region: us-east-1
ServiceToken:
Fn::GetAtt: CustomAcmCertificateLambda.Arn
DependsOn: InternalZoneSSLCertEast
ShortlinkZoneSSLCertEast:
Type: Custom::DNSCertificate
Properties:
DomainName:
Fn::GetAtt: ShortlinkZoneInfo.Name
DomainValidationOptions:
- DomainName:
Fn::GetAtt: ShortlinkZoneInfo.Name
HostedZoneId:
Fn::GetAtt: ShortlinkZoneInfo.Id
ValidationMethod: DNS
Region: us-east-1
ServiceToken:
Fn::GetAtt: CustomAcmCertificateLambda.Arn
DependsOn: InternalZoneSSLCert
InternalZoneSSLCert:
Type: Custom::DNSCertificate
Properties:
DomainName:
Fn::Sub: '*.${InternalZoneInfo.Name}'
SubjectAlternativeNames:
- Fn::Sub: ${LowerStackName.Value}-cors-proxy.${InternalZoneInfo.Name}
DomainValidationOptions:
- DomainName:
Fn::GetAtt: InternalZoneInfo.Name
HostedZoneId:
Ref: InternalZone
ValidationMethod: DNS
Region:
Fn::Sub: ${AWS::Region}
ServiceToken:
Fn::GetAtt: CustomAcmCertificateLambda.Arn
InternalZoneSSLCertEast:
Type: Custom::DNSCertificate
Condition: IsNonEast
Properties:
DomainName:
Fn::Sub: '*.${InternalZoneInfo.Name}'
SubjectAlternativeNames:
- Fn::Sub: ${LowerStackName.Value}-cors-proxy.${InternalZoneInfo.Name}
DomainValidationOptions:
- DomainName:
Fn::GetAtt: InternalZoneInfo.Name
HostedZoneId:
Ref: InternalZone
ValidationMethod: DNS
Region: us-east-1
ServiceToken:
Fn::GetAtt: CustomAcmCertificateLambda.Arn
DependsOn: ShortlinkZoneSSLCertEast
SESDomainRole:
Type: AWS::IAM::Role
Properties:
RoleName:
Fn::Sub: ${AWS::StackName}-SES
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Policies:
- PolicyName: provision-ses
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- ses:DeleteIdentity
- ses:GetIdentityDkimAttributes
- ses:GetIdentityMailFromDomainAttributes
- ses:GetIdentityVerificationAttributes
- ses:SetIdentityDomainDkim
- ses:SetIdentityMailFromDomain
- ses:VerifyDomainDkim
- ses:VerifyDomainIdentity
- ses:VerifyEmailIdentity
Resource:
- '*'
SESDomainRecords:
Type: AWS::Route53::RecordSetGroup
Properties:
HostedZoneName:
Fn::Sub: ${EmailZoneInfo.Name}.
RecordSets:
Fn::GetAtt: SESDomain.Route53RecordSets
SESDomain:
Type: Custom::SES_Domain
Properties:
ServiceToken:
Fn::GetAtt: SESDomainApplication.Outputs.SESDomainFunctionArn
Domain:
Fn::GetAtt: EmailZoneInfo.Name
EnableReceive: false
EnableSend: true
MailFromSubdomain:
Ref: EmailSubdomain
TTL: 1800
CustomDMARC: '"v=DMARC1;p=reject;pct=100;aspf=r;"'
Region: us-east-1
EmailAddress:
Ref: AdminEmailAddress
SendEmailUser:
Type: AWS::IAM::User
Properties:
UserName:
Fn::Sub: ${AWS::StackName}-send-email
Policies:
- PolicyName: send-email
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- ses:SendRawEmail
Resource: '*'
Condition:
StringEquals:
ses:FromAddress:
Fn::Sub: noreply@${EmailSubdomain}.${SESDomain}
- Effect: Allow
Action:
- ses:SendRawEmail
Resource: '*'
Condition:
StringEquals:
ses:FromAddress:
Fn::Sub: noreply@${SESDomain}
SendEmailAccessKey:
Type: AWS::IAM::AccessKey
DependsOn:
- SendEmailUser
Properties:
UserName:
Fn::Sub: ${AWS::StackName}-send-email
KeymasterRole:
Type: AWS::IAM::Role
Properties:
RoleName:
Fn::Sub: ${AWS::StackName}-KeymasterRole
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Path: /
Policies:
- PolicyName: keymaster
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- s3:PutObject
Resource:
- Fn::Sub: ${BoxKeysBucket.Arn}/*
- Effect: Allow
Action:
- secretsmanager:GetSecretValue
Resource:
Fn::If:
- IsRestore
- Ref: RestoreAppDbSecretArn
- Fn::Sub: arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:NONE
- Effect: Allow
Action:
- ssm:PutParameter
- ssm:DeleteParameter
Resource:
Fn::Sub: arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/keymaster/${AWS::StackName}/*
- Effect: Allow
Action:
- ssm:GetParameter
Resource:
Fn::If:
- IsRestore
- Fn::Sub: arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/keymaster/${RestoreStackName}/*
- Fn::Sub: arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/keymaster/${AWS::StackName}/*
- Effect: Allow
Action:
- ssm:DescribeParameters
Resource:
- Fn::Sub: arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:*
- Effect: Allow
Action:
- ssm:DeleteParameters
Resource:
- Fn::Sub: arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/ita/${AWS::StackName}/*
DeleteItaParameters:
Type: Custom::DeleteParameters
Properties:
ServiceToken:
Fn::GetAtt: KeymasterApplication.Outputs.DeleteParametersFunctionArn
Region:
Fn::Sub: ${AWS::Region}
Prefix:
Fn::Sub: /ita/${AWS::StackName}/
EmptyBucketRole:
Type: AWS::IAM::Role
Properties:
RoleName:
Fn::Sub: ${AWS::StackName}-EmptyBucket
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Path: /
Policies:
- PolicyName: delete-s3-files
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- s3:DeleteObject
Resource:
- Fn::Sub: ${AssetsBucket.Arn}/*
- Fn::Sub: ${LinkRedirectorBucket.Arn}/*
- Fn::Sub: ${SpeelycaptorScratchBucket.Arn}/*
- Fn::Sub: ${BoxKeysBucket.Arn}/*
- Effect: Allow
Action:
- s3:ListBucket
Resource:
- Fn::Sub: ${AssetsBucket.Arn}
- Fn::Sub: ${LinkRedirectorBucket.Arn}
- Fn::Sub: ${BoxKeysBucket.Arn}
- Fn::Sub: ${SpeelycaptorScratchBucket.Arn}
EmptyBucketFunction:
Type: AWS::Lambda::Function
Properties:
Handler: index.handler
Runtime: python3.9
Role:
Fn::GetAtt: EmptyBucketRole.Arn
Timeout: 240
Code:
ZipFile: |
import json
import boto3
import urllib3
from botocore.exceptions import ClientError
http = urllib3.PoolManager()
def handler(event, context):
try:
bucketNames = event['ResourceProperties']['BucketNames']
if event['RequestType'] == 'Delete':
for bucketName in bucketNames:
s3 = boto3.resource('s3')
try:
s3.meta.client.head_bucket(Bucket=bucketName)
bucket = s3.Bucket(bucketName)
for obj in bucket.objects.filter():
s3.Object(bucket.name, obj.key).delete()
except ClientError:
# Bucket already removed/never existed
pass
sendResponseCfn(event, context, "SUCCESS")
except Exception as e:
print(e)
sendResponseCfn(event, context, "FAILED")
def sendResponseCfn(event, context, responseStatus):
response_body = {'Status': responseStatus,
'Reason': 'Log stream name: ' + context.log_stream_name,
'PhysicalResourceId': context.log_stream_name,
'StackId': event['StackId'],
'RequestId': event['RequestId'],
'LogicalResourceId': event['LogicalResourceId'],
'Data': json.loads("{}")}
http.request('PUT', event['ResponseURL'], body=json.dumps(response_body))
SESPasswordRole:
Type: AWS::IAM::Role
Properties:
RoleName:
Fn::Sub: ${AWS::StackName}-SESPassword
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Path: /
Policies: []
SESPasswordFunction:
Type: AWS::Lambda::Function
Properties:
FunctionName:
Fn::Sub: ${AWS::StackName}-ses-password
Handler: index.handler
Runtime: python3.9
Role:
Fn::GetAtt: SESPasswordRole.Arn
Timeout: 240
Code:
ZipFile: |
import json
import boto3
import hmac
import hashlib
import base64
import argparse
import urllib3
DATE = "11111111"
SERVICE = "ses"
MESSAGE = "SendRawEmail"
TERMINAL = "aws4_request"
VERSION = 0x04
http = urllib3.PoolManager()
def sign(key, msg):
return hmac.new(key, msg.encode('utf-8'), hashlib.sha256).digest()
def calculateKey(secretAccessKey, region):
signature = sign(("AWS4" + secretAccessKey).encode('utf-8'), DATE)
signature = sign(signature, region)
signature = sign(signature, SERVICE)
signature = sign(signature, TERMINAL)
signature = sign(signature, MESSAGE)
signatureAndVersion = bytes([VERSION]) + signature
smtpPassword = base64.b64encode(signatureAndVersion)
return smtpPassword.decode('utf-8')
def handler(event, context):
try:
if event['RequestType'] == 'Delete':
sendResponseCfn(event, context, "SUCCESS", {})
return
secretAccessKey = event['ResourceProperties']['SecretAccessKey']
region = event['ResourceProperties']['Region']
key = calculateKey(secretAccessKey, region)
sendResponseCfn(event, context, "SUCCESS", { "Key": key })
except Exception as e:
print(e)
sendResponseCfn(event, context, "FAILED", {})
def sendResponseCfn(event, context, responseStatus, data):
response_body = {'Status': responseStatus,
'Reason': 'Log stream name: ' + context.log_stream_name,
'PhysicalResourceId': context.log_stream_name,
'StackId': event['StackId'],
'RequestId': event['RequestId'],
'LogicalResourceId': event['LogicalResourceId'],
'Data': data}
http.request('PUT', event['ResponseURL'], body=json.dumps(response_body))
StackTopicHandlerRole:
Type: AWS::IAM::Role
Properties:
RoleName:
Fn::Sub: ${AWS::StackName}-StackTopicHandler
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Path: /
Policies:
- PolicyName: stack-topic-handler-role
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- autoscaling:DescribeAutoScalingGroups
- ec2:DescribeInstances
Resource:
- '*'
- Effect: Allow
Action:
- route53:ChangeResourceRecordSets
- route53:ListResourceRecordSets
Resource:
Fn::Sub: arn:aws:route53:::hostedzone/${InternalZoneInfo.Id}
- Effect: Allow
Action:
- route53:CreateHealthCheck
- route53:ListHealthChecks
- route53:DeleteHealthCheck
- route53:GetHostedZone
Resource: '*'
- Effect: Allow
Action:
- route53:*
Resource:
- Fn::Sub: arn:aws:route53:::hostedzone/${InternalZoneInfo.Id}
- Fn::If:
- HasManagedDomain
- Fn::Sub: arn:aws:route53:::hostedzone/${ExternalZoneInfo.Id}
- arn:aws:route53:::hostedzone/DEADZONE*
- Effect: Allow
Action:
- cloudformation:DescribeStacks
- cloudformation:GetTemplate
- cloudformation:UpdateStack
Resource:
Ref: AWS::StackId
- Effect: Allow
Action:
- sns:ListTagsForResource
Resource: '*'
- Effect: Allow
Action:
- autoscaling:*
Resource:
- Fn::Sub: arn:aws:autoscaling:${AWS::Region}:${AWS::AccountId}:autoScalingGroup:*:autoScalingGroupName/${AWS::StackName}-app
- Fn::Sub: arn:aws:autoscaling:${AWS::Region}:${AWS::AccountId}:autoScalingGroup:*:autoScalingGroupName/${AWS::StackName}-stream
- Effect: Allow
Action:
- elasticloadbalancing:*
Resource:
- Fn::Sub: arn:aws:elasticloadbalancing:${AWS::Region}:${AWS::AccountId}:loadbalancer/app/${AWS::StackName}-app/*
- Fn::Sub: arn:aws:elasticloadbalancing:${AWS::Region}:${AWS::AccountId}:loadbalancer/app/${AWS::StackName}-app
- Fn::Sub: arn:aws:elasticloadbalancing:${AWS::Region}:${AWS::AccountId}:targetgroup/${AWS::StackName}-ret/*
- Fn::Sub: arn:aws:elasticloadbalancing:${AWS::Region}:${AWS::AccountId}:listener/app/${AWS::StackName}-app/*
- Fn::Sub: arn:aws:elasticloadbalancing:${AWS::Region}:${AWS::AccountId}:listener-rule/app/${AWS::StackName}-app/*
- Effect: Allow
Action:
- ec2:*
Resource:
- Fn::Sub: arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:security-group/*
- Effect: Allow
Action:
- ec2:Describe*
- ec2:Get*
- route53:Get*
- autoscaling:Describe*
- rds:Describe*
- elasticloadbalancing:Describe*
- elasticloadbalancingv2:Describe*
Resource:
- '*'
StackTopicCloudfrontPolicy:
Type: AWS::IAM::Policy
Properties:
Roles:
- Ref: StackTopicHandlerRole
PolicyName:
Fn::Sub: ${AWS::StackName}-stack-topic-cf-policy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- cloudfront:*
Resource:
Fn::Sub: arn:aws:cloudfront::${AWS::AccountId}:distribution/${AppCloudfrontDistribution}
StackTopicHandler:
Type: AWS::Lambda::Function
Properties:
FunctionName:
Fn::Sub: ${AWS::StackName}-stack-topic-handler
Description: Registers Node IPs into Round Robin DNS and manages stack if budgets
are hit
Handler: index.handler
Runtime: nodejs14.x
Role:
Fn::GetAtt: StackTopicHandlerRole.Arn
Timeout: 900
Code:
ZipFile:
Fn::Sub: |
const AWS=require("aws-sdk"),promisify=e=>t=>new Promise((a,n)=>e(t,(e,t)=>{e?(console.log(e),n(e)):a(t)})),OFFLINE_SETTING="Offline - Temporarily shut off servers";async function handleBudgetAlert(e,t){const a=e.Records[0].Sns.TopicArn,n=new AWS.SNS,s=(await promisify(n.listTagsForResource.bind(n))({ResourceArn:a})).Tags,r=s.find(e=>"stack-name"===e.Key).Value,o=s.find(e=>"stack-region"===e.Key).Value,i=new AWS.CloudFormation({region:o});await new Promise(async e=>{let t;const a=async()=>{const a=await promisify(i.describeStacks.bind(i))({StackName:r});if(a){const n=a.Stacks[0].StackStatus;if(n.endsWith("_COMPLETE")||n.endsWith("_FAILED"))return t&&clearInterval(t),e(),!0}return!1};await a()||(t=setInterval(a,3e4))});const c=(await promisify(i.describeStacks.bind(i))({StackName:r})).Stacks[0].Parameters,d=[];for(const e of c)"StackOffline"===e.ParameterKey?d.push({ParameterKey:e.ParameterKey,ParameterValue:OFFLINE_SETTING}):d.push({ParameterKey:e.ParameterKey,UsePreviousValue:!0});await promisify(i.updateStack.bind(i))({StackName:r,UsePreviousTemplate:!0,Parameters:d,Capabilities:["CAPABILITY_IAM","CAPABILITY_NAMED_IAM"]})}async function handleASGMessage(e,t){const a=e.AutoScalingGroupName,n=e.Event,s="${LowerStackName.Value}-app.${InternalZoneInfo.Name}.";if("autoscaling:EC2_INSTANCE_LAUNCH"===n||"autoscaling:EC2_INSTANCE_TERMINATE"===n||"INSTANCE_REBOOT"===n){const e=new AWS.AutoScaling({region:"${AWS::Region}"}),t=new AWS.EC2({region:"${AWS::Region}"}),n=new AWS.Route53,r=await promisify(e.describeAutoScalingGroups.bind(e))({AutoScalingGroupNames:[a],MaxRecords:1}),o=(await promisify(n.listResourceRecordSets.bind(n))({StartRecordName:s,StartRecordType:"A",HostedZoneId:"${InternalZoneInfo.Id}",MaxItems:"100"})).ResourceRecordSets,i=r.AutoScalingGroups[0].Instances.map(e=>e.InstanceId),c=await promisify(t.describeInstances.bind(t))({DryRun:!1,InstanceIds:i}),d=[];for(let e=0;e<c.Reservations.length;e++){const t=c.Reservations[e];for(let e=0;e<t.Instances.length;e++){const a=t.Instances[e].NetworkInterfaces[0].Association.PublicIp;a&&d.indexOf(a)<0&&d.push(a)}}for(let e=0,t=o.length;e<t;e++){const t=o[e];if(t.Name!==s||"A"!==t.Type)continue;const a=t.ResourceRecords.length>0&&t.ResourceRecords[0].Value;if(a&&!d.find(e=>e===a))try{await promisify(n.changeResourceRecordSets.bind(n))({ChangeBatch:{Changes:[{Action:"DELETE",ResourceRecordSet:{MultiValueAnswer:!0,Name:t.Name,Type:t.Type,TTL:t.TTL,SetIdentifier:t.SetIdentifier,ResourceRecords:t.ResourceRecords,HealthCheckId:t.HealthCheckId}}]},HostedZoneId:"${InternalZoneInfo.Id}"})}catch(e){}}let l=(await promisify(n.listHealthChecks.bind(n))({MaxItems:"100"})).HealthChecks;if(d.length>1)for(let e=0,t=d.length;e<t;e++){const t=d[e];if(!l.find(e=>e.HealthCheckConfig.IPAddress===t))try{await promisify(n.createHealthCheck.bind(n))({CallerReference:Math.floor(1e9*Math.random()).toString(),HealthCheckConfig:{EnableSNI:!0,FailureThreshold:2,FullyQualifiedDomainName:s,IPAddress:t,Port:443,RequestInterval:10,ResourcePath:"/health",Type:"HTTPS"}})}catch(e){}}l=(await promisify(n.listHealthChecks.bind(n))({})).HealthChecks;for(let e=0,t=d.length;e<t;e++){const t=d[e];if(!o.find(e=>e.Name===s&&e.ResourceRecords&&e.ResourceRecords.length&&e.ResourceRecords[0].Value===t)){const e=l.find(e=>e.HealthCheckConfig.IPAddress===t);try{await promisify(n.changeResourceRecordSets.bind(n))({ChangeBatch:{Changes:[{Action:"UPSERT",ResourceRecordSet:{MultiValueAnswer:!0,Name:s,Type:"A",HealthCheckId:e?e.Id:null,TTL:15,SetIdentifier:t,ResourceRecords:[{Value:t}]}}]},HostedZoneId:"${InternalZoneInfo.Id}"})}catch(e){}}}for(let e=0,t=l.length;e<t;e++){const t=l[e];if(1===d.length||!d.find(e=>t.HealthCheckConfig.IPAddress===e))try{await promisify(n.deleteHealthCheck.bind(n))({HealthCheckId:t.Id})}catch(e){}}}else console.log("Unsupported ASG event: "+a+" "+n),t.done("Unsupported ASG event: "+a+" "+n)}exports.handler=async function(e,t){if(e.Records[0].Sns.Message.indexOf("Budget Name")>=0)return handleBudgetAlert(e,t);return handleASGMessage(JSON.parse(e.Records[0].Sns.Message),t)};
InvokeStackTopicHandlerPermission:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:InvokeFunction
FunctionName:
Ref: StackTopicHandler
Principal: sns.amazonaws.com
StackTopicHandlerTopicPolicy:
Type: AWS::SNS::TopicPolicy
Properties:
Topics:
- Ref: StackTopic
PolicyDocument:
Version: '2012-10-17'
Statement:
Fn::If:
- HasDbMonthlyBudget
- - Effect: Allow
Sid:
Fn::Sub: ${AWS::StackName}-topic-policy-stmt-asg
Action: sns:Publish
Resource:
Ref: StackTopic
Principal:
AWS: '*'
Condition:
ArnLike:
AWS:SourceArn:
Ref: AppASG
- Effect: Allow
Sid:
Fn::Sub: ${AWS::StackName}-topic-policy-stmt-budget
Action: sns:Publish
Resource:
Ref: StackTopic
Principal:
Service: budgets.amazonaws.com
- - Effect: Allow
Sid:
Fn::Sub: ${AWS::StackName}-topic-policy-stmt-asg
Action: sns:Publish
Resource:
Ref: StackTopic
Principal:
AWS: '*'
Condition:
ArnLike:
AWS:SourceArn:
Ref: AppASG
EFSCreateOrRestoreRole:
Type: AWS::IAM::Role
Properties:
RoleName:
Fn::Sub: ${AWS::StackName}-EFSCreateOrRestore
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Path: /
Policies:
- PolicyName: efs-create-or-restore-role
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- elasticfilesystem:*
- kms:Get*
- kms:Describe*
- kms:Decrypt
- backup:StartRestoreJob
- backup:Get*
- backup:Describe*
- backup:List*
Resource:
- '*'
- Effect: Allow
Action:
- iam:PassRole
Resource:
- Fn::GetAtt: DailyBackupRole.Arn
EFSCreateOrRestore:
Type: AWS::Lambda::Function
Properties:
FunctionName:
Fn::Sub: ${AWS::StackName}-efs-create-or-restore
Description: Creates or restores EFS volumes
Handler: index.handler
Runtime: nodejs14.x
Role:
Fn::GetAtt: EFSCreateOrRestoreRole.Arn
Timeout: 900
Code:
ZipFile:
Fn::Sub: |
const https=require("https"),url=require("url"),AWS=require("aws-sdk"),promisify=e=>t=>new Promise((s,o)=>e(t,(e,t)=>{e?(console.log(e),o(e)):s(t)}));function getReason(e){return e?e.message:""}async function sendResponse(e,t,s,o,r,i){const n={StackId:t.StackId,RequestId:t.RequestId,LogicalResourceId:t.LogicalResourceId,PhysicalResourceId:e,Status:o,Reason:getReason(i)+" See details in CloudWatch Log: "+s.logStreamName,Data:r},a=JSON.stringify(n),c=url.parse(t.ResponseURL),u={hostname:c.hostname,port:443,path:c.path,method:"PUT",headers:{"content-type":"","content-length":a.length}};await new Promise((e,t)=>{const o=https.request(u,()=>{s.done(null,r),e()});o.on("error",e=>{console.log("sendResponse Error:\n",e),s.done(e),t()}),o.write(a),o.end()})}exports.handler=async function(e,t){const s=new AWS.EFS;if("Create"==e.RequestType||"Update"==e.RequestType){const o=e.ResourceProperties.PerformanceMode||"generalPurpose",r=e.ResourceProperties.ThroughputMode||"bursting",i=e.RequestId;let n=null;"provisioned"===r&&(n=e.ResourceProperties.ProvisionedThroughputInMibps);const a=e.ResourceProperties.Encrypted||!1,c=e.ResourceProperties.FileSystemTags||[],u=e.ResourceProperties.KmsKeyId||null;let d;if("Create"===e.RequestType){const l=e.ResourceProperties.RestoreBackupVaultName,p=e.ResourceProperties.RestoreRecoveryPointArn,y=e.ResourceProperties.RestoreIamRoleArn;if(p&&l){const R=new AWS.Backup,m=(await promisify(R.getRecoveryPointRestoreMetadata.bind(R))({BackupVaultName:l,RecoveryPointArn:p})).RestoreMetadata,S=(await promisify(R.startRestoreJob.bind(R))({RecoveryPointArn:p,Metadata:{"file-system-id":m["file-system-id"],PerformanceMode:o,CreationToken:i,Encrypted:a?"true":"false",KmsKeyId:u,newFileSystem:"true"},ResourceType:"EFS",IdempotencyToken:i,IamRoleArn:y})).RestoreJobId,I=await new Promise(async(s,o)=>{let r;const i=async()=>{const i=await promisify(R.describeRestoreJob.bind(R))({RestoreJobId:S});return("COMPLETED"===i.Status||"FAILED"===i.Status)&&(r&&clearInterval(r),"COMPLETED"===i.Status?s(i.CreatedResourceArn):(await sendResponse(null,e,t,"FAILED",{},"Restore job failed."),o()),!0)};await i()||(r=setInterval(i,1e4))});d=I.split("/")[1],await promisify(s.createTags.bind(s))({FileSystemId:d,Tags:c}),"bursting"!==r&&await promisify(s.updateFileSystem.bind(s))({FileSystemId:d,ThroughputMode:r,ProvisionedThroughputInMibps:n})}else d=(await promisify(s.createFileSystem.bind(s))({PerformanceMode:o,CreationToken:i,ThroughputMode:r,Encrypted:a,Tags:c,KmsKeyId:u,ProvisionedThroughputInMibps:n})).FileSystemId}else d=e.PhysicalResourceId,await promisify(s.updateFileSystem.bind(s))({FileSystemId:d,ThroughputMode:r,ProvisionedThroughputInMibps:n});await new Promise(async e=>{let t;const o=async()=>{return"available"===(await promisify(s.describeFileSystems.bind(s))({FileSystemId:d})).FileSystems[0].LifeCycleState&&(t&&clearInterval(t),e(),!0)};await o()||(t=setInterval(o,1e4))});const l=e.ResourceProperties.LifecyclePolicies;return l&&await promisify(s.putLifecycleConfiguration.bind(s))({FileSystemId:d,LifecyclePolicies:l}),void await sendResponse(d,e,t,"SUCCESS")}if("Delete"==e.RequestType){const o=e.PhysicalResourceId;return await promisify(s.deleteFileSystem.bind(s))({FileSystemId:o}),void await sendResponse(o,e,t,"SUCCESS")}};
SendEmailPassword:
Type: Custom::SendEmailPassword
Properties:
ServiceToken:
Fn::GetAtt: SESPasswordFunction.Arn
SecretAccessKey:
Fn::GetAtt: SendEmailAccessKey.SecretAccessKey
Region: us-east-1
DependsOn:
- SendEmailUser
BoxKeysBucket:
Type: AWS::S3::Bucket
Properties:
AccessControl: Private
BucketName:
Fn::Join:
- '-'
- - Fn::Sub: ${LowerStackName.Value}-box-keys
- Fn::Select:
- 0
- Fn::Split:
- '-'
- Fn::Select:
- 2
- Fn::Split:
- /
- Ref: AWS::StackId
EmptyBuckets:
Type: Custom::EmptyBuckets
Properties:
ServiceToken:
Fn::GetAtt: EmptyBucketFunction.Arn
BucketNames:
- Ref: BoxKeysBucket
- Ref: SpeelycaptorScratchBucket
- Ref: AssetsBucket
- Ref: LinkRedirectorBucket
DependsOn:
- EmptyBucketRole
SshTOTP:
Type: Custom::SshTOTP
Properties:
ServiceToken:
Fn::GetAtt: KeymasterApplication.Outputs.GenerateTOTPFunctionArn
BucketName:
Ref: BoxKeysBucket
BucketRegion:
Ref: AWS::Region
StackName:
Ref: AWS::StackName
JWTKeys:
Type: Custom::JWTKeys
Properties:
ServiceToken:
Fn::GetAtt: KeymasterApplication.Outputs.GenerateJWTKeysFunctionArn
BucketName:
Ref: BoxKeysBucket
BucketRegion:
Ref: AWS::Region
VapidKeys:
Type: Custom::VapidKeys
Properties:
ServiceToken:
Fn::GetAtt: KeymasterApplication.Outputs.GenerateVapidKeysFunctionArn
BucketName:
Ref: BoxKeysBucket
BucketRegion:
Ref: AWS::Region
BioRingKey:
Type: Custom::BioRingKey
Properties:
ServiceToken:
Fn::GetAtt: KeymasterApplication.Outputs.GenerateRingKeyFunctionArn
BucketName:
Ref: BoxKeysBucket
BucketRegion:
Ref: AWS::Region
KeyName:
Ref: AWS::StackName
BioServiceKeys:
Type: Custom::BioServiceKeys
Properties:
ServiceToken:
Fn::GetAtt: KeymasterApplication.Outputs.GenerateServiceKeyFunctionArn
BucketName:
Ref: BoxKeysBucket
BucketRegion:
Ref: AWS::Region
ServiceNames:
- reticulum.default
- janus-gateway.default
- postgrest.default
- coturn.default
- certbot.default
- ita.default
- speelycaptor.default
- photomnemonic.default
- youtube-dl-api-server.default
- pgbouncer.default
- imgproxy.default
- hubs.default
- spoke.default
- polycosm-static-assets.default
Org:
Ref: AWS::StackName
BioUserKey:
Type: Custom::BioUserKey
Properties:
ServiceToken:
Fn::GetAtt: KeymasterApplication.Outputs.GenerateUserKeyFunctionArn
BucketName:
Ref: BoxKeysBucket
BucketRegion:
Ref: AWS::Region
UserName: polycosm-config-user
SpeelycaptorScratchBucket:
Type: AWS::S3::Bucket
Properties:
AccessControl: Private
BucketName:
Fn::Join:
- '-'
- - Fn::Sub: ${LowerStackName.Value}-speelycaptor-scratch
- Fn::Select:
- 0
- Fn::Split:
- '-'
- Fn::Select:
- 2
- Fn::Split:
- /
- Ref: AWS::StackId
SpeelycaptorRole:
Type: AWS::IAM::Role
Properties:
RoleName:
Fn::Sub: ${AWS::StackName}-Speelycaptor
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Sid: ''
Path: /
SpeelycaptorRolePolicy:
Type: AWS::IAM::Policy
Properties:
PolicyName: speelycaptor-policy
Roles:
- Ref: SpeelycaptorRole
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: s3:GetObject
Resource:
Fn::Sub: ${SpeelycaptorScratchBucket.Arn}/*
- Effect: Allow
Action: s3:PutObject
Resource:
Fn::Sub: ${SpeelycaptorScratchBucket.Arn}/*
- Effect: Allow
Action: s3:PutObjectAcl
Resource:
Fn::Sub: ${SpeelycaptorScratchBucket.Arn}/*
- Effect: Allow
Action: s3:GetObject
Resource:
Fn::Sub: ${SpeelycaptorScratchBucket.Arn}/*
- Effect: Allow
Action: s3:ListBucket
Resource:
Fn::Sub: ${SpeelycaptorScratchBucket.Arn}
- Effect: Allow
Action:
- ec2:DescribeInstances
- ec2:CreateNetworkInterface
- ec2:AttachNetworkInterface
- ec2:DescribeNetworkInterfaces
- ec2:DeleteNetworkInterface
- autoscaling:CompleteLifecycleAction
Resource: '*'
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource:
Fn::Sub: arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*:*:*
SpeelycaptorApiGatewayRestApi:
Type: AWS::ApiGateway::RestApi
Properties:
Name: speelycaptor
EndpointConfiguration:
Types:
- EDGE
SpeelycaptorApiGatewayResourceInit:
Type: AWS::ApiGateway::Resource
Properties:
ParentId:
Fn::GetAtt: SpeelycaptorApiGatewayRestApi.RootResourceId
PathPart: init
RestApiId:
Ref: SpeelycaptorApiGatewayRestApi
SpeelycaptorApiGatewayResourceConvert:
Type: AWS::ApiGateway::Resource
Properties:
ParentId:
Fn::GetAtt: SpeelycaptorApiGatewayRestApi.RootResourceId
PathPart: convert
RestApiId:
Ref: SpeelycaptorApiGatewayRestApi
SpeelycaptorApiGatewayMethodInitGet:
Type: AWS::ApiGateway::Method
Properties:
HttpMethod: GET
ResourceId:
Ref: SpeelycaptorApiGatewayResourceInit
RestApiId:
Ref: SpeelycaptorApiGatewayRestApi
ApiKeyRequired: false
AuthorizationType: NONE
Integration:
IntegrationHttpMethod: POST
Type: AWS_PROXY
Uri:
Fn::Sub: arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${SpeelycaptorApplication.Outputs.SpeelycaptorInitFunctionArn}/invocations
MethodResponses: []
SpeelycaptorApiGatewayMethodConvertGet:
Type: AWS::ApiGateway::Method
Properties:
HttpMethod: GET
ResourceId:
Ref: SpeelycaptorApiGatewayResourceConvert
RestApiId:
Ref: SpeelycaptorApiGatewayRestApi
ApiKeyRequired: false
AuthorizationType: NONE
Integration:
IntegrationHttpMethod: POST
Type: AWS_PROXY
Uri:
Fn::Sub: arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${SpeelycaptorApplication.Outputs.SpeelycaptorConvertFunctionArn}/invocations
MethodResponses: []
SpeelycaptorApiGatewayDeployment:
Type: AWS::ApiGateway::Deployment
Properties:
RestApiId:
Ref: SpeelycaptorApiGatewayRestApi
StageName: prod
DependsOn:
- SpeelycaptorApiGatewayMethodInitGet
- SpeelycaptorApiGatewayMethodConvertGet
SpeelycaptorInitLambdaPermissionApiGateway:
Type: AWS::Lambda::Permission
Properties:
FunctionName:
Fn::GetAtt: SpeelycaptorApplication.Outputs.SpeelycaptorInitFunctionArn
Action: lambda:InvokeFunction
Principal:
Fn::Sub: apigateway.${AWS::URLSuffix}
SourceArn:
Fn::Sub: arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${SpeelycaptorApiGatewayRestApi}/*/*
SpeelycaptorConvertLambdaPermissionApiGateway:
Type: AWS::Lambda::Permission
Properties:
FunctionName:
Fn::GetAtt: SpeelycaptorApplication.Outputs.SpeelycaptorConvertFunctionArn
Action: lambda:InvokeFunction
Principal:
Fn::Sub: apigateway.${AWS::URLSuffix}
SourceArn:
Fn::Sub: arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${SpeelycaptorApiGatewayRestApi}/*/*
PhotomnemonicRole:
Type: AWS::IAM::Role
Properties:
RoleName:
Fn::Sub: ${AWS::StackName}-Photomnemonic
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Sid: ''
Path: /
PhotomnemonicRolePolicy:
Type: AWS::IAM::Policy
Properties:
PolicyName: photomnemonic-policy
Roles:
- Ref: PhotomnemonicRole
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- ec2:DescribeInstances
- ec2:CreateNetworkInterface
- ec2:AttachNetworkInterface
- ec2:DescribeNetworkInterfaces
- ec2:DeleteNetworkInterface
- autoscaling:CompleteLifecycleAction
Resource: '*'
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource:
Fn::Sub: arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*:*:*
PhotomnemonicApiGatewayRestApi:
Type: AWS::ApiGateway::RestApi
Properties:
Name: photomnemonic
BinaryMediaTypes:
- '*/*'
EndpointConfiguration:
Types:
- EDGE
Body:
swagger: 2
paths:
/screenshot:
get:
parameters:
- name: url
in: query
required: true
x-amazon-apigateway-binary-media-types:
- '*/*'
x-amazon-apigateway-integration:
httpMethod: POST
responses:
contentHandling: CONVERT_TO_BINARY,
type: aws_proxy
contentHandling: CONVERT_TO_BINARY
uri:
Fn::Sub: arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${PhotomnemonicApplication.Outputs.PhotomnemonicScreenshotFunctionArn}/invocations
PhotomnemonicApiGatewayDeployment:
Type: AWS::ApiGateway::Deployment
Properties:
RestApiId:
Ref: PhotomnemonicApiGatewayRestApi
StageName: prod
DependsOn:
- PhotomnemonicApiGatewayRestApi
PhotomnemonicScreenshotLambdaPermissionApiGateway:
Type: AWS::Lambda::Permission
Properties:
FunctionName:
Fn::GetAtt: PhotomnemonicApplication.Outputs.PhotomnemonicScreenshotFunctionArn
Action: lambda:InvokeFunction
Principal:
Fn::Sub: apigateway.${AWS::URLSuffix}
SourceArn:
Fn::Sub: arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${PhotomnemonicApiGatewayRestApi}/*/*
NearsparkRole:
Type: AWS::IAM::Role
Properties:
RoleName:
Fn::Sub: ${AWS::StackName}-Nearspark
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Sid: ''
Path: /
NearsparkRolePolicy:
Type: AWS::IAM::Policy
Properties:
PolicyName: nearspark-policy
Roles:
- Ref: NearsparkRole
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- ec2:DescribeInstances
- ec2:CreateNetworkInterface
- ec2:AttachNetworkInterface
- ec2:DescribeNetworkInterfaces
- ec2:DeleteNetworkInterface
- autoscaling:CompleteLifecycleAction
Resource: '*'
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource:
Fn::Sub: arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*:*:*
SESDomainApplication:
Type: AWS::Serverless::Application
Properties:
Location:
ApplicationId:
Fn::FindInMap:
- AWSMPServerlessResourcesRegionalizedMappings
- Ref: AWS::Region
- awscfnsesdomain5f452d36bfff4c02a4175c663b64fd08
SemanticVersion: 0.4.2
Parameters:
SESDomainRoleArn:
Fn::GetAtt: SESDomainRole.Arn
KeymasterApplication:
Type: AWS::Serverless::Application
Properties:
Location:
ApplicationId:
Fn::FindInMap:
- AWSMPServerlessResourcesRegionalizedMappings
- Ref: AWS::Region
- keymasterpublic5f452d36bfff4c02a4175c663b64fd08
SemanticVersion: 0.0.1
Parameters:
KeymasterRoleArn:
Fn::GetAtt: KeymasterRole.Arn
NearsparkApplication:
Type: AWS::Serverless::Application
Properties:
Location:
ApplicationId:
Fn::FindInMap:
- AWSMPServerlessResourcesRegionalizedMappings
- Ref: AWS::Region
- nearspark5f452d36bfff4c02a4175c663b64fd08
SemanticVersion: 0.1.1
Parameters:
NearsparkRoleArn:
Fn::GetAtt: NearsparkRole.Arn
SpeelycaptorApplication:
Type: AWS::Serverless::Application
Properties:
Location:
ApplicationId:
Fn::FindInMap:
- AWSMPServerlessResourcesRegionalizedMappings
- Ref: AWS::Region
- speelycaptor5f452d36bfff4c02a4175c663b64fd08
SemanticVersion: 0.1.3
Parameters:
SpeelycaptorRoleArn:
Fn::GetAtt: SpeelycaptorRole.Arn
SpeelycaptorScratchBucketId:
Ref: SpeelycaptorScratchBucket
SpeelycaptorScratchBucketArn:
Fn::GetAtt: SpeelycaptorScratchBucket.Arn
SpeelycaptorScratchBucketRegion:
Ref: AWS::Region
PhotomnemonicApplication:
Type: AWS::Serverless::Application
Properties:
Location:
ApplicationId:
Fn::FindInMap:
- AWSMPServerlessResourcesRegionalizedMappings
- Ref: AWS::Region
- photomnemonic5f452d36bfff4c02a4175c663b64fd08
SemanticVersion: 0.3.0
Parameters:
PhotomnemonicRoleArn:
Fn::GetAtt: PhotomnemonicRole.Arn
NearsparkApiGatewayRestApi:
Type: AWS::ApiGateway::RestApi
Properties:
Name: nearspark
BinaryMediaTypes:
- '*/*'
EndpointConfiguration:
Types:
- EDGE
Body:
swagger: 2
paths:
/thumbnail/{url}:
get:
consumes:
- application/json
parameters:
- name: url
in: path
required: true
type: string
- name: width
in: query
required: true
type: number
- name: height
in: query
required: true
type: number
- name: fit
in: query
required: false
type: string
- name: position
in: query
required: false
type: string
- name: gravity
in: query
required: false
type: string
- name: strategy
in: query
required: false
type: string
- name: background
in: query
required: false
type: string
- name: withoutEnlargement
in: query
required: false
type: boolean
requestTemplates:
application/json: '{"url": "$input.params(''url'')"}'
x-amazon-apigateway-binary-media-types:
- '*/*'
x-amazon-apigateway-integration:
httpMethod: POST
responses:
contentHandling: CONVERT_TO_BINARY,
type: aws_proxy
contentHandling: CONVERT_TO_BINARY
uri:
Fn::Sub: arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${NearsparkApplication.Outputs.NearsparkThumbnailFunctionArn}/invocations
NearsparkApiGatewayDeployment:
Type: AWS::ApiGateway::Deployment
Properties:
RestApiId:
Ref: NearsparkApiGatewayRestApi
StageName: prod
DependsOn:
- NearsparkApiGatewayRestApi
NearsparkThumbnailLambdaPermissionApiGateway:
Type: AWS::Lambda::Permission
Properties:
FunctionName:
Fn::GetAtt: NearsparkApplication.Outputs.NearsparkThumbnailFunctionArn
Action: lambda:InvokeFunction
Principal:
Fn::Sub: apigateway.${AWS::URLSuffix}
SourceArn:
Fn::Sub: arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${NearsparkApiGatewayRestApi}/*/*
NearsparkDNS:
Type: AWS::Route53::RecordSet
Properties:
Name:
Fn::Sub: ${LowerStackName.Value}-nearspark.${InternalZoneInfo.Name}
HostedZoneId:
Ref: InternalZone
Type: A
AliasTarget:
DNSName:
Fn::GetAtt: NearsparkCloudfrontDistribution.DomainName
HostedZoneId: Z2FDTNDATAQYW2
NearsparkCloudfrontDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Origins:
- Id: ApiGateway
DomainName:
Fn::Sub: ${NearsparkApiGatewayRestApi}.execute-api.${AWS::Region}.amazonaws.com
CustomOriginConfig:
HTTPPort: 80
HTTPSPort: 443
OriginProtocolPolicy: https-only
OriginSSLProtocols:
- SSLv3
- TLSv1
- TLSv1.1
- TLSv1.2
OriginPath: /prod
Enabled: true
HttpVersion: http2
PriceClass: PriceClass_All
Aliases:
- Fn::Sub: ${LowerStackName.Value}-nearspark.${InternalZoneInfo.Name}
DefaultCacheBehavior:
AllowedMethods:
- GET
- HEAD
- OPTIONS
CachedMethods:
- GET
- HEAD
TargetOriginId: ApiGateway
ForwardedValues:
QueryString: true
Headers: []
Cookies:
Forward: none
ViewerProtocolPolicy: redirect-to-https
MinTTL: '0'
DefaultTTL: '3600'
ViewerCertificate:
AcmCertificateArn:
Fn::If:
- IsNonEast
- Ref: InternalZoneSSLCertEast
- Ref: InternalZoneSSLCert
SslSupportMethod: sni-only
AssetsBucket:
Type: AWS::S3::Bucket
Properties:
BucketName:
Fn::Join:
- '-'
- - Fn::Sub: ${LowerStackName.Value}-assets
- Fn::Select:
- 0
- Fn::Split:
- '-'
- Fn::Select:
- 2
- Fn::Split:
- /
- Ref: AWS::StackId
OwnershipControls:
Rules:
- ObjectOwnership: ObjectWriter
PublicAccessBlockConfiguration:
BlockPublicAcls: false
BlockPublicPolicy: false
IgnorePublicAcls: false
RestrictPublicBuckets: false
CorsConfiguration:
CorsRules:
- AllowedHeaders:
- '*'
AllowedMethods:
- GET
- HEAD
AllowedOrigins:
- Fn::Sub: https://${DomainName}
- Fn::Sub: https://${ShortlinkZoneInfo.Name}
- Fn::Sub: https://hubs.local:8080
- Fn::Sub: https://localhost:8080
ExposedHeaders: []
MaxAge: 31536000
AssetsPublicBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
PolicyDocument:
Id: PublicAssets
Version: '2012-10-17'
Statement:
- Sid: PublicReadForGetBucketObjects
Effect: Allow
Principal: '*'
Action: s3:GetObject
Resource:
Fn::Sub: ${AssetsBucket.Arn}/*
Bucket:
Ref: AssetsBucket
RootRedirectorBucket:
Type: AWS::S3::Bucket
Properties:
BucketName:
Fn::Join:
- '-'
- - Fn::Sub: ${LowerStackName.Value}-root-redirector
- Fn::Select:
- 0
- Fn::Split:
- '-'
- Fn::Select:
- 2
- Fn::Split:
- /
- Ref: AWS::StackId
OwnershipControls:
Rules:
- ObjectOwnership: ObjectWriter
PublicAccessBlockConfiguration:
BlockPublicAcls: false
BlockPublicPolicy: false
IgnorePublicAcls: false
RestrictPublicBuckets: false
CorsConfiguration:
CorsRules:
- AllowedHeaders:
- '*'
AllowedMethods:
- GET
- HEAD
AllowedOrigins:
- '*'
ExposedHeaders:
- Date
- ETag
MaxAge: 31536000
WebsiteConfiguration:
IndexDocument: index.html
ErrorDocument: error.html
RoutingRules:
- RedirectRule:
ReplaceKeyPrefixWith:
Fn::GetAtt: ParsedOfflineRedirectUrl.S3ReplaceKeyPrefixWith
Protocol:
Fn::GetAtt: ParsedOfflineRedirectUrl.S3Protocol
HostName:
Fn::GetAtt: ParsedOfflineRedirectUrl.S3Hostname
HttpRedirectCode: 307
LinkRedirectorBucket:
Type: AWS::S3::Bucket
Properties:
BucketName:
Fn::Join:
- '-'
- - Fn::Sub: ${LowerStackName.Value}-link-redirector
- Fn::Select:
- 0
- Fn::Split:
- '-'
- Fn::Select:
- 2
- Fn::Split:
- /
- Ref: AWS::StackId
OwnershipControls:
Rules:
- ObjectOwnership: ObjectWriter
PublicAccessBlockConfiguration:
BlockPublicAcls: false
BlockPublicPolicy: false
IgnorePublicAcls: false
RestrictPublicBuckets: false
CorsConfiguration:
CorsRules:
- AllowedHeaders:
- '*'
AllowedMethods:
- GET
- HEAD
AllowedOrigins:
- '*'
ExposedHeaders:
- Date
- ETag
MaxAge: 31536000
WebsiteConfiguration:
IndexDocument: link-redirector-index.html
ErrorDocument: link-redirector-error.html
RoutingRules:
- RedirectRule:
ReplaceKeyPrefixWith: link/
Protocol: https
HostName:
Ref: DomainName
LinkRedirectorPublicBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
PolicyDocument:
Id: PublicLinkRedirector
Version: '2012-10-17'
Statement:
- Sid: PublicReadForGetBucketObjects
Effect: Allow
Principal: '*'
Action: s3:GetObject
Resource:
Fn::Sub: ${LinkRedirectorBucket.Arn}/*
Bucket:
Ref: LinkRedirectorBucket
LinkRedirectorDNS:
Type: AWS::Route53::RecordSet
Properties:
Name:
Fn::GetAtt: ShortlinkZoneInfo.Name
HostedZoneId:
Ref: ShortlinkZone
Type: A
AliasTarget:
DNSName:
Fn::GetAtt: LinkRedirectorCloudfrontDistribution.DomainName
HostedZoneId: Z2FDTNDATAQYW2
LinkRedirectorCloudfrontDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Origins:
- Id:
Fn::Sub: ${AWS::StackName}-link-redirector
DomainName:
Fn::Select:
- 2
- Fn::Split:
- /
- Fn::GetAtt: LinkRedirectorBucket.WebsiteURL
CustomOriginConfig:
HTTPPort: 80
HTTPSPort: 443
OriginProtocolPolicy: http-only
OriginSSLProtocols:
- SSLv3
- TLSv1
- TLSv1.1
- TLSv1.2
Enabled: true
PriceClass: PriceClass_All
Aliases:
- Fn::GetAtt: ShortlinkZoneInfo.Name
DefaultCacheBehavior:
AllowedMethods:
- GET
- HEAD
- OPTIONS
CachedMethods:
- GET
- HEAD
TargetOriginId:
Fn::Sub: ${AWS::StackName}-link-redirector
ForwardedValues:
QueryString: false
Headers:
- Origin
- Access-Control-Request-Method
- Access-Control-Request-Headers
- Accept
Cookies:
Forward: none
ViewerProtocolPolicy: allow-all
MinTTL: 86400
DefaultTTL: 86400
MaxTTL: 86400
Restrictions:
GeoRestriction:
RestrictionType: none
Locations: []
ViewerCertificate:
AcmCertificateArn:
Ref: ShortlinkZoneSSLCertEast
SslSupportMethod: sni-only
MinimumProtocolVersion: TLSv1
AppALBSecurityGroup:
Type: AWS::EC2::SecurityGroup
Condition: HasALB
Properties:
GroupName:
Fn::Sub: ${AWS::StackName}-app-alb
GroupDescription: App ALB
VpcId:
Ref: VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp:
Fn::If:
- HasInboundCidrOverride
- Ref: InboundCidrOverride
- 0.0.0.0/0
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp:
Fn::If:
- HasInboundCidrOverride
- Ref: InboundCidrOverride
- 0.0.0.0/0
BioRingSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName:
Fn::Sub: ${AWS::StackName}-bio-ring
GroupDescription: Bio
VpcId:
Ref: VPC
BioRingRPCIngress:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId:
Ref: BioRingSecurityGroup
IpProtocol: tcp
FromPort: 9638
ToPort: 9638
SourceSecurityGroupId:
Ref: BioRingSecurityGroup
BioRingRPCEgress:
Type: AWS::EC2::SecurityGroupEgress
Properties:
GroupId:
Ref: BioRingSecurityGroup
IpProtocol: tcp
FromPort: 9638
ToPort: 9638
DestinationSecurityGroupId:
Ref: BioRingSecurityGroup
BioRingRPCUDPIngress:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId:
Ref: BioRingSecurityGroup
IpProtocol: udp
FromPort: 9638
ToPort: 9638
SourceSecurityGroupId:
Ref: BioRingSecurityGroup
BioRingRPCUDPEgress:
Type: AWS::EC2::SecurityGroupEgress
Properties:
GroupId:
Ref: BioRingSecurityGroup
IpProtocol: udp
FromPort: 9638
ToPort: 9638
DestinationSecurityGroupId:
Ref: BioRingSecurityGroup
AppSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName:
Fn::Sub: ${AWS::StackName}-app
GroupDescription: App
VpcId:
Ref: VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort:
Fn::FindInMap:
- ServicesMeta
- RetExternal
- Port
ToPort:
Fn::FindInMap:
- ServicesMeta
- RetExternal
- Port
CidrIp:
Fn::If:
- HasInboundCidrOverride
- Ref: InboundCidrOverride
- 0.0.0.0/0
- IpProtocol: tcp
FromPort:
Fn::FindInMap:
- ServicesMeta
- RetTurnExternal
- Port
ToPort:
Fn::FindInMap:
- ServicesMeta
- RetTurnExternal
- Port
CidrIp:
Fn::If:
- HasInboundCidrOverride
- Ref: InboundCidrOverride
- 0.0.0.0/0
- IpProtocol: udp
FromPort:
Fn::FindInMap:
- ServicesMeta
- RetTurnExternal
- Port
ToPort:
Fn::FindInMap:
- ServicesMeta
- RetTurnExternal
- Port
CidrIp:
Fn::If:
- HasInboundCidrOverride
- Ref: InboundCidrOverride
- 0.0.0.0/0
- IpProtocol: tcp
FromPort:
Fn::FindInMap:
- ServicesMeta
- DialogExternal
- AppPort
ToPort:
Fn::FindInMap:
- ServicesMeta
- DialogExternal
- AppPort
CidrIp:
Fn::If:
- HasInboundCidrOverride
- Ref: InboundCidrOverride
- 0.0.0.0/0
- IpProtocol: tcp
FromPort:
Fn::FindInMap:
- ServicesMeta
- DialogTurnExternal
- Port
ToPort:
Fn::FindInMap:
- ServicesMeta
- DialogTurnExternal
- Port
CidrIp:
Fn::If:
- HasInboundCidrOverride
- Ref: InboundCidrOverride
- 0.0.0.0/0
- IpProtocol: udp
FromPort:
Fn::FindInMap:
- ServicesMeta
- DialogTurnExternal
- Port
ToPort:
Fn::FindInMap:
- ServicesMeta
- DialogTurnExternal
- Port
CidrIp:
Fn::If:
- HasInboundCidrOverride
- Ref: InboundCidrOverride
- 0.0.0.0/0
- IpProtocol: udp
FromPort:
Fn::FindInMap:
- ServicesMeta
- DialogWebRTCFrom
- Port
ToPort:
Fn::FindInMap:
- ServicesMeta
- DialogWebRTCTo
- Port
CidrIp:
Fn::If:
- HasInboundCidrOverride
- Ref: InboundCidrOverride
- 0.0.0.0/0
- IpProtocol: tcp
FromPort:
Fn::FindInMap:
- ServicesMeta
- DialogWebRTCFrom
- Port
ToPort:
Fn::FindInMap:
- ServicesMeta
- DialogWebRTCTo
- Port
CidrIp:
Fn::If:
- HasInboundCidrOverride
- Ref: InboundCidrOverride
- 0.0.0.0/0
- IpProtocol: tcp
FromPort:
Fn::FindInMap:
- ServicesMeta
- Ssh
- Port
ToPort:
Fn::FindInMap:
- ServicesMeta
- Ssh
- Port
CidrIp:
Fn::If:
- HasInboundSSHCidrOverride
- Ref: InboundSSHCidrOverride
- 0.0.0.0/0
- IpProtocol: icmp
FromPort: -1
ToPort: -1
CidrIp:
Fn::If:
- HasInboundSSHCidrOverride
- Ref: InboundSSHCidrOverride
- 0.0.0.0/0
- IpProtocol: icmpv6
FromPort: -1
ToPort: -1
CidrIp:
Fn::If:
- HasInboundSSHCidrOverride
- Ref: InboundSSHCidrOverride
- 0.0.0.0/0
SecurityGroupEgress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 587
ToPort: 587
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 25
ToPort: 25
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 2525
ToPort: 2525
CidrIp: 0.0.0.0/0
- IpProtocol: udp
FromPort: 123
ToPort: 123
CidrIp: 0.0.0.0/0
- IpProtocol: udp
FromPort: 0
ToPort: 65535
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 53
ToPort: 53
CidrIp: 0.0.0.0/0
AppALBReticulumIngress:
Type: AWS::EC2::SecurityGroupIngress
Condition: HasALB
Properties:
GroupId:
Ref: AppSecurityGroup
IpProtocol: tcp
FromPort:
Fn::FindInMap:
- ServicesMeta
- RetInternal
- Port
ToPort:
Fn::FindInMap:
- ServicesMeta
- RetInternal
- Port
SourceSecurityGroupId:
Ref: AppALBSecurityGroup
AppALBReticulumEgress:
Type: AWS::EC2::SecurityGroupEgress
Condition: HasALB
Properties:
GroupId:
Ref: AppALBSecurityGroup
IpProtocol: tcp
FromPort:
Fn::FindInMap:
- ServicesMeta
- RetInternal
- Port
ToPort:
Fn::FindInMap:
- ServicesMeta
- RetInternal
- Port
DestinationSecurityGroupId:
Ref: AppSecurityGroup
AppJanusAdminAppIngress:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId:
Ref: AppSecurityGroup
IpProtocol: tcp
FromPort:
Fn::FindInMap:
- ServicesMeta
- DialogAdmin
- Port
ToPort:
Fn::FindInMap:
- ServicesMeta
- DialogAdmin
- Port
SourceSecurityGroupId:
Ref: AppSecurityGroup
AppJanusAdminStreamEgress:
Type: AWS::EC2::SecurityGroupEgress
Properties:
GroupId:
Ref: AppSecurityGroup
IpProtocol: tcp
FromPort:
Fn::FindInMap:
- ServicesMeta
- DialogAdmin
- Port
ToPort:
Fn::FindInMap:
- ServicesMeta
- DialogAdmin
- Port
DestinationSecurityGroupId:
Ref: StreamSecurityGroup
AppEPMDAppIngress:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId:
Ref: AppSecurityGroup
IpProtocol: tcp
FromPort: 4369
ToPort: 4369
SourceSecurityGroupId:
Ref: AppSecurityGroup
AppErlangAppIngress:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId:
Ref: AppSecurityGroup
IpProtocol: tcp
FromPort: 9000
ToPort: 9100
SourceSecurityGroupId:
Ref: AppSecurityGroup
AppDbEgress:
Type: AWS::EC2::SecurityGroupEgress
Properties:
GroupId:
Ref: AppSecurityGroup
IpProtocol: tcp
ToPort:
Fn::FindInMap:
- ServicesMeta
- PostgreSQL
- Port
FromPort:
Fn::FindInMap:
- ServicesMeta
- PostgreSQL
- Port
DestinationSecurityGroupId:
Ref: AppDbSecurityGroup
StreamDbEgress:
Type: AWS::EC2::SecurityGroupEgress
Properties:
GroupId:
Ref: StreamSecurityGroup
IpProtocol: tcp
ToPort:
Fn::FindInMap:
- ServicesMeta
- PostgreSQL
- Port
FromPort:
Fn::FindInMap:
- ServicesMeta
- PostgreSQL
- Port
DestinationSecurityGroupId:
Ref: AppDbSecurityGroup
AppFullSelfEgress:
Type: AWS::EC2::SecurityGroupEgress
Properties:
GroupId:
Ref: AppSecurityGroup
IpProtocol: tcp
FromPort: 0
ToPort: 65535
DestinationSecurityGroupId:
Ref: AppSecurityGroup
AppRole:
Type: AWS::IAM::Role
Properties:
RoleName:
Fn::Sub: ${AWS::StackName}-app
AssumeRolePolicyDocument:
Statement:
- Action:
- sts:AssumeRole
Effect: Allow
Principal:
Service: ec2.amazonaws.com
Version: '2012-10-17'
ManagedPolicyArns:
- Ref: BasePolicy
- Ref: CleanupRecordsEC2AppPolicy
StreamRole:
Type: AWS::IAM::Role
Properties:
RoleName:
Fn::Sub: ${AWS::StackName}-stream
AssumeRolePolicyDocument:
Statement:
- Action:
- sts:AssumeRole
Effect: Allow
Principal:
Service: ec2.amazonaws.com
Version: '2012-10-17'
ManagedPolicyArns:
- Ref: BasePolicy
CleanupRecordsEC2AppPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- autoscaling:DescribeAutoScalingGroups
- ec2:DescribeInstances
Resource:
- '*'
- Effect: Allow
Action:
- route53:ChangeResourceRecordSets
- route53:ListResourceRecordSets
Resource:
Fn::Sub: arn:aws:route53:::hostedzone/${InternalZoneInfo.Id}
- Effect: Allow
Action:
- route53:*
Resource:
- Fn::Sub: arn:aws:route53:::hostedzone/${InternalZoneInfo.Id}
- Fn::If:
- HasManagedDomain
- Fn::Sub: arn:aws:route53:::hostedzone/${ExternalZoneInfo.Id}
- arn:aws:route53:::hostedzone/DEADZONE*
- Effect: Allow
Action:
- autoscaling:DescribeAutoScalingGroups
- ec2:DescribeInstances
Resource:
- '*'
- Effect: Allow
Action:
- route53:ChangeResourceRecordSets
- route53:ListResourceRecordSets
Resource:
Fn::Sub: arn:aws:route53:::hostedzone/${InternalZoneInfo.Id}
BasePolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- ec2:DescribeInstances
Resource: '*'
- Effect: Allow
Action:
- ec2:CreateTags
Resource: '*'
- Effect: Allow
Action:
- ec2:DescribeTags
Resource: '*'
- Effect: Allow
Action:
- ssm:DescribeAssociation
- ssm:GetDeployablePatchSnapshotForInstance
- ssm:GetDocument
- ssm:DescribeDocument
- ssm:GetManifest
- ssm:GetParameters
- ssm:ListAssociations
- ssm:ListInstanceAssociations
- ssm:PutInventory
- ssm:PutComplianceItems
- ssm:PutConfigurePackageResult
- ssm:UpdateAssociationStatus
- ssm:UpdateInstanceAssociationStatus
- ssm:UpdateInstanceInformation
Resource: '*'
- Effect: Allow
Action:
- ssmmessages:CreateControlChannel
- ssmmessages:CreateDataChannel
- ssmmessages:OpenControlChannel
- ssmmessages:OpenDataChannel
Resource: '*'
- Effect: Allow
Action:
- sns:Publish
Resource:
Ref: StackTopic
- Effect: Allow
Action:
- ec2messages:AcknowledgeMessage
- ec2messages:DeleteMessage
- ec2messages:FailMessage
- ec2messages:GetEndpoint
- ec2messages:GetMessages
- ec2messages:SendReply
Resource: '*'
- Effect: Allow
Action:
- route53:ChangeResourceRecordSets
Resource:
Fn::Sub: arn:aws:route53:::hostedzone/${InternalZoneInfo.Id}
- Effect: Allow
Action:
- route53:ListHostedZones
- route53:GetChange
Resource: '*'
- Effect: Allow
Action:
- iam:GetUser
- iam:ListAccessKeys
Resource:
Fn::GetAtt: SendEmailUser.Arn
- Effect: Allow
Action: s3:GetObject
Resource:
Fn::Sub: ${BoxKeysBucket.Arn}/*
- Effect: Allow
Action:
- s3:ListBucket
Resource:
- Fn::Sub: ${BoxKeysBucket.Arn}
- Effect: Allow
Action:
- s3:GetObject
- s3:DeleteObject
- s3:PutObject
- s3:PutObjectAcl
Resource:
- Fn::Sub: ${AssetsBucket.Arn}/hubs/*
- Fn::Sub: ${AssetsBucket.Arn}/spoke/*
- Fn::Sub: ${AssetsBucket.Arn}/assets/*
- Fn::Sub: ${LinkRedirectorBucket.Arn}/*
- Effect: Allow
Action:
- s3:ListBucket
Resource:
- Fn::Sub: ${AssetsBucket.Arn}
- Fn::Sub: ${LinkRedirectorBucket.Arn}
- Effect: Allow
Action: cloudformation:DescribeStacks
Resource:
Ref: AWS::StackId
- Effect: Allow
Action:
- ssm:PutParameter
- ssm:DeleteParameter
- ssm:GetParameterHistory
- ssm:GetParametersByPath
- ssm:GetParameters
- ssm:GetParameter
- ssm:DeleteParameters
Resource:
- Fn::Sub: arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/ita/${AWS::StackName}/*
- Fn::Sub: arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/keymaster/${AWS::StackName}/*
- Fn::Sub: arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/keymaster/${AWS::StackName}
- Effect: Allow
Action:
- ses:GetSendQuota
Resource: '*'
- Effect: Allow
Action:
- lambda:ListFunctions
- lambda:ListTags
Resource: '*'
AppALB:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Condition: HasALB
Properties:
Name:
Fn::Sub: ${AWS::StackName}-app
LoadBalancerAttributes:
- Key: idle_timeout.timeout_seconds
Value: 120
SecurityGroups:
- Ref: AppALBSecurityGroup
Subnets:
- Ref: SubnetAPublic
- Ref: SubnetBPublic
DependsOn:
- AppCloudfrontDistribution
- AppAssetsCloudfrontDistribution
- NearsparkCloudfrontDistribution
- LinkRedirectorCloudfrontDistribution
AppALBDNSInternal:
Type: AWS::Route53::RecordSet
Condition: HasALB
Properties:
Name:
Fn::Sub: ${LowerStackName.Value}-app-alb.${InternalZoneInfo.Name}
HostedZoneId:
Ref: InternalZone
Type: A
AliasTarget:
DNSName:
Fn::GetAtt: AppALB.DNSName
HostedZoneId:
Fn::GetAtt: AppALB.CanonicalHostedZoneID
AppALBRetTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Condition: HasALB
Properties:
Name:
Fn::Sub: ${AWS::StackName}-ret
VpcId:
Ref: VPC
Port:
Fn::FindInMap:
- ServicesMeta
- RetInternal
- Port
Protocol: HTTPS
TargetGroupAttributes:
- Key: deregistration_delay.timeout_seconds
Value: 0
HealthCheckPath: /health
HealthCheckProtocol: HTTPS
HealthyThresholdCount: 2
UnhealthyThresholdCount: 2
HealthCheckIntervalSeconds: 10
HealthCheckTimeoutSeconds: 5
AppALBRetSSLListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Condition: HasALB
Properties:
LoadBalancerArn:
Ref: AppALB
Port: 443
Protocol: HTTPS
SslPolicy: ELBSecurityPolicy-2015-05
Certificates:
- CertificateArn:
Fn::If:
- HasManagedDomain
- Fn::If:
- IsEast
- Ref: ExternalZoneSSLCertLocalIfEast
- Ref: ExternalZoneSSLCertLocalIfNonEast
- Ref: UnmanagedDomainCertArn
DefaultActions:
- TargetGroupArn:
Fn::If:
- PerformOfflineRedirect
- Ref: AWS::NoValue
- Ref: AppALBRetTargetGroup
Type:
Fn::If:
- PerformOfflineRedirect
- redirect
- forward
RedirectConfig:
Fn::If:
- PerformOfflineRedirect
- Protocol:
Fn::GetAtt: ParsedOfflineRedirectUrl.ALBProtocol
Port: 443
Host:
Fn::GetAtt: ParsedOfflineRedirectUrl.ALBHost
Path:
Fn::GetAtt: ParsedOfflineRedirectUrl.ALBPath
Query:
Fn::GetAtt: ParsedOfflineRedirectUrl.ALBQuery
StatusCode: HTTP_302
- Ref: AWS::NoValue
AppALBRetSSLListenerInternalCert:
Type: AWS::ElasticLoadBalancingV2::ListenerCertificate
Condition: HasALB
Properties:
ListenerArn:
Ref: AppALBRetSSLListener
Certificates:
- CertificateArn:
Ref: InternalZoneSSLCert
AppALBRetClearListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Condition: HasALB
Properties:
LoadBalancerArn:
Ref: AppALB
Port: 80
Protocol: HTTP
DefaultActions:
- Type: redirect
RedirectConfig:
Port: '443'
Protocol: HTTPS
StatusCode: HTTP_301
AppALBRetListenerRule:
Type: AWS::ElasticLoadBalancingV2::ListenerRule
Condition: HasALB
Properties:
ListenerArn:
Ref: AppALBRetSSLListener
Priority: 1
Actions:
- Type: forward
TargetGroupArn:
Ref: AppALBRetTargetGroup
Conditions:
- Field: path-pattern
Values:
- /
AppInstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
InstanceProfileName:
Fn::Sub: ${AWS::StackName}-app
Roles:
- Ref: AppRole
StreamInstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
InstanceProfileName:
Fn::Sub: ${AWS::StackName}-stream
Roles:
- Ref: StreamRole
DomainDNS:
Type: AWS::Route53::RecordSet
Condition: HasManagedDomain
Properties:
Name:
Ref: DomainName
HostedZoneId:
Fn::GetAtt: ExternalZoneInfo.Id
Type: A
AliasTarget:
DNSName:
Fn::If:
- HasALB
- Fn::GetAtt: AppALB.DNSName
- Fn::GetAtt: AppCloudfrontDistribution.DomainName
HostedZoneId:
Fn::If:
- HasALB
- Fn::GetAtt: AppALB.CanonicalHostedZoneID
- Z2FDTNDATAQYW2
ParsedOfflineRedirectUrl:
Type: Custom::ParsedURL
Properties:
URL:
Ref: StackOfflineRedirectUrl
ServiceToken:
Fn::GetAtt: ParseURL.Arn
LowerStackName:
Type: Custom::ToLower
Properties:
String:
Fn::Sub: ${AWS::StackName}
ServiceToken:
Fn::GetAtt: ToLower.Arn
AppCloudfrontDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Enabled: true
Origins:
- Id:
Fn::Sub: ${AWS::StackName}-app
DomainName:
Fn::If:
- RequestedALB
- Fn::Sub: ${LowerStackName.Value}-app-alb.${InternalZoneInfo.Name}
- Fn::Sub: ${LowerStackName.Value}-app.${InternalZoneInfo.Name}
CustomOriginConfig:
HTTPPort: 80
HTTPSPort: 443
OriginProtocolPolicy: https-only
OriginSSLProtocols:
- SSLv3
- TLSv1
- TLSv1.1
- TLSv1.2
OriginReadTimeout: 60
- Id:
Fn::Sub: ${AWS::StackName}-redirector
DomainName:
Fn::Select:
- 2
- Fn::Split:
- /
- Fn::GetAtt: RootRedirectorBucket.WebsiteURL
CustomOriginConfig:
HTTPPort: 80
HTTPSPort: 443
OriginProtocolPolicy: http-only
OriginSSLProtocols:
- SSLv3
- TLSv1
- TLSv1.1
- TLSv1.2
- Id:
Fn::Sub: ${AWS::StackName}-app-assets
DomainName:
Fn::GetAtt: AssetsBucket.RegionalDomainName
S3OriginConfig:
OriginAccessIdentity: ''
Restrictions:
GeoRestriction:
RestrictionType: none
Locations: []
Aliases:
- Ref: DomainName
HttpVersion: http2
PriceClass: PriceClass_All
CustomErrorResponses:
- ErrorCode: 403
ErrorCachingMinTTL: 0
- ErrorCode: 404
ErrorCachingMinTTL: 0
- ErrorCode: 500
ErrorCachingMinTTL: 0
- ErrorCode: 502
ResponseCode: 502
ErrorCachingMinTTL: 0
ResponsePagePath:
Fn::Sub: /assets/pages/unavailable.html
- ErrorCode: 503
ResponseCode: 503
ErrorCachingMinTTL: 0
ResponsePagePath:
Fn::Sub: /assets/pages/unavailable.html
- ErrorCode: 504
ResponseCode: 504
ErrorCachingMinTTL: 0
ResponsePagePath:
Fn::Sub: /assets/pages/unavailable.html
CacheBehaviors:
- PathPattern: /assets/*
Compress: true
AllowedMethods:
- GET
- HEAD
- OPTIONS
CachedMethods:
- GET
- HEAD
TargetOriginId:
Fn::Sub: ${AWS::StackName}-app-assets
ForwardedValues:
QueryString: true
Headers:
- Origin
- Content-Type
- Access-Control-Request-Method
- Access-Control-Request-Headers
- Accept
Cookies:
Forward: none
ViewerProtocolPolicy: https-only
DefaultCacheBehavior:
Compress: true
AllowedMethods:
- GET
- HEAD
- POST
- PATCH
- PUT
- DELETE
- OPTIONS
CachedMethods:
- GET
- HEAD
TargetOriginId:
Fn::If:
- PerformOfflineRedirect
- Fn::Sub: ${AWS::StackName}-redirector
- Fn::Sub: ${AWS::StackName}-app
ForwardedValues:
QueryString:
Fn::If:
- PerformOfflineRedirect
- false
- true
Headers:
Fn::If:
- PerformOfflineRedirect
- []
- - Origin
- Content-Type
- Range
- Host
- Authorization
- Access-Control-Request-Method
- Access-Control-Request-Headers
- Accept
Cookies:
Forward: none
ViewerProtocolPolicy: redirect-to-https
MinTTL: 0
DefaultTTL: 3600
MaxTTL: 3600
ViewerCertificate:
AcmCertificateArn:
Fn::If:
- HasManagedDomain
- Fn::If:
- IsNonEast
- Ref: ExternalZoneSSLCertEast
- Ref: ExternalZoneSSLCertLocalIfEast
- Ref: UnmanagedDomainEastCertArn
SslSupportMethod: sni-only
MinimumProtocolVersion: TLSv1
AppAssetsCloudfrontDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Enabled: true
Origins:
- Id:
Fn::Sub: ${AWS::StackName}-app-assets
DomainName:
Fn::GetAtt: AssetsBucket.RegionalDomainName
S3OriginConfig:
OriginAccessIdentity: ''
- Id:
Fn::Sub: ${AWS::StackName}-app
DomainName:
Fn::If:
- RequestedALB
- Fn::Sub: ${LowerStackName.Value}-app-alb.${InternalZoneInfo.Name}
- Fn::Sub: ${LowerStackName.Value}-app.${InternalZoneInfo.Name}
CustomOriginConfig:
HTTPPort: 80
HTTPSPort: 443
OriginProtocolPolicy: https-only
OriginSSLProtocols:
- SSLv3
- TLSv1
- TLSv1.1
- TLSv1.2
Restrictions:
GeoRestriction:
RestrictionType: none
Locations: []
Aliases:
- Fn::Sub: ${LowerStackName.Value}-assets.${InternalZoneInfo.Name}
- Fn::Sub: ${LowerStackName.Value}-cors-proxy.${InternalZoneInfo.Name}
HttpVersion: http2
PriceClass: PriceClass_All
CustomErrorResponses:
- ErrorCode: 403
ErrorCachingMinTTL: 0
- ErrorCode: 404
ErrorCachingMinTTL: 0
- ErrorCode: 500
ErrorCachingMinTTL: 0
- ErrorCode: 502
ErrorCachingMinTTL: 0
- ErrorCode: 503
ErrorCachingMinTTL: 0
- ErrorCode: 504
ErrorCachingMinTTL: 0
CacheBehaviors:
- PathPattern: /files/*
Compress: true
AllowedMethods:
- GET
- HEAD
- OPTIONS
CachedMethods:
- GET
- HEAD
TargetOriginId:
Fn::Sub: ${AWS::StackName}-app
ForwardedValues:
QueryString: true
Headers:
- Origin
- Content-Type
- Host
- Authorization
- Access-Control-Request-Method
- Access-Control-Request-Headers
- Accept
- Range
Cookies:
Forward: none
ViewerProtocolPolicy: https-only
MinTTL: 0
DefaultTTL: 3600
MaxTTL: 3600
- PathPattern: /http*
Compress: true
AllowedMethods:
- GET
- HEAD
- POST
- PATCH
- PUT
- DELETE
- OPTIONS
CachedMethods:
- GET
- HEAD
TargetOriginId:
Fn::Sub: ${AWS::StackName}-app
ForwardedValues:
QueryString: true
Headers:
- Origin
- Content-Type
- Range
- Host
- Authorization
- Access-Control-Request-Method
- Access-Control-Request-Headers
- Accept
Cookies:
Forward: none
ViewerProtocolPolicy: https-only
MinTTL: 0
DefaultTTL: 3600
MaxTTL: 3600
DefaultCacheBehavior:
Compress: true
AllowedMethods:
- GET
- HEAD
- OPTIONS
CachedMethods:
- GET
- HEAD
TargetOriginId:
Fn::Sub: ${AWS::StackName}-app-assets
ForwardedValues:
QueryString: true
Headers:
- Origin
- Content-Type
- Access-Control-Request-Method
- Access-Control-Request-Headers
- Accept
Cookies:
Forward: none
ViewerProtocolPolicy: https-only
MinTTL: 0
DefaultTTL: 3600
MaxTTL: 3600
ViewerCertificate:
AcmCertificateArn:
Fn::If:
- IsNonEast
- Ref: InternalZoneSSLCertEast
- Ref: InternalZoneSSLCert
SslSupportMethod: sni-only
MinimumProtocolVersion: TLSv1
AppAssetsDNS:
Type: AWS::Route53::RecordSet
Properties:
Name:
Fn::Sub: ${LowerStackName.Value}-assets.${InternalZoneInfo.Name}
HostedZoneId:
Ref: InternalZone
Type: A
AliasTarget:
DNSName:
Fn::GetAtt: AppAssetsCloudfrontDistribution.DomainName
HostedZoneId: Z2FDTNDATAQYW2
StreamSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName:
Fn::Sub: ${AWS::StackName}-stream
GroupDescription: Stream
VpcId:
Ref: VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort:
Fn::FindInMap:
- ServicesMeta
- DialogExternal
- StreamPort
ToPort:
Fn::FindInMap:
- ServicesMeta
- DialogExternal
- StreamPort
CidrIp:
Fn::If:
- HasInboundCidrOverride
- Ref: InboundCidrOverride
- 0.0.0.0/0
- IpProtocol: tcp
FromPort:
Fn::FindInMap:
- ServicesMeta
- DialogTurnExternal
- Port
ToPort:
Fn::FindInMap:
- ServicesMeta
- DialogTurnExternal
- Port
CidrIp:
Fn::If:
- HasInboundCidrOverride
- Ref: InboundCidrOverride
- 0.0.0.0/0
- IpProtocol: udp
FromPort:
Fn::FindInMap:
- ServicesMeta
- DialogTurnExternal
- Port
ToPort:
Fn::FindInMap:
- ServicesMeta
- DialogTurnExternal
- Port
CidrIp:
Fn::If:
- HasInboundCidrOverride
- Ref: InboundCidrOverride
- 0.0.0.0/0
- IpProtocol: udp
FromPort:
Fn::FindInMap:
- ServicesMeta
- DialogWebRTCFrom
- Port
ToPort:
Fn::FindInMap:
- ServicesMeta
- DialogWebRTCTo
- Port
CidrIp:
Fn::If:
- HasInboundCidrOverride
- Ref: InboundCidrOverride
- 0.0.0.0/0
- IpProtocol: tcp
FromPort:
Fn::FindInMap:
- ServicesMeta
- DialogWebRTCFrom
- Port
ToPort:
Fn::FindInMap:
- ServicesMeta
- DialogWebRTCTo
- Port
CidrIp:
Fn::If:
- HasInboundCidrOverride
- Ref: InboundCidrOverride
- 0.0.0.0/0
- IpProtocol: tcp
FromPort:
Fn::FindInMap:
- ServicesMeta
- Ssh
- Port
ToPort:
Fn::FindInMap:
- ServicesMeta
- Ssh
- Port
CidrIp:
Fn::If:
- HasInboundSSHCidrOverride
- Ref: InboundSSHCidrOverride
- 0.0.0.0/0
- IpProtocol: icmp
FromPort: -1
ToPort: -1
CidrIp:
Fn::If:
- HasInboundSSHCidrOverride
- Ref: InboundSSHCidrOverride
- 0.0.0.0/0
- IpProtocol: icmpv6
FromPort: -1
ToPort: -1
CidrIp:
Fn::If:
- HasInboundSSHCidrOverride
- Ref: InboundSSHCidrOverride
- 0.0.0.0/0
SecurityGroupEgress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
- IpProtocol: udp
FromPort: 123
ToPort: 123
CidrIp: 0.0.0.0/0
- IpProtocol: udp
FromPort: 0
ToPort: 65535
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 53
ToPort: 53
CidrIp: 0.0.0.0/0
StreamJanusAdminAppIngress:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId:
Ref: StreamSecurityGroup
IpProtocol: tcp
FromPort:
Fn::FindInMap:
- ServicesMeta
- DialogAdmin
- Port
ToPort:
Fn::FindInMap:
- ServicesMeta
- DialogAdmin
- Port
SourceSecurityGroupId:
Ref: AppSecurityGroup
StreamFullSelfEgress:
Type: AWS::EC2::SecurityGroupEgress
Properties:
GroupId:
Ref: StreamSecurityGroup
IpProtocol: tcp
FromPort: 0
ToPort: 65535
DestinationSecurityGroupId:
Ref: StreamSecurityGroup
CorsProxyDNS:
Type: AWS::Route53::RecordSet
Properties:
Name:
Fn::Sub: ${LowerStackName.Value}-cors-proxy.${InternalZoneInfo.Name}
HostedZoneId:
Ref: InternalZone
Type: A
AliasTarget:
DNSName:
Fn::GetAtt: AppAssetsCloudfrontDistribution.DomainName
HostedZoneId: Z2FDTNDATAQYW2
AppLaunchConfiguration:
Type: AWS::AutoScaling::LaunchConfiguration
Properties:
AssociatePublicIpAddress: true
BlockDeviceMappings:
- DeviceName: /dev/sda1
Ebs:
VolumeSize: 8
IamInstanceProfile:
Ref: AppInstanceProfile
InstanceType:
Ref: AppInstanceType
SecurityGroups:
- Ref: AppSecurityGroup
- Ref: BioRingSecurityGroup
- Ref: StorageConnectSecurityGroup
ImageId:
Fn::FindInMap:
- Regions
- Ref: AWS::Region
- ImageId
KeyName:
Ref: KeyPair
UserData:
Fn::Base64: |
#!/usr/bin/env bash
/opt/polycosm/polycosm_boot.sh aws aws
AppPlacementGroup:
Type: AWS::EC2::PlacementGroup
Properties:
Strategy:
Fn::If:
- AppIsNoPlacement
- cluster
- Fn::If:
- AppIsClusterPlacement
- Fn::FindInMap:
- InstanceTypeMeta
- Ref: AppInstanceType
- PlacementForCluster
- Ref: AppPlacementGroupStrategy
StackTopic:
Type: AWS::SNS::Topic
Properties:
TopicName:
Fn::Sub: ${AWS::StackName}-topic
DisplayName: Topic for handling scaling events and budget triggers
Subscription:
- Protocol: lambda
Endpoint:
Fn::GetAtt: StackTopicHandler.Arn
Tags:
- Key: stack-name
Value:
Fn::Sub: ${AWS::StackName}
- Key: stack-region
Value:
Fn::Sub: ${AWS::Region}
AppASG:
Type: AWS::AutoScaling::AutoScalingGroup
UpdatePolicy:
AutoScalingRollingUpdate:
MaxBatchSize: 10
PauseTime: PT30M
SuspendProcesses:
- HealthCheck
- ReplaceUnhealthy
- AZRebalance
- AlarmNotification
- ScheduledActions
MinInstancesInService:
Fn::If:
- IsOffline
- 0
- 1
WaitOnResourceSignals: true
Properties:
AutoScalingGroupName:
Fn::Sub: ${AWS::StackName}-app
AvailabilityZones:
Fn::If:
- AppIsClusterPlacement
- - Fn::GetAtt: SubnetAPublic.AvailabilityZone
- - Fn::GetAtt: SubnetAPublic.AvailabilityZone
- Fn::GetAtt: SubnetBPublic.AvailabilityZone
VPCZoneIdentifier:
Fn::If:
- AppIsClusterPlacement
- - Ref: SubnetAPublic
- - Ref: SubnetAPublic
- Ref: SubnetBPublic
TargetGroupARNs:
Fn::If:
- HasALB
- - Ref: AppALBRetTargetGroup
- Ref: AWS::NoValue
MinSize: 0
MaxSize: 64
NotificationConfigurations:
- NotificationTypes:
- autoscaling:EC2_INSTANCE_LAUNCH
- autoscaling:EC2_INSTANCE_TERMINATE
TopicARN:
Ref: StackTopic
DesiredCapacity:
Fn::If:
- IsOffline
- 0
- 1
PlacementGroup:
Fn::If:
- AppIsNoPlacement
- Ref: AWS::NoValue
- Ref: AppPlacementGroup
LaunchConfigurationName:
Ref: AppLaunchConfiguration
Tags:
- Key: polycosm-roles
Value:
Fn::If:
- HasStreamingServers
- app
- app,stream
PropagateAtLaunch: true
- Key: polycosm-type
Value: app
PropagateAtLaunch: true
- Key: bio-ring
Value:
Fn::Sub: ${AWS::StackName}
PropagateAtLaunch: true
DependsOn:
- SshTOTP
- JWTKeys
- VapidKeys
- BioRingKey
- BioServiceKeys
- BioUserKey
StreamLaunchConfiguration:
Type: AWS::AutoScaling::LaunchConfiguration
Properties:
AssociatePublicIpAddress: true
BlockDeviceMappings:
- DeviceName: /dev/sda1
Ebs:
VolumeSize: 8
IamInstanceProfile:
Ref: StreamInstanceProfile
InstanceType: t3.micro
SecurityGroups:
- Ref: StreamSecurityGroup
- Ref: BioRingSecurityGroup
- Ref: StorageConnectSecurityGroup
ImageId:
Fn::FindInMap:
- Regions
- Ref: AWS::Region
- ImageId
KeyName:
Ref: KeyPair
UserData:
Fn::Base64: |
#!/usr/bin/env bash
/opt/polycosm/polycosm_boot.sh aws aws
StreamASG:
Type: AWS::AutoScaling::AutoScalingGroup
UpdatePolicy:
AutoScalingRollingUpdate:
MaxBatchSize: 10
PauseTime: PT30M
SuspendProcesses:
- HealthCheck
- ReplaceUnhealthy
- AZRebalance
- AlarmNotification
- ScheduledActions
MinInstancesInService:
Fn::If:
- IsOffline
- 0
- 0
WaitOnResourceSignals: true
Properties:
AutoScalingGroupName:
Fn::Sub: ${AWS::StackName}-stream
AvailabilityZones:
- Fn::GetAtt: SubnetAPublic.AvailabilityZone
- Fn::GetAtt: SubnetBPublic.AvailabilityZone
VPCZoneIdentifier:
- Ref: SubnetAPublic
- Ref: SubnetBPublic
MinSize: 0
MaxSize: 64
DesiredCapacity:
Fn::If:
- IsOffline
- 0
- 0
LaunchConfigurationName:
Ref: StreamLaunchConfiguration
Tags:
- Key: polycosm-roles
Value: stream
PropagateAtLaunch: true
- Key: polycosm-type
Value: stream
PropagateAtLaunch: true
- Key: bio-ring
Value:
Fn::Sub: ${AWS::StackName}
PropagateAtLaunch: true
DependsOn:
- SshTOTP
- JWTKeys
- VapidKeys
- BioRingKey
- BioServiceKeys
- BioUserKey
StorageEFS:
Type: Custom::EFS
DeletionPolicy: Retain
Properties:
PerformanceMode: generalPurpose
LifecyclePolicies:
- TransitionToIA: AFTER_30_DAYS
FileSystemTags:
- Key: backup
Value: daily
- Key: Name
Value:
Fn::Sub: ${AWS::StackName}-storage
RestoreBackupVaultName:
Ref: RestoreBackupVaultName
RestoreRecoveryPointArn:
Ref: RestoreRecoveryPointArn
RestoreIamRoleArn:
Fn::GetAtt: DailyBackupRole.Arn
ServiceToken:
Fn::GetAtt: EFSCreateOrRestore.Arn
StorageEFSAppMountTargetAPrivate:
Type: AWS::EFS::MountTarget
Properties:
FileSystemId:
Ref: StorageEFS
SecurityGroups:
- Ref: StorageSecurityGroup
SubnetId:
Ref: SubnetAPrivate
StorageEFSAppMountTargetBPrivate:
Type: AWS::EFS::MountTarget
Properties:
FileSystemId:
Ref: StorageEFS
SecurityGroups:
- Ref: StorageSecurityGroup
SubnetId:
Ref: SubnetBPrivate
StorageSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName:
Fn::Sub: ${AWS::StackName}-storage-fs
GroupDescription: Storage EFS
VpcId:
Ref: VPC
StorageConnectSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName:
Fn::Sub: ${AWS::StackName}-storage-fs-connect
GroupDescription: Storage EFS Connect
VpcId:
Ref: VPC
SecurityGroupEgress:
- IpProtocol: tcp
FromPort: 2049
ToPort: 2049
DestinationSecurityGroupId:
Ref: StorageSecurityGroup
StorageFSConnectIngress:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId:
Ref: StorageSecurityGroup
IpProtocol: tcp
FromPort: 2049
ToPort: 2049
SourceSecurityGroupId:
Ref: StorageConnectSecurityGroup
DailyBackupVault:
Type: AWS::Backup::BackupVault
DeletionPolicy: Retain
Properties:
BackupVaultName:
Fn::Join:
- '-'
- - Fn::Sub: ${AWS::StackName}-daily-backup
- Fn::Select:
- 0
- Fn::Split:
- '-'
- Fn::Select:
- 2
- Fn::Split:
- /
- Ref: AWS::StackId
EncryptionKeyArn:
Fn::If:
- CreateDiskEncryptionKey
- Fn::GetAtt: DiskKmsKey.Arn
- Ref: AWS::NoValue
DailyBackupPlan:
Type: AWS::Backup::BackupPlan
Properties:
BackupPlan:
BackupPlanName:
Fn::Sub: ${AWS::StackName}-daily-backup-plan
BackupPlanRule:
- RuleName: Daily
TargetBackupVault:
Ref: DailyBackupVault
ScheduleExpression: cron(0 10 ? * * *)
DependsOn: DailyBackupVault
DailyBackupRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- backup.amazonaws.com
Action:
- sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSBackupServiceRolePolicyForBackup
- arn:aws:iam::aws:policy/service-role/AWSBackupServiceRolePolicyForRestores
DailyBackupSelection:
Type: AWS::Backup::BackupSelection
Properties:
BackupSelection:
SelectionName:
Fn::Sub: ${AWS::StackName}-daily-backup-selection
IamRoleArn:
Fn::GetAtt: DailyBackupRole.Arn
ListOfTags:
- ConditionType: STRINGEQUALS
ConditionKey: backup
ConditionValue: daily
BackupPlanId:
Ref: DailyBackupPlan
DependsOn: DailyBackupPlan
AppDbSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName:
Fn::Sub: ${AWS::StackName}-app-db
GroupDescription: DB
VpcId:
Ref: VPC
SecurityGroupIngress:
- IpProtocol: tcp
ToPort:
Fn::FindInMap:
- ServicesMeta
- PostgreSQL
- Port
FromPort:
Fn::FindInMap:
- ServicesMeta
- PostgreSQL
- Port
SourceSecurityGroupId:
Ref: AppSecurityGroup
- IpProtocol: tcp
ToPort:
Fn::FindInMap:
- ServicesMeta
- PostgreSQL
- Port
FromPort:
Fn::FindInMap:
- ServicesMeta
- PostgreSQL
- Port
SourceSecurityGroupId:
Ref: StreamSecurityGroup
DiskKmsKey:
Type: AWS::KMS::Key
Condition: CreateDiskEncryptionKey
DeletionPolicy: Retain
Properties:
Description: DB encryption key
EnableKeyRotation: true
KeyPolicy:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
AWS:
Fn::Sub: arn:aws:iam::${AWS::AccountId}:root
Action: kms:*
Resource: '*'
- Effect: Allow
Principal:
AWS: '*'
Action:
- kms:Encrypt
- kms:Decrypt
- kms:ReEncrypt*
- kms:GenerateDataKey*
- kms:CreateGrant
- kms:ListGrants
- kms:DescribeKey
Resource: '*'
Condition:
StringEquals:
kms:CallerAccount:
Ref: AWS::AccountId
kms:ViaService:
Fn::Sub: rds.${AWS::Region}.amazonaws.com
DiskKmsKeyAlias:
Type: AWS::KMS::Alias
Condition: CreateDiskEncryptionKey
DeletionPolicy: Retain
DependsOn: AppDb
Properties:
AliasName:
Fn::Join:
- '-'
- - Fn::Sub: alias/db-app-db-${AWS::StackName}
- Fn::Select:
- 0
- Fn::Split:
- '-'
- Fn::Select:
- 2
- Fn::Split:
- /
- Ref: AWS::StackId
TargetKeyId:
Ref: DiskKmsKey
AppDbSubnet:
Type: AWS::RDS::DBSubnetGroup
Properties:
DBSubnetGroupDescription: app-db
DBSubnetGroupName:
Fn::Sub: ${LowerStackName.Value}-app-db
SubnetIds:
- Ref: SubnetAPrivate
- Ref: SubnetBPrivate
RestoreAppDbSecret:
Type: Custom::ReadSecret
Condition: IsRestore
Properties:
ServiceToken:
Fn::GetAtt: KeymasterApplication.Outputs.ReadSecretFunctionArn
SecretArn:
Ref: RestoreAppDbSecretArn
AppDbSecret:
Type: AWS::SecretsManager::Secret
DeletionPolicy: Retain
Properties:
Fn::If:
- IsRestore
- Description:
Fn::Sub: ${AWS::StackName} Database Secret
SecretString:
Fn::GetAtt: RestoreAppDbSecret.SecretString
- Description:
Fn::Sub: ${AWS::StackName} Database Secret
GenerateSecretString:
SecretStringTemplate: '{ "username": "postgres" }'
GenerateStringKey: password
PasswordLength: 64
ExcludePunctuation: true
AppDbSecretAttachment:
Type: AWS::SecretsManager::SecretTargetAttachment
Properties:
SecretId:
Ref: AppDbSecret
TargetId:
Ref: AppDb
TargetType: AWS::RDS::DBCluster
AppDbSecretResourcePolicy:
Type: AWS::SecretsManager::ResourcePolicy
Properties:
SecretId:
Ref: AppDbSecret
ResourcePolicy:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
AWS:
Fn::GetAtt: AppRole.Arn
Action: secretsmanager:GetSecretValue
Resource: '*'
- Effect: Deny
Principal:
AWS:
Fn::Sub: arn:aws:iam::${AWS::AccountId}:root
Action: secretsmanager:DeleteSecret
Resource: '*'
AppPostgrestDbSecret:
Type: Custom::GenerateSecret
DeletionPolicy: Retain
Properties:
Description: PostgREST admin db password
ServiceToken:
Fn::GetAtt: KeymasterApplication.Outputs.GenerateSecretFunctionArn
Name: postgrest-db-secret
Region:
Ref: AWS::Region
StackName:
Ref: AWS::StackName
CopyFromStackName:
Fn::If:
- IsRestore
- Ref: RestoreStackName
- Ref: AWS::NoValue
CopyFromStackRegion:
Fn::If:
- IsRestore
- Ref: AWS::Region
- Ref: AWS::NoValue
AppDb:
Type: AWS::RDS::DBCluster
Properties:
AvailabilityZones:
- Fn::GetAtt: SubnetAPrivate.AvailabilityZone
- Fn::GetAtt: SubnetBPrivate.AvailabilityZone
BackupRetentionPeriod:
Ref: DbBackupRetentionPeriod
DatabaseName: polycosm_production
DBClusterIdentifier:
Fn::Sub: ${AWS::StackName}-app-db
DBSubnetGroupName:
Ref: AppDbSubnet
Engine: aurora-postgresql
EngineMode: serverless
EngineVersion: 11.13
KmsKeyId:
Fn::If:
- CreateDiskEncryptionKey
- Ref: DiskKmsKey
- Ref: AWS::NoValue
StorageEncrypted: true
MasterUsername:
Fn::If:
- IsRestore
- Ref: AWS::NoValue
- Fn::Join:
- ''
- - '{{resolve:secretsmanager:'
- Ref: AppDbSecret
- :SecretString:username}}
MasterUserPassword:
Fn::If:
- IsRestore
- Ref: AWS::NoValue
- Fn::Join:
- ''
- - '{{resolve:secretsmanager:'
- Ref: AppDbSecret
- :SecretString:password}}
ScalingConfiguration:
AutoPause:
Fn::If:
- IsOffline
- true
- Fn::If:
- EnableDbAutoPause
- true
- false
MaxCapacity:
Ref: DbMaxCapacity
MinCapacity: 2
SecondsUntilAutoPause: 300
SnapshotIdentifier:
Fn::If:
- IsRestore
- Ref: RestoreDbSnapshotIdentifier
- Ref: AWS::NoValue
VpcSecurityGroupIds:
- Ref: AppDbSecurityGroup
Tags:
- Key: Name
Value:
Fn::Sub: app-db
AppDbBudget:
Type: AWS::Budgets::Budget
Condition: HasDbMonthlyBudget
DependsOn: StackTopicHandlerTopicPolicy
Properties:
Budget:
BudgetLimit:
Amount:
Ref: DatabaseMonthlyBudget
Unit: USD
BudgetType: COST
TimeUnit: MONTHLY
CostFilters:
UsageType:
- Fn::Join:
- '-'
- - Fn::FindInMap:
- Regions
- Ref: AWS::Region
- Abbreviation
- Aurora:ServerlessUsage
NotificationsWithSubscribers:
- Notification:
NotificationType: ACTUAL
ComparisonOperator: GREATER_THAN
Threshold: 100
Subscribers:
- SubscriptionType: EMAIL
Address:
Ref: AdminEmailAddress
- SubscriptionType: SNS
Address:
Ref: StackTopic
JanusAndReticulumAdminSecret:
Type: Custom::GenerateSecret
DependsOn: AppPostgrestDbSecret
Properties:
Description: Janus and Reticulum Admin Secret
ServiceToken:
Fn::GetAtt: KeymasterApplication.Outputs.GenerateSecretFunctionArn
Name: janus-and-reticulum-admin
Region:
Ref: AWS::Region
StackName:
Ref: AWS::StackName
PhoenixKeySecret:
Type: Custom::GenerateSecret
DeletionPolicy: Retain
DependsOn: JanusAndReticulumAdminSecret
Properties:
Description: Phoenix secret base key
ServiceToken:
Fn::GetAtt: KeymasterApplication.Outputs.GenerateSecretFunctionArn
Name: phoenix-key
Region:
Ref: AWS::Region
StackName:
Ref: AWS::StackName
CopyFromStackName:
Fn::If:
- IsRestore
- Ref: RestoreStackName
- Ref: AWS::NoValue
CopyFromStackRegion:
Fn::If:
- IsRestore
- Ref: AWS::Region
- Ref: AWS::NoValue
GuardianSecretKeySecret:
Type: Custom::GenerateSecret
DependsOn: PhoenixKeySecret
Properties:
Description: Reticulum guardian secret key
ServiceToken:
Fn::GetAtt: KeymasterApplication.Outputs.GenerateSecretFunctionArn
Name: guardian-secret-key
Region:
Ref: AWS::Region
StackName:
Ref: AWS::StackName
ReticulumOAuthTokenSecret:
Type: Custom::GenerateSecret
DependsOn: GuardianSecretKeySecret
Properties:
Description: Reticulum OAuth Token
ServiceToken:
Fn::GetAtt: KeymasterApplication.Outputs.GenerateSecretFunctionArn
Name: reticulum-oauth-token
Region:
Ref: AWS::Region
StackName:
Ref: AWS::StackName
ReticulumCookieSecret:
Type: Custom::GenerateSecret
DependsOn: ReticulumOAuthTokenSecret
Properties:
Description: Reticulum cookie
ServiceToken:
Fn::GetAtt: KeymasterApplication.Outputs.GenerateSecretFunctionArn
Name: reticulum-cookie
Region:
Ref: AWS::Region
StackName:
Ref: AWS::StackName
ReticulumBotAccessKeySecret:
Type: Custom::GenerateSecret
DependsOn: ReticulumCookieSecret
Properties:
Description: Reticulum bot access key
ServiceToken:
Fn::GetAtt: KeymasterApplication.Outputs.GenerateSecretFunctionArn
Name: reticulum-bot-access-key
Region:
Ref: AWS::Region
StackName:
Ref: AWS::StackName
SendEmailPasswordSecret:
Type: Custom::GenerateSecret
DependsOn: ReticulumBotAccessKeySecret
Properties:
Description: Send email password
ServiceToken:
Fn::GetAtt: KeymasterApplication.Outputs.GenerateSecretFunctionArn
Name: send-email-password
Region:
Ref: AWS::Region
StackName:
Ref: AWS::StackName
SecretString:
Fn::GetAtt: SendEmailPassword.Key
Outputs:
AddressForRootDomain:
Description: Serving domain name for your hub. If you did not set up your domain
on Route 53, you should add a CNAME DNS record pointing to this domain name
to to start serving your hub from your domain.
Value:
Fn::If:
- HasALB
- Fn::GetAtt: AppALB.DNSName
- Fn::GetAtt: AppCloudfrontDistribution.DomainName
DomainName:
Description: Primary hub domain name [reticulum/phx/url_host,reticulum/phx/static_url_host,hubs/general/reticulum_server,spoke/general/reticulum_server,spoke/general/hubs_server,reticulum/turn/realm,coturn/general/realm]
Value:
Ref: DomainName
CorsOrigins:
Description: Allowed CORS origins [reticulum/security/cors_origins]
Value:
Fn::Sub: https://${DomainName},https://${ShortlinkZoneInfo.Name},https://hubs.local:8080,https://localhost:8080
NonCorsProxyDomains:
Description: Non-CORS proxied domains [hubs/general/non_cors_proxy_domains,spoke/general/non_cors_proxy_domains]
Value:
Fn::Sub: ${DomainName},${InternalZoneInfo.Name},${AppAssetsDNS},${LowerStackName.Value}-${AWS::AccountId}-hubs-worker.com
JanusAndReticulumAdminSecret:
Description: Janus and Reticulum Admin Secret [reticulum/phx/admin_access_key!read-keymaster-secret,janus-gateway/general/admin_secret!read-keymaster-secret,reticulum/janus/admin_secret!read-keymaster-secret]
Value: janus-and-reticulum-admin
JanusMediaRtpPortRange:
Description: Janus WebRTC Port Range [janus-gateway/media/rtp_port_range]
Value:
Fn::Join:
- '-'
- - Fn::FindInMap:
- ServicesMeta
- DialogWebRTCFrom
- Port
- Fn::FindInMap:
- ServicesMeta
- DialogWebRTCTo
- Port
JanusNatIceIgnoreList:
Description: Janus ICE Ignore List [janus-gateway/nat/ice_ignore_list]
Value: eth0:0
JanusInternalPort:
Description: Janus Internal Port [janus-gateway/transports.websockets/wss_port]
Value:
Fn::FindInMap:
- ServicesMeta
- DialogInternal
- Port
JanusExternalPort:
Description: Janus External Port [reticulum/janus/janus_port]
Value:
Fn::If:
- HasStreamingServers
- Fn::FindInMap:
- ServicesMeta
- DialogExternal
- StreamPort
- Fn::FindInMap:
- ServicesMeta
- DialogExternal
- AppPort
JanusAdminPort:
Description: Janus Admin Port [janus-gateway/transports.http/admin_port,reticulum/janus/admin_port]
Value:
Fn::FindInMap:
- ServicesMeta
- DialogAdmin
- Port
JanusServiceName:
Description: Janus Service Name [reticulum/janus/service_name]
Value: janus-gateway
RetInternalPort:
Description: Reticulum Internal Port [reticulum/phx/port]
Value:
Fn::FindInMap:
- ServicesMeta
- RetInternal
- Port
RetPhoenixSecretKey:
Description: Reticulum Phoenix Secret Key [reticulum/phx/secret_key!read-keymaster-secret]
Value: phoenix-key
RetExternalPort:
Description: Reticulum URL Port [reticulum/phx/url_port]
Value:
Fn::FindInMap:
- ServicesMeta
- RetExternal
- Port
CorsProxyDNS:
Description: CORS Proxy Domain Name [reticulum/phx/cors_proxy_url_host,hubs/general/cors_proxy_server,spoke/general/cors_proxy_server]
Value:
Ref: CorsProxyDNS
AppDNS:
Description: Load balanced app DNS [reticulum/phx/secondary_url_host]
Value:
Fn::Sub: ${LowerStackName.Value}-app.${InternalZoneInfo.Name}
RetHostnameDNSSuffix:
Description: Reticulum Hostname DNS Suffix [reticulum/run/hostname_dns_suffix]
Value: -local
GuardianSecretKeySecret:
Description: Guardian Secret Key [reticulum/guardian/secret_key!read-keymaster-secret]
Value: guardian-secret-key
ReticulumPermsKey:
Description: Reticulum Perms Key [reticulum/guardian/perms_key!read-s3-file-escaped]
Value:
Fn::Sub: s3://${BoxKeysBucket}/jwt-key.pem
ReticulumVapidKeys:
Description: Reticulum Vapid Keys [reticulum/web_push/public_key!read-s3-file-as-json(publicKey),reticulum/web_push/private_key!read-s3-file-as-json(privateKey)]
Value:
Fn::Sub: s3://${BoxKeysBucket}/vapid.json
ReticulumWebPushSubject:
Description: Reticulum Web Push Subject [reticulum/web_push/subject]
Value:
Fn::Sub: mailto:info@${SESDomain}
ReticulumOAuthTokenSecret:
Description: Reticulum OAuth Token Secret [reticulum/guardian/oauth_token_key!read-keymaster-secret]
Value: reticulum-oauth-token
ReticulumCookieSecret:
Description: Reticulum Cookie Secret [reticulum/erlang/node_cookie!read-keymaster-secret]
Value: reticulum-cookie
ReticulumBotAccessKeySecret:
Description: Reticulum Bot Access Key Secret [reticulum/ret/bot_access_key!read-keymaster-secret]
Value: reticulum-bot-access-key
AppDbSecret:
Description: App DB Secret [ita/db/password!read-aws-secret]
Value:
Ref: AppDbSecret
AppPostgrestDbSecret:
Description: App PostgREST DB Secret [reticulum/db/postgrest_password!read-keymaster-secret]
Value: postgrest-db-secret
AppDbDatabase:
Description: App DB Database [reticulum/db/database,ita/db/database]
Value: polycosm_production
AppDbHostname:
Description: App DB Hostname [ita/db/hostname]
Value:
Fn::GetAtt: AppDb.Endpoint.Address
PostgrestDbURI:
Description: Postgrest DB URI [postgrest/db/uri!inject-keymaster-secret]
Value:
Fn::Sub: postgres://postgrest_authenticator:{postgrest-db-secret}@${AppDb.Endpoint.Address}/polycosm_production
PgbouncerDb:
Description: Pgbouncer DB Info [pgbouncer/pgbouncer/db!inject-aws-secret]
Value:
Fn::Sub: |-
polycosm_production = host=${AppDb.Endpoint.Address} dbname=polycosm_production user=postgres password={${AppDbSecret}} pool_mode=transaction
polycosm_locking = host=${AppDb.Endpoint.Address} dbname=polycosm_production user=postgres password={${AppDbSecret}} pool_mode=session
AssetsBucket:
Description: Assets bucket name [hubs/deploy/target,spoke/deploy/target,polycosm-static-assets/deploy/assets_target]
Value:
Ref: AssetsBucket
LinkRedirectorBucket:
Description: Link redirector bucket name [polycosm-static-assets/deploy/redirector_target]
Value:
Ref: LinkRedirectorBucket
HubsBaseAssetsPath:
Description: Base assets path for hubs CDN assets [hubs/general/base_assets_path]
Value:
Fn::Sub: https://${AppAssetsDNS}/hubs/
HubsPageOrigin:
Description: Hubs page origin [reticulum/pages/hubs_page_origin]
Value:
Fn::Sub: https://${AssetsBucket.RegionalDomainName}/hubs/pages/latest
SpokeBaseAssetsPath:
Description: Base assets path for spoke CDN assets [spoke/general/base_assets_path]
Value:
Fn::Sub: https://${AppAssetsDNS}/spoke/
SpokePageOrigin:
Description: Spoke page origin [reticulum/pages/spoke_page_origin]
Value:
Fn::Sub: https://${AssetsBucket.RegionalDomainName}/spoke/pages/latest
AssetsDomain:
Description: Assets domain for worker
Value:
Fn::GetAtt: AssetsBucket.RegionalDomainName
ClientDeployType:
Description: Client deploy type [hubs/deploy/type,spoke/deploy/type,polycosm-static-assets/deploy/type]
Value: s3
NearsparkHost:
Description: Host of the nearspark service endpoint [hubs/general/thumbnail_server,spoke/general/thumbnail_server,reticulum/phx/thumbnail_url_host]
Value:
Fn::Sub: ${NearsparkDNS}
SpeelycaptorEndpoint:
Description: Speelycaptor Service URL [reticulum/speelycaptor/speelycaptor_endpoint]
Value:
Fn::Sub: https://${SpeelycaptorApiGatewayRestApi}.execute-api.${AWS::Region}.${AWS::URLSuffix}/prod
PhotomnemonicEndpoint:
Description: Photomnemonic Service URL [reticulum/resolver/photomnemonic_endpoint]
Value:
Fn::Sub: https://${PhotomnemonicApiGatewayRestApi}.execute-api.${AWS::Region}.${AWS::URLSuffix}/prod
StorageEFSId:
Description: Storage EFS volume id
Value:
Ref: StorageEFS
AppAssetsDNS:
Description: Storage URL Host [reticulum/uploads/host]
Value:
Fn::Sub: https://${AppAssetsDNS}
AppAssetsHost:
Description: Storage Host [hubs/general/uploads_host]
Value:
Fn::Sub: ${AppAssetsDNS}
SmtpSendAccessKey:
Description: SMTP send access key for email zone [reticulum/email/username]
Value:
Ref: SendEmailAccessKey
SmtpSendPassword:
Description: SMTP send access key for email zone [reticulum/email/password!read-keymaster-secret]
Value: send-email-password
SmtpSendFromAddress:
Description: SMTP send from address [reticulum/email/from]
Value:
Fn::If:
- HasEmailSubdomain
- Fn::Sub: noreply@${EmailSubdomain}.${SESDomain}
- Fn::Sub: noreply@${SESDomain}
StmpServer:
Description: SMTP server [reticulum/email/server]
Value: email-smtp.us-east-1.amazonaws.com
SESRegion:
Description: SES region
Value: us-east-1
InternalZoneId:
Description: Internal Zone Id
Value:
Fn::GetAtt: InternalZoneInfo.Id
InternalZoneDomainName:
Description: Internal Zone Domain Name
Value:
Fn::GetAtt: InternalZoneInfo.Name
BoxKeysBucketName:
Description: Box Keys Bucket Name
Value:
Ref: BoxKeysBucket
BucketRegion:
Description: Bucket Region [hubs/deploy/region,spoke/deploy/region,polycosm-static-assets/deploy/assets_region,polycosm-static-assets/deploy/redirector_region]
Value:
Ref: AWS::Region
ShortlinkDomainName:
Description: Shortlink Domain Name [hubs/general/shortlink_domain,reticulum/phx/link_url_host]
Value:
Fn::GetAtt: ShortlinkZoneInfo.Name
AdminEmailAddress:
Description: Administrator Email Address [reticulum/accounts/admin_email]
Value:
Ref: AdminEmailAddress
LetsEncryptEmailAddress:
Description: Let's Encrypt Email Address [certbot/general/admin_email]
Value:
Fn::If:
- HasLetsEncryptEmail
- Ref: LetsEncryptEmailAddress
- ''
CertBotPlugin:
Description: Certbot plugin [certbot/general/plugin]
Value: dns-route53
StackName:
Description: Stack name [reticulum/ret/pool]
Value:
Ref: AWS::StackName
MaxStorage:
Description: Max storage in GB [reticulum/uploads/quota_gb]
Value:
Ref: MaxStorage
StackTopic:
Description: Stack SNS Topic
Value:
Ref: StackTopic
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment