Skip to content

Commit b168cd0

Browse files
committed
feat(vms): implement terraform script to deploy AWS inf
1 parent b414166 commit b168cd0

File tree

4 files changed

+333
-1
lines changed

4 files changed

+333
-1
lines changed

packages/backend/.gitignore

+11-1
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,14 @@ dist/
6969
node_modules/
7070

7171
# Admin SDK key
72-
serviceAccountKey.json
72+
serviceAccountKey.json
73+
74+
# AWS related files
75+
aws/.terraform
76+
aws/lambda.zip
77+
aws/terraform.tfstate
78+
aws/terraform.tfstate.json
79+
aws/terraform.tfstate.backup
80+
aws/.terraform.lock.hcl
81+
aws/lambda/node_modules
82+
aws/lambda/package-lock.json

packages/backend/aws/lambda/lambda.js

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { EC2Client, DescribeInstancesCommand, StopInstancesCommand, CreateTagsCommand } from "@aws-sdk/client-ec2";
2+
3+
const ec2 = new EC2Client({ region: 'us-east-1' });
4+
5+
export const handler = async (event) => {
6+
console.log('Received event:', JSON.stringify(event, null, 2));
7+
8+
// Extract the SNS message which will be the instanceId
9+
const instanceId = event.Records[0].Sns.Message;
10+
11+
// Get information about the instance
12+
const params = {
13+
InstanceIds: [instanceId]
14+
};
15+
16+
try {
17+
const describeInstancesCommand = new DescribeInstancesCommand(params)
18+
let data = await ec2.send(describeInstancesCommand)
19+
const instance = data.Reservations[0].Instances[0]
20+
21+
// Check if the instance has the "p0tionec2instance" name tag
22+
const hasCorrectNameTag = instance.Tags.some(tag => tag.Key === 'Name' && tag.Value === 'p0tionec2instance')
23+
// Check if the instance has been already initialized
24+
const alreadyInitialized = instance.Tags.some(tag => tag.Key === 'Initialized' && tag.Value === 'true')
25+
26+
if (hasCorrectNameTag && !alreadyInitialized) {
27+
// If the instance has the correct name tag and it is not initialized yet, stop it
28+
const stopInstancesCommand = new StopInstancesCommand(params)
29+
data = await ec2.send(stopInstancesCommand)
30+
console.log('StopInstances succeeded:', data)
31+
32+
// Mark the instance as initialized
33+
const createTagsCommand = new CreateTagsCommand({
34+
Resources: [instanceId],
35+
Tags: [
36+
{
37+
Key: 'Initialized',
38+
Value: 'true'
39+
}
40+
]
41+
})
42+
await ec2.send(createTagsCommand)
43+
console.log(`Instance ${instanceId} has been marked as initialized.`)
44+
} else {
45+
console.log(`Instance ${instanceId} does not meet the requirements, ignoring...`)
46+
}
47+
} catch (err) {
48+
console.log('Error', err)
49+
}
50+
}
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"name": "p0tion-lambda",
3+
"version": "1.0.0",
4+
"description": "A Lambda function to stop ec2 instances",
5+
"main": "lambda.js",
6+
"keywords": [],
7+
"author": "",
8+
"license": "ISC",
9+
"dependencies": {
10+
"@aws-sdk/client-ec2": "^3.363.0"
11+
}
12+
}

packages/backend/aws/main.tf

