Skip to content

Commit 0268515

Browse files
Integrate AWS Parameter Store for Zappa/Infrastructure deployments (#2551)
* Use AWS SSM Parameter Store to handle environment variables * Use focused policy for read access * Update documentation * Add flag for create_rds_proxy * set default value of create_rds_proxy to false * Populate Zappa/Lambda environment variables from ssm/parameter store * Update documentation * Update example * add default configurations * add security group db from lambda
1 parent 2175602 commit 0268515

File tree

23 files changed

+494
-330
lines changed

23 files changed

+494
-330
lines changed

backend/settings/staging.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@ class Staging(Base):
1717
traces_sample_rate=0.5,
1818
)
1919

20-
AWS_ACCESS_KEY_ID = values.SecretValue()
21-
AWS_SECRET_ACCESS_KEY = values.SecretValue()
2220
AWS_STORAGE_BUCKET_NAME = "owasp-nest"
2321
AWS_S3_CUSTOM_DOMAIN = f"{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com"
2422
AWS_S3_OBJECT_PARAMETERS = {

backend/wsgi.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,30 @@
22

33
import os
44

5+
6+
def _populate_environ_from_ssm():
7+
ssm_param_path = os.getenv("AWS_SYSTEMS_MANAGER_PARAM_STORE_PATH")
8+
if not ssm_param_path:
9+
return
10+
11+
from pathlib import Path
12+
13+
import boto3
14+
15+
client = boto3.client("ssm")
16+
paginator = client.get_paginator("get_parameters_by_path")
17+
response_iterator = paginator.paginate(Path=ssm_param_path, WithDecryption=True)
18+
19+
for page in response_iterator:
20+
for param in page["Parameters"]:
21+
os.environ[Path(param["Name"]).name] = param["Value"]
22+
23+
24+
_populate_environ_from_ssm()
25+
526
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings.local")
627
os.environ.setdefault("DJANGO_CONFIGURATION", "Local")
728

8-
from configurations.wsgi import get_wsgi_application
29+
from configurations.wsgi import get_wsgi_application # noqa: E402
930

1031
application = get_wsgi_application()

backend/zappa_settings.example.json

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,23 @@
11
{
22
"staging": {
33
"app_function": "wsgi.application",
4-
"django_settings": "settings.staging",
5-
"environment_variables": {
6-
"DJANGO_ALGOLIA_APPLICATION_ID": "${DJANGO_ALGOLIA_APPLICATION_ID}",
7-
"DJANGO_ALGOLIA_WRITE_API_KEY": "${DJANGO_ALGOLIA_WRITE_API_KEY}",
8-
"DJANGO_ALLOWED_HOSTS": "${DJANGO_ALLOWED_HOSTS}",
9-
"DJANGO_AWS_ACCESS_KEY_ID": "${DJANGO_AWS_ACCESS_KEY_ID}",
10-
"DJANGO_AWS_SECRET_ACCESS_KEY": "${DJANGO_AWS_SECRET_ACCESS_KEY}",
11-
"DJANGO_CONFIGURATION": "Staging",
12-
"DJANGO_DB_HOST": "${DJANGO_DB_HOST}",
13-
"DJANGO_DB_NAME": "${DJANGO_DB_NAME}",
14-
"DJANGO_DB_USER": "${DJANGO_DB_USER}",
15-
"DJANGO_DB_PORT": "${DJANGO_DB_PORT}",
16-
"DJANGO_DB_PASSWORD": "${DJANGO_DB_PASSWORD}",
17-
"DJANGO_OPEN_AI_SECRET_KEY": "${DJANGO_OPEN_AI_SECRET_KEY}",
18-
"DJANGO_REDIS_HOST": "${DJANGO_REDIS_HOST}",
19-
"DJANGO_REDIS_PASSWORD": "${DJANGO_REDIS_PASSWORD}",
20-
"DJANGO_SECRET_KEY": "${DJANGO_SECRET_KEY}",
21-
"DJANGO_SENTRY_DSN": "${DJANGO_SENTRY_DSN}",
22-
"DJANGO_SLACK_BOT_TOKEN": "${DJANGO_SLACK_BOT_TOKEN}",
23-
"DJANGO_SLACK_SIGNING_SECRET": "${DJANGO_SLACK_SIGNING_SECRET}"
4+
"aws_environment_variables": {
5+
"AWS_SYSTEMS_MANAGER_PARAM_STORE_PATH": "/owasp-nest/staging"
246
},
7+
"django_settings": "settings.staging",
8+
"extra_permissions": [
9+
{
10+
"Effect": "Allow",
11+
"Action": [
12+
"ssm:GetParametersByPath",
13+
"ssm:GetParameter"
14+
],
15+
"Resource": [
16+
"arn:aws:ssm:${AWS_REGION}:${AWS_ACCOUNT_ID}:parameter/owasp-nest/staging",
17+
"arn:aws:ssm:${AWS_REGION}:${AWS_ACCOUNT_ID}:parameter/owasp-nest/staging/*"
18+
]
19+
}
20+
],
2521
"manage_roles": true,
2622
"project_name": "nest-backend",
2723
"runtime": "python3.13",

infrastructure/README.md

Lines changed: 14 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,6 @@ Follow these steps to set up the infrastructure:
3939
cat terraform.tfvars.example > terraform.tfvars
4040
```
4141

42-
- Update the default `django_` prefixed variables. (database/redis credentials will be added later)
43-
4442
3. **Apply Changes**:
4543

4644
- Init terraform if needed:
@@ -55,51 +53,11 @@ Follow these steps to set up the infrastructure:
5553
terraform apply
5654
```
5755

58-
4. **Copy Outputs**:
59-
60-
- Run the following command to view all outputs. Use the `-raw` flag for sensitive outputs.
61-
- Copy required outputs (i.e. `database_endpoint`, `db_password`, `redis_auth_token`, and `redis_endpoint`)
62-
to the previously created `terraform.tfvars`:
56+
4. **Populate Secrets**:
6357

64-
```bash
65-
terraform output
66-
```
58+
- Visit the AWS Console > Systems Manager > Parameter Store.
59+
- Populate all `DJANGO_*` secrets that have `to-be-set-in-aws-console` value.
6760

68-
Example Output:
69-
70-
```bash
71-
database_endpoint = "owasp-nest-staging-proxy.proxy-000000000000.us-east-2.rds.amazonaws.com"
72-
db_password = <sensitive>
73-
ecr_repository_url = "000000000000.dkr.ecr.us-east-2.amazonaws.com/owasp-nest-staging-backend"
74-
lambda_security_group_id = "sg-00000000000000000"
75-
private_subnet_ids = [
76-
"subnet-00000000000000000",
77-
"subnet-11111111111111111",
78-
"subnet-22222222222222222",
79-
]
80-
redis_auth_token = <sensitive>
81-
redis_endpoint = "master.owasp-nest-staging-cache.aaaaaa.region1.cache.amazonaws.com"
82-
zappa_s3_bucket = "owasp-nest-zappa-deployments"
83-
```
84-
85-
```bash
86-
terraform output -raw db_password
87-
```
88-
89-
```bash
90-
terraform output -raw redis_auth_token
91-
```
92-
93-
5. **Apply The Changes Again**:
94-
95-
- Apply the changes again using the following command:
96-
97-
```bash
98-
terraform apply
99-
```
100-
101-
*Note*: Step 4 and 5 ensure that ECS/Fargate tasks have proper environment variables.
102-
These two steps will be removed when AWS Secrets Manager is integrated.
10361

10462
## Setting up Zappa
10563

@@ -145,11 +103,15 @@ The Django backend deployment is managed by Zappa. This includes the API Gateway
145103

146104
5. **Deploy**:
147105

106+
- *Note*: Make sure to populate all `DJANGO_*` secrets that are set as `to-be-set-in-aws-console`
107+
in the Parameter Store. The deployment might fail with no logs if secrets such as
108+
`DJANGO_SLACK_BOT_TOKEN` are invalid.
109+
148110
```bash
149111
zappa deploy staging
150112
```
151113

152-
Once deployed, Zappa will provide you with a URL. You can use this URL to test the API.
114+
Once deployed, use the URL provided by Zappa to test the API.
153115

154116
## Setup Database
155117

@@ -224,6 +186,12 @@ Migrate and load data into the new database.
224186

225187
## Helpful Commands
226188

189+
- To view logs for a `staging` deployment run:
190+
191+
```bash
192+
zappa tail staging
193+
```
194+
227195
- To update a Zappa `staging` deployment run:
228196

229197
```bash

infrastructure/main.tf

Lines changed: 25 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -19,26 +19,6 @@ locals {
1919
ManagedBy = "Terraform"
2020
Project = var.project_name
2121
}
22-
django_environment_variables = {
23-
DJANGO_ALGOLIA_APPLICATION_ID = var.django_algolia_application_id
24-
DJANGO_ALGOLIA_WRITE_API_KEY = var.django_algolia_write_api_key
25-
DJANGO_ALLOWED_HOSTS = var.django_allowed_hosts
26-
DJANGO_AWS_ACCESS_KEY_ID = var.django_aws_access_key_id
27-
DJANGO_AWS_SECRET_ACCESS_KEY = var.django_aws_secret_access_key
28-
DJANGO_CONFIGURATION = var.django_configuration
29-
DJANGO_DB_HOST = var.django_db_host
30-
DJANGO_DB_NAME = var.django_db_name
31-
DJANGO_DB_USER = var.django_db_user
32-
DJANGO_DB_PORT = var.django_db_port
33-
DJANGO_DB_PASSWORD = var.django_db_password
34-
DJANGO_OPEN_AI_SECRET_KEY = var.django_open_ai_secret_key
35-
DJANGO_REDIS_HOST = var.django_redis_host
36-
DJANGO_REDIS_PASSWORD = var.django_redis_password
37-
DJANGO_SECRET_KEY = var.django_secret_key
38-
DJANGO_SENTRY_DSN = var.django_sentry_dsn
39-
DJANGO_SLACK_BOT_TOKEN = var.django_slack_bot_token
40-
DJANGO_SLACK_SIGNING_SECRET = var.django_slack_signing_secret
41-
}
4222
}
4323

4424
module "cache" {
@@ -47,7 +27,6 @@ module "cache" {
4727
common_tags = local.common_tags
4828
environment = var.environment
4929
project_name = var.project_name
50-
redis_auth_token = var.redis_auth_token
5130
redis_engine_version = var.redis_engine_version
5231
redis_node_type = var.redis_node_type
5332
redis_num_cache_nodes = var.redis_num_cache_nodes
@@ -60,6 +39,7 @@ module "database" {
6039
source = "./modules/database"
6140

6241
common_tags = local.common_tags
42+
create_rds_proxy = var.create_rds_proxy
6343
db_allocated_storage = var.db_allocated_storage
6444
db_backup_retention_period = var.db_backup_retention_period
6545
db_engine_version = var.db_engine_version
@@ -68,7 +48,7 @@ module "database" {
6848
db_password = var.db_password
6949
db_storage_type = var.db_storage_type
7050
db_subnet_ids = module.networking.private_subnet_ids
71-
db_username = var.db_username
51+
db_user = var.db_user
7252
environment = var.environment
7353
project_name = var.project_name
7454
proxy_security_group_ids = [module.security.rds_proxy_sg_id]
@@ -80,7 +60,7 @@ module "ecs" {
8060

8161
aws_region = var.aws_region
8262
common_tags = local.common_tags
83-
django_environment_variables = local.django_environment_variables
63+
container_parameters_arns = module.parameters.ssm_parameter_arns
8464
environment = var.environment
8565
fixtures_read_only_policy_arn = module.storage.fixtures_read_only_policy_arn
8666
fixtures_s3_bucket = var.fixtures_s3_bucket
@@ -101,15 +81,31 @@ module "networking" {
10181
vpc_cidr = var.vpc_cidr
10282
}
10383

84+
module "parameters" {
85+
source = "./modules/parameters"
86+
87+
common_tags = local.common_tags
88+
db_host = module.database.db_proxy_endpoint
89+
db_name = var.db_name
90+
db_password = module.database.db_password
91+
db_port = var.db_port
92+
db_user = var.db_user
93+
environment = var.environment
94+
project_name = var.project_name
95+
redis_host = module.cache.redis_primary_endpoint
96+
redis_password = module.cache.redis_auth_token
97+
}
98+
10499
module "security" {
105100
source = "./modules/security"
106101

107-
common_tags = local.common_tags
108-
db_port = var.db_port
109-
environment = var.environment
110-
project_name = var.project_name
111-
redis_port = var.redis_port
112-
vpc_id = module.networking.vpc_id
102+
common_tags = local.common_tags
103+
create_rds_proxy = var.create_rds_proxy
104+
db_port = var.db_port
105+
environment = var.environment
106+
project_name = var.project_name
107+
redis_port = var.redis_port
108+
vpc_id = module.networking.vpc_id
113109
}
114110

115111
module "storage" {

infrastructure/modules/cache/main.tf

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,9 @@ terraform {
1414
}
1515

1616
locals {
17-
generate_redis_auth_token = var.redis_auth_token == null || var.redis_auth_token == ""
18-
parameter_group_name = "default.redis${local.redis_major_version}"
19-
redis_auth_token = local.generate_redis_auth_token ? random_password.redis_auth_token[0].result : var.redis_auth_token
20-
redis_major_version = split(".", var.redis_engine_version)[0]
17+
parameter_group_name = "default.redis${local.redis_major_version}"
18+
redis_auth_token = random_password.redis_auth_token[0].result
19+
redis_major_version = split(".", var.redis_engine_version)[0]
2120
}
2221

2322
resource "aws_elasticache_subnet_group" "main" {
@@ -29,7 +28,7 @@ resource "aws_elasticache_subnet_group" "main" {
2928
}
3029

3130
resource "random_password" "redis_auth_token" {
32-
count = local.generate_redis_auth_token ? 1 : 0
31+
count = 1
3332
length = 32
3433
# Redis auth token has specific requirements for special characters.
3534
override_special = "!&#$^<>-"

infrastructure/modules/cache/variables.tf

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,6 @@ variable "project_name" {
2626
type = string
2727
}
2828

29-
variable "redis_auth_token" {
30-
description = "The auth token for Redis"
31-
type = string
32-
sensitive = true
33-
default = null
34-
}
35-
3629
variable "redis_engine_version" {
3730
description = "The version of the Redis engine"
3831
type = string

0 commit comments

Comments
 (0)