├── 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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
161 |
162 |
163 |
164 | Clicking on those links will show you more detailed logs for each system including consensus logging for core.
165 |
166 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
156 |
157 |
158 |
159 | Clicking on those links will show you more detailed logs for each system including consensus logging for core.
160 |
161 | 
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}'
--------------------------------------------------------------------------------