Skip to content

Commit 5e8a76f

Browse files
committed
feat(ec2): merge udpates and add tests
1 parent 2658c1a commit 5e8a76f

File tree

6 files changed

+878
-26
lines changed

6 files changed

+878
-26
lines changed

packages/actions/src/helpers/ec2.ts

+42-24
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,23 @@ const instancesTypes = {
149149
}
150150
}
151151

152+
// 1. create ssh key in ec2 tab -> save the name
153+
// 2. IAM role: access to ssh key ("iam:GetSSHPublicKey",)
154+
// 3. IAM role: ec2 access
155+
// 4. ec2 give role for s3 access
156+
// 5. have an api (express) running on the vm
157+
// 6. have a script that runs on the vm that does the verification
158+
// 7. JWT Authorization: Bearer <token>
159+
// each circuit document needs to have the instance id of the vm
160+
/*
161+
{
162+
bucket: "x",
163+
action: "verify/checkStatus",
164+
"zKeyIndex": 0,
165+
"zKeyStoragePath": /circuit/..,
166+
}
167+
*/
168+
152169
/**
153170
* Creates a new EC2 instance
154171
* @param ec2 <EC2Client> the EC2 client to talk to AWS
@@ -164,7 +181,9 @@ export const createEC2Instance = async (ec2: EC2Client): Promise<P0tionEC2Instan
164181
"sudo apt install awscli -y",
165182
"touch /tmp/test.txt",
166183
"echo 'hello world' > /tmp/test.txt",
167-
"aws s3 cp /tmp/test.txt s3://p0tion-test-bucket/test.txt"
184+
"aws s3 cp /tmp/test.txt s3://p0tion-test-bucket/test.txt",
185+
"npm install -g p0tion-api",
186+
"p0tion-api ",
168187
]
169188

170189
// create the params
@@ -190,7 +209,7 @@ export const createEC2Instance = async (ec2: EC2Client): Promise<P0tionEC2Instan
190209
throw new Error("Could not create a new EC2 instance")
191210
}
192211

193-
const instance: P0tionEC2Instance = {
212+
const instance: P0tionEC2Instance = {
194213
InstanceId: response.Instances![0].InstanceId!,
195214
ImageId: response.Instances![0].ImageId!,
196215
InstanceType: response.Instances![0].InstanceType!,
@@ -207,7 +226,7 @@ export const createEC2Instance = async (ec2: EC2Client): Promise<P0tionEC2Instan
207226
* @param instanceId <string> the id of the instance to check
208227
* @returns <Promise<bool>> the status of the instance
209228
*/
210-
export const checkEC2Status = async (ec2Client: EC2Client, instanceId: string) => {
229+
export const checkEC2Status = async (ec2Client: EC2Client, instanceId: string): Promise<boolean> => {
211230
const command = new DescribeInstanceStatusCommand({
212231
InstanceIds: [instanceId]
213232
})
@@ -216,8 +235,28 @@ export const checkEC2Status = async (ec2Client: EC2Client, instanceId: string) =
216235
if (response.$metadata.httpStatusCode !== 200) {
217236
throw new Error("Could not get the status of the EC2 instance")
218237
}
238+
219239
return response.InstanceStatuses![0].InstanceState!.Name === "running"
240+
}
220241

242+
/**
243+
* Get the IP of an EC2 instance
244+
* @notice the IP will change at every restart
245+
* @param ec2Client <EC2Client> the EC2 client to talk to AWS
246+
* @param instanceId <string> the id of the instance to get the IP of
247+
* @returns <Promise<string>> the IP of the instance
248+
*/
249+
export const getEC2Ip = async (ec2Client: EC2Client, instanceId: string) => {
250+
const command = new DescribeInstancesCommand({
251+
InstanceIds: [instanceId]
252+
})
253+
254+
const response = await ec2Client.send(command)
255+
if (response.$metadata.httpStatusCode !== 200) {
256+
throw new Error("Could not get the IP of the EC2 instance")
257+
}
258+
259+
return response.Reservations![0].Instances![0].PublicIpAddress
221260
}
222261

223262
/**
@@ -274,24 +313,3 @@ export const terminateEC2Instance = async (ec2: EC2Client, instanceId: string) =
274313
throw new Error("Could not terminate the EC2 instance")
275314
}
276315
}
277-
278-
/**
279-
* Get the EC2 public ip
280-
* @notice At each restart, the EC2 instance gets a new IP
281-
* @param ec2 <EC2Client> the EC2 client to talk to AWS
282-
* @param instanceId <string> the id of the instance to get the IP of
283-
* @returns <Promise<string>> the IP of the instance
284-
*/
285-
export const getEC2Ip = async (ec2: EC2Client, instanceId: string): Promise<string> => {
286-
const command = new DescribeInstancesCommand({
287-
InstanceIds: [instanceId]
288-
})
289-
290-
const response = await ec2.send(command)
291-
292-
if (response.$metadata.httpStatusCode !== 200) {
293-
throw new Error("Could not get the EC2 instance")
294-
}
295-
296-
return response.Reservations![0].Instances![0].PublicIpAddress
297-
}

packages/actions/src/index.ts

+8
Original file line numberDiff line numberDiff line change
@@ -134,3 +134,11 @@ export {
134134
ContributionDocumentReferenceAndData,
135135
FirebaseServices
136136
} from "./types/index"
137+
export {
138+
createEC2Instance,
139+
terminateEC2Instance,
140+
stopEC2Instance,
141+
startEC2Instance,
142+
checkEC2Status,
143+
getEC2Ip
144+
} from "./helpers/ec2"

packages/actions/test/unit/ec2.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ describe("EC2", () => {
3939
it("getEC2Ip should return an ip", async () => {
4040
const ip = await getEC2Ip(ec2, instance.InstanceId!)
4141
expect(ip).to.not.be.undefined
42-
previousIp = ip
42+
previousIp = ip!
4343
})
4444

4545
it("stopEC2Instance should stop an instance", async () => {

packages/backend/src/functions/circuit.ts

+24-1
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,17 @@ import {
2929
ParticipantContributionStep,
3030
CeremonyState,
3131
Contribution,
32-
blake512FromPath
32+
blake512FromPath,
3333
} from "@p0tion/actions"
3434
import { FinalizeCircuitData, VerifyContributionData } from "../types/index"
3535
import { LogLevel } from "../types/enums"
3636
import { COMMON_ERRORS, logAndThrowError, printLog, SPECIFIC_ERRORS } from "../lib/errors"
3737
import {
38+
createEC2Client,
3839
createTemporaryLocalPath,
3940
deleteObject,
4041
downloadArtifactFromS3Bucket,
42+
getAWSVariables,
4143
getCeremonyCircuits,
4244
getCircuitDocumentByPosition,
4345
getCurrentServerTimestampInMillis,
@@ -431,6 +433,27 @@ export const verifycontribution = functionsV2.https.onCall(
431433
`${prefix}_${isFinalizing ? finalContributionIndex : lastZkeyIndex}.zkey`
432434
)
433435

436+
// get variables for aws
437+
const awsVariables = getAWSVariables()
438+
439+
// get ec2 client
440+
const ec2Client = createEC2Client()
441+
442+
// start vm
443+
// const response = await createEc2Instance()
444+
445+
446+
// check status
447+
448+
// get ip
449+
450+
// call api
451+
452+
// wait
453+
454+
// call api for result
455+
456+
434457
// Prepare temporary file paths.
435458
// (nb. these are needed to download the necessary artifacts for verification from AWS S3).
436459
const verificationTranscriptTemporaryLocalPath = createTemporaryLocalPath(

packages/backend/src/lib/utils.ts

+43
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
} from "firebase-admin/firestore"
99
import admin from "firebase-admin"
1010
import dotenv from "dotenv"
11+
import { EC2Client } from "@aws-sdk/client-ec2"
1112
import { DeleteObjectCommand, GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3"
1213
import { getSignedUrl } from "@aws-sdk/s3-request-presigner"
1314
import { createWriteStream } from "node:fs"
@@ -357,3 +358,45 @@ export const getGitHubVariables = (): any => {
357358
minimumPublicRepos: Number(process.env.GITHUB_MINIMUM_PUBLIC_REPOS)
358359
}
359360
}
361+
362+
/**
363+
* Fetch the variables related to EC2 verification
364+
* @returns <any> - the AWS EC2 variables.
365+
*/
366+
export const getAWSVariables = (): any => {
367+
if (
368+
!process.env.AWS_ACCESS_KEY_ID ||
369+
!process.env.AWS_SECRET_ACCESS_KEY ||
370+
!process.env.AWS_ROLE_ARN ||
371+
!process.env.AWS_AMI_ID ||
372+
!process.env.AWS_KEY_NAME
373+
)
374+
logAndThrowError(COMMON_ERRORS.CM_WRONG_CONFIGURATION)
375+
376+
return {
377+
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
378+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
379+
region: process.env.AWS_REGION || "us-east-1",
380+
roleArn: process.env.AWS_ROLE_ARN!,
381+
amiId: process.env.AWS_AMI_ID!,
382+
keyName: process.env.AWS_KEY_NAME!
383+
}
384+
}
385+
386+
/**
387+
* Create an EC2 client object
388+
* @returns <Promise<EC2Client>> an EC2 client
389+
*/
390+
export const createEC2Client = async (): Promise<EC2Client> => {
391+
const { accessKeyId, secretAccessKey, region } = getAWSVariables()
392+
393+
const ec2: EC2Client = new EC2Client({
394+
credentials: {
395+
accessKeyId: accessKeyId,
396+
secretAccessKey: secretAccessKey
397+
},
398+
region: region
399+
})
400+
401+
return ec2
402+
}

0 commit comments

Comments
 (0)