Skip to content

Commit 932c4a4

Browse files
Add Remote State Management (#2699)
* add remote state management * refactor staging into its own directory * change steps to account for staging * bot suggestions * sort alphabetically * Update code * Update documentation * update formatting * update code
1 parent 52cacc7 commit 932c4a4

File tree

15 files changed

+323
-62
lines changed

15 files changed

+323
-62
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,11 @@ __pycache__
2525
*.log
2626
*.pdf
2727
*.pem
28+
*.tfbackend
2829
*.tfstate
2930
*.tfstate.*
31+
*.tfvars
32+
!*.tfvars.example
3033
**/.terraform/
3134
backend/*nest-backend-dev*.tar.gz
3235
backend/*nest-backend-dev*.zip
@@ -46,7 +49,6 @@ frontend/pnpm-debug.log*
4649
frontend/test-results/
4750
frontend/yarn-debug.log*
4851
frontend/yarn-error.log*
49-
infrastructure/terraform.tfvars
5052
logs
5153
node_modules/
5254
TODO

cspell/custom-dict.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ slackbot
127127
slideshare
128128
speakerdeck
129129
superfences
130+
tfbackend
130131
tiktok
131132
tsc
132133
turbopack

infrastructure/README.md

Lines changed: 40 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -15,45 +15,56 @@ Ensure you have the following setup/installed:
1515

1616
Follow these steps to set up the infrastructure:
1717

18-
1. **Change the Directory**:
18+
1. **Setup Backend (one-time setup)**:
1919

20-
- Change the directory using the following command:
21-
22-
```bash
23-
cd infrastructure/
24-
```
25-
26-
*Note*: The following steps assume the current working directory is `infrastructure/`
27-
28-
2. **Create Variables File**:
20+
- Navigate to the backend directory:
21+
```bash
22+
cd infrastructure/backend/
23+
```
24+
*Note:* Optionally change the region: set `aws_region` in a `.tfvars` file.
2925

30-
- Create a local variables file in the `infrastructure` directory:
26+
- Initialize Terraform if needed:
27+
```bash
28+
terraform init
29+
```
3130

32-
```bash
33-
touch terraform.tfvars
34-
```
31+
- Apply the changes to create the backend resources:
32+
```bash
33+
terraform apply
34+
```
3535

36-
- Copy the contents from the template file into your new local environment file:
36+
2. **Setup Main Infrastructure (staging)**:
3737

38-
```bash
39-
cat terraform.tfvars.example > terraform.tfvars
40-
```
38+
- Navigate to the main infrastructure directory. If you are in `infrastructure/backend`, you can use:
39+
```bash
40+
cd ../staging/
41+
```
4142

42-
3. **Apply Changes**:
43+
- Create a local variables file:
44+
```bash
45+
touch terraform.tfvars
46+
```
4347

44-
- Init terraform if needed:
48+
- Copy the contents from the example file:
49+
```bash
50+
cat terraform.tfvars.example > terraform.tfvars
51+
```
4552

46-
```bash
47-
terraform init
48-
```
53+
- *Note:* Optionally change the region:
54+
- set `aws_region` in a `.tfvars` file.
55+
- set `region` in a `.tfbackend` file and provide it using `terraform init -backend-config=<file>`.
4956

50-
- Apply the changes and create the infrastructure using the following command:
57+
- Initialize Terraform with the backend configuration:
58+
```bash
59+
terraform init
60+
```
5161

52-
```bash
53-
terraform apply
54-
```
62+
- Apply the changes to create the main infrastructure using the command:
63+
```bash
64+
terraform apply
65+
```
5566

56-
4. **Populate Secrets**:
67+
3. **Populate Secrets**
5768

5869
- Visit the AWS Console > Systems Manager > Parameter Store.
5970
- Populate all `DJANGO_*` secrets that have `to-be-set-in-aws-console` value.
@@ -68,7 +79,7 @@ The Django backend deployment is managed by Zappa. This includes the API Gateway
6879
- Change the directory to `backend/` using the following command:
6980

7081
```bash
71-
cd ../backend/
82+
cd ../../backend/
7283
```
7384

7485
*Note*: The following steps assume the current working directory is `backend/`

infrastructure/backend/.terraform.lock.hcl

Lines changed: 25 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

infrastructure/backend/main.tf

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
terraform {
2+
required_version = "1.14.0"
3+
required_providers {
4+
aws = {
5+
source = "hashicorp/aws"
6+
version = "6.22.0"
7+
}
8+
}
9+
}
10+
11+
data "aws_iam_policy_document" "logs" {
12+
statement {
13+
actions = ["s3:PutObject"]
14+
effect = "Allow"
15+
resources = ["${aws_s3_bucket.logs.arn}/*"]
16+
sid = "s3-log-delivery"
17+
18+
principals {
19+
type = "Service"
20+
identifiers = ["logging.s3.amazonaws.com"]
21+
}
22+
}
23+
}
24+
25+
data "aws_iam_policy_document" "state_https_only" {
26+
policy_id = "ForceHTTPS"
27+
28+
statement {
29+
actions = ["s3:*"]
30+
sid = "HTTPSOnly"
31+
effect = "Deny"
32+
33+
condition {
34+
test = "Bool"
35+
values = ["false"]
36+
variable = "aws:SecureTransport"
37+
}
38+
principals {
39+
identifiers = ["*"]
40+
type = "AWS"
41+
}
42+
resources = [
43+
aws_s3_bucket.state.arn,
44+
"${aws_s3_bucket.state.arn}/*",
45+
]
46+
}
47+
}
48+
49+
resource "aws_dynamodb_table" "state_lock" {
50+
name = "${var.project_name}-terraform-state-lock"
51+
billing_mode = "PAY_PER_REQUEST"
52+
hash_key = "LockID"
53+
tags = {
54+
Name = "${var.project_name}-terraform-state-lock"
55+
}
56+
57+
attribute {
58+
name = "LockID"
59+
type = "S"
60+
}
61+
point_in_time_recovery {
62+
enabled = true
63+
}
64+
}
65+
66+
resource "aws_s3_bucket" "logs" { # NOSONAR
67+
bucket = "${var.project_name}-terraform-state-logs"
68+
tags = {
69+
Name = "${var.project_name}-terraform-state-logs"
70+
}
71+
}
72+
73+
resource "aws_s3_bucket" "state" { # NOSONAR
74+
bucket = "${var.project_name}-terraform-state"
75+
tags = {
76+
Name = "${var.project_name}-terraform-state"
77+
}
78+
}
79+
80+
resource "aws_s3_bucket_lifecycle_configuration" "state" {
81+
bucket = aws_s3_bucket.state.id
82+
83+
rule {
84+
id = "delete-old-versions"
85+
status = "Enabled"
86+
87+
abort_incomplete_multipart_upload {
88+
days_after_initiation = var.abort_incomplete_multipart_upload_days
89+
}
90+
noncurrent_version_expiration {
91+
noncurrent_days = var.noncurrent_version_expiration_days
92+
}
93+
}
94+
}
95+
96+
resource "aws_s3_bucket_lifecycle_configuration" "logs" {
97+
bucket = aws_s3_bucket.logs.id
98+
99+
rule {
100+
id = "expire-logs"
101+
status = "Enabled"
102+
103+
abort_incomplete_multipart_upload {
104+
days_after_initiation = var.abort_incomplete_multipart_upload_days
105+
}
106+
expiration {
107+
days = var.expire_log_days
108+
}
109+
}
110+
}
111+
112+
resource "aws_s3_bucket_logging" "state" {
113+
bucket = aws_s3_bucket.state.id
114+
target_bucket = aws_s3_bucket.logs.id
115+
target_prefix = "s3/"
116+
}
117+
118+
resource "aws_s3_bucket_policy" "logs" {
119+
bucket = aws_s3_bucket.logs.id
120+
policy = data.aws_iam_policy_document.logs.json
121+
}
122+
123+
resource "aws_s3_bucket_policy" "state" {
124+
bucket = aws_s3_bucket.state.id
125+
policy = data.aws_iam_policy_document.state_https_only.json
126+
}
127+
128+
resource "aws_s3_bucket_public_access_block" "logs" {
129+
block_public_acls = true
130+
block_public_policy = true
131+
bucket = aws_s3_bucket.logs.id
132+
ignore_public_acls = true
133+
restrict_public_buckets = true
134+
}
135+
136+
resource "aws_s3_bucket_public_access_block" "state" {
137+
block_public_acls = true
138+
block_public_policy = true
139+
bucket = aws_s3_bucket.state.id
140+
ignore_public_acls = true
141+
restrict_public_buckets = true
142+
}
143+
144+
resource "aws_s3_bucket_server_side_encryption_configuration" "logs" {
145+
bucket = aws_s3_bucket.logs.id
146+
rule {
147+
apply_server_side_encryption_by_default {
148+
sse_algorithm = "AES256"
149+
}
150+
}
151+
}
152+
153+
resource "aws_s3_bucket_server_side_encryption_configuration" "state" {
154+
bucket = aws_s3_bucket.state.id
155+
rule {
156+
apply_server_side_encryption_by_default {
157+
sse_algorithm = "AES256"
158+
}
159+
}
160+
}
161+
162+
resource "aws_s3_bucket_versioning" "state" {
163+
bucket = aws_s3_bucket.state.id
164+
versioning_configuration {
165+
status = "Enabled"
166+
}
167+
}
168+
169+
resource "aws_s3_bucket_versioning" "logs" {
170+
bucket = aws_s3_bucket.logs.id
171+
versioning_configuration {
172+
status = "Enabled"
173+
}
174+
}

infrastructure/backend/outputs.tf

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
output "dynamodb_table_name" {
2+
description = "The name of the DynamoDB table for Terraform state locking"
3+
value = aws_dynamodb_table.state_lock.name
4+
}
5+
6+
output "s3_bucket_name" {
7+
description = "The name of the S3 bucket for Terraform state"
8+
value = aws_s3_bucket.state.bucket
9+
}
File renamed without changes.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
variable "abort_incomplete_multipart_upload_days" {
2+
description = "Specifies the number of days after which an incomplete multipart upload is aborted."
3+
type = number
4+
default = 7
5+
}
6+
7+
variable "aws_region" {
8+
description = "The AWS region to deploy resources in."
9+
type = string
10+
default = "us-east-2"
11+
}
12+
13+
variable "expire_log_days" {
14+
description = "The number of days to expire logs after."
15+
type = number
16+
default = 90
17+
}
18+
19+
variable "noncurrent_version_expiration_days" {
20+
description = "The number of days an object is noncurrent before it is expired."
21+
type = number
22+
default = 30
23+
}
24+
25+
variable "project_name" {
26+
description = "The name of the project."
27+
type = string
28+
default = "owasp-nest"
29+
}

0 commit comments

Comments
 (0)