Add terraform/development for retrieving credentials for local development use

This commit is contained in:
Ryan Ahearn
2023-03-13 12:58:50 -04:00
parent 886db509a0
commit 627149402c
6 changed files with 238 additions and 36 deletions

View File

@@ -36,24 +36,39 @@ In addition to terraform directories in the api and admin apps above:
## Terraform
### Development
There are several remote services required for local development:
* s3
* ses
* sns
Credentials for these services are created by running:
1. `cd terraform/development`
1. `./run.sh`
This will append credentials to your `.env` file. You will need to manually clean up any prior runs from that file if you run that command again.
Offboarding: Service key bindings can be cleaned up from cloud.gov by running `./run.sh -d` yourself, or another developer running `./run.sh -d -u USER_TO_CLEANUP`
### Cloud.gov
The cloud.gov environment is configured with Terraform. See [the `terraform` folder](../terraform/) to learn about that.
## AWS
In addition to services provisioned through cloud.gov, we have several services provisioned directly in AWS. Our AWS services are currently located in the us-west-2 region using the tts-sandbox account. We plan to move to GovCloud shortly.
In addition to services provisioned through cloud.gov, we have several services provisioned via [supplemental service brokers](https://github.com/GSA/usnotify-ssb) in AWS. Our AWS services are currently located in [several regions](https://github.com/GSA/usnotify-ssb#aws-accounts-and-regions-in-use) using Studio-controlled AWS accounts.
To send messages, we use Amazon Web Services SNS and SES. In addition, we use AWS Pinpoint to provision and manage phone numbers, short codes, and long codes for sending SMS.
In SES, we are currently using the "sandbox" mode. This requires email addresses to be pre-registered in the AWS console in order to receive emails. The DKIM settings live under the verified domain entry.
In SNS, we have 3 topics for SMS receipts. These are not currently functional, so senders won't know the status of messages.
Through Pinpoint, the API needs at least one number so that the application itself can send SMS for authentication codes.
The API also has access to AWS S3 buckets for storing CSVs of messages and contact lists. It does not access a third S3 bucket that stores agency logos.
SES and SNS for use by the cloud.gov-deployed apps is currently in the process of migrating to being provisioned through cloud.gov. Currently, SES, SNS, and S3 for local-development are still manually provisioned in AWS.
## New Relic
We are using [New Relic](https://one.newrelic.com/nr1-core?account=3389907) for application monitoring and error reporting. When requesting access to New Relic, ask to be added to the Benefits-Studio subaccount.
@@ -62,32 +77,42 @@ We are using [New Relic](https://one.newrelic.com/nr1-core?account=3389907) for
- [ ] Join [the GSA GitHub org](https://github.com/GSA/GitHub-Administration#join-the-gsa-organization)
- [ ] Get permissions for the repos
- [ ] Get access to the cloud.gov org && space
- [ ] Get access to the cloud.gov org && spaces
- [ ] Get [access to AWS](https://handbook.tts.gsa.gov/launching-software/infrastructure/#cloud-service-provider-csp-sandbox-accounts), if necessary
- [ ] Get [access to New Relic](https://handbook.tts.gsa.gov/tools/new-relic/#how-do-i-get-access-to-new-relic), if necessary
- [ ] Pull down creds from cloud.gov and create the local .env file
- [ ] Create the local `.env` file by copying `sample.env` and running `./run.sh` within the `terraform/development` folder
- [ ] Do stuff!
## Setting up the infrastructure
These steps are required for new cloud.gov environments. Local development borrows SES & SNS infrastructure from the `notify-staging` cloud.gov space, so these steps are not required for new developers.
### Steps to prepare SES
1. After the first deploy of the application with the SSB-brokered SES service completes:
1. Log into the SES console and navigate to the SNS subscription page.
2. Select "Request confirmation" for any subscriptions still in "Pending Confirmation" state
2. (For sandbox SES accounts) Go to SES console for \$AWS_REGION and create new origin and destination emails. AWS will send a verification via email which you'll need to complete.
3. Find and replace instances in the repo of "testsender", "testreceiver" and "dispostable.com", with your origin and destination email addresses, which you verified in step 1 above.
1. Select "Request confirmation" for any subscriptions still in "Pending Confirmation" state
1. Find and replace instances in the repo of "testsender", "testreceiver" and "dispostable.com", with your origin and destination email addresses, which you verified in step 1 above.
TODO: create env vars for these origin and destination email addresses for the root service, and create new migrations to update postgres seed fixtures
### Steps to prepare SNS
1. Go to Pinpoints console for \$AWS_PINPOINT_REGION and choose "create new project", then "configure for sms"
2. Tick the box at the top to enable SMS, choose "transactional" as the default type and save
3. In the lefthand sidebar, go the "SMS and Voice" (bottom) and choose "Phone Numbers"
4. Under "Number Settings" choose "Request Phone Number"
5. Choose Toll-free number, tick SMS, untick Voice, choose "transactional", hit next and then "request"
6. Go to SNS console for \$AWS_PINPOINT_REGION, look at lefthand sidebar under "Mobile" and go to "Text Messaging (SMS)"
7. Scroll down to "Sandbox destination phone numbers" and tap "Add phone number" then follow the steps to verify (you'll need to be able to retrieve a code sent to each number)
#### Move SNS out of sandbox.
At this point, you _should_ be able to complete both the email and phone verification steps of the Notify user sign up process! 🎉
1. Visit the SNS console for the region you will be sending from. Notes:
1. SNS settings are per-region, so each environment must have its own region
1. Pinpoint and SNS have confusing regional availability, so ensure both are available before submitting any requests.
1. Choose `Text messaging (SMS)` from the sidebar
1. Click the `Exit SMS Sandbox` button and submit the support request. This request should take at most a day to complete. Be sure to request a higher sending limit at the same time.
#### Request new phone numbers
1. Go to Pinpoint console for the same region you are using SNS in.
1. In the lefthand sidebar, go the `SMS and Voice` (bottom) and choose `Phone Numbers`
1. Under `Number Settings` choose `Request Phone Number`
1. Choose Toll-free number, tick SMS, untick Voice, choose `transactional`, hit next and then `request`
1. Select `Toll-free registrations` and `Create registration`
1. Select the number you just created and then `Register existing toll-free number`
1. Complete and submit the form. Approval usually takes about 2 weeks.
1. Set this phone number as the `AWS_US_TOLL_FREE_NUMBER` in the environment you are creating

View File

@@ -1,14 +1,14 @@
# STEPS TO SET UP
#
# 1. Pull down AWS creds from cloud.gov using `cf env`, then update AWS section
# 1. Copy this file to `.env`
#
# 2. Update NEW_RELIC_LICENSE_KEY
# 2. Update NEW_RELIC_LICENSE_KEY, if you are testing new relic integrations in development
#
# 3. Uncomment either the Docker setup or the direct setup
#
# 4. Comment out the other setup
#
# 5. If needed, set `NOTIFY_EMAIL_DOMAIN` with the domain your emails will come from (i.e. the "origination email" in your SES project)
# 5. Run `cd terraform/development; ./run.sh` to include service credentials in `.env`
#
# ## REBUILD THE DEVCONTAINER WHEN YOU MODIFY .ENV ###
@@ -16,26 +16,23 @@
#############################################################
# AWS
AWS_ACCESS_KEY_ID="don't write secrets to the sample file"
AWS_SECRET_ACCESS_KEY="don't write secrets to the sample file"
AWS_REGION=us-west-2
AWS_US_TOLL_FREE_NUMBER=+18446120782
AWS_US_TOLL_FREE_NUMBER=+18556438890
#############################################################
# Local Docker setup, all overwritten in cloud.gov
ADMIN_BASE_URL=http://admin:6012
API_HOST_NAME=http://dev:6011
REDIS_URL=redis://redis:6380
DATABASE_URL=postgresql://postgres:chummy@db:5432/notification_api
SQLALCHEMY_DATABASE_TEST_URI=postgresql://postgres:chummy@db:5432/test_notification_api
# ADMIN_BASE_URL=http://admin:6012
# API_HOST_NAME=http://dev:6011
# REDIS_URL=redis://redis:6380
# DATABASE_URL=postgresql://postgres:chummy@db:5432/notification_api
# SQLALCHEMY_DATABASE_TEST_URI=postgresql://postgres:chummy@db:5432/test_notification_api
# Local direct setup, all overwritten in cloud.gov
# ADMIN_BASE_URL=http://localhost:6012
# API_HOST_NAME=http://localhost:6011
# REDIS_URL=redis://localhost:6379
# DATABASE_URL=postgresql://localhost:5432/notification_api
# SQLALCHEMY_DATABASE_TEST_URI=postgresql://localhost:5432/test_notification_api
ADMIN_BASE_URL=http://localhost:6012
API_HOST_NAME=http://localhost:6011
REDIS_URL=redis://localhost:6379
DATABASE_URL=postgresql://localhost:5432/notification_api
SQLALCHEMY_DATABASE_TEST_URI=postgresql://localhost:5432/test_notification_api
#############################################################
@@ -47,7 +44,6 @@ NOTIFY_ENVIRONMENT=development
STATSD_HOST=localhost
SES_STUB_URL=None
NOTIFY_APP_NAME=api
# NOTIFY_EMAIL_DOMAIN=notify.sandbox.10x.gsa.gov
#############################################################

View File

@@ -0,0 +1,91 @@
locals {
cf_org_name = "gsa-tts-benefits-studio-prototyping"
cf_space_name = "notify-local-dev"
recursive_delete = true
key_name = "${var.username}-dev-key"
}
module "csv_upload_bucket" {
source = "github.com/18f/terraform-cloudgov//s3?ref=v0.2.0"
cf_org_name = local.cf_org_name
cf_space_name = local.cf_space_name
recursive_delete = local.recursive_delete
name = "${var.username}-csv-upload-bucket"
}
resource "cloudfoundry_service_key" "csv_key" {
name = local.key_name
service_instance = module.csv_upload_bucket.bucket_id
}
module "contact_list_bucket" {
source = "github.com/18f/terraform-cloudgov//s3?ref=v0.2.0"
cf_org_name = local.cf_org_name
cf_space_name = local.cf_space_name
recursive_delete = local.recursive_delete
name = "${var.username}-contact-list-bucket"
}
resource "cloudfoundry_service_key" "contact_list_key" {
name = local.key_name
service_instance = module.contact_list_bucket.bucket_id
}
data "cloudfoundry_space" "staging" {
org_name = local.cf_org_name
name = "notify-staging"
}
data "cloudfoundry_service_instance" "ses_email" {
name_or_id = "notify-api-ses-staging"
space = data.cloudfoundry_space.staging.id
}
resource "cloudfoundry_service_key" "ses_key" {
name = local.key_name
service_instance = data.cloudfoundry_service_instance.ses_email.id
}
data "cloudfoundry_service_instance" "sns_sms" {
name_or_id = "notify-api-sns-staging"
space = data.cloudfoundry_space.staging.id
}
resource "cloudfoundry_service_key" "sns_key" {
name = local.key_name
service_instance = data.cloudfoundry_service_instance.sns_sms.id
}
locals {
credentials = <<EOM
#############################################################
# CSV_UPLOAD_BUCKET
CSV_BUCKET_NAME=${cloudfoundry_service_key.csv_key.credentials.bucket}
CSV_AWS_ACCESS_KEY_ID=${cloudfoundry_service_key.csv_key.credentials.access_key_id}
CSV_AWS_SECRET_ACCESS_KEY=${cloudfoundry_service_key.csv_key.credentials.secret_access_key}
CSV_AWS_REGION=${cloudfoundry_service_key.csv_key.credentials.region}
# CONTACT_LIST_BUCKET
CONTACT_BUCKET_NAME=${cloudfoundry_service_key.contact_list_key.credentials.bucket}
CONTACT_AWS_ACCESS_KEY_ID=${cloudfoundry_service_key.contact_list_key.credentials.access_key_id}
CONTACT_AWS_SECRET_ACCESS_KEY=${cloudfoundry_service_key.contact_list_key.credentials.secret_access_key}
CONTACT_AWS_REGION=${cloudfoundry_service_key.contact_list_key.credentials.region}
# SES_EMAIL
SES_AWS_ACCESS_KEY_ID=${cloudfoundry_service_key.ses_key.credentials.smtp_user}
SES_AWS_SECRET_ACCESS_KEY=${cloudfoundry_service_key.ses_key.credentials.secret_access_key}
SES_AWS_REGION=${cloudfoundry_service_key.ses_key.credentials.region}
SES_DOMAIN_ARN=${cloudfoundry_service_key.ses_key.credentials.domain_arn}
# SNS_SMS
SNS_AWS_ACCESS_KEY_ID=${cloudfoundry_service_key.sns_key.credentials.aws_access_key_id}
SNS_AWS_SECRET_ACCESS_KEY=${cloudfoundry_service_key.sns_key.credentials.aws_secret_access_key}
SNS_AWS_REGION=${cloudfoundry_service_key.sns_key.credentials.region}
EOM
}
resource "null_resource" "output_creds_to_env" {
triggers = {
always_run = timestamp()
}
provisioner "local-exec" {
working_dir = "../.."
command = "echo \"${local.credentials}\" >> .env"
}
}

View File

@@ -0,0 +1,16 @@
terraform {
required_version = "~> 1.0"
required_providers {
cloudfoundry = {
source = "cloudfoundry-community/cloudfoundry"
version = "0.50.5"
}
}
}
provider "cloudfoundry" {
api_url = "https://api.fr.cloud.gov"
user = var.cf_user
password = var.cf_password
app_logs_max = 30
}

69
terraform/development/run.sh Executable file
View File

@@ -0,0 +1,69 @@
#!/usr/bin/env bash
username=`whoami`
org="gsa-tts-benefits-studio-prototyping"
usage="
$0: Create development infrastructure
Usage:
$0 -h
$0 [-u <USER NAME>] [-k]
Options:
-h: show help and exit
-u <USER NAME>: your username. Default: $username
-k: keep service user. Default is to remove them after run
-d: Destroy development resources. Default is to create them
Notes:
* Requires cf-cli@8
"
action="apply"
creds="remove"
while getopts ":hkdu:" opt; do
case "$opt" in
u)
username=${OPTARG}
;;
k)
creds="keep"
;;
d)
action="destroy"
;;
h)
echo "$usage"
exit 0
;;
esac
done
set -e
service_account="$username-terraform"
if [[ ! -f "secrets.auto.tfvars" ]]; then
# create user in notify-local-dev space to create s3 buckets
../create_service_account.sh -s notify-local-dev -u $service_account > secrets.auto.tfvars
# grant user access to notify-staging to create a service key for SES and SNS
cg_username=`cf service-key $service_account service-account-key | tail -n +2 | jq -r '.credentials.username'`
cf set-space-role $cg_username $org notify-staging SpaceDeveloper
fi
set +e
terraform init
terraform $action -var="username=$username"
set -e
if [[ $creds = "remove" ]]; then
../destroy_service_account.sh -s notify-local-dev -u $service_account
rm secrets.auto.tfvars
fi
exit 0

View File

@@ -0,0 +1,5 @@
variable "cf_password" {
sensitive = true
}
variable "cf_user" {}
variable "username" {}