+260
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
provider "aws" {
2+
region = "us-east-1"
3+
}
4+
5+
# create SNS
6+
resource "aws_sns_topic" "p0tion_sns_topic" {
7+
name = "p0tion_sns_topic"
8+
}
9+
10+
# create Lambda execution role
11+
data "aws_iam_policy_document" "p0tion_assume_role_policy_lambda" {
12+
statement {
13+
actions = ["sts:AssumeRole"]
14+
15+
principals {
16+
type = "Service"
17+
identifiers = ["lambda.amazonaws.com"]
18+
}
19+
}
20+
}
21+
22+
resource "aws_iam_role" "p0tion_lambda_role" {
23+
name = "p0tion_lambda_role"
24+
assume_role_policy = data.aws_iam_policy_document.p0tion_assume_role_policy_lambda.json
25+
}
26+
27+
# Execution role with EC2 and logs permissions
28+
resource "aws_iam_role_policy" "p0tion_lambda_exec_role_policy" {
29+
name = "p0tion_lambda_exec_role_policy"
30+
role = aws_iam_role.p0tion_lambda_role.id
31+
32+
policy = <<EOF
33+
{
34+
"Version": "2012-10-17",
35+
"Statement": [
36+
{
37+
"Sid": "p0tionLambdaExec",
38+
"Effect": "Allow",
39+
"Action": [
40+
"ec2:DescribeInstances",
41+
"ec2:StopInstances",
42+
"ec2:CreateTags",
43+
"logs:CreateLogGroup",
44+
"logs:CreateLogStream",
45+
"logs:PutLogEvents"
46+
],
47+
"Resource": "*"
48+
}
49+
]
50+
}
51+
EOF
52+
}
53+
54+
# Deploy the Lambda with the code to stop the EC2 instance
55+
resource "aws_lambda_function" "p0tion_lambda_stop_vm" {
56+
filename = "./lambda.zip"
57+
function_name = "p0tion_lambda_stop_vm"
58+
role = aws_iam_role.p0tion_lambda_role.arn
59+
handler = "lambda_function.lambda_handler"
60+
61+
runtime = "nodejs18.x"
62+
source_code_hash = filebase64sha256("./lambda.zip")
63+
timeout = 300
64+
65+
environment {
66+
variables = {
67+
SNS_TOPIC_ARN = aws_sns_topic.p0tion_sns_topic.arn
68+
}
69+
}
70+
}
71+
72+
# Allow the lambda to be triggered by SNS
73+
resource "aws_lambda_permission" "sns" {
74+
statement_id = "AllowExecutionFromSNS"
75+
action = "lambda:InvokeFunction"
76+
function_name = aws_lambda_function.p0tion_lambda_stop_vm.function_name
77+
principal = "sns.amazonaws.com"
78+
source_arn = aws_sns_topic.p0tion_sns_topic.arn
79+
}
80+
81+
resource "aws_sns_topic_subscription" "lambda" {
82+
topic_arn = aws_sns_topic.p0tion_sns_topic.arn
83+
protocol = "lambda"
84+
endpoint = aws_lambda_function.p0tion_lambda_stop_vm.arn
85+
}
86+
87+
# Create instance role for EC2
88+
data "aws_iam_policy_document" "p0tion_assume_role_policy_ec2" {
89+
statement {
90+
actions = ["sts:AssumeRole"]
91+
92+
principals {
93+
type = "Service"
94+
identifiers = ["ec2.amazonaws.com"]
95+
}
96+
}
97+
}
98+
99+
# Associate the role with the instance profile
100+
resource "aws_iam_role" "p0tion_ec2_role" {
101+
name = "p0tion_ec2_role"
102+
assume_role_policy = data.aws_iam_policy_document.p0tion_assume_role_policy_ec2.json
103+
}
104+
105+
# EC2 SNS policy
106+
resource "aws_iam_role_policy" "p0tion_ec2_sns" {
107+
name = "p0tion_ec2_sns"
108+
role = aws_iam_role.p0tion_ec2_role.id
109+
110+
policy = <<EOF
111+
{
112+
"Version": "2012-10-17",
113+
"Statement": [
114+
{
115+
"Sid": "p0tionEC2SNS",
116+
"Effect": "Allow",
117+
"Action": "sns:Publish",
118+
"Resource": "${aws_sns_topic.p0tion_sns_topic.arn}"
119+
}
120+
]
121+
}
122+
EOF
123+
}
124+
125+
# EC2 S3 and SSM policy
126+
resource "aws_iam_role_policy" "p0tion_ec2_s3_ssm" {
127+
name = "p0tion_ec2_s3_ssm"
128+
role = aws_iam_role.p0tion_ec2_role.id
129+
130+
policy = <<EOF
131+
{
132+
"Version": "2012-10-17",
133+
"Statement": [
134+
{
135+
"Sid": "p0tionEc2S3SSM",
136+
"Effect": "Allow",
137+
"Action": [
138+
"s3:ListBucket",
139+
"s3:PutObject",
140+
"s3:GetObject",
141+
"s3:PutObjectAcl",
142+
"ssm:UpdateInstanceInformation",
143+
"ssmmessages:CreateControlChannel",
144+
"ssmmessages:CreateDataChannel",
145+
"ssmmessages:OpenControlChannel",
146+
"ssmmessages:OpenDataChannel"
147+
],
148+
"Resource": "${aws_sns_topic.p0tion_sns_topic.arn}"
149+
}
150+
]
151+
}
152+
EOF
153+
}
154+
155+
# IAM user for all operations
156+
resource "aws_iam_user" "p0tion_iam_user" {
157+
name = "p0tion_iam_user"
158+
}
159+
160+
resource "aws_iam_access_key" "p0tion_access_key" {
161+
user = aws_iam_user.p0tion_iam_user.name
162+
}
163+
164+
resource "aws_iam_user_policy" "p0tion_s3_ssm" {
165+
name = "p0tion_s3_ssm"
166+
user = aws_iam_user.p0tion_iam_user.name
167+
168+
policy = <<EOF
169+
{
170+
"Version": "2012-10-17",
171+
"Statement": [
172+
{
173+
"Sid": "S3andEC2andSSM",
174+
"Effect": "Allow",
175+
"Action": [
176+
"s3:CreateBucket",
177+
"s3:ListBucket",
178+
"s3:ListMultipartUploadParts",
179+
"s3:GetObject",
180+
"s3:AbortMultipartUpload",
181+
"s3:GetObjectVersion",
182+
"s3:HeadBucket",
183+
"ec2:RunInstances",
184+
"ec2:DescribeInstanceStatus",
185+
"ec2:CreateTags",
186+
"iam:PassRole"
187+
],
188+
"Resource": "*"
189+
}
190+
]
191+
}
192+
EOF
193+
}
194+
195+
resource "aws_iam_user_policy" "p0tion_ec2_privileged" {
196+
name = "p0tion_ec2_privileged"
197+
user = aws_iam_user.p0tion_iam_user.name
198+
199+
policy = <<EOF
200+
{
201+
"Version": "2012-10-17",
202+
"Statement": [
203+
{
204+
"Sid": "EC2Privileged",
205+
"Effect": "Allow",
206+
"Action": [
207+
"ec2:StopInstances",
208+
"ec2:TerminateInstances",
209+
"ec2:StartInstances",
210+
"ssm:SendCommand",
211+
"ssm:GetCommandInvocation"
212+
],
213+
"Resource": "*"
214+
}
215+
]
216+
}
217+
EOF
218+
}
219+
220+
resource "aws_iam_user_policy" "p0tion_s3_privileged" {
221+
name = "p0tion_s3_privileged"
222+
user = aws_iam_user.p0tion_iam_user.name
223+
224+
policy = <<EOF
225+
{
226+
"Version": "2012-10-17",
227+
"Statement": [
228+
{
229+
"Sid": "S3Privileged",
230+
"Effect": "Allow",
231+
"Action": [
232+
"s3:DeleteObject",
233+
"s3:DeleteBucket",
234+
"s3:PutBucketPublicAccessBlock",
235+
"s3:PutBucketCORS",
236+
"s3:PutBucketObjectLockConfiguration",
237+
"s3:PutBucketAcl",
238+
"s3:PutBucketVersioning",
239+
"s3:PutObject",
240+
"s3:PutObjectAcl",
241+
"s3:PutBucketOwnershipControls"
242+
],
243+
"Resource": "*"
244+
}
245+
]
246+
}
247+
EOF
248+
}
249+
250+
# The user access keys
251+
output "access_key" {
252+
value = aws_iam_access_key.p0tion_access_key.id
253+
description = "The access key ID"
254+
}
255+
256+
output "secret_key" {
257+
value = aws_iam_access_key.p0tion_access_key.secret
258+
description = "The secret access key. This key will be encrypted and stored in the state file, use terraform output secret_key"
259+
sensitive = true
260+
}

0 commit comments

Comments
 (0)