├── quickstart ├── images │ ├── review-top.png │ ├── review-bottom.png │ ├── select-template.png │ ├── specify-details.png │ ├── create-in-progress.png │ └── create-complete-output.png ├── README.md └── master.yaml ├── single-ec2 ├── images │ ├── container-logs.png │ └── container-log-details.png ├── README.md └── master.yaml ├── single-ec2-single-rds ├── images │ ├── specify-details.png │ └── create-complete-output.png ├── README.md └── master.yaml ├── .gitmodules ├── .circleci └── config.yml ├── README.md ├── docker-images └── docker-compose.yml └── LICENSE /quickstart/images/review-top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starformlabs/stellar-nebulaforge-aws/HEAD/quickstart/images/review-top.png -------------------------------------------------------------------------------- /quickstart/images/review-bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starformlabs/stellar-nebulaforge-aws/HEAD/quickstart/images/review-bottom.png -------------------------------------------------------------------------------- /quickstart/images/select-template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starformlabs/stellar-nebulaforge-aws/HEAD/quickstart/images/select-template.png -------------------------------------------------------------------------------- /quickstart/images/specify-details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starformlabs/stellar-nebulaforge-aws/HEAD/quickstart/images/specify-details.png -------------------------------------------------------------------------------- /single-ec2/images/container-logs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starformlabs/stellar-nebulaforge-aws/HEAD/single-ec2/images/container-logs.png -------------------------------------------------------------------------------- /quickstart/images/create-in-progress.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starformlabs/stellar-nebulaforge-aws/HEAD/quickstart/images/create-in-progress.png -------------------------------------------------------------------------------- /single-ec2/images/container-log-details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starformlabs/stellar-nebulaforge-aws/HEAD/single-ec2/images/container-log-details.png -------------------------------------------------------------------------------- /quickstart/images/create-complete-output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starformlabs/stellar-nebulaforge-aws/HEAD/quickstart/images/create-complete-output.png -------------------------------------------------------------------------------- /single-ec2-single-rds/images/specify-details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starformlabs/stellar-nebulaforge-aws/HEAD/single-ec2-single-rds/images/specify-details.png -------------------------------------------------------------------------------- /single-ec2-single-rds/images/create-complete-output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starformlabs/stellar-nebulaforge-aws/HEAD/single-ec2-single-rds/images/create-complete-output.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "docker-images/stellar-core"] 2 | path = docker-images/stellar-core 3 | url = https://github.com/starformlabs/docker-stellar-core.git 4 | [submodule "docker-images/stellar-horizon"] 5 | path = docker-images/stellar-horizon 6 | url = https://github.com/starformlabs/docker-stellar-horizon.git 7 | [submodule "docker-images/stellar-postgres"] 8 | path = docker-images/stellar-postgres 9 | url = https://github.com/starformlabs/docker-stellar-postgres.git 10 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: circleci/python:3.6.5-stretch-node 6 | 7 | steps: 8 | - checkout 9 | 10 | # aws cli is installed to ~/.local and cached under a static key which currently matches the version 11 | # If a newer version is needed, changing the key name will force a cache miss and a clean install 12 | - restore_cache: 13 | keys: 14 | - awscli-1.15.4 15 | 16 | - run: 17 | name: Install awscli 18 | command: | 19 | if [ ! -f ~/.local/bin/aws ]; then 20 | pip install awscli --upgrade --user 21 | fi 22 | 23 | - save_cache: 24 | key: awscli-1.15.4 25 | paths: 26 | - "~/.local" 27 | 28 | - deploy: 29 | name: Copy templates to s3 30 | command: | 31 | ~/.local/bin/aws s3 sync . s3://public.starformlabs.io/nebulaforge/aws/ \ 32 | --exclude "*" --include "*.yaml" \ 33 | --content-type "text/plain" \ 34 | --delete -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NebulaForge: Launch Stellar infrastructure on AWS 2 | 3 | NebulaForge enables developers looking to build a project or business on [Stellar](https://www.stellar.org/) to quickly 4 | deploy the necessary infrastructure in the cloud. 5 | 6 | We plan to target a variety of use cases, from new developers looking to get started with an single-server node, 7 | all the way up to anchors looking to deploy highly available production infrastructure. These "infrastructure-as-code" 8 | reference architectures will be built using AWS CloudFormation templates. 9 | 10 | This allows users to initiate a "**one-click**" deployment on their own AWS account by simply clicking on a link and 11 | filling out a few parameters on a web interface. 12 | 13 | ![specify template parameters](quickstart/images/specify-details.png) 14 |
15 |
16 | 17 | ## Templates 18 | - [quickstart](quickstart) - Deploys the well-known all-in-one [stellar/quickstart](https://github.com/stellar/docker-stellar-core-horizon) docker image running in one container on a single EC2 instance. 19 | - [single-ec2](single-ec2) - Deploys individual cloud-optimized docker images for [core, horizon and postgres](docker-images) running in separate containers on the same EC2 instance. More inline with best practices for container deployment. 20 | - [single-ec2-single-rds](single-ec2-single-rds) - Similar to single-ec2, but [RDS](https://aws.amazon.com/rds/postgresql/) is used to manage the database and an [EFS](https://aws.amazon.com/efs/) backed volume is used to store [local state](https://www.stellar.org/developers/stellar-core/software/admin.html#database-and-local-state). 21 | 22 | ## Roadmap 23 | - Retain data after stack deletion with the ability to resume. 24 | - RDS Aurora support. 25 | - More complex public/private VPC setup, multi-AZ failover. 26 | - Multiple horizon instances with load balancing. 27 | - Multiple participation profiles - watcher, archiver, basic validator, full validator. 28 | - Test harness for validating the infrastructure. 29 | - Additional software - Federation server, Bridge server, Compliance server. 30 | - Logging, Monitoring, Alerting and other best practices. 31 | - Option to use EKS (Kubernetes) instead of ECS when it becomes available. -------------------------------------------------------------------------------- /docker-images/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | # This compose file is used for local testing only, it is not actually used on AWS 4 | # Data is not persisted 5 | services: 6 | db: 7 | image: starformlabs/stellar-postgres:9.6 8 | environment: 9 | # used by postgres 10 | POSTGRES_USER: 'postgres' # Postgres superuser 11 | POSTGRES_PASSWORD: 'pgsecretpassword' # Sets the pw for the Postgres superuser 12 | 13 | # used by init.sh 14 | STELLAR_DB_USER: 'stellar' # stellar user will be created in init.sh 15 | STELLAR_DB_PASSWORD: 'stellarsecretpassword' # pw for the stellar user is set in init.sh 16 | 17 | core: 18 | image: starformlabs/stellar-core:9.2.0-551-7561c1d5 19 | environment: 20 | # used by entry.sh 21 | DB_USER: 'stellar' 22 | DB_PASS: 'stellarsecretpassword' 23 | DB_NAME: 'core' 24 | DB_HOST: 'db' 25 | DB_PORT: '5432' 26 | DATA_DIR: '/data' 27 | 28 | # used by stellar-core 29 | NETWORK_PASSPHRASE: 'Test SDF Network ; September 2015' 30 | KNOWN_PEERS: 'core-testnet1.stellar.org,core-testnet2.stellar.org,core-testnet3.stellar.org' 31 | DATABASE: 'postgresql://dbname=core host=db port=5432 user=stellar password=stellarsecretpassword' 32 | BUCKET_DIR_PATH: '/data/core/buckets' 33 | UNSAFE_QUORUM: 'true' 34 | FAILURE_SAFETY: '1' 35 | CATCHUP_RECENT: '8640' 36 | LOG_FILE_PATH: '' # Sends logs to stdout 37 | COMMANDS: 'll?level=error&partition=Overlay' # Reduce chatty logging about connections 38 | QUORUM_SET: | 39 | [{ 40 | "threshold_percent": 51, 41 | "validators": ["GDKXE2OZMJIPOSLNA6N6F2BVCI3O777I2OOC4BV7VOYUEHYX7RTRYA7Y sdf1", 42 | "GCUCJTIYXSOXKBSNFGNFWW5MUQ54HKRPGJUTQFJ5RQXZXNOLNXYDHRAP sdf2", 43 | "GC2V2EFSXN6SQTWVYA5EPJPBWWIMSD2XQNKUOHGEKB535AQE2I6IXV2Z sdf3"] 44 | }] 45 | HISTORY: | 46 | { 47 | "h1": { "get": "curl -sf https://s3-eu-west-1.amazonaws.com/history.stellar.org/prd/core-testnet/core_testnet_001/{0} -o {1}" }, 48 | "h2": { "get": "curl -sf https://s3-eu-west-1.amazonaws.com/history.stellar.org/prd/core-testnet/core_testnet_002/{0} -o {1}" }, 49 | "h3": { "get": "curl -sf https://s3-eu-west-1.amazonaws.com/history.stellar.org/prd/core-testnet/core_testnet_003/{0} -o {1}" } 50 | } 51 | 52 | horizon: 53 | image: starformlabs/stellar-horizon:0.12.3 54 | ports: 55 | - '8000:8000' 56 | environment: 57 | # used by entry.sh 58 | DB_USER: 'stellar' 59 | DB_PASS: 'stellarsecretpassword' 60 | DB_NAME: 'horizon' 61 | DB_HOST: 'db' 62 | DB_PORT: '5432' 63 | DATA_DIR: '/data' 64 | 65 | # used by horizon 66 | PORT: '8000' 67 | DATABASE_URL: 'postgres://stellar:stellarsecretpassword@db:5432/horizon?sslmode=disable' 68 | STELLAR_CORE_DATABASE_URL: 'postgres://stellar:stellarsecretpassword@db:5432/core?sslmode=disable' 69 | STELLAR_CORE_URL: 'http://core:11626' 70 | LOG_LEVEL: 'info' 71 | INGEST: 'true' 72 | PER_HOUR_RATE_LIMIT: '72000' 73 | NETWORK_PASSPHRASE: 'Test SDF Network ; September 2015' -------------------------------------------------------------------------------- /quickstart/README.md: -------------------------------------------------------------------------------- 1 | # QUICKSTART 2 | 3 | This [CloudFormation](https://aws.amazon.com/cloudformation/) template deploys the [stellar/quickstart](https://github.com/stellar/docker-stellar-core-horizon) 4 | docker image running in a non-validating, ephemeral configuration connected to the test network. The deployment uses 5 | a single VPC subnet in a single availability zone. 6 | 7 | Stellar-core, Horizon and PostgreSQL are all running in the same container on the same EC2 instance. [AWS ECS](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/Welcome.html) 8 | is used to manage the container, and EC2 Autoscaling will replace the instance if it crashes or is terminated, but 9 | there is never more than one instance running at a time. 10 | 11 | 12 | ## Prerequisites 13 | Aside from having an AWS account, the only prerequisite for deploying this template is that you have an EC2 key pair. 14 | The key pair allows you to SSH into your instance. If you don't already have a key pair you can [create one via the console](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-key-pairs.html#having-ec2-create-your-key-pair) 15 | or upload an existing one. 16 | 17 | 18 | ## Launch 19 | Click the links below to launch this stack in the AWS region of your choice. If your desired region is 20 | not listed, just copy one of the URLs and edit the region accordingly. 21 | 22 | | AWS Region | Short name | | 23 | | -- | -- | -- | 24 | | US East (N. Virginia) | us-east-1 | [Launch Stack :rocket:](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/new?stackName=stellar-quickstart&templateURL=https://s3.amazonaws.com/public.starformlabs.io/nebulaforge/aws/quickstart/master.yaml) 25 | | US East (Ohio) | us-east-2 | [Launch Stack :rocket:](https://console.aws.amazon.com/cloudformation/home?region=us-east-2#/stacks/new?stackName=stellar-quickstart&templateURL=https://s3.amazonaws.com/public.starformlabs.io/nebulaforge/aws/quickstart/master.yaml) 26 | | US West (N. California) | us-west-1 | [Launch Stack :rocket:](https://console.aws.amazon.com/cloudformation/home?region=us-west-1#/stacks/new?stackName=stellar-quickstart&templateURL=https://s3.amazonaws.com/public.starformlabs.io/nebulaforge/aws/quickstart/master.yaml) 27 | | US West (Oregon) | us-west-2 | [Launch Stack :rocket:](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?stackName=stellar-quickstart&templateURL=https://s3.amazonaws.com/public.starformlabs.io/nebulaforge/aws/quickstart/master.yaml) 28 | | EU (Ireland) | eu-west-1 | [Launch Stack :rocket:](https://console.aws.amazon.com/cloudformation/home?region=eu-west-1#/stacks/new?stackName=stellar-quickstart&templateURL=https://s3.amazonaws.com/public.starformlabs.io/nebulaforge/aws/quickstart/master.yaml) 29 | | EU (London) | eu-west-2 | [Launch Stack :rocket:](https://console.aws.amazon.com/cloudformation/home?region=eu-west-2#/stacks/new?stackName=stellar-quickstart&templateURL=https://s3.amazonaws.com/public.starformlabs.io/nebulaforge/aws/quickstart/master.yaml) 30 | | EU (Frankfurt) | eu-central-1 | [Launch Stack :rocket:](https://console.aws.amazon.com/cloudformation/home?region=eu-central-1#/stacks/new?stackName=stellar-quickstart&templateURL=https://s3.amazonaws.com/public.starformlabs.io/nebulaforge/aws/quickstart/master.yaml) 31 | | Asia Pacific (Tokyo) | ap-northeast-1 | [Launch Stack :rocket:](https://console.aws.amazon.com/cloudformation/home?region=ap-northeast-1#/stacks/new?stackName=stellar-quickstart&templateURL=https://s3.amazonaws.com/public.starformlabs.io/nebulaforge/aws/quickstart/master.yaml) 32 | | Asia Pacific (Mumbai) | ap-south-1 | [Launch Stack :rocket:](https://console.aws.amazon.com/cloudformation/home?region=ap-south-1#/stacks/new?stackName=stellar-quickstart&templateURL=https://s3.amazonaws.com/public.starformlabs.io/nebulaforge/aws/quickstart/master.yaml) 33 | | Asia Pacific (Sydney) | ap-southeast-2 | [Launch Stack :rocket:](https://console.aws.amazon.com/cloudformation/home?region=ap-southeast-2#/stacks/new?stackName=stellar-quickstart&templateURL=https://s3.amazonaws.com/public.starformlabs.io/nebulaforge/aws/quickstart/master.yaml) 34 | 35 | 36 | ## Cost 37 | The template creates a number of resources but the majority of them do not attract charges. You *will* be billed for 38 | the following resources: 39 | - [A single EC2 instance](https://aws.amazon.com/ec2/pricing/on-demand/) 40 | - [30GB of EBS gp2 storage](https://aws.amazon.com/ebs/pricing/) 41 | - [Data Transfer](https://aws.amazon.com/ec2/pricing/on-demand/#Data_Transfer) 42 | 43 | ### Pay as you go 44 | AWS Instances and EBS volumes are now billed per second (1 minute minimum) so you can run your instance for as 45 | long or little as you like. **Estimated** monthly and daily prices for entry level instances are below. 46 | 47 | #### t2.nano (cheapest) 48 | - EC2 Instance - $4.25 49 | - EBS Storage - $3.00 50 | - Data Transfer - $0.50 51 | - Total: **$7.75**/month | **$0.26**/day 52 | 53 | #### t2.micro (default) 54 | - EC2 Instance - $8.50 55 | - EBS Storage - $3.00 56 | - Data Transfer - $0.50 57 | - Total: **$12**/month | **$0.40**/day 58 | 59 | ### Free Tier 60 | New AWS accounts are usually eligible for a [12 month free tier benefit](https://aws.amazon.com/free/?awsf.default=categories%2312monthsfree). 61 | If you are still eligible for the free tier and use the t2.micro instance type, your only cost will be bandwidth. Note 62 | that **t2.nano** instances are **NOT eligible for the free tier**. 63 | 64 | #### t2.micro with 12 month Free Tier 65 | - EC2 Instance - $0.00 66 | - EBS Storage - $0.00 67 | - Data Transfer - $0.50 68 | - Total: **$0.50**/month | **$0.02**/day 69 | 70 | #### Data Transfer 71 | Data Transfer pricing on AWS can be complex and usage patterns will vary from user to user. **$0.50 per month is a baseline 72 | estimate** for SCP traffic and basic interaction with horizon. If you plan to do high volume testing or make your 73 | horizon instance public be sure to keep an eye on the Data Transfer section of your bill. 74 | 75 | #### CloudWatch 76 | CloudWatch logging can also potentially attract charges, but it has a *permanent* free tier of 5GB ingested before 77 | charges apply. A full month of logs including consensus details on the test network is estimated to be less 78 | than 0.5GB of data. 79 | 80 | #### Disclaimer 81 | While we attempt to provide useful and up to date information, you are responsible for your own AWS 82 | account and the resources that you are charged for. Always be vigilant about doubling checking to ensure that the 83 | resources used are what your expect. 84 |
85 |
86 | 87 | ## Template 88 | The template URL is a part of the launch link, so will be auto-selected by default. You don't need to change anything 89 | on this screen. [Click here to view the template](https://s3.amazonaws.com/public.starformlabs.io/nebulaforge/aws/quickstart/master.yaml) 90 | directly, it never hurts to double check what you are deploying to your account! 91 | 92 | ![template selection screen](images/select-template.png) 93 |
94 | 95 | ## Set Parameters 96 | Most of the default parameters can be left as is, however you must specify: 97 | - An SSH Key Pair to be associated with the instance (choose from the dropdown). 98 | - An IP address range that is allowed to SSH into the instance in [CIDR](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) 99 | format. 100 | - (Optional) An IP address range that is allowed to access the Horizon API via HTTP. Leave blank to block external access. 101 | 102 | **NOTE: To restrict access to your current IP [Find your IP](https://www.google.com/search?q=ip) and then add /32 to the end.** 103 | 104 | ![specify template parameters](images/specify-details.png) 105 |
106 |
107 | 108 | ## Options and Review 109 | You can **skip the options screen** entirely. On the review screen you can double check the parameters that you set. 110 | Be sure to **check the acknowledgement checkbox** at the end. It is to confirm that you know that the template is 111 | creating and IAM Role. Click the "Learn more" link in the warning box if you don't understand what that means. 112 | 113 | ![select the IAM resources checkbox](images/review-bottom.png) 114 |
115 |
116 | 117 | ## In Progress 118 | 119 | It will take about 5 minutes for everything to be deloyed. You will see the resources being created in the events tab 120 | 121 | ![create in progress](images/create-in-progress.png) 122 |
123 |
124 | 125 | ## Create Complete 126 | 127 | Once the deployment is done the status will be CREATE_COMPLETE. Switch to the **Outputs** tab to see the relevant URLs 128 | - The SSH URL shows the IP of the server and the username. Authentication is done using the key pair specified earlier. 129 | - The Horizon URL is a direct link to the Horizon API. 130 | - The ContainersLogGroup URL where you see the basic supervisord startup logs for core, horizon and postgres. 131 | - The DatabaseUsername value and DatabasePassword link to the Parameter Store 132 | 133 | ![output URLs](images/create-complete-output.png) 134 |
135 |
136 | 137 | ## Finding the PostgreSQL password 138 | 139 | If you need database access, the PostgreSQL password is auto-generated by the template and stored securely in the 140 | SSM Parameter Store. Click on the relevant link in the output tab to be taken to that page then click the link to 141 | **Show** the value. 142 | 143 | 144 | ## Final Notes 145 | 146 | 1. Horizon is only accessible over HTTP, not HTTPS. 147 | 148 | 1. The PostgreSQL port is not exposed to the internet. You must SSH into the server (or tunnel over SSH) to access the database. 149 | 150 | 1. Please remember to report issues specific to the [docker image](https://github.com/stellar/docker-stellar-core-horizon/issues), 151 | [stellar-core](https://github.com/stellar/stellar-core/issues), [horizon](https://github.com/stellar/go/issues), etc 152 | to the appropriate repo and only report issues related to the CloudFormation template [here](https://github.com/starformlabs/stellar-nebulaforge-aws/issues). 153 | -------------------------------------------------------------------------------- /single-ec2-single-rds/README.md: -------------------------------------------------------------------------------- 1 | # SINGLE-EC2-SINGLE-RDS 2 | 3 | This [CloudFormation](https://aws.amazon.com/cloudformation/) template deploys [docker images for core and horizon](https://github.com/starformlabs/stellar-nebulaforge-aws/tree/master/docker-images) 4 | running in a non-validating configuration connected to the test network. The deployment uses a VPC with one public 5 | subnet and two private subnets. 6 | 7 | Core and Horizon are running in separate containers on the same EC2 instance. [AWS ECS](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/Welcome.html) 8 | is used to manage the container, and EC2 Autoscaling will replace the instance if it crashes or is terminated, but 9 | there is never more than one instance running at a time. **[RDS](https://aws.amazon.com/rds/postgresql/)** is being used to manage the database 10 | and an **[EFS](https://aws.amazon.com/efs/)** backed volume is being used to store [local state](https://www.stellar.org/developers/stellar-core/software/admin.html#database-and-local-state). 11 | 12 | 13 | ## Prerequisites 14 | Aside from having an AWS account, the only prerequisite for deploying this template is that you have an EC2 key pair. 15 | The key pair allows you to SSH into your instance. If you don't already have a key pair you can [create one via the console](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-key-pairs.html#having-ec2-create-your-key-pair) 16 | or upload an existing one. 17 | 18 | 19 | ## Launch 20 | Click the links below to launch this stack in the AWS region of your choice. If your desired region is 21 | not listed, just copy one of the URLs and edit the region accordingly. 22 | 23 | | AWS Region | Short name | | 24 | | -- | -- | -- | 25 | | US East (N. Virginia) | us-east-1 | [Launch Stack :rocket:](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/new?stackName=stellar-single-ec2-single-rds&templateURL=https://s3.amazonaws.com/public.starformlabs.io/nebulaforge/aws/single-ec2-single-rds/master.yaml) 26 | | US East (Ohio) | us-east-2 | [Launch Stack :rocket:](https://console.aws.amazon.com/cloudformation/home?region=us-east-2#/stacks/new?stackName=stellar-single-ec2-single-rds&templateURL=https://s3.amazonaws.com/public.starformlabs.io/nebulaforge/aws/single-ec2-single-rds/master.yaml) 27 | | US West (N. California) | us-west-1 | [Launch Stack :rocket:](https://console.aws.amazon.com/cloudformation/home?region=us-west-1#/stacks/new?stackName=stellar-single-ec2-single-rds&templateURL=https://s3.amazonaws.com/public.starformlabs.io/nebulaforge/aws/single-ec2-single-rds/master.yaml) 28 | | US West (Oregon) | us-west-2 | [Launch Stack :rocket:](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?stackName=stellar-single-ec2-single-rds&templateURL=https://s3.amazonaws.com/public.starformlabs.io/nebulaforge/aws/single-ec2-single-rds/master.yaml) 29 | | EU (Ireland) | eu-west-1 | [Launch Stack :rocket:](https://console.aws.amazon.com/cloudformation/home?region=eu-west-1#/stacks/new?stackName=stellar-single-ec2-single-rds&templateURL=https://s3.amazonaws.com/public.starformlabs.io/nebulaforge/aws/single-ec2-single-rds/master.yaml) 30 | | EU (Frankfurt) | eu-central-1 | [Launch Stack :rocket:](https://console.aws.amazon.com/cloudformation/home?region=eu-central-1#/stacks/new?stackName=stellar-single-ec2-single-rds&templateURL=https://s3.amazonaws.com/public.starformlabs.io/nebulaforge/aws/single-ec2-single-rds/master.yaml) 31 | | Asia Pacific (Sydney) | ap-southeast-2 | [Launch Stack :rocket:](https://console.aws.amazon.com/cloudformation/home?region=ap-southeast-2#/stacks/new?stackName=stellar-single-ec2-single-rds&templateURL=https://s3.amazonaws.com/public.starformlabs.io/nebulaforge/aws/single-ec2-single-rds/master.yaml) 32 | 33 | 34 | ## Cost 35 | The template creates a number of resources but the majority of them do not attract charges. You *will* be billed for 36 | the following resources: 37 | - [A single EC2 instance](https://aws.amazon.com/ec2/pricing/on-demand/) 38 | - [30GB of EBS gp2 storage](https://aws.amazon.com/ebs/pricing/) 39 | - [A single RDS instance w/ 20GB of storage](https://aws.amazon.com/rds/postgresql/pricing/) 40 | - [1GB of EFS data usage](https://aws.amazon.com/efs/pricing/) 41 | - [Data Transfer](https://aws.amazon.com/ec2/pricing/on-demand/#Data_Transfer) 42 | 43 | ### Pay as you go 44 | AWS Instances and EBS volumes are now billed per second (1 minute minimum) so you can run your instance for as 45 | long or little as you like. **Estimated** monthly and daily prices for entry level instances are below. 46 | 47 | #### t2.nano/db.t2.micro (cheapest) 48 | - EC2 Instance - $4.25 49 | - EBS Storage - $3.00 50 | - RDS Instance - $13.20 51 | - RDS Storage - $2.30 52 | - EFS - $0.30 53 | - Data Transfer - $0.50 54 | - Total: **$23.55**/month | **$0.79**/day 55 | 56 | #### t2.micro/db.t2.micro (default) 57 | - EC2 Instance - $8.50 58 | - EBS Storage - $3.00 59 | - RDS Instance - $13.20 60 | - RDS Storage - $2.30 61 | - EFS - $0.30 62 | - Data Transfer - $0.50 63 | - Total: **$27.80**/month | **$0.93**/day 64 | 65 | ### Free Tier 66 | New AWS accounts are usually eligible for a [12 month free tier benefit](https://aws.amazon.com/free/?awsf.default=categories%2312monthsfree). 67 | If you are still eligible for the free tier and use the t2.micro instance type, your only cost will be bandwidth. Note 68 | that **t2.nano** instances are **NOT eligible for the free tier**. 69 | 70 | ##### t2.micro/db.t2.micro with 12 month Free Tier 71 | - EC2 Instance - $0.00 72 | - EBS Storage - $0.00 73 | - RDS Instance - $0.00 74 | - RDS Storage - $0.00 75 | - EFS - $0.00 76 | - Data Transfer - $0.50 77 | - Total: **$0.50**/month | **$0.02**/day 78 | 79 | #### Data Transfer 80 | Data Transfer pricing on AWS can be complex and usage patterns will vary from user to user. **$0.50 per month is a baseline 81 | estimate** for SCP traffic and basic interaction with horizon. If you plan to do high volume testing or make your 82 | horizon instance public be sure to keep an eye on the Data Transfer section of your bill. 83 | 84 | #### CloudWatch 85 | CloudWatch logging can also potentially attract charges, but it has a *permanent* free tier of 5GB ingested before 86 | charges apply. A full month of logs including consensus details on the test network is estimated to be less 87 | than 0.5GB of data. 88 | 89 | #### Disclaimer 90 | While we attempt to provide useful and up to date information, you are responsible for your own AWS 91 | account and the resources that you are charged for. Always be vigilant about doubling checking to ensure that the 92 | resources used are what your expect. 93 |
94 |
95 | 96 | ## Template 97 | The template URL is a part of the launch link, so will be auto-selected by default. You don't need to change anything 98 | on this screen. [Click here to view the template](https://s3.amazonaws.com/public.starformlabs.io/nebulaforge/aws/single-ec2-single-rds/master.yaml) 99 | directly, it never hurts to double check what you are deploying to your account! 100 | 101 | ![template selection screen](../quickstart/images/select-template.png) 102 |
103 | 104 | ## Set Parameters 105 | Most of the default parameters can be left as is, however you must specify: 106 | - An SSH Key Pair to be associated with the instance (choose from the dropdown). 107 | - An IP address range that is allowed to SSH into the instance in [CIDR](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) 108 | format. 109 | - (Optional) An IP address range that is allowed to access the Horizon API via HTTP. Leave blank to block external access. 110 | 111 | **NOTE: To restrict access to your current IP [Find your IP](https://www.google.com/search?q=ip) and then add /32 to the end.** 112 | 113 | ![specify template parameters](images/specify-details.png) 114 |
115 |
116 | 117 | ## Options and Review 118 | You can **skip the options screen** entirely. On the review screen you can double check the parameters that you set. 119 | Be sure to **check the acknowledgement checkbox** at the end. It is to confirm that you know that the template is 120 | creating and IAM Role. Click the "Learn more" link in the warning box if you don't understand what that means. 121 | 122 | ![select the IAM resources checkbox](../quickstart/images/review-bottom.png) 123 |
124 |
125 | 126 | ## In Progress 127 | 128 | It will take about 15 minutes for everything to be deloyed. You will see the resources being created in the events tab 129 | 130 | ![create in progress](../quickstart/images/create-in-progress.png) 131 |
132 |
133 | 134 | ## Create Complete 135 | 136 | Once the deployment is done the status will be CREATE_COMPLETE. Switch to the **Outputs** tab to see the relevant URLs 137 | - The SSH URL shows the IP of the server and the username. Authentication is done using the key pair specified earlier. 138 | - The Horizon URL is a direct link to the Horizon API. 139 | - The ContainersLogGroup link will take you to a CloudWatch page with separate log streams for core, horizon and postgres. 140 | - The ECS link will show you information about the state of the cluster. 141 | - The DbAppUser value and DbSuperUserPassword link to the Parameter Store (Theses are the credentials used by stellar-core and horizon) 142 | - The DbSuperUser value and DbSuperUserPassword link to the Parameter Store. 143 | 144 | ![output URLs](images/create-complete-output.png) 145 |
146 |
147 | 148 | ## Finding the PostgreSQL passwords 149 | 150 | If you need database access, the PostgreSQL passwords are auto-generated by the template and stored securely in the 151 | SSM Parameter Store. Click on the relevant link in the output tab to be taken to that page then click the link to 152 | **Show** the value. 153 | 154 | ## Viewing log output 155 | 156 | This template allows you to view the detailed logs generated by Stellar Core and Horizon without SSHing into your instance. 157 | If you click on the **ContainersLogGroup** link in the outputs tab it will take to a page that links to the log streams 158 | for core, horizon and the database. 159 | 160 | ![containers log group](../single-ec2/images/container-logs.png) 161 |
162 |
163 | 164 | Clicking on those links will show you more detailed logs for each system including consensus logging for core. 165 | 166 | ![stellar core log details](../single-ec2/images/container-log-details.png) 167 |
168 |
169 | 170 | ## Final Notes 171 | 172 | 1. Horizon is only accessible over HTTP, not HTTPS. 173 | 174 | 1. The PostgreSQL port is not exposed to the internet. You must SSH into the server (or tunnel over SSH) to access the database. 175 | 176 | 1. Please remember to report issues specific to the [docker image](https://github.com/stellar/docker-stellar-core-horizon/issues), 177 | [stellar-core](https://github.com/stellar/stellar-core/issues), [horizon](https://github.com/stellar/go/issues), etc 178 | to the appropriate repo and only report issues related to the CloudFormation template [here](https://github.com/starformlabs/stellar-nebulaforge-aws/issues). 179 | -------------------------------------------------------------------------------- /single-ec2/README.md: -------------------------------------------------------------------------------- 1 | # SINGLE-EC2 2 | 3 | This [CloudFormation](https://aws.amazon.com/cloudformation/) template deploys a [trio of docker images](https://github.com/starformlabs/stellar-nebulaforge-aws/tree/master/docker-images) 4 | running in a non-validating, ephemeral configuration connected to the test network. The deployment uses 5 | a single VPC subnet in a single availability zone. 6 | 7 | Core, Horizon and PostgreSQL are all running in separate containers on the same EC2 instance. [AWS ECS](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/Welcome.html) 8 | is used to manage the container, and EC2 Autoscaling will replace the instance if it crashes or is terminated, but 9 | there is never more than one instance running at a time. 10 | 11 | The main difference between this template and [quickstart](quickstart) is that we follow the best practice of splitting 12 | Core, Horizon and PostgreSQL into **separate containers**, and that logs are automatically sent to AWS CloudWatch 13 | allowing user to [review detailed logs directly from a web browser](#viewing-log-output) . 14 | 15 | 16 | ## Prerequisites 17 | Aside from having an AWS account, the only prerequisite for deploying this template is that you have an EC2 key pair. 18 | The key pair allows you to SSH into your instance. If you don't already have a key pair you can [create one via the console](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-key-pairs.html#having-ec2-create-your-key-pair) 19 | or upload an existing one. 20 | 21 | 22 | ## Launch 23 | Click the links below to launch this stack in the AWS region of your choice. If your desired region is 24 | not listed, just copy one of the URLs and edit the region accordingly. 25 | 26 | | AWS Region | Short name | | 27 | | -- | -- | -- | 28 | | US East (N. Virginia) | us-east-1 | [Launch Stack :rocket:](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/new?stackName=stellar-single-ec2&templateURL=https://s3.amazonaws.com/public.starformlabs.io/nebulaforge/aws/single-ec2/master.yaml) 29 | | US East (Ohio) | us-east-2 | [Launch Stack :rocket:](https://console.aws.amazon.com/cloudformation/home?region=us-east-2#/stacks/new?stackName=stellar-single-ec2&templateURL=https://s3.amazonaws.com/public.starformlabs.io/nebulaforge/aws/single-ec2/master.yaml) 30 | | US West (N. California) | us-west-1 | [Launch Stack :rocket:](https://console.aws.amazon.com/cloudformation/home?region=us-west-1#/stacks/new?stackName=stellar-single-ec2&templateURL=https://s3.amazonaws.com/public.starformlabs.io/nebulaforge/aws/single-ec2/master.yaml) 31 | | US West (Oregon) | us-west-2 | [Launch Stack :rocket:](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?stackName=stellar-single-ec2&templateURL=https://s3.amazonaws.com/public.starformlabs.io/nebulaforge/aws/single-ec2/master.yaml) 32 | | EU (Ireland) | eu-west-1 | [Launch Stack :rocket:](https://console.aws.amazon.com/cloudformation/home?region=eu-west-1#/stacks/new?stackName=stellar-single-ec2&templateURL=https://s3.amazonaws.com/public.starformlabs.io/nebulaforge/aws/single-ec2/master.yaml) 33 | | EU (London) | eu-west-2 | [Launch Stack :rocket:](https://console.aws.amazon.com/cloudformation/home?region=eu-west-2#/stacks/new?stackName=stellar-single-ec2&templateURL=https://s3.amazonaws.com/public.starformlabs.io/nebulaforge/aws/single-ec2/master.yaml) 34 | | EU (Frankfurt) | eu-central-1 | [Launch Stack :rocket:](https://console.aws.amazon.com/cloudformation/home?region=eu-central-1#/stacks/new?stackName=stellar-single-ec2&templateURL=https://s3.amazonaws.com/public.starformlabs.io/nebulaforge/aws/single-ec2/master.yaml) 35 | | Asia Pacific (Tokyo) | ap-northeast-1 | [Launch Stack :rocket:](https://console.aws.amazon.com/cloudformation/home?region=ap-northeast-1#/stacks/new?stackName=stellar-single-ec2&templateURL=https://s3.amazonaws.com/public.starformlabs.io/nebulaforge/aws/single-ec2/master.yaml) 36 | | Asia Pacific (Mumbai) | ap-south-1 | [Launch Stack :rocket:](https://console.aws.amazon.com/cloudformation/home?region=ap-south-1#/stacks/new?stackName=stellar-single-ec2&templateURL=https://s3.amazonaws.com/public.starformlabs.io/nebulaforge/aws/single-ec2/master.yaml) 37 | | Asia Pacific (Sydney) | ap-southeast-2 | [Launch Stack :rocket:](https://console.aws.amazon.com/cloudformation/home?region=ap-southeast-2#/stacks/new?stackName=stellar-single-ec2&templateURL=https://s3.amazonaws.com/public.starformlabs.io/nebulaforge/aws/single-ec2/master.yaml) 38 | 39 | 40 | ## Cost 41 | The template creates a number of resources but the majority of them do not attract charges. You *will* be billed for 42 | the following resources: 43 | - [A single EC2 instance](https://aws.amazon.com/ec2/pricing/on-demand/) 44 | - [30GB of EBS gp2 storage](https://aws.amazon.com/ebs/pricing/) 45 | - [Data Transfer](https://aws.amazon.com/ec2/pricing/on-demand/#Data_Transfer) 46 | 47 | ### Pay as you go 48 | AWS Instances and EBS volumes are now billed per second (1 minute minimum) so you can run your instance for as 49 | long or little as you like. **Estimated** monthly and daily prices for entry level instances are below. 50 | 51 | #### t2.nano (cheapest) 52 | - EC2 Instance - $4.25 53 | - EBS Storage - $3.00 54 | - Data Transfer - $0.50 55 | - Total: **$7.75**/month | **$0.26**/day 56 | 57 | #### t2.micro (default) 58 | - EC2 Instance - $8.50 59 | - EBS Storage - $3.00 60 | - Data Transfer - $0.50 61 | - Total: **$12**/month | **$0.40**/day 62 | 63 | ### Free Tier 64 | New AWS accounts are usually eligible for a [12 month free tier benefit](https://aws.amazon.com/free/?awsf.default=categories%2312monthsfree). 65 | If you are still eligible for the free tier and use the t2.micro instance type, your only cost will be bandwidth. Note 66 | that **t2.nano** instances are **NOT eligible for the free tier**. 67 | 68 | #### t2.micro with 12 month Free Tier 69 | - EC2 Instance - $0.00 70 | - EBS Storage - $0.00 71 | - Data Transfer - $0.50 72 | - Total: **$0.50**/month | **$0.02**/day 73 | 74 | #### Data Transfer 75 | Data Transfer pricing on AWS can be complex and usage patterns will vary from user to user. **$0.50 per month is a baseline 76 | estimate** for SCP traffic and basic interaction with horizon. If you plan to do high volume testing or make your 77 | horizon instance public be sure to keep an eye on the Data Transfer section of your bill. 78 | 79 | #### CloudWatch 80 | CloudWatch logging can also potentially attract charges, but it has a *permanent* free tier of 5GB ingested before 81 | charges apply. A full month of logs including consensus details on the test network is estimated to be less 82 | than 0.5GB of data. 83 | 84 | #### Disclaimer 85 | While we attempt to provide useful and up to date information, you are responsible for your own AWS 86 | account and the resources that you are charged for. Always be vigilant about doubling checking to ensure that the 87 | resources used are what your expect. 88 |
89 |
90 | 91 | ## Template 92 | The template URL is a part of the launch link, so will be auto-selected by default. You don't need to change anything 93 | on this screen. [Click here to view the template](https://s3.amazonaws.com/public.starformlabs.io/nebulaforge/aws/single-ec2/master.yaml) 94 | directly, it never hurts to double check what you are deploying to your account! 95 | 96 | ![template selection screen](../quickstart/images/select-template.png) 97 |
98 | 99 | ## Set Parameters 100 | Most of the default parameters can be left as is, however you must specify: 101 | - An SSH Key Pair to be associated with the instance (choose from the dropdown). 102 | - An IP address range that is allowed to SSH into the instance in [CIDR](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) 103 | format. 104 | - (Optional) An IP address range that is allowed to access the Horizon API via HTTP. Leave blank to block external access. 105 | 106 | **NOTE: To restrict access to your current IP [Find your IP](https://www.google.com/search?q=ip) and then add /32 to the end.** 107 | 108 | ![specify template parameters](../quickstart/images/specify-details.png) 109 |
110 |
111 | 112 | ## Options and Review 113 | You can **skip the options screen** entirely. On the review screen you can double check the parameters that you set. 114 | Be sure to **check the acknowledgement checkbox** at the end. It is to confirm that you know that the template is 115 | creating and IAM Role. Click the "Learn more" link in the warning box if you don't understand what that means. 116 | 117 | ![select the IAM resources checkbox](../quickstart/images/review-bottom.png) 118 |
119 |
120 | 121 | ## In Progress 122 | 123 | It will take about 5 minutes for everything to be deloyed. You will see the resources being created in the events tab 124 | 125 | ![create in progress](../quickstart/images/create-in-progress.png) 126 |
127 |
128 | 129 | ## Create Complete 130 | 131 | Once the deployment is done the status will be CREATE_COMPLETE. Switch to the **Outputs** tab to see the relevant URLs 132 | - The SSH URL shows the IP of the server and the username. Authentication is done using the key pair specified earlier. 133 | - The Horizon URL is a direct link to the Horizon API. 134 | - The ContainersLogGroup link will take you to a CloudWatch page with separate log streams for core, horizon and postgres. 135 | - The ECS link will show you information about the state of the cluster. 136 | - The DbAppUser value and DbSuperUserPassword link to the Parameter Store (Theses are the credentials used by stellar-core and horizon) 137 | - The DbSuperUser value and DbSuperUserPassword link to the Parameter Store. 138 | 139 | ![output URLs](../quickstart/images/create-complete-output.png) 140 |
141 |
142 | 143 | ## Finding the PostgreSQL passwords 144 | 145 | If you need database access, the PostgreSQL passwords are auto-generated by the template and stored securely in the 146 | SSM Parameter Store. Click on the relevant link in the output tab to be taken to that page then click the link to 147 | **Show** the value. 148 | 149 | ## Viewing log output 150 | 151 | This template allows you to view the detailed logs generated by Stellar Core and Horizon without SSHing into your instance. 152 | If you click on the **ContainersLogGroup** link in the outputs tab it will take to a page that links to the log streams 153 | for core, horizon and the database. 154 | 155 | ![containers log group](images/container-logs.png) 156 |
157 |
158 | 159 | Clicking on those links will show you more detailed logs for each system including consensus logging for core. 160 | 161 | ![stellar core log details](images/container-log-details.png) 162 |
163 |
164 | 165 | ## Final Notes 166 | 167 | 1. Horizon is only accessible over HTTP, not HTTPS. 168 | 169 | 1. The PostgreSQL port is not exposed to the internet. You must SSH into the server (or tunnel over SSH) to access the database. 170 | 171 | 1. Please remember to report issues specific to the [docker image](https://github.com/stellar/docker-stellar-core-horizon/issues), 172 | [stellar-core](https://github.com/stellar/stellar-core/issues), [horizon](https://github.com/stellar/go/issues), etc 173 | to the appropriate repo and only report issues related to the CloudFormation template [here](https://github.com/starformlabs/stellar-nebulaforge-aws/issues). 174 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /quickstart/master.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | 3 | Description: Deploys the stellar/quickstart docker image (ephemeral mode, testnet) in a Public VPC with a single availibility zone. 4 | 5 | Metadata: 6 | AWS::CloudFormation::Interface: 7 | ParameterGroups: 8 | - Label: 9 | default: "Instance" 10 | Parameters: [InstanceType, InstanceKeyPair] 11 | - Label: 12 | default: "Access" 13 | Parameters: [SshIpRange, HorizonIpRange, HorizonPort] 14 | 15 | ParameterLabels: 16 | InstanceType: 17 | default: "Instance Type" 18 | InstanceKeyPair: 19 | default: "Key Pair" 20 | SshIpRange: 21 | default: "IP Range for SSH Access" 22 | HorizonIpRange: 23 | default: "IP Range for Horizon Access" 24 | HorizonPort: 25 | default: "Horizon Port" 26 | 27 | Parameters: 28 | InstanceType: 29 | Type: String 30 | Default: t2.micro 31 | AllowedValues: [t2.nano, t2.micro, t2.small, t2.medium, t2.large, m4.large, m5.large, c4.large, c5.large] 32 | ConstraintDescription: Please choose a valid instance type. 33 | 34 | InstanceKeyPair: 35 | Description: EC2 key pair used to SSH into the instance 36 | Type: AWS::EC2::KeyPair::KeyName 37 | MinLength: 1 # Force check for value before deployment 38 | 39 | SshIpRange: 40 | Description: IP range (CIDR notation) that will be allowed to SSH into the instance. Use https://www.google.com/search?q=ip to find you IP and enter it as x.x.x.x/32. 41 | Type: String 42 | AllowedPattern: "^([0-9]+\\.){3}[0-9]+/[0-9]{1,2}$" 43 | ConstraintDescription: "Must be a valid IP CIDR range of the form x.x.x.x/x." 44 | MinLength: 9 45 | Default: 123.456.789.0/32 46 | 47 | HorizonIpRange: 48 | Description: IP range (CIDR notation) that will be allowed to access the Horizon. Leave blank to block all external access 49 | Type: String 50 | AllowedPattern: "^(([0-9]+\\.){3}[0-9]+/[0-9]{1,2})*$" 51 | ConstraintDescription: "Must be a valid IP CIDR range of the form x.x.x.x/x." 52 | 53 | HorizonPort: 54 | Description: EC2 Instance port for Horizon (access may still be restricted by IP) 55 | Type: Number 56 | Default: 8000 57 | 58 | 59 | Conditions: 60 | AllowHorizonAccess: !Not [!Equals [ !Ref HorizonIpRange, '' ]] 61 | 62 | Mappings: 63 | # Latest ECS optimized AMIs as of Apr 04 2019: 64 | # amzn-ami-2017.09.k-amazon-ecs-optimized. ECS agent: 1.17.2, Docker: 17.12.0-ce, ecs-init: 1.17.2-1 65 | # You can find the latest available images here: http://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-optimized_AMI.html 66 | AwsRegionMap: 67 | us-east-2: 68 | AMI: ami-0aa9ee1fc70e57450 69 | us-east-1: 70 | AMI: ami-007571470797b8ffa 71 | us-west-2: 72 | AMI: ami-0302f3ec240b9d23c 73 | us-west-1: 74 | AMI: ami-0935a5e8655c6d896 75 | eu-west-3: 76 | AMI: ami-0b419de35e061d9df 77 | eu-west-2: 78 | AMI: ami-0380c676fcff67fd5 79 | eu-west-1: 80 | AMI: ami-0b8e62ddc09226d0a 81 | eu-central-1: 82 | AMI: ami-01b63d839941375df 83 | eu-north-1: 84 | AMI: ami-03f8f3eb89dcfe553 85 | ap-northeast-2: 86 | AMI: ami-0c57dafd95a102862 87 | ap-northeast-1: 88 | AMI: ami-086ca990ae37efc1b 89 | ap-southeast-2: 90 | AMI: ami-0d28e5e0f13248294 91 | ap-southeast-1: 92 | AMI: ami-0627e2913cf6756ed 93 | ca-central-1: 94 | AMI: ami-0835b198c8a7aced4 95 | ap-south-1: 96 | AMI: ami-05de310b944d67cde 97 | sa-east-1: 98 | AMI: ami-09987452123fadc5b 99 | us-gov-east-1: 100 | AMI: ami-07dfc9cdc48d8649a 101 | us-gov-west-1: 102 | AMI: ami-914229f0 103 | 104 | CustomParamsMap: 105 | Get: 106 | DbPort: 5432 107 | 108 | # These are all soft limits so they are not enforced or representative of per container usage 109 | # These memory limits are effectively just being used to reserve the full capacity of the EC2 instance for the service 110 | # A specific amount is reserved by each instance type for system usage. The full advertised amount is not available. 111 | # You have to log into and instance and run "free -m" to determine what is available to that instance type. 112 | # https://docs.aws.amazon.com/AmazonECS/latest/developerguide/memory-management.html 113 | MemoryReservationMap: 114 | t2.nano: 115 | quickstart: 480 116 | t2.micro: 117 | quickstart: 990 118 | t2.small: 119 | quickstart: 1990 120 | t2.medium: 121 | quickstart: 3945 122 | t2.large: 123 | quickstart: 7980 124 | m4.large: 125 | quickstart: 7980 126 | m5.large: 127 | quickstart: 7680 128 | c4.large: 129 | quickstart: 3760 130 | c5.large: 131 | quickstart: 3705 132 | 133 | Resources: 134 | # Network 135 | Vpc: 136 | Type: AWS::EC2::VPC 137 | Properties: 138 | EnableDnsSupport: true 139 | EnableDnsHostnames: true 140 | CidrBlock: '10.200.0.0/16' 141 | Tags: 142 | - Key: Name 143 | Value: !Ref 'AWS::StackName' 144 | 145 | PublicSubnet: 146 | Type: AWS::EC2::Subnet 147 | Properties: 148 | AvailabilityZone: !Select [ 0, !GetAZs '' ] 149 | VpcId: !Ref 'Vpc' 150 | CidrBlock: '10.200.0.0/24' 151 | MapPublicIpOnLaunch: true 152 | Tags: 153 | - Key: Name 154 | Value: !Ref 'AWS::StackName' 155 | 156 | InternetGateway: 157 | Type: AWS::EC2::InternetGateway 158 | Properties: 159 | Tags: 160 | - Key: Name 161 | Value: !Ref 'AWS::StackName' 162 | 163 | GatewayAttachement: 164 | Type: AWS::EC2::VPCGatewayAttachment 165 | Properties: 166 | VpcId: !Ref 'Vpc' 167 | InternetGatewayId: !Ref 'InternetGateway' 168 | 169 | PublicRouteTable: 170 | Type: AWS::EC2::RouteTable 171 | Properties: 172 | VpcId: !Ref 'Vpc' 173 | Tags: 174 | - Key: Name 175 | Value: !Ref 'AWS::StackName' 176 | 177 | PublicRoute: 178 | Type: AWS::EC2::Route 179 | DependsOn: GatewayAttachement 180 | Properties: 181 | RouteTableId: !Ref 'PublicRouteTable' 182 | DestinationCidrBlock: '0.0.0.0/0' 183 | GatewayId: !Ref 'InternetGateway' 184 | 185 | PublicSubnetRouteTableAssociation: 186 | Type: AWS::EC2::SubnetRouteTableAssociation 187 | Properties: 188 | SubnetId: !Ref PublicSubnet 189 | RouteTableId: !Ref PublicRouteTable 190 | 191 | # ECS Resources 192 | EcsCluster: 193 | Type: AWS::ECS::Cluster 194 | 195 | EcsHostSecurityGroup: 196 | Type: AWS::EC2::SecurityGroup 197 | Properties: 198 | GroupName: !Ref AWS::StackName 199 | GroupDescription: Access to the ECS hosts that run containers 200 | VpcId: !Ref 'Vpc' 201 | SecurityGroupIngress: 202 | - CidrIp: !Ref SshIpRange 203 | Description: Allow SSH access via a specific IP Range 204 | IpProtocol: 'tcp' 205 | FromPort: 22 206 | ToPort: 22 207 | Tags: 208 | - Key: Name 209 | Value: !Ref 'AWS::StackName' 210 | 211 | # Specify this here instead of above so that we can use the "Condition" key 212 | EcsSecurityGroupIngressHorizon: 213 | Type: AWS::EC2::SecurityGroupIngress 214 | Condition: AllowHorizonAccess 215 | Properties: 216 | GroupId: !Ref 'EcsHostSecurityGroup' 217 | Description: Allow Horizon access via a specific IP Range 218 | CidrIp: !Ref HorizonIpRange 219 | IpProtocol: 'tcp' 220 | FromPort: !Ref 'HorizonPort' 221 | ToPort: !Ref 'HorizonPort' 222 | 223 | EcsSecurityGroupIngressFromSelf: 224 | Type: AWS::EC2::SecurityGroupIngress 225 | Properties: 226 | GroupId: !Ref 'EcsHostSecurityGroup' 227 | Description: Ingress from other hosts in the same security group 228 | IpProtocol: -1 229 | SourceSecurityGroupId: !Ref 'EcsHostSecurityGroup' 230 | 231 | # This launches the actual EC2 instance that will register as a member of the cluster and run the containers. 232 | EcsAutoScalingGroup: 233 | Type: AWS::AutoScaling::AutoScalingGroup 234 | Properties: 235 | VPCZoneIdentifier: 236 | - !Ref PublicSubnet 237 | LaunchConfigurationName: !Ref 'EcsLaunchConfiguration' 238 | MinSize: 1 239 | MaxSize: 2 # Allows capacity for AutoScalingReplacingUpdate 240 | DesiredCapacity: 1 241 | MetricsCollection: 242 | - Granularity: '1Minute' 243 | Tags: 244 | - Key: Name 245 | Value: !Ref 'AWS::StackName' 246 | PropagateAtLaunch: 'true' 247 | CreationPolicy: 248 | ResourceSignal: 249 | Timeout: PT10M 250 | UpdatePolicy: 251 | AutoScalingReplacingUpdate: 252 | WillReplace: 'true' 253 | 254 | EcsInstanceElasticIP: 255 | Type: AWS::EC2::EIP 256 | DependsOn: GatewayAttachement # Associating the IP with the vpc requires that we add this dependency explicitly 257 | Properties: 258 | Domain: vpc 259 | 260 | EcsLaunchConfiguration: 261 | Type: AWS::AutoScaling::LaunchConfiguration 262 | Properties: 263 | ImageId: !FindInMap [AwsRegionMap, !Ref 'AWS::Region', AMI] 264 | SecurityGroups: [!Ref 'EcsHostSecurityGroup'] 265 | InstanceType: !Ref 'InstanceType' 266 | IamInstanceProfile: !Ref 'Ec2InstanceProfile' 267 | InstanceMonitoring: false # Disable detailed monitoring at the instance level to avoid additional charges. ECS provides detailed CPU data 268 | KeyName: !Ref 'InstanceKeyPair' 269 | AssociatePublicIpAddress: 'true' # Will be replaced by elastic IP, but we need a public ip at the start to install packages 270 | UserData: 271 | Fn::Base64: !Sub 272 | - | 273 | #!/bin/bash 274 | 275 | ##### INSTALL UPDATES AND TOOLS 276 | yum update -y --security 277 | yum install -y aws-cli aws-cfn-bootstrap awslogs jq 278 | 279 | ##### ASSIGN ELASTIC IP 280 | # Get the instance id from instance metadata and assign the elastic ip to this instance 281 | instance_id=$(curl http://169.254.169.254/latest/meta-data/instance-id) 282 | aws ec2 associate-address --instance-id $instance_id --allocation-id ${EcsInstanceElasticIP.AllocationId} --region ${AWS::Region} 283 | 284 | ##### SETUP INSTANCE LOGS 285 | # Set the region to send CloudWatch Logs data to the region where the container instance is located 286 | sed -i -e "s/region = us-east-1/region = ${AWS::Region}/g" /etc/awslogs/awscli.conf 287 | 288 | # Inject the CloudWatch Logs configuration file contents 289 | cat > /etc/awslogs/awslogs.conf <<- EOF 290 | [general] 291 | state_file = /var/lib/awslogs/agent-state 292 | 293 | [/var/log/messages] 294 | file = /var/log/messages 295 | log_group_name = ${instancesLogGroup} 296 | log_stream_name = {container_instance_id}/var/log/messages 297 | datetime_format = %b %d %H:%M:%S 298 | 299 | [/var/log/cfn-init.log] 300 | file = /var/log/cfn-init.log 301 | log_group_name = ${instancesLogGroup} 302 | log_stream_name = {container_instance_id}/var/log/cfn-init.log 303 | datetime_format = %Y-%m-%dT%H:%M:%SZ 304 | 305 | [/var/log/cloud-init-output.log] 306 | file = /var/log/cloud-init-output.log 307 | log_group_name = ${instancesLogGroup} 308 | log_stream_name = {container_instance_id}/var/log/cloud-init-output.log 309 | datetime_format = %Y-%m-%dT%H:%M:%SZ 310 | EOF 311 | 312 | # Get the instance id from instance metadata and replace the container instance ID placeholders with the actual values 313 | container_instance_id=$(curl http://169.254.169.254/latest/meta-data/instance-id) 314 | sed -i -e "s/{container_instance_id}/$container_instance_id/g" /etc/awslogs/awslogs.conf 315 | 316 | # Start/Enable awslogs service 317 | service awslogs start 318 | chkconfig awslogs on 319 | 320 | ##### ECS CLUSTER CONFIG 321 | # Update the cluster config with the cluster name 322 | echo ECS_CLUSTER=${EcsCluster} >> /etc/ecs/ecs.config 323 | 324 | ##### SIGNAL ASG 325 | # Send a signal to the AutoScalingGroup with the return value of the last command to let it know creation completed 326 | /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource EcsAutoScalingGroup --region ${AWS::Region} 327 | - instancesLogGroup: !Sub '/stellar/${AWS::StackName}-instances' # hardcoded in outputs also 328 | 329 | Ec2InstanceProfile: 330 | Type: AWS::IAM::InstanceProfile 331 | Properties: 332 | Path: / 333 | Roles: [!Ref 'Ec2Role'] 334 | 335 | # Role for the EC2 hosts. This allows the ECS agent on the EC2 hosts to communicate with the ECS control plane. 336 | Ec2Role: 337 | Type: AWS::IAM::Role 338 | Properties: 339 | Path: / 340 | ManagedPolicyArns: ['arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role'] 341 | Policies: # Additional permissions beyond AmazonEC2ContainerServiceforEC2Role used for instance logging 342 | - PolicyName: 'AllowInstanceLogs' 343 | PolicyDocument: 344 | Version: '2012-10-17' 345 | Statement: 346 | - Effect: Allow 347 | Action: [ 'logs:CreateLogGroup', 'logs:DescribeLogStreams' ] 348 | Resource: 'arn:aws:logs:*:*:*' 349 | - PolicyName: 'AssociateElasticIP' 350 | PolicyDocument: 351 | Version: '2012-10-17' 352 | Statement: 353 | - Effect: Allow 354 | Action: [ 'ec2:AllocateAddress', 'ec2:AssociateAddress', 'ec2:DescribeAddresses', 'ec2:DisassociateAddress' ] 355 | Resource: '*' 356 | AssumeRolePolicyDocument: 357 | Statement: 358 | - Effect: Allow 359 | Principal: 360 | Service: ['ec2.amazonaws.com'] 361 | Action: ['sts:AssumeRole'] 362 | 363 | EcsService: 364 | Type: AWS::ECS::Service 365 | Properties: 366 | Cluster: !Ref EcsCluster 367 | DesiredCount: 1 368 | DeploymentConfiguration: 369 | MaximumPercent: 100 # Shutdown the old task before starting the new one, don't want the writing to efs/db simultaneously 370 | MinimumHealthyPercent: 0 371 | TaskDefinition: !Ref EcsTaskDefinition 372 | 373 | EcsTaskDefinition: 374 | Type: AWS::ECS::TaskDefinition 375 | Properties: 376 | Family: !Ref 'AWS::StackName' 377 | ContainerDefinitions: 378 | - Name: 'quickstart' 379 | Essential: true 380 | Image: stellar/quickstart 381 | Command: ["--testnet"] 382 | MemoryReservation: !FindInMap [MemoryReservationMap, !Ref 'InstanceType', quickstart] 383 | Environment: 384 | - Name: 'POSTGRES_PASSWORD' 385 | Value: !GetAtt DatabasePassword.Password 386 | PortMappings: 387 | - ContainerPort: 8000 388 | HostPort: !Ref 'HorizonPort' 389 | - ContainerPort: !FindInMap [CustomParamsMap, Get, DbPort] 390 | HostPort: !FindInMap [CustomParamsMap, Get, DbPort] 391 | LogConfiguration: 392 | LogDriver: awslogs 393 | Options: 394 | awslogs-group: !Ref 'ContainersLogGroup' 395 | awslogs-region: !Ref 'AWS::Region' 396 | awslogs-stream-prefix: !Ref 'AWS::StackName' 397 | 398 | DatabasePassword: 399 | Type: 'AWS::CloudFormation::CustomResource' 400 | Properties: 401 | ServiceToken: !GetAtt PasswordGeneratorLambda.Arn 402 | ParameterNamePrefix: !Sub '/${AWS::StackName}/database/master-password' 403 | 404 | PasswordGeneratorLambda: 405 | Type: 'AWS::Lambda::Function' 406 | Properties: 407 | Description: 'Generates random password' 408 | Handler: 'index.handler' 409 | Role: !GetAtt PasswordGeneratorRole.Arn 410 | Code: 411 | ZipFile: | 412 | const response = require('cfn-response'); 413 | const AWS = require('aws-sdk'); 414 | const ssm = new AWS.SSM(); 415 | 416 | exports.handler = (event, context) => { 417 | if (event.RequestType == 'Delete') { 418 | // Remove param when CloudFormation deletes the resource. The param name is the PhysicalResourceId 419 | ssm.deleteParameter({ Name: event.PhysicalResourceId }).promise() 420 | .then((data) => { 421 | return response.send(event, context, response.SUCCESS, data); 422 | }).catch((err)=> { 423 | return response.send(event, context, response.FAILED, err); 424 | }); 425 | } 426 | else{ // Create or Update. Update (only happens when param name changes) will return a new physical id which will cause CF to delete the old one 427 | let responseData; 428 | 429 | new BackportedSecretsManager().getRandomPassword({ PasswordLength: 45, ExcludePunctuation: true }).promise() 430 | .then((data) => { 431 | const password = data.RandomPassword.substring(0, 32); // We only really wanted 32 chars for the password 432 | const randomString = data.RandomPassword.substring(32); // Last 13 used to add randomness to the SSM param name to avoid deletion on replacement 433 | const paramName = event.ResourceProperties.ParameterNamePrefix + '-' + randomString; 434 | 435 | responseData = { 436 | ParameterName: paramName, 437 | EncodedParameterName: encodeURIComponent(encodeURIComponent(paramName)), // Double encoded to work with AWS console 438 | Password: password 439 | } 440 | 441 | const params = { 442 | Name: paramName, 443 | Type: 'SecureString', 444 | Value: password, 445 | Overwrite: true 446 | }; 447 | return ssm.putParameter(params).promise(); 448 | }).then(() => { 449 | return response.send(event, context, response.SUCCESS, responseData, responseData.ParameterName); // Use param name as PhysicalResourceId 450 | }).catch((err)=> { 451 | return response.send(event, context, response.FAILED, err); 452 | }); 453 | } 454 | }; 455 | 456 | // The Nodejs SDK currently available to Lambda doesn't yet support the Secrets Manager API 457 | // This is the code from latest sdk required to support a minimal version of getRandomPassword() only 458 | 459 | const BackportedSecretsManager = AWS.Service.defineService('secretsmanager', ['2017-10-17']); 460 | AWS.apiLoader.services['secretsmanager'] = {}; 461 | Object.defineProperty(AWS.apiLoader.services['secretsmanager'], '2017-10-17', { 462 | get: function get() { return secretsmanagerModel; }, enumerable: true, configurable: true 463 | }); 464 | 465 | const secretsmanagerModel = { 466 | version: '2.0', 467 | metadata: { 468 | apiVersion: '2017-10-17', 469 | endpointPrefix: 'secretsmanager', 470 | jsonVersion: '1.1', 471 | protocol: 'json', 472 | serviceFullName: 'AWS Secrets Manager', 473 | serviceId: 'Secrets Manager', 474 | signatureVersion: 'v4', 475 | signingName: 'secretsmanager', 476 | targetPrefix: 'secretsmanager', 477 | uid: 'secretsmanager-2017-10-17' 478 | }, 479 | operations: { 480 | GetRandomPassword: { 481 | input: { type: 'structure', members: { PasswordLength: { type: 'long' }, ExcludePunctuation: { type: 'boolean' } } }, 482 | output: { type: 'structure', members: { RandomPassword: {} } } 483 | } 484 | } 485 | }; 486 | Runtime: 'nodejs6.10' 487 | Timeout: '30' 488 | 489 | PasswordGeneratorRole: 490 | Type: 'AWS::IAM::Role' 491 | Properties: 492 | Path: '/' 493 | ManagedPolicyArns: ['arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'] 494 | Policies: 495 | - PolicyName: 'GeneratePassword' 496 | PolicyDocument: 497 | Version: '2012-10-17' 498 | Statement: 499 | - Effect: Allow 500 | Action: 'secretsmanager:GetRandomPassword' 501 | Resource: '*' 502 | - PolicyName: 'CreateSsmParams' 503 | PolicyDocument: 504 | Version: '2012-10-17' 505 | Statement: 506 | - Effect: Allow 507 | Action: ['ssm:PutParameter', 'ssm:DeleteParameter', 'kms:Encrypt'] 508 | Resource: "*" 509 | AssumeRolePolicyDocument: 510 | Version: '2012-10-17' 511 | Statement: 512 | - Effect: Allow 513 | Principal: 514 | Service: 'lambda.amazonaws.com' 515 | Action: 'sts:AssumeRole' 516 | 517 | ContainersLogGroup: 518 | Type: AWS::Logs::LogGroup 519 | Properties: 520 | LogGroupName: !Sub '/stellar/${AWS::StackName}-containers' 521 | RetentionInDays: 7 522 | 523 | Outputs: 524 | SshUrl: 525 | Value: !Sub 'ssh://ec2-user@${EcsInstanceElasticIP}' 526 | HorizonUrl: 527 | Condition: AllowHorizonAccess 528 | Value: !Sub 'http://${EcsInstanceElasticIP}:${HorizonPort}' 529 | ContainersLogGroup: 530 | Value: !Sub 'https://console.aws.amazon.com/cloudwatch/home?region=${AWS::Region}#logStream:group=${ContainersLogGroup}' 531 | InstancesLogGroup: 532 | Value: !Sub 'https://console.aws.amazon.com/cloudwatch/home?region=${AWS::Region}#logStream:group=/stellar/${AWS::StackName}-instances' 533 | AutoScalingGroup: 534 | Value: !Sub 'https://console.aws.amazon.com/ec2/autoscaling/home?region=${AWS::Region}#AutoScalingGroups:id=${EcsAutoScalingGroup};view=instances' 535 | ECS: 536 | Value: !Sub 'https://console.aws.amazon.com/ecs/home?region=${AWS::Region}#/clusters/${EcsCluster}/services/${EcsService.Name}/events' 537 | DatabaseUsername: 538 | Value: 'stellar' # Currently hardcoded in https://github.com/stellar/docker-stellar-core-horizon/blob/master/start 539 | DatabasePassword: 540 | Value: !Sub 'https://console.aws.amazon.com/systems-manager/parameters/${DatabasePassword.EncodedParameterName}/description?region=${AWS::Region}' -------------------------------------------------------------------------------- /single-ec2/master.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | 3 | Description: Deploys Stellar Core, Horizon and PostgreSQL on a single EC2 server using ECS 4 | 5 | Metadata: 6 | AWS::CloudFormation::Interface: 7 | ParameterGroups: 8 | - Label: 9 | default: "Stellar" 10 | Parameters: [StellarCoreVersion, HorizonVersion] 11 | - Label: 12 | default: "Instance" 13 | Parameters: [InstanceType, InstanceKeyPair] 14 | - Label: 15 | default: "Access" 16 | Parameters: [SshIpRange, HorizonIpRange, HorizonPort] 17 | - Label: 18 | default: "Database" 19 | Parameters: [PostgresVersion] 20 | 21 | ParameterLabels: 22 | StellarCoreVersion: 23 | default: "Stellar Core Version" 24 | HorizonVersion: 25 | default: "Horizon Version" 26 | PostgresVersion: 27 | default: "PostgreSQL Version" 28 | InstanceType: 29 | default: "Instance Type" 30 | InstanceKeyPair: 31 | default: "Key Pair" 32 | SshIpRange: 33 | default: "IP Range for SSH Access" 34 | HorizonIpRange: 35 | default: "IP Range for Horizon Access" 36 | HorizonPort: 37 | default: "Horizon Port" 38 | 39 | Parameters: 40 | StellarCoreVersion: 41 | Description: Tag/Version of the docker image to be deployed 42 | Type: String 43 | Default: '9.2.0-551-7561c1d5' 44 | AllowedValues: [latest, 9.2.0-551-7561c1d5] 45 | 46 | HorizonVersion: 47 | Description: Tag/Version of the docker image to be deployed 48 | Type: String 49 | Default: '0.12.3' 50 | AllowedValues: [latest, 0.12.2, 0.12.3] 51 | 52 | PostgresVersion: 53 | Type: String 54 | Default: '9.6' 55 | AllowedValues: [9.6] 56 | 57 | InstanceType: 58 | Type: String 59 | Default: t2.micro 60 | AllowedValues: [t2.nano, t2.micro, t2.small, t2.medium, t2.large, m4.large, m5.large, c4.large, c5.large] 61 | ConstraintDescription: Please choose a valid instance type. 62 | 63 | InstanceKeyPair: 64 | Description: EC2 key pair used to SSH into the instance 65 | Type: AWS::EC2::KeyPair::KeyName 66 | MinLength: 1 # Force check for value before deployment 67 | 68 | SshIpRange: 69 | Description: IP range (CIDR notation) that will be allowed to SSH into the instance. Use https://www.google.com/search?q=ip to find you IP and enter it as x.x.x.x/32. 70 | Type: String 71 | AllowedPattern: "^([0-9]+\\.){3}[0-9]+/[0-9]{1,2}$" 72 | ConstraintDescription: "Must be a valid IP CIDR range of the form x.x.x.x/x." 73 | MinLength: 9 74 | Default: 123.456.789.0/32 75 | 76 | HorizonIpRange: 77 | Description: IP range (CIDR notation) that will be allowed to access the Horizon. Leave blank to block all external access 78 | Type: String 79 | AllowedPattern: "^(([0-9]+\\.){3}[0-9]+/[0-9]{1,2})*$" 80 | ConstraintDescription: "Must be a valid IP CIDR range of the form x.x.x.x/x." 81 | 82 | HorizonPort: 83 | Description: EC2 Instance port for Horizon (access may still be restricted by IP) 84 | Type: Number 85 | Default: 8000 86 | 87 | 88 | Conditions: 89 | AllowHorizonAccess: !Not [!Equals [ !Ref HorizonIpRange, '' ]] 90 | 91 | Mappings: 92 | # Latest ECS optimized AMIs as of Apr 02 2018: 93 | # amzn-ami-2017.09.k-amazon-ecs-optimized. ECS agent: 1.17.2, Docker: 17.12.0-ce, ecs-init: 1.17.2-1 94 | # You can find the latest available images here: http://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-optimized_AMI.html 95 | AwsRegionMap: 96 | ap-northeast-1: 97 | AMI: ami-5add893c 98 | ap-northeast-2: 99 | AMI: ami-ba74d8d4 100 | ap-south-1: 101 | AMI: ami-2149114e 102 | ap-southeast-1: 103 | AMI: ami-acbcefd0 104 | ap-southeast-2: 105 | AMI: ami-4cc5072e 106 | ca-central-1: 107 | AMI: ami-a535b2c1 108 | eu-central-1: 109 | AMI: ami-ac055447 110 | eu-west-1: 111 | AMI: ami-bfb5fec6 112 | eu-west-2: 113 | AMI: ami-a48d6bc3 114 | eu-west-3: 115 | AMI: ami-914afcec 116 | sa-east-1: 117 | AMI: ami-d3bce9bf 118 | us-east-1: 119 | AMI: ami-cb17d8b6 120 | us-east-2: 121 | AMI: ami-1b90a67e 122 | us-west-1: 123 | AMI: ami-9cbbaffc 124 | us-west-2: 125 | AMI: ami-05b5277d 126 | 127 | StellarConfigMap: 128 | All: 129 | BucketDir: '/data/core/buckets' 130 | CatchupRecent: '8640' 131 | LogFilePath: '' 132 | Test: 133 | NetworkPassphrase: 'Test SDF Network ; September 2015' 134 | KnownPeers: 'core-testnet1.stellar.org,core-testnet2.stellar.org,core-testnet3.stellar.org' 135 | UnsafeQuorum: 'true' 136 | FailureSafety: '1' 137 | Commands: 'll?level=error&partition=Overlay' # Reduce chatty logging about connectionsS 138 | History: | 139 | { 140 | "h1": { "get": "curl -sf https://s3-eu-west-1.amazonaws.com/history.stellar.org/prd/core-testnet/core_testnet_001/{0} -o {1}" }, 141 | "h2": { "get": "curl -sf https://s3-eu-west-1.amazonaws.com/history.stellar.org/prd/core-testnet/core_testnet_002/{0} -o {1}" }, 142 | "h3": { "get": "curl -sf https://s3-eu-west-1.amazonaws.com/history.stellar.org/prd/core-testnet/core_testnet_003/{0} -o {1}" } 143 | } 144 | QuorumSet: | 145 | [{ 146 | "threshold_percent": 51, 147 | "validators": ["GDKXE2OZMJIPOSLNA6N6F2BVCI3O777I2OOC4BV7VOYUEHYX7RTRYA7Y sdf1", 148 | "GCUCJTIYXSOXKBSNFGNFWW5MUQ54HKRPGJUTQFJ5RQXZXNOLNXYDHRAP sdf2", 149 | "GC2V2EFSXN6SQTWVYA5EPJPBWWIMSD2XQNKUOHGEKB535AQE2I6IXV2Z sdf3"] 150 | }] 151 | 152 | HorizonConfigMap: 153 | Test: 154 | NetworkPassphrase: 'Test SDF Network ; September 2015' 155 | LogLevel: 'info' 156 | Ingest: 'true' 157 | PerHourRateLimit: '72000' 158 | 159 | CustomParamsMap: 160 | Get: 161 | DbSuperUser: 'postgres' 162 | DbAppUser: 'stellar' 163 | DbPort: 5432 164 | DbCoreSchemaName: 'core' 165 | DbHorizonSchemaName: 'horizon' 166 | StellarHttpPort: 11626 167 | StellarPeerPort: 11625 168 | ContainerDataDirectory: '/data' 169 | 170 | # These are all soft limits so they are not enforced or representative of per container usage 171 | # These memory limits are effectively just being used to reserve the full capacity of the EC2 instance for the service 172 | # A specific amount is reserved by each instance type for system usage. The full advertised amount is not available. 173 | # You have to log into and instance and run "free -m" to determine what is available to that instance type. 174 | # https://docs.aws.amazon.com/AmazonECS/latest/developerguide/memory-management.html 175 | MemoryReservationMap: 176 | t2.nano: 177 | db: 160 178 | core: 160 179 | horizon: 160 180 | t2.micro: 181 | db: 330 182 | core: 330 183 | horizon: 330 184 | t2.small: 185 | db: 665 186 | core: 665 187 | horizon: 665 188 | t2.medium: 189 | db: 1315 190 | core: 1315 191 | horizon: 1315 192 | t2.large: 193 | db: 2658 194 | core: 2658 195 | horizon: 2658 196 | m4.large: 197 | db: 2658 198 | core: 2658 199 | horizon: 2658 200 | m5.large: 201 | db: 2560 202 | core: 2560 203 | horizon: 2560 204 | c4.large: 205 | db: 1250 206 | core: 1250 207 | horizon: 1250 208 | c5.large: 209 | db: 1235 210 | core: 1235 211 | horizon: 1235 212 | 213 | Resources: 214 | # Network 215 | Vpc: 216 | Type: AWS::EC2::VPC 217 | Properties: 218 | EnableDnsSupport: true 219 | EnableDnsHostnames: true 220 | CidrBlock: '10.200.0.0/16' 221 | Tags: 222 | - Key: Name 223 | Value: !Ref 'AWS::StackName' 224 | 225 | PublicSubnet: 226 | Type: AWS::EC2::Subnet 227 | Properties: 228 | AvailabilityZone: !Select [ 0, !GetAZs '' ] 229 | VpcId: !Ref 'Vpc' 230 | CidrBlock: '10.200.0.0/24' 231 | MapPublicIpOnLaunch: true 232 | Tags: 233 | - Key: Name 234 | Value: !Ref 'AWS::StackName' 235 | 236 | InternetGateway: 237 | Type: AWS::EC2::InternetGateway 238 | Properties: 239 | Tags: 240 | - Key: Name 241 | Value: !Ref 'AWS::StackName' 242 | 243 | GatewayAttachement: 244 | Type: AWS::EC2::VPCGatewayAttachment 245 | Properties: 246 | VpcId: !Ref 'Vpc' 247 | InternetGatewayId: !Ref 'InternetGateway' 248 | 249 | PublicRouteTable: 250 | Type: AWS::EC2::RouteTable 251 | Properties: 252 | VpcId: !Ref 'Vpc' 253 | Tags: 254 | - Key: Name 255 | Value: !Ref 'AWS::StackName' 256 | 257 | PublicRoute: 258 | Type: AWS::EC2::Route 259 | DependsOn: GatewayAttachement 260 | Properties: 261 | RouteTableId: !Ref 'PublicRouteTable' 262 | DestinationCidrBlock: '0.0.0.0/0' 263 | GatewayId: !Ref 'InternetGateway' 264 | 265 | PublicSubnetRouteTableAssociation: 266 | Type: AWS::EC2::SubnetRouteTableAssociation 267 | Properties: 268 | SubnetId: !Ref PublicSubnet 269 | RouteTableId: !Ref PublicRouteTable 270 | 271 | # ECS Resources 272 | EcsCluster: 273 | Type: AWS::ECS::Cluster 274 | 275 | EcsHostSecurityGroup: 276 | Type: AWS::EC2::SecurityGroup 277 | Properties: 278 | GroupName: !Ref AWS::StackName 279 | GroupDescription: Access to the ECS hosts that run containers 280 | VpcId: !Ref 'Vpc' 281 | SecurityGroupIngress: 282 | - CidrIp: !Ref SshIpRange 283 | Description: Allow SSH access via a specific IP Range 284 | IpProtocol: 'tcp' 285 | FromPort: 22 286 | ToPort: 22 287 | Tags: 288 | - Key: Name 289 | Value: !Ref 'AWS::StackName' 290 | 291 | # Specify this here instead of above so that we can use the "Condition" key 292 | EcsSecurityGroupIngressHorizon: 293 | Type: AWS::EC2::SecurityGroupIngress 294 | Condition: AllowHorizonAccess 295 | Properties: 296 | GroupId: !Ref 'EcsHostSecurityGroup' 297 | Description: Allow Horizon access via a specific IP Range 298 | CidrIp: !Ref HorizonIpRange 299 | IpProtocol: 'tcp' 300 | FromPort: !Ref 'HorizonPort' 301 | ToPort: !Ref 'HorizonPort' 302 | 303 | EcsSecurityGroupIngressFromSelf: 304 | Type: AWS::EC2::SecurityGroupIngress 305 | Properties: 306 | GroupId: !Ref 'EcsHostSecurityGroup' 307 | Description: Ingress from other hosts in the same security group 308 | IpProtocol: -1 309 | SourceSecurityGroupId: !Ref 'EcsHostSecurityGroup' 310 | 311 | # This launches the actual EC2 instance that will register as a member of the cluster and run the containers. 312 | EcsAutoScalingGroup: 313 | Type: AWS::AutoScaling::AutoScalingGroup 314 | Properties: 315 | VPCZoneIdentifier: 316 | - !Ref PublicSubnet 317 | LaunchConfigurationName: !Ref 'EcsLaunchConfiguration' 318 | MinSize: 1 319 | MaxSize: 2 # Allows capacity for AutoScalingReplacingUpdate 320 | DesiredCapacity: 1 321 | MetricsCollection: 322 | - Granularity: '1Minute' 323 | Tags: 324 | - Key: Name 325 | Value: !Ref 'AWS::StackName' 326 | PropagateAtLaunch: 'true' 327 | CreationPolicy: 328 | ResourceSignal: 329 | Timeout: PT10M 330 | UpdatePolicy: 331 | AutoScalingReplacingUpdate: 332 | WillReplace: 'true' 333 | 334 | EcsInstanceElasticIP: 335 | Type: AWS::EC2::EIP 336 | DependsOn: GatewayAttachement # Associating the IP with the vpc requires that we add this dependency explicitly 337 | Properties: 338 | Domain: vpc 339 | 340 | EcsLaunchConfiguration: 341 | Type: AWS::AutoScaling::LaunchConfiguration 342 | Properties: 343 | ImageId: !FindInMap [AwsRegionMap, !Ref 'AWS::Region', AMI] 344 | SecurityGroups: [!Ref 'EcsHostSecurityGroup'] 345 | InstanceType: !Ref 'InstanceType' 346 | IamInstanceProfile: !Ref 'Ec2InstanceProfile' 347 | InstanceMonitoring: false # Disable detailed monitoring at the instance level to avoid additional charges. ECS provides detailed CPU data 348 | KeyName: !Ref 'InstanceKeyPair' 349 | AssociatePublicIpAddress: 'true' # Will be replaced by elastic IP, but we need a public ip at the start to install packages 350 | UserData: 351 | Fn::Base64: !Sub 352 | - | 353 | #!/bin/bash 354 | 355 | ##### INSTALL UPDATES AND TOOLS 356 | yum update -y --security 357 | yum install -y aws-cli aws-cfn-bootstrap awslogs jq 358 | 359 | ##### ASSIGN ELASTIC IP 360 | # Get the instance id from instance metadata and assign the elastic ip to this instance 361 | instance_id=$(curl http://169.254.169.254/latest/meta-data/instance-id) 362 | aws ec2 associate-address --instance-id $instance_id --allocation-id ${EcsInstanceElasticIP.AllocationId} --region ${AWS::Region} 363 | 364 | ##### SETUP INSTANCE LOGS 365 | # Set the region to send CloudWatch Logs data to the region where the container instance is located 366 | sed -i -e "s/region = us-east-1/region = ${AWS::Region}/g" /etc/awslogs/awscli.conf 367 | 368 | # Inject the CloudWatch Logs configuration file contents 369 | cat > /etc/awslogs/awslogs.conf <<- EOF 370 | [general] 371 | state_file = /var/lib/awslogs/agent-state 372 | 373 | [/var/log/messages] 374 | file = /var/log/messages 375 | log_group_name = ${instancesLogGroup} 376 | log_stream_name = {container_instance_id}/var/log/messages 377 | datetime_format = %b %d %H:%M:%S 378 | 379 | [/var/log/cfn-init.log] 380 | file = /var/log/cfn-init.log 381 | log_group_name = ${instancesLogGroup} 382 | log_stream_name = {container_instance_id}/var/log/cfn-init.log 383 | datetime_format = %Y-%m-%dT%H:%M:%SZ 384 | 385 | [/var/log/cloud-init-output.log] 386 | file = /var/log/cloud-init-output.log 387 | log_group_name = ${instancesLogGroup} 388 | log_stream_name = {container_instance_id}/var/log/cloud-init-output.log 389 | datetime_format = %Y-%m-%dT%H:%M:%SZ 390 | EOF 391 | 392 | # Get the instance id from instance metadata and replace the container instance ID placeholders with the actual values 393 | container_instance_id=$(curl http://169.254.169.254/latest/meta-data/instance-id) 394 | sed -i -e "s/{container_instance_id}/$container_instance_id/g" /etc/awslogs/awslogs.conf 395 | 396 | # Start/Enable awslogs service 397 | service awslogs start 398 | chkconfig awslogs on 399 | 400 | ##### ECS CLUSTER CONFIG 401 | # Update the cluster config with the cluster name 402 | echo ECS_CLUSTER=${EcsCluster} >> /etc/ecs/ecs.config 403 | 404 | ##### SIGNAL ASG 405 | # Send a signal to the AutoScalingGroup with the return value of the last command to let it know creation completed 406 | /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource EcsAutoScalingGroup --region ${AWS::Region} 407 | - instancesLogGroup: !Sub '/stellar/${AWS::StackName}-instances' # hardcoded in outputs also 408 | 409 | Ec2InstanceProfile: 410 | Type: AWS::IAM::InstanceProfile 411 | Properties: 412 | Path: / 413 | Roles: [!Ref 'Ec2Role'] 414 | 415 | # Role for the EC2 hosts. This allows the ECS agent on the EC2 hosts to communicate with the ECS control plane. 416 | Ec2Role: 417 | Type: AWS::IAM::Role 418 | Properties: 419 | Path: / 420 | ManagedPolicyArns: ['arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role'] 421 | Policies: # Additional permissions beyond AmazonEC2ContainerServiceforEC2Role used for instance logging 422 | - PolicyName: 'AllowInstanceLogs' 423 | PolicyDocument: 424 | Version: '2012-10-17' 425 | Statement: 426 | - Effect: Allow 427 | Action: [ 'logs:CreateLogGroup', 'logs:DescribeLogStreams' ] 428 | Resource: 'arn:aws:logs:*:*:*' 429 | - PolicyName: 'AssociateElasticIP' 430 | PolicyDocument: 431 | Version: '2012-10-17' 432 | Statement: 433 | - Effect: Allow 434 | Action: [ 'ec2:AllocateAddress', 'ec2:AssociateAddress', 'ec2:DescribeAddresses', 'ec2:DisassociateAddress' ] 435 | Resource: '*' 436 | AssumeRolePolicyDocument: 437 | Statement: 438 | - Effect: Allow 439 | Principal: 440 | Service: ['ec2.amazonaws.com'] 441 | Action: ['sts:AssumeRole'] 442 | 443 | EcsService: 444 | Type: AWS::ECS::Service 445 | Properties: 446 | Cluster: !Ref EcsCluster 447 | DesiredCount: 1 448 | DeploymentConfiguration: 449 | MaximumPercent: 100 # Shutdown the old task before starting the new one, don't want the writing to efs/db simultaneously 450 | MinimumHealthyPercent: 0 451 | TaskDefinition: !Ref EcsTaskDefinition 452 | 453 | EcsTaskDefinition: 454 | Type: AWS::ECS::TaskDefinition 455 | Properties: 456 | Family: !Ref 'AWS::StackName' 457 | NetworkMode: host 458 | ContainerDefinitions: 459 | - Name: 'database' 460 | Essential: true 461 | Image: !Sub 'starformlabs/stellar-postgres:${PostgresVersion}' 462 | MemoryReservation: !FindInMap [MemoryReservationMap, !Ref 'InstanceType', db] 463 | Environment: 464 | - Name: 'POSTGRES_USER' 465 | Value: !FindInMap [CustomParamsMap, Get, DbSuperUser] 466 | - Name: 'POSTGRES_PASSWORD' 467 | Value: !GetAtt DbSuperUserPassword.Password 468 | - Name: 'STELLAR_DB_USER' 469 | Value: !FindInMap [CustomParamsMap, Get, DbAppUser] 470 | - Name: 'STELLAR_DB_PASSWORD' 471 | Value: !GetAtt DbAppPassword.Password 472 | PortMappings: 473 | - ContainerPort: !FindInMap [CustomParamsMap, Get, DbPort] 474 | LogConfiguration: 475 | LogDriver: awslogs 476 | Options: 477 | awslogs-group: !Ref 'ContainersLogGroup' 478 | awslogs-region: !Ref 'AWS::Region' 479 | awslogs-stream-prefix: !Ref 'AWS::StackName' 480 | 481 | - Name: 'core' 482 | Essential: true 483 | Image: !Sub 'starformlabs/stellar-core:${StellarCoreVersion}' 484 | MemoryReservation: !FindInMap [MemoryReservationMap, !Ref 'InstanceType', core] 485 | PortMappings: 486 | - ContainerPort: !FindInMap [CustomParamsMap, Get, StellarPeerPort] 487 | Environment: 488 | # Used by entry.sh 489 | - Name: DB_USER 490 | Value: !FindInMap [CustomParamsMap, Get, DbAppUser] 491 | - Name: DB_PASS 492 | Value: !GetAtt DbAppPassword.Password 493 | - Name: DB_NAME 494 | Value: !FindInMap [CustomParamsMap, Get, DbCoreSchemaName] 495 | - Name: DB_HOST 496 | Value: 'localhost' # we are using host based networking so containers can access each other on localhost 497 | - Name: DB_PORT 498 | Value: !FindInMap [CustomParamsMap, Get, DbPort] 499 | - Name: DATA_DIR 500 | Value: !FindInMap [CustomParamsMap, Get, ContainerDataDirectory] 501 | 502 | # Used by Stellar Core 503 | - Name: DATABASE 504 | Value: 505 | Fn::Sub: 506 | - 'postgresql://dbname=${db} host=${host} port=${port} user=${user} password=${pass}' 507 | - db: !FindInMap [CustomParamsMap, Get, DbCoreSchemaName] 508 | host: 'localhost' # we are using host based networking so containers can access each other on localhost 509 | port: !FindInMap [CustomParamsMap, Get, DbPort] 510 | user: !FindInMap [CustomParamsMap, Get, DbAppUser] 511 | pass: !GetAtt DbAppPassword.Password 512 | - Name: NETWORK_PASSPHRASE 513 | Value: !FindInMap [StellarConfigMap, Test, NetworkPassphrase] 514 | - Name: KNOWN_PEERS 515 | Value: !FindInMap [StellarConfigMap, Test, KnownPeers] 516 | - Name: BUCKET_DIR_PATH 517 | Value: !FindInMap [StellarConfigMap, All, BucketDir] 518 | - Name: UNSAFE_QUORUM 519 | Value: !FindInMap [StellarConfigMap, Test, UnsafeQuorum] 520 | - Name: FAILURE_SAFETY 521 | Value: !FindInMap [StellarConfigMap, Test, FailureSafety] 522 | - Name: CATCHUP_RECENT 523 | Value: !FindInMap [StellarConfigMap, All, CatchupRecent] 524 | - Name: LOG_FILE_PATH 525 | Value: !FindInMap [StellarConfigMap, All, LogFilePath] 526 | - Name: QUORUM_SET 527 | Value: !FindInMap [StellarConfigMap, Test, QuorumSet] 528 | - Name: HISTORY 529 | Value: !FindInMap [StellarConfigMap, Test, History] 530 | - Name: COMMANDS 531 | Value: !FindInMap [StellarConfigMap, Test, Commands] 532 | LogConfiguration: 533 | LogDriver: awslogs 534 | Options: 535 | awslogs-group: !Ref 'ContainersLogGroup' 536 | awslogs-region: !Ref 'AWS::Region' 537 | awslogs-stream-prefix: !Ref 'AWS::StackName' 538 | 539 | - Name: 'horizon' 540 | Essential: true 541 | Image: !Sub 'starformlabs/stellar-horizon:${HorizonVersion}' 542 | MemoryReservation: !FindInMap [MemoryReservationMap, !Ref 'InstanceType', horizon] 543 | PortMappings: 544 | - ContainerPort: !Ref HorizonPort 545 | Environment: 546 | # Used by entry.sh 547 | - Name: DB_USER 548 | Value: !FindInMap [CustomParamsMap, Get, DbAppUser] 549 | - Name: DB_PASS 550 | Value: !GetAtt DbAppPassword.Password 551 | - Name: DB_NAME 552 | Value: !FindInMap [CustomParamsMap, Get, DbHorizonSchemaName] 553 | - Name: DB_HOST 554 | Value: 'localhost' # we are using host based networking so containers can access each other on localhost 555 | - Name: DB_PORT 556 | Value: !FindInMap [CustomParamsMap, Get, DbPort] 557 | - Name: DATA_DIR 558 | Value: !FindInMap [CustomParamsMap, Get, ContainerDataDirectory] 559 | 560 | # Used by Horizon directly 561 | - Name: PORT # Horizon Port 562 | Value: !Ref HorizonPort 563 | - Name: DATABASE_URL 564 | Value: 565 | Fn::Sub: 566 | - 'postgres://${user}:${pass}@${host}:${port}/${db}?sslmode=disable' 567 | - db: !FindInMap [CustomParamsMap, Get, DbHorizonSchemaName] 568 | user: !FindInMap [CustomParamsMap, Get, DbAppUser] 569 | pass: !GetAtt DbAppPassword.Password 570 | host: 'localhost' # we are using host based networking so containers can access each other on localhost 571 | port: !FindInMap [CustomParamsMap, Get, DbPort] 572 | - Name: STELLAR_CORE_DATABASE_URL 573 | Value: 574 | Fn::Sub: 575 | - 'postgres://${user}:${pass}@${host}:${port}/${db}?sslmode=disable' 576 | - db: !FindInMap [CustomParamsMap, Get, DbCoreSchemaName] 577 | user: !FindInMap [CustomParamsMap, Get, DbAppUser] 578 | pass: !GetAtt DbAppPassword.Password 579 | host: 'localhost' # we are using host based networking so containers can access each other on localhost 580 | port: !FindInMap [CustomParamsMap, Get, DbPort] 581 | - Name: STELLAR_CORE_URL 582 | Value: 583 | Fn::Sub: 584 | - 'http://${host}:${port}' 585 | - host: 'localhost' # we are using host based networking so containers can access each other on localhost 586 | port: !FindInMap [CustomParamsMap, Get, StellarHttpPort] 587 | - Name: LOG_LEVEL 588 | Value: !FindInMap [HorizonConfigMap, Test, LogLevel] 589 | - Name: INGEST 590 | Value: !FindInMap [HorizonConfigMap, Test, Ingest] 591 | - Name: PER_HOUR_RATE_LIMIT 592 | Value: !FindInMap [HorizonConfigMap, Test, PerHourRateLimit] 593 | - Name: NETWORK_PASSPHRASE 594 | Value: !FindInMap [HorizonConfigMap, Test, NetworkPassphrase] 595 | LogConfiguration: 596 | LogDriver: awslogs 597 | Options: 598 | awslogs-group: !Ref 'ContainersLogGroup' 599 | awslogs-region: !Ref 'AWS::Region' 600 | awslogs-stream-prefix: !Ref 'AWS::StackName' 601 | 602 | DbSuperUserPassword: 603 | Type: 'AWS::CloudFormation::CustomResource' 604 | Properties: 605 | ServiceToken: !GetAtt PasswordGeneratorLambda.Arn 606 | ParameterNamePrefix: !Sub '/${AWS::StackName}/database/master-password' 607 | 608 | DbAppPassword: 609 | Type: 'AWS::CloudFormation::CustomResource' 610 | Properties: 611 | ServiceToken: !GetAtt PasswordGeneratorLambda.Arn 612 | ParameterNamePrefix: !Sub '/${AWS::StackName}/database/app-password' 613 | 614 | PasswordGeneratorLambda: 615 | Type: 'AWS::Lambda::Function' 616 | Properties: 617 | Description: 'Generates random password' 618 | Handler: 'index.handler' 619 | Role: !GetAtt PasswordGeneratorRole.Arn 620 | Code: 621 | ZipFile: | 622 | const response = require('cfn-response'); 623 | const AWS = require('aws-sdk'); 624 | const ssm = new AWS.SSM(); 625 | 626 | exports.handler = (event, context) => { 627 | if (event.RequestType == 'Delete') { 628 | // Remove param when CloudFormation deletes the resource. The param name is the PhysicalResourceId 629 | ssm.deleteParameter({ Name: event.PhysicalResourceId }).promise() 630 | .then((data) => { 631 | return response.send(event, context, response.SUCCESS, data); 632 | }).catch((err)=> { 633 | return response.send(event, context, response.FAILED, err); 634 | }); 635 | } 636 | else{ // Create or Update. Update (only happens when param name changes) will return a new physical id which will cause CF to delete the old one 637 | let responseData; 638 | 639 | new BackportedSecretsManager().getRandomPassword({ PasswordLength: 45, ExcludePunctuation: true }).promise() 640 | .then((data) => { 641 | const password = data.RandomPassword.substring(0, 32); // We only really wanted 32 chars for the password 642 | const randomString = data.RandomPassword.substring(32); // Last 13 used to add randomness to the SSM param name to avoid deletion on replacement 643 | const paramName = event.ResourceProperties.ParameterNamePrefix + '-' + randomString; 644 | 645 | responseData = { 646 | ParameterName: paramName, 647 | EncodedParameterName: encodeURIComponent(encodeURIComponent(paramName)), // Double encoded to work with AWS console 648 | Password: password 649 | } 650 | 651 | const params = { 652 | Name: paramName, 653 | Type: 'SecureString', 654 | Value: password, 655 | Overwrite: true 656 | }; 657 | return ssm.putParameter(params).promise(); 658 | }).then(() => { 659 | return response.send(event, context, response.SUCCESS, responseData, responseData.ParameterName); // Use param name as PhysicalResourceId 660 | }).catch((err)=> { 661 | return response.send(event, context, response.FAILED, err); 662 | }); 663 | } 664 | }; 665 | 666 | // The Nodejs SDK currently available to Lambda doesn't yet support the Secrets Manager API 667 | // This is the code from latest sdk required to support a minimal version of getRandomPassword() only 668 | 669 | const BackportedSecretsManager = AWS.Service.defineService('secretsmanager', ['2017-10-17']); 670 | AWS.apiLoader.services['secretsmanager'] = {}; 671 | Object.defineProperty(AWS.apiLoader.services['secretsmanager'], '2017-10-17', { 672 | get: function get() { return secretsmanagerModel; }, enumerable: true, configurable: true 673 | }); 674 | 675 | const secretsmanagerModel = { 676 | version: '2.0', 677 | metadata: { 678 | apiVersion: '2017-10-17', 679 | endpointPrefix: 'secretsmanager', 680 | jsonVersion: '1.1', 681 | protocol: 'json', 682 | serviceFullName: 'AWS Secrets Manager', 683 | serviceId: 'Secrets Manager', 684 | signatureVersion: 'v4', 685 | signingName: 'secretsmanager', 686 | targetPrefix: 'secretsmanager', 687 | uid: 'secretsmanager-2017-10-17' 688 | }, 689 | operations: { 690 | GetRandomPassword: { 691 | input: { type: 'structure', members: { PasswordLength: { type: 'long' }, ExcludePunctuation: { type: 'boolean' } } }, 692 | output: { type: 'structure', members: { RandomPassword: {} } } 693 | } 694 | } 695 | }; 696 | Runtime: 'nodejs6.10' 697 | Timeout: '30' 698 | 699 | PasswordGeneratorRole: 700 | Type: 'AWS::IAM::Role' 701 | Properties: 702 | Path: '/' 703 | ManagedPolicyArns: ['arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'] 704 | Policies: 705 | - PolicyName: 'GeneratePassword' 706 | PolicyDocument: 707 | Version: '2012-10-17' 708 | Statement: 709 | - Effect: Allow 710 | Action: 'secretsmanager:GetRandomPassword' 711 | Resource: '*' 712 | - PolicyName: 'CreateSsmParams' 713 | PolicyDocument: 714 | Version: '2012-10-17' 715 | Statement: 716 | - Effect: Allow 717 | Action: ['ssm:PutParameter', 'ssm:DeleteParameter', 'kms:Encrypt'] 718 | Resource: "*" 719 | AssumeRolePolicyDocument: 720 | Version: '2012-10-17' 721 | Statement: 722 | - Effect: Allow 723 | Principal: 724 | Service: 'lambda.amazonaws.com' 725 | Action: 'sts:AssumeRole' 726 | 727 | ContainersLogGroup: 728 | Type: AWS::Logs::LogGroup 729 | Properties: 730 | LogGroupName: !Sub '/stellar/${AWS::StackName}-containers' 731 | RetentionInDays: 7 732 | 733 | Outputs: 734 | SshUrl: 735 | Value: !Sub 'ssh://ec2-user@${EcsInstanceElasticIP}' 736 | HorizonUrl: 737 | Condition: AllowHorizonAccess 738 | Value: !Sub 'http://${EcsInstanceElasticIP}:${HorizonPort}' 739 | ContainersLogGroup: 740 | Value: !Sub 'https://console.aws.amazon.com/cloudwatch/home?region=${AWS::Region}#logStream:group=${ContainersLogGroup}' 741 | InstancesLogGroup: 742 | Value: !Sub 'https://console.aws.amazon.com/cloudwatch/home?region=${AWS::Region}#logStream:group=/stellar/${AWS::StackName}-instances' 743 | AutoScalingGroup: 744 | Value: !Sub 'https://console.aws.amazon.com/ec2/autoscaling/home?region=${AWS::Region}#AutoScalingGroups:id=${EcsAutoScalingGroup};view=instances' 745 | ECS: 746 | Value: !Sub 'https://console.aws.amazon.com/ecs/home?region=${AWS::Region}#/clusters/${EcsCluster}/services/${EcsService.Name}/events' 747 | DbSuperUser: 748 | Value: !FindInMap [CustomParamsMap, Get, DbSuperUser] 749 | DbSuperUserPassword: 750 | Value: !Sub 'https://console.aws.amazon.com/systems-manager/parameters/${DbSuperUserPassword.EncodedParameterName}/description?region=${AWS::Region}' 751 | DbAppUser: 752 | Value: !FindInMap [CustomParamsMap, Get, DbAppUser] 753 | DbAppPassword: 754 | Value: !Sub 'https://console.aws.amazon.com/systems-manager/parameters/${DbAppPassword.EncodedParameterName}/description?region=${AWS::Region}' -------------------------------------------------------------------------------- /single-ec2-single-rds/master.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | 3 | Description: Deploys Stellar Core and Horizon on a single EC2 server using ECS. RDS is used for Postgres and EFS for data 4 | 5 | Metadata: 6 | AWS::CloudFormation::Interface: 7 | ParameterGroups: 8 | - Label: 9 | default: "Stellar" 10 | Parameters: [StellarCoreVersion, HorizonVersion] 11 | - Label: 12 | default: "Instance" 13 | Parameters: [InstanceType, InstanceKeyPair] 14 | - Label: 15 | default: "Access" 16 | Parameters: [SshIpRange, HorizonIpRange, HorizonPort] 17 | - Label: 18 | default: "Database" 19 | Parameters: [PostgresVersion, DbInstanceType, DbStorage] 20 | 21 | ParameterLabels: 22 | StellarCoreVersion: 23 | default: "Stellar Core Version" 24 | HorizonVersion: 25 | default: "Horizon Version" 26 | PostgresVersion: 27 | default: "PostgreSQL Version" 28 | InstanceType: 29 | default: "Instance Type" 30 | InstanceKeyPair: 31 | default: "Key Pair" 32 | SshIpRange: 33 | default: "IP Range for SSH Access" 34 | HorizonIpRange: 35 | default: "IP Range for Horizon Access" 36 | HorizonPort: 37 | default: "Horizon Port" 38 | DbInstanceType: 39 | default: "Instance Type" 40 | DbStorage: 41 | default: "Storage" 42 | 43 | Parameters: 44 | StellarCoreVersion: 45 | Description: Tag/Version of the docker image to be deployed 46 | Type: String 47 | Default: '9.2.0-551-7561c1d5' 48 | AllowedValues: [latest, 9.2.0-551-7561c1d5] 49 | 50 | HorizonVersion: 51 | Description: Tag/Version of the docker image to be deployed 52 | Type: String 53 | Default: '0.12.3' 54 | AllowedValues: [latest, 0.12.2, 0.12.3] 55 | 56 | PostgresVersion: 57 | Type: String 58 | Default: 9.6.8 59 | AllowedValues: 60 | - 9.6.8 61 | - 9.6.6 62 | - 10.3 63 | 64 | InstanceType: 65 | Type: String 66 | Default: t2.micro 67 | AllowedValues: [t2.nano, t2.micro, t2.small, t2.medium, t2.large, m4.large, m5.large, c4.large, c5.large] 68 | ConstraintDescription: Please choose a valid instance type. 69 | 70 | InstanceKeyPair: 71 | Description: EC2 key pair used to SSH into the instance 72 | Type: AWS::EC2::KeyPair::KeyName 73 | MinLength: 1 # Force check for value before deployment 74 | 75 | SshIpRange: 76 | Description: IP range (CIDR notation) that will be allowed to SSH into the instance. Use https://www.google.com/search?q=ip to find you IP and enter it as x.x.x.x/32. 77 | Type: String 78 | AllowedPattern: "^([0-9]+\\.){3}[0-9]+/[0-9]{1,2}$" 79 | ConstraintDescription: "Must be a valid IP CIDR range of the form x.x.x.x/x." 80 | MinLength: 9 81 | Default: 123.456.789.0/32 82 | 83 | HorizonIpRange: 84 | Description: IP range (CIDR notation) that will be allowed to access the Horizon. Leave blank to block all external access 85 | Type: String 86 | AllowedPattern: "^(([0-9]+\\.){3}[0-9]+/[0-9]{1,2})*$" 87 | ConstraintDescription: "Must be a valid IP CIDR range of the form x.x.x.x/x." 88 | 89 | HorizonPort: 90 | Description: EC2 Instance port for Horizon (access may still be restricted by IP) 91 | Type: Number 92 | Default: 8000 93 | 94 | DbInstanceType: 95 | Default: db.t2.micro 96 | Type: String 97 | ConstraintDescription: DB instance type not supported 98 | AllowedValues: 99 | - db.t2.micro 100 | - db.t2.small 101 | - db.t2.medium 102 | - db.t2.large 103 | - db.t2.xlarge 104 | - db.m4.large 105 | - db.m4.xlarge 106 | - db.r4.large 107 | - db.r4.xlarge 108 | 109 | DbStorage: 110 | Type: Number 111 | Description: Database storage size in gigabytes (GB) 112 | MinValue: 20 113 | ConstraintDescription: Enter a size of at least 20 GB 114 | Default: 20 115 | 116 | 117 | Conditions: 118 | AllowHorizonAccess: !Not [!Equals [ !Ref HorizonIpRange, '' ]] 119 | 120 | Mappings: 121 | # Latest ECS optimized AMIs as of Apr 02 2018: 122 | # amzn-ami-2017.09.k-amazon-ecs-optimized. ECS agent: 1.17.2, Docker: 17.12.0-ce, ecs-init: 1.17.2-1 123 | # You can find the latest available images here: http://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-optimized_AMI.html 124 | AwsRegionMap: 125 | ap-northeast-1: 126 | AMI: ami-5add893c 127 | ap-northeast-2: 128 | AMI: ami-ba74d8d4 129 | ap-south-1: 130 | AMI: ami-2149114e 131 | ap-southeast-1: 132 | AMI: ami-acbcefd0 133 | ap-southeast-2: 134 | AMI: ami-4cc5072e 135 | ca-central-1: 136 | AMI: ami-a535b2c1 137 | eu-central-1: 138 | AMI: ami-ac055447 139 | eu-west-1: 140 | AMI: ami-bfb5fec6 141 | eu-west-2: 142 | AMI: ami-a48d6bc3 143 | eu-west-3: 144 | AMI: ami-914afcec 145 | sa-east-1: 146 | AMI: ami-d3bce9bf 147 | us-east-1: 148 | AMI: ami-cb17d8b6 149 | us-east-2: 150 | AMI: ami-1b90a67e 151 | us-west-1: 152 | AMI: ami-9cbbaffc 153 | us-west-2: 154 | AMI: ami-05b5277d 155 | 156 | StellarConfigMap: 157 | All: 158 | BucketDir: '/data/core/buckets' 159 | CatchupRecent: '8640' 160 | LogFilePath: '' 161 | Test: 162 | NetworkPassphrase: 'Test SDF Network ; September 2015' 163 | KnownPeers: 'core-testnet1.stellar.org,core-testnet2.stellar.org,core-testnet3.stellar.org' 164 | UnsafeQuorum: 'true' 165 | FailureSafety: '1' 166 | Commands: 'll?level=error&partition=Overlay' # Reduce chatty logging about connectionsS 167 | History: | 168 | { 169 | "h1": { "get": "curl -sf https://s3-eu-west-1.amazonaws.com/history.stellar.org/prd/core-testnet/core_testnet_001/{0} -o {1}" }, 170 | "h2": { "get": "curl -sf https://s3-eu-west-1.amazonaws.com/history.stellar.org/prd/core-testnet/core_testnet_002/{0} -o {1}" }, 171 | "h3": { "get": "curl -sf https://s3-eu-west-1.amazonaws.com/history.stellar.org/prd/core-testnet/core_testnet_003/{0} -o {1}" } 172 | } 173 | QuorumSet: | 174 | [{ 175 | "threshold_percent": 51, 176 | "validators": ["GDKXE2OZMJIPOSLNA6N6F2BVCI3O777I2OOC4BV7VOYUEHYX7RTRYA7Y sdf1", 177 | "GCUCJTIYXSOXKBSNFGNFWW5MUQ54HKRPGJUTQFJ5RQXZXNOLNXYDHRAP sdf2", 178 | "GC2V2EFSXN6SQTWVYA5EPJPBWWIMSD2XQNKUOHGEKB535AQE2I6IXV2Z sdf3"] 179 | }] 180 | 181 | HorizonConfigMap: 182 | Test: 183 | NetworkPassphrase: 'Test SDF Network ; September 2015' 184 | LogLevel: 'info' 185 | Ingest: 'true' 186 | PerHourRateLimit: '72000' 187 | 188 | CustomParamsMap: 189 | Get: 190 | DbSuperUser: 'postgres' 191 | DbAppUser: 'stellar' 192 | DbPort: 5432 193 | DbCoreSchemaName: 'core' 194 | DbHorizonSchemaName: 'horizon' 195 | StellarHttpPort: 11626 196 | StellarPeerPort: 11625 197 | RdsDbEngine: 'postgres' 198 | EfsPort: 2049 199 | EfsInstanceMountPoint: '/mnt/efs' 200 | ContainerDataDirectory: '/data' 201 | 202 | # These are all soft limits so they are not enforced or representative of per container usage 203 | # These memory limits are effectively just being used to reserve the full capacity of the EC2 instance for the service 204 | # A specific amount is reserved by each instance type for system usage. The full advertised amount is not available. 205 | # You have to log into and instance and run "free -m" to determine what is available to that instance type. 206 | # https://docs.aws.amazon.com/AmazonECS/latest/developerguide/memory-management.html 207 | MemoryReservationMap: 208 | t2.nano: 209 | core: 240 210 | horizon: 240 211 | t2.micro: 212 | core: 495 213 | horizon: 495 214 | t2.small: 215 | core: 998 216 | horizon: 998 217 | t2.medium: 218 | core: 1973 219 | horizon: 1973 220 | t2.large: 221 | core: 3990 222 | horizon: 3990 223 | m4.large: 224 | core: 3990 225 | horizon: 3990 226 | m5.large: 227 | core: 3842 228 | horizon: 3842 229 | c4.large: 230 | core: 1880 231 | horizon: 1880 232 | c5.large: 233 | core: 1854 234 | horizon: 1854 235 | 236 | 237 | Resources: 238 | # Network 239 | Vpc: 240 | Type: AWS::EC2::VPC 241 | Properties: 242 | EnableDnsSupport: true 243 | EnableDnsHostnames: true 244 | CidrBlock: '10.200.0.0/16' 245 | Tags: 246 | - Key: Name 247 | Value: !Ref 'AWS::StackName' 248 | 249 | PrivateSubnet1: 250 | Type: AWS::EC2::Subnet 251 | Properties: 252 | AvailabilityZone: !Select [ 0, !GetAZs '' ] 253 | VpcId: !Ref 'Vpc' 254 | CidrBlock: '10.200.0.0/24' 255 | MapPublicIpOnLaunch: false 256 | Tags: 257 | - Key: Name 258 | Value: !Sub '${AWS::StackName} Private Subnet 1' 259 | 260 | PrivateSubnet2: 261 | Type: AWS::EC2::Subnet 262 | Properties: 263 | AvailabilityZone: !Select [ 1, !GetAZs '' ] 264 | VpcId: !Ref 'Vpc' 265 | CidrBlock: '10.200.1.0/24' 266 | MapPublicIpOnLaunch: false 267 | Tags: 268 | - Key: Name 269 | Value: !Sub '${AWS::StackName} Private Subnet 2' 270 | 271 | PrivateRouteTable: 272 | Type: AWS::EC2::RouteTable 273 | Properties: 274 | VpcId: !Ref 'Vpc' 275 | Tags: 276 | - Key: Name 277 | Value: !Sub '${AWS::StackName} Private Routes' 278 | 279 | PrivateRouteTableAssociation1: 280 | Type: AWS::EC2::SubnetRouteTableAssociation 281 | Properties: 282 | RouteTableId: !Ref PrivateRouteTable 283 | SubnetId: !Ref PrivateSubnet1 284 | 285 | PrivateRouteTableAssociation2: 286 | Type: AWS::EC2::SubnetRouteTableAssociation 287 | Properties: 288 | RouteTableId: !Ref PrivateRouteTable 289 | SubnetId: !Ref PrivateSubnet2 290 | 291 | PublicSubnet: 292 | Type: AWS::EC2::Subnet 293 | Properties: 294 | AvailabilityZone: !Select [ 0, !GetAZs '' ] 295 | VpcId: !Ref 'Vpc' 296 | CidrBlock: '10.200.10.0/24' 297 | MapPublicIpOnLaunch: true 298 | Tags: 299 | - Key: Name 300 | Value: !Sub '${AWS::StackName} Public Subnet' 301 | 302 | InternetGateway: 303 | Type: AWS::EC2::InternetGateway 304 | Properties: 305 | Tags: 306 | - Key: Name 307 | Value: !Ref 'AWS::StackName' 308 | 309 | GatewayAttachement: 310 | Type: AWS::EC2::VPCGatewayAttachment 311 | Properties: 312 | VpcId: !Ref 'Vpc' 313 | InternetGatewayId: !Ref 'InternetGateway' 314 | 315 | PublicRouteTable: 316 | Type: AWS::EC2::RouteTable 317 | Properties: 318 | VpcId: !Ref 'Vpc' 319 | Tags: 320 | - Key: Name 321 | Value: !Sub '${AWS::StackName} Public Routes' 322 | 323 | PublicRoute: 324 | Type: AWS::EC2::Route 325 | DependsOn: GatewayAttachement 326 | Properties: 327 | RouteTableId: !Ref 'PublicRouteTable' 328 | DestinationCidrBlock: '0.0.0.0/0' 329 | GatewayId: !Ref 'InternetGateway' 330 | 331 | PublicSubnetRouteTableAssociation: 332 | Type: AWS::EC2::SubnetRouteTableAssociation 333 | Properties: 334 | SubnetId: !Ref PublicSubnet 335 | RouteTableId: !Ref PublicRouteTable 336 | 337 | # ECS Resources 338 | EcsCluster: 339 | Type: AWS::ECS::Cluster 340 | 341 | EcsHostSecurityGroup: 342 | Type: AWS::EC2::SecurityGroup 343 | Properties: 344 | GroupName: !Ref AWS::StackName 345 | GroupDescription: Access to the ECS hosts that run containers 346 | VpcId: !Ref 'Vpc' 347 | SecurityGroupIngress: 348 | - CidrIp: !Ref SshIpRange 349 | Description: Allow SSH access via a specific IP Range 350 | IpProtocol: 'tcp' 351 | FromPort: 22 352 | ToPort: 22 353 | Tags: 354 | - Key: Name 355 | Value: !Sub '${AWS::StackName}-instances' 356 | 357 | # Specify this here instead of above so that we can use the "Condition" key 358 | EcsSecurityGroupIngressHorizon: 359 | Type: AWS::EC2::SecurityGroupIngress 360 | Condition: AllowHorizonAccess 361 | Properties: 362 | GroupId: !Ref 'EcsHostSecurityGroup' 363 | Description: Allow Horizon access via a specific IP Range 364 | CidrIp: !Ref HorizonIpRange 365 | IpProtocol: 'tcp' 366 | FromPort: !Ref 'HorizonPort' 367 | ToPort: !Ref 'HorizonPort' 368 | 369 | EcsSecurityGroupIngressFromSelf: 370 | Type: AWS::EC2::SecurityGroupIngress 371 | Properties: 372 | GroupId: !Ref 'EcsHostSecurityGroup' 373 | Description: Ingress from other hosts in the same security group 374 | IpProtocol: -1 375 | SourceSecurityGroupId: !Ref 'EcsHostSecurityGroup' 376 | 377 | # This launches the actual EC2 instance that will register as a member of the cluster and run the containers. 378 | EcsAutoScalingGroup: 379 | Type: AWS::AutoScaling::AutoScalingGroup 380 | DependsOn: 381 | - EfsMountTarget1 382 | - EfsMountTarget2 383 | Properties: 384 | VPCZoneIdentifier: 385 | - !Ref PublicSubnet 386 | LaunchConfigurationName: !Ref 'EcsLaunchConfiguration' 387 | MinSize: 1 388 | MaxSize: 2 # Allows capacity for AutoScalingReplacingUpdate 389 | DesiredCapacity: 1 390 | MetricsCollection: 391 | - Granularity: '1Minute' 392 | Tags: 393 | - Key: Name 394 | Value: !Ref 'AWS::StackName' 395 | PropagateAtLaunch: 'true' 396 | CreationPolicy: 397 | ResourceSignal: 398 | Timeout: PT10M 399 | UpdatePolicy: 400 | AutoScalingReplacingUpdate: 401 | WillReplace: 'true' 402 | 403 | EcsInstanceElasticIP: 404 | Type: AWS::EC2::EIP 405 | DependsOn: GatewayAttachement # Associating the IP with the vpc requires that we add this dependency explicitly 406 | Properties: 407 | Domain: vpc 408 | 409 | EcsLaunchConfiguration: 410 | Type: AWS::AutoScaling::LaunchConfiguration 411 | Properties: 412 | ImageId: !FindInMap [AwsRegionMap, !Ref 'AWS::Region', AMI] 413 | SecurityGroups: [!Ref 'EcsHostSecurityGroup'] 414 | InstanceType: !Ref 'InstanceType' 415 | IamInstanceProfile: !Ref 'Ec2InstanceProfile' 416 | InstanceMonitoring: false # Disable detailed monitoring at the instance level to avoid additional charges. ECS provides detailed CPU data 417 | KeyName: !Ref 'InstanceKeyPair' 418 | AssociatePublicIpAddress: 'true' # Will be replaced by elastic IP, but we need a public ip at the start to install packages 419 | UserData: 420 | Fn::Base64: !Sub 421 | - | 422 | Content-Type: multipart/mixed; boundary="==BOUNDARY==" 423 | MIME-Version: 1.0 424 | 425 | --==BOUNDARY== 426 | Content-Type: text/cloud-boothook; charset="us-ascii" 427 | 428 | ##### INSTALL TOOLS 429 | cloud-init-per once install_packages yum install -y aws-cli amazon-efs-utils aws-cfn-bootstrap awslogs jq 430 | cloud-init-per once install_postgres yum install -y postgresql96 # separate line to make it obvious when diffing other templates 431 | 432 | ##### MOUNT EFS FILESYSTEM 433 | # Mount EFS using amazon-efs-utils and save exit code to be used later by cfn-signal 434 | # Mounting is done in cloud-boothook to make sure it is done before docker/ecs starts 435 | cloud-init-per once mkdir_efs mkdir -p ${efsInstanceMountPoint} 436 | mount -t efs ${EfsFileSystem} ${efsInstanceMountPoint} 437 | echo -n $? > /var/log/efs-mount-exit-code.log 438 | 439 | --==BOUNDARY== 440 | Content-Type: text/x-shellscript; charset="us-ascii" 441 | #!/bin/bash 442 | 443 | ##### INSTALL SECURITY UPDATES 444 | # Do this here rather in cloud-boothook so we can see it easier in the logs on cloudwatch 445 | yum update -y --security 446 | 447 | ##### ASSIGN ELASTIC IP 448 | # Get the instance id from instance metadata and assign the elastic ip to this instance 449 | instance_id=$(curl http://169.254.169.254/latest/meta-data/instance-id) 450 | aws ec2 associate-address --instance-id $instance_id --allocation-id ${EcsInstanceElasticIP.AllocationId} --region ${AWS::Region} 451 | 452 | ##### SETUP INSTANCE LOGS 453 | # Set the region to send CloudWatch Logs data to the region where the container instance is located 454 | sed -i -e "s/region = us-east-1/region = ${AWS::Region}/g" /etc/awslogs/awscli.conf 455 | 456 | # Inject the CloudWatch Logs configuration file contents 457 | cat > /etc/awslogs/awslogs.conf <<- EOF 458 | [general] 459 | state_file = /var/lib/awslogs/agent-state 460 | 461 | [/var/log/messages] 462 | file = /var/log/messages 463 | log_group_name = ${instancesLogGroup} 464 | log_stream_name = {container_instance_id}/var/log/messages 465 | datetime_format = %b %d %H:%M:%S 466 | 467 | [/var/log/cfn-init.log] 468 | file = /var/log/cfn-init.log 469 | log_group_name = ${instancesLogGroup} 470 | log_stream_name = {container_instance_id}/var/log/cfn-init.log 471 | datetime_format = %Y-%m-%dT%H:%M:%SZ 472 | 473 | [/var/log/cloud-init-output.log] 474 | file = /var/log/cloud-init-output.log 475 | log_group_name = ${instancesLogGroup} 476 | log_stream_name = {container_instance_id}/var/log/cloud-init-output.log 477 | datetime_format = %Y-%m-%dT%H:%M:%SZ 478 | 479 | [/var/log/amazon/efs/mount.log] 480 | file = /var/log/amazon/efs/mount.log 481 | log_group_name = ${instancesLogGroup} 482 | log_stream_name = {container_instance_id}/var/log/amazon/efs/mount.log 483 | datetime_format = %Y-%m-%dT%H:%M:%SZ 484 | EOF 485 | 486 | # Get the instance id from instance metadata and replace the container instance ID placeholders with the actual values 487 | container_instance_id=$(curl http://169.254.169.254/latest/meta-data/instance-id) 488 | sed -i -e "s/{container_instance_id}/$container_instance_id/g" /etc/awslogs/awslogs.conf 489 | 490 | # Start/Enable awslogs service 491 | service awslogs start 492 | chkconfig awslogs on 493 | 494 | ##### ECS CLUSTER CONFIG 495 | # Update the cluster config with the cluster name 496 | echo ECS_CLUSTER=${EcsCluster} >> /etc/ecs/ecs.config 497 | 498 | ##### DB INIT 499 | # Wait for postgres. Note pg_isready works without user/pass but including them avoids login errors in the server logs 500 | export PGPASSWORD=${DbSuperUserPassword.Password} 501 | while ! pg_isready -h "${Database.Endpoint.Address}" -U "${dbSuperUser}" -p "${dbPort}" --quiet ; do 502 | echo "Waiting for postgres to be available..." 503 | sleep 5 504 | done 505 | 506 | # Check if stellar user and schemas have already been created. If not, create them. 507 | export PGPASSWORD=${DbAppPassword.Password} 508 | if psql -h "${Database.Endpoint.Address}" -U "${dbAppUser}" -p "${dbPort}" -c 'select 1' "${dbCoreSchema}" && \ 509 | psql -h "${Database.Endpoint.Address}" -U "${dbAppUser}" -p "${dbPort}" -c 'select 1' "${dbHorizonSchema}" 510 | then 511 | echo "User/Schema already exists" 512 | else 513 | echo "User/Schema does not exist. Creating." 514 | export PGPASSWORD=${DbSuperUserPassword.Password} 515 | psql -h "${Database.Endpoint.Address}" -U "${dbSuperUser}" -p "${dbPort}" -v ON_ERROR_STOP=1 <<-EOSQL 516 | CREATE USER ${dbAppUser} WITH PASSWORD '${DbAppPassword.Password}'; 517 | CREATE DATABASE ${dbCoreSchema}; 518 | CREATE DATABASE ${dbHorizonSchema}; 519 | GRANT ALL PRIVILEGES ON DATABASE ${dbCoreSchema} TO ${dbAppUser}; 520 | GRANT ALL PRIVILEGES ON DATABASE ${dbHorizonSchema} TO ${dbAppUser}; 521 | EOSQL 522 | # Note. The lack of indentation above is required to properly close the heredoc 523 | fi 524 | 525 | ##### SIGNAL ASG 526 | # Send a signal to the AutoScalingGroup with the return value of the EFS mount command to let it know creation completed 527 | efs_exit_code=$(cat /var/log/efs-mount-exit-code.log) 528 | /opt/aws/bin/cfn-signal -e $efs_exit_code --stack ${AWS::StackName} --resource EcsAutoScalingGroup --region ${AWS::Region} 529 | 530 | # The variables are substituted above in addition to other template resources and attributes like ${EcsCluster} 531 | - dbSuperUser: !FindInMap [CustomParamsMap, Get, DbSuperUser] 532 | dbAppUser: !FindInMap [CustomParamsMap, Get, DbAppUser] 533 | dbPort: !FindInMap [CustomParamsMap, Get, DbPort] 534 | dbCoreSchema: !FindInMap [CustomParamsMap, Get, DbCoreSchemaName] 535 | dbHorizonSchema: !FindInMap [CustomParamsMap, Get, DbHorizonSchemaName] 536 | efsInstanceMountPoint: !FindInMap [CustomParamsMap, Get, EfsInstanceMountPoint] 537 | instancesLogGroup: !Sub '/stellar/${AWS::StackName}-instances' # hardcoded in outputs also 538 | 539 | Ec2InstanceProfile: 540 | Type: AWS::IAM::InstanceProfile 541 | Properties: 542 | Path: / 543 | Roles: [!Ref 'Ec2Role'] 544 | 545 | # Role for the EC2 hosts. This allows the ECS agent on the EC2 hosts to communicate with the ECS control plane. 546 | Ec2Role: 547 | Type: AWS::IAM::Role 548 | Properties: 549 | Path: / 550 | ManagedPolicyArns: ['arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role'] 551 | Policies: # Additional permissions beyond AmazonEC2ContainerServiceforEC2Role used for instance logging 552 | - PolicyName: 'AllowInstanceLogs' 553 | PolicyDocument: 554 | Version: '2012-10-17' 555 | Statement: 556 | - Effect: Allow 557 | Action: [ 'logs:CreateLogGroup', 'logs:DescribeLogStreams' ] 558 | Resource: 'arn:aws:logs:*:*:*' 559 | - PolicyName: 'AssociateElasticIP' 560 | PolicyDocument: 561 | Version: '2012-10-17' 562 | Statement: 563 | - Effect: Allow 564 | Action: [ 'ec2:AllocateAddress', 'ec2:AssociateAddress', 'ec2:DescribeAddresses', 'ec2:DisassociateAddress' ] 565 | Resource: '*' 566 | AssumeRolePolicyDocument: 567 | Statement: 568 | - Effect: Allow 569 | Principal: 570 | Service: ['ec2.amazonaws.com'] 571 | Action: ['sts:AssumeRole'] 572 | 573 | # EFS 574 | EfsSecurityGroup: 575 | Type: AWS::EC2::SecurityGroup 576 | Properties: 577 | GroupDescription: EFS Security Groups 578 | VpcId: !Ref 'Vpc' 579 | SecurityGroupIngress: 580 | - SourceSecurityGroupId: !Ref EcsHostSecurityGroup 581 | Description: Allows ECS hosts to access the EFS 582 | IpProtocol: 'tcp' 583 | FromPort: !FindInMap [CustomParamsMap, Get, EfsPort] 584 | ToPort: !FindInMap [CustomParamsMap, Get, EfsPort] 585 | Tags: 586 | - Key: Name 587 | Value: !Sub ${AWS::StackName}-efs 588 | 589 | EfsFileSystem: 590 | Type: AWS::EFS::FileSystem 591 | Properties: 592 | Encrypted: false 593 | PerformanceMode: generalPurpose 594 | FileSystemTags: 595 | - Key: Name 596 | Value: !Sub ${AWS::StackName}-efs 597 | 598 | EfsMountTarget1: 599 | Type: AWS::EFS::MountTarget 600 | Properties: 601 | FileSystemId: !Ref EfsFileSystem 602 | SecurityGroups: 603 | - !Ref EfsSecurityGroup 604 | SubnetId: !Ref PrivateSubnet1 605 | 606 | EfsMountTarget2: 607 | Type: AWS::EFS::MountTarget 608 | Properties: 609 | FileSystemId: !Ref EfsFileSystem 610 | SecurityGroups: 611 | - !Ref EfsSecurityGroup 612 | SubnetId: !Ref PrivateSubnet2 613 | 614 | # DATABASE 615 | DatabaseSecurityGroup: 616 | Type: AWS::EC2::SecurityGroup 617 | Properties: 618 | GroupDescription: Access to the RDS DB 619 | VpcId: !Ref Vpc 620 | SecurityGroupIngress: 621 | - SourceSecurityGroupId: !Ref EcsHostSecurityGroup 622 | Description: Allow Postgress access from ECS hosts 623 | IpProtocol: 'tcp' 624 | FromPort: !FindInMap [CustomParamsMap, Get, DbPort] 625 | ToPort: !FindInMap [CustomParamsMap, Get, DbPort] 626 | Tags: 627 | - Key: Name 628 | Value: !Sub '${AWS::StackName}-db' 629 | 630 | DatabaseSubnetGroup: 631 | Type: AWS::RDS::DBSubnetGroup 632 | Properties: 633 | DBSubnetGroupDescription: Database subnet group 634 | SubnetIds: 635 | - !Ref PrivateSubnet1 636 | - !Ref PrivateSubnet2 637 | Tags: 638 | - Key: Name 639 | Value: !Ref 'AWS::StackName' 640 | 641 | Database: 642 | Type: AWS::RDS::DBInstance 643 | Properties: 644 | DBSubnetGroupName: !Ref DatabaseSubnetGroup 645 | VPCSecurityGroups: 646 | - !Ref DatabaseSecurityGroup 647 | Engine: !FindInMap [CustomParamsMap, Get, RdsDbEngine] 648 | EngineVersion: !Ref PostgresVersion 649 | MasterUsername: !FindInMap [CustomParamsMap, Get, DbSuperUser] # Hardcoded to make sure users never accidentally wipe out their db by changing it 650 | MasterUserPassword: !GetAtt DbSuperUserPassword.Password 651 | DBInstanceClass: !Ref DbInstanceType 652 | AllocatedStorage: !Ref DbStorage 653 | StorageType: gp2 654 | MultiAZ: false 655 | AvailabilityZone: !GetAtt PublicSubnet.AvailabilityZone # Ensure that the DB is in the same AZ as the instance to avoid inter-AZ charges 656 | CopyTagsToSnapshot: true 657 | Tags: 658 | - Key: Name 659 | Value: !Ref 'AWS::StackName' 660 | 661 | EcsService: 662 | Type: AWS::ECS::Service 663 | DependsOn: Database 664 | Properties: 665 | Cluster: !Ref EcsCluster 666 | DesiredCount: 1 667 | DeploymentConfiguration: 668 | MaximumPercent: 100 # Shutdown the old task before starting the new one, don't want the writing to efs/db simultaneously 669 | MinimumHealthyPercent: 0 670 | TaskDefinition: !Ref EcsTaskDefinition 671 | 672 | EcsTaskDefinition: 673 | Type: AWS::ECS::TaskDefinition 674 | Properties: 675 | Family: !Ref 'AWS::StackName' 676 | NetworkMode: host 677 | Volumes: 678 | - Name: efs-volume 679 | Host: 680 | SourcePath: !FindInMap [CustomParamsMap, Get, EfsInstanceMountPoint] 681 | ContainerDefinitions: 682 | - Name: 'core' 683 | Essential: true 684 | Image: !Sub 'starformlabs/stellar-core:${StellarCoreVersion}' 685 | MemoryReservation: !FindInMap [MemoryReservationMap, !Ref 'InstanceType', core] 686 | MountPoints: 687 | - SourceVolume: efs-volume 688 | ContainerPath: !FindInMap [CustomParamsMap, Get, ContainerDataDirectory] 689 | PortMappings: 690 | - ContainerPort: !FindInMap [CustomParamsMap, Get, StellarPeerPort] 691 | Environment: 692 | # Used by entry.sh 693 | - Name: DB_USER 694 | Value: !FindInMap [CustomParamsMap, Get, DbAppUser] 695 | - Name: DB_PASS 696 | Value: !GetAtt DbAppPassword.Password 697 | - Name: DB_NAME 698 | Value: !FindInMap [CustomParamsMap, Get, DbCoreSchemaName] 699 | - Name: DB_HOST 700 | Value: !GetAtt Database.Endpoint.Address 701 | - Name: DB_PORT 702 | Value: !FindInMap [CustomParamsMap, Get, DbPort] 703 | - Name: DATA_DIR 704 | Value: !FindInMap [CustomParamsMap, Get, ContainerDataDirectory] 705 | 706 | # Used by Stellar Core 707 | - Name: DATABASE 708 | Value: 709 | Fn::Sub: 710 | - 'postgresql://dbname=${db} host=${host} port=${port} user=${user} password=${pass}' 711 | - db: !FindInMap [CustomParamsMap, Get, DbCoreSchemaName] 712 | host: !GetAtt Database.Endpoint.Address 713 | port: !FindInMap [CustomParamsMap, Get, DbPort] 714 | user: !FindInMap [CustomParamsMap, Get, DbAppUser] 715 | pass: !GetAtt DbAppPassword.Password 716 | - Name: NETWORK_PASSPHRASE 717 | Value: !FindInMap [StellarConfigMap, Test, NetworkPassphrase] 718 | - Name: KNOWN_PEERS 719 | Value: !FindInMap [StellarConfigMap, Test, KnownPeers] 720 | - Name: BUCKET_DIR_PATH 721 | Value: !FindInMap [StellarConfigMap, All, BucketDir] 722 | - Name: UNSAFE_QUORUM 723 | Value: !FindInMap [StellarConfigMap, Test, UnsafeQuorum] 724 | - Name: FAILURE_SAFETY 725 | Value: !FindInMap [StellarConfigMap, Test, FailureSafety] 726 | - Name: CATCHUP_RECENT 727 | Value: !FindInMap [StellarConfigMap, All, CatchupRecent] 728 | - Name: LOG_FILE_PATH 729 | Value: !FindInMap [StellarConfigMap, All, LogFilePath] 730 | - Name: QUORUM_SET 731 | Value: !FindInMap [StellarConfigMap, Test, QuorumSet] 732 | - Name: HISTORY 733 | Value: !FindInMap [StellarConfigMap, Test, History] 734 | - Name: COMMANDS 735 | Value: !FindInMap [StellarConfigMap, Test, Commands] 736 | LogConfiguration: 737 | LogDriver: awslogs 738 | Options: 739 | awslogs-group: !Ref 'ContainersLogGroup' 740 | awslogs-region: !Ref 'AWS::Region' 741 | awslogs-stream-prefix: !Ref 'AWS::StackName' 742 | 743 | - Name: 'horizon' 744 | Essential: true 745 | Image: !Sub 'starformlabs/stellar-horizon:${HorizonVersion}' 746 | MemoryReservation: !FindInMap [MemoryReservationMap, !Ref 'InstanceType', horizon] 747 | MountPoints: 748 | - SourceVolume: efs-volume 749 | ContainerPath: !FindInMap [CustomParamsMap, Get, ContainerDataDirectory] 750 | PortMappings: 751 | - ContainerPort: !Ref HorizonPort 752 | Environment: 753 | # Used by entry.sh 754 | - Name: DB_USER 755 | Value: !FindInMap [CustomParamsMap, Get, DbAppUser] 756 | - Name: DB_PASS 757 | Value: !GetAtt DbAppPassword.Password 758 | - Name: DB_NAME 759 | Value: !FindInMap [CustomParamsMap, Get, DbHorizonSchemaName] 760 | - Name: DB_HOST 761 | Value: !GetAtt Database.Endpoint.Address 762 | - Name: DB_PORT 763 | Value: !FindInMap [CustomParamsMap, Get, DbPort] 764 | - Name: DATA_DIR 765 | Value: !FindInMap [CustomParamsMap, Get, ContainerDataDirectory] 766 | 767 | # Used by Horizon directly 768 | - Name: PORT # Horizon Port 769 | Value: !Ref HorizonPort 770 | - Name: DATABASE_URL 771 | Value: 772 | Fn::Sub: 773 | - 'postgres://${user}:${pass}@${host}:${port}/${db}?sslmode=disable' 774 | - db: !FindInMap [CustomParamsMap, Get, DbHorizonSchemaName] 775 | user: !FindInMap [CustomParamsMap, Get, DbAppUser] 776 | pass: !GetAtt DbAppPassword.Password 777 | host: !GetAtt Database.Endpoint.Address 778 | port: !FindInMap [CustomParamsMap, Get, DbPort] 779 | - Name: STELLAR_CORE_DATABASE_URL 780 | Value: 781 | Fn::Sub: 782 | - 'postgres://${user}:${pass}@${host}:${port}/${db}?sslmode=disable' 783 | - db: !FindInMap [CustomParamsMap, Get, DbCoreSchemaName] 784 | user: !FindInMap [CustomParamsMap, Get, DbAppUser] 785 | pass: !GetAtt DbAppPassword.Password 786 | host: !GetAtt Database.Endpoint.Address 787 | port: !FindInMap [CustomParamsMap, Get, DbPort] 788 | - Name: STELLAR_CORE_URL 789 | Value: 790 | Fn::Sub: 791 | - 'http://${host}:${port}' 792 | - host: 'localhost' # we are using host based networking so containers can access each other on localhost 793 | port: !FindInMap [CustomParamsMap, Get, StellarHttpPort] 794 | - Name: LOG_LEVEL 795 | Value: !FindInMap [HorizonConfigMap, Test, LogLevel] 796 | - Name: INGEST 797 | Value: !FindInMap [HorizonConfigMap, Test, Ingest] 798 | - Name: PER_HOUR_RATE_LIMIT 799 | Value: !FindInMap [HorizonConfigMap, Test, PerHourRateLimit] 800 | - Name: NETWORK_PASSPHRASE 801 | Value: !FindInMap [HorizonConfigMap, Test, NetworkPassphrase] 802 | LogConfiguration: 803 | LogDriver: awslogs 804 | Options: 805 | awslogs-group: !Ref 'ContainersLogGroup' 806 | awslogs-region: !Ref 'AWS::Region' 807 | awslogs-stream-prefix: !Ref 'AWS::StackName' 808 | 809 | DbSuperUserPassword: 810 | Type: 'AWS::CloudFormation::CustomResource' 811 | Properties: 812 | ServiceToken: !GetAtt PasswordGeneratorLambda.Arn 813 | ParameterNamePrefix: !Sub '/${AWS::StackName}/database/master-password' 814 | 815 | DbAppPassword: 816 | Type: 'AWS::CloudFormation::CustomResource' 817 | Properties: 818 | ServiceToken: !GetAtt PasswordGeneratorLambda.Arn 819 | ParameterNamePrefix: !Sub '/${AWS::StackName}/database/app-password' 820 | 821 | PasswordGeneratorLambda: 822 | Type: 'AWS::Lambda::Function' 823 | Properties: 824 | Description: 'Generates random password' 825 | Handler: 'index.handler' 826 | Role: !GetAtt PasswordGeneratorRole.Arn 827 | Code: 828 | ZipFile: | 829 | const response = require('cfn-response'); 830 | const AWS = require('aws-sdk'); 831 | const ssm = new AWS.SSM(); 832 | 833 | exports.handler = (event, context) => { 834 | if (event.RequestType == 'Delete') { 835 | // Remove param when CloudFormation deletes the resource. The param name is the PhysicalResourceId 836 | ssm.deleteParameter({ Name: event.PhysicalResourceId }).promise() 837 | .then((data) => { 838 | return response.send(event, context, response.SUCCESS, data); 839 | }).catch((err)=> { 840 | return response.send(event, context, response.FAILED, err); 841 | }); 842 | } 843 | else{ // Create or Update. Update (only happens when param name changes) will return a new physical id which will cause CF to delete the old one 844 | let responseData; 845 | 846 | new BackportedSecretsManager().getRandomPassword({ PasswordLength: 45, ExcludePunctuation: true }).promise() 847 | .then((data) => { 848 | const password = data.RandomPassword.substring(0, 32); // We only really wanted 32 chars for the password 849 | const randomString = data.RandomPassword.substring(32); // Last 13 used to add randomness to the SSM param name to avoid deletion on replacement 850 | const paramName = event.ResourceProperties.ParameterNamePrefix + '-' + randomString; 851 | 852 | responseData = { 853 | ParameterName: paramName, 854 | EncodedParameterName: encodeURIComponent(encodeURIComponent(paramName)), // Double encoded to work with AWS console 855 | Password: password 856 | } 857 | 858 | const params = { 859 | Name: paramName, 860 | Type: 'SecureString', 861 | Value: password, 862 | Overwrite: true 863 | }; 864 | return ssm.putParameter(params).promise(); 865 | }).then(() => { 866 | return response.send(event, context, response.SUCCESS, responseData, responseData.ParameterName); // Use param name as PhysicalResourceId 867 | }).catch((err)=> { 868 | return response.send(event, context, response.FAILED, err); 869 | }); 870 | } 871 | }; 872 | 873 | // The Nodejs SDK currently available to Lambda doesn't yet support the Secrets Manager API 874 | // This is the code from latest sdk required to support a minimal version of getRandomPassword() only 875 | 876 | const BackportedSecretsManager = AWS.Service.defineService('secretsmanager', ['2017-10-17']); 877 | AWS.apiLoader.services['secretsmanager'] = {}; 878 | Object.defineProperty(AWS.apiLoader.services['secretsmanager'], '2017-10-17', { 879 | get: function get() { return secretsmanagerModel; }, enumerable: true, configurable: true 880 | }); 881 | 882 | const secretsmanagerModel = { 883 | version: '2.0', 884 | metadata: { 885 | apiVersion: '2017-10-17', 886 | endpointPrefix: 'secretsmanager', 887 | jsonVersion: '1.1', 888 | protocol: 'json', 889 | serviceFullName: 'AWS Secrets Manager', 890 | serviceId: 'Secrets Manager', 891 | signatureVersion: 'v4', 892 | signingName: 'secretsmanager', 893 | targetPrefix: 'secretsmanager', 894 | uid: 'secretsmanager-2017-10-17' 895 | }, 896 | operations: { 897 | GetRandomPassword: { 898 | input: { type: 'structure', members: { PasswordLength: { type: 'long' }, ExcludePunctuation: { type: 'boolean' } } }, 899 | output: { type: 'structure', members: { RandomPassword: {} } } 900 | } 901 | } 902 | }; 903 | Runtime: 'nodejs6.10' 904 | Timeout: '30' 905 | 906 | PasswordGeneratorRole: 907 | Type: 'AWS::IAM::Role' 908 | Properties: 909 | Path: '/' 910 | ManagedPolicyArns: ['arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'] 911 | Policies: 912 | - PolicyName: 'GeneratePassword' 913 | PolicyDocument: 914 | Version: '2012-10-17' 915 | Statement: 916 | - Effect: Allow 917 | Action: 'secretsmanager:GetRandomPassword' 918 | Resource: '*' 919 | - PolicyName: 'CreateSsmParams' 920 | PolicyDocument: 921 | Version: '2012-10-17' 922 | Statement: 923 | - Effect: Allow 924 | Action: ['ssm:PutParameter', 'ssm:DeleteParameter', 'kms:Encrypt'] 925 | Resource: "*" 926 | AssumeRolePolicyDocument: 927 | Version: '2012-10-17' 928 | Statement: 929 | - Effect: Allow 930 | Principal: 931 | Service: 'lambda.amazonaws.com' 932 | Action: 'sts:AssumeRole' 933 | 934 | ContainersLogGroup: 935 | Type: AWS::Logs::LogGroup 936 | Properties: 937 | LogGroupName: !Sub '/stellar/${AWS::StackName}-containers' 938 | RetentionInDays: 7 939 | 940 | Outputs: 941 | SshUrl: 942 | Value: !Sub 'ssh://ec2-user@${EcsInstanceElasticIP}' 943 | HorizonUrl: 944 | Condition: AllowHorizonAccess 945 | Value: !Sub 'http://${EcsInstanceElasticIP}:${HorizonPort}' 946 | Database: 947 | Value: !Sub 'https://console.aws.amazon.com/rds/home?region=${AWS::Region}#dbinstance:id=${Database}' 948 | ContainersLogGroup: 949 | Value: !Sub 'https://console.aws.amazon.com/cloudwatch/home?region=${AWS::Region}#logStream:group=${ContainersLogGroup}' 950 | InstancesLogGroup: 951 | Value: !Sub 'https://console.aws.amazon.com/cloudwatch/home?region=${AWS::Region}#logStream:group=/stellar/${AWS::StackName}-instances' 952 | AutoScalingGroup: 953 | Value: !Sub 'https://console.aws.amazon.com/ec2/autoscaling/home?region=${AWS::Region}#AutoScalingGroups:id=${EcsAutoScalingGroup};view=instances' 954 | ECS: 955 | Value: !Sub 'https://console.aws.amazon.com/ecs/home?region=${AWS::Region}#/clusters/${EcsCluster}/services/${EcsService.Name}/events' 956 | DbSuperUser: 957 | Value: !FindInMap [CustomParamsMap, Get, DbSuperUser] 958 | DbSuperUserPassword: 959 | Value: !Sub 'https://console.aws.amazon.com/systems-manager/parameters/${DbSuperUserPassword.EncodedParameterName}/description?region=${AWS::Region}' 960 | DbAppUser: 961 | Value: !FindInMap [CustomParamsMap, Get, DbAppUser] 962 | DbAppPassword: 963 | Value: !Sub 'https://console.aws.amazon.com/systems-manager/parameters/${DbAppPassword.EncodedParameterName}/description?region=${AWS::Region}' --------------------------------------------------------------------------------