diff --git a/Dockerfile b/Dockerfile index 35a05ccd3..da30c7846 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,6 +7,14 @@ ENV STARKNET_SOLIS_ACCOUNT_ADDRESS="" ENV STARKNET_SOLIS_ACCOUNT_PRIVATE_KEY="" ENV RUST_LOG=info +# Create a user with the correct UID and GID +RUN groupadd -g 1000 appgroup && + useradd -u 1000 -g appgroup -m appuser && + chown -R appuser:appgroup /app + +# Switch to the new user +USER appuser + WORKDIR /app # Copy the entire workspace context into the container diff --git a/aws/cdk-solis-ecs/bin/cdk-solis.ts b/aws/cdk-solis-ecs/bin/cdk-solis.ts index 04ad9389a..1c87188ca 100644 --- a/aws/cdk-solis-ecs/bin/cdk-solis.ts +++ b/aws/cdk-solis-ecs/bin/cdk-solis.ts @@ -1,36 +1,44 @@ import * as cdk from "aws-cdk-lib"; +import * as ecs from "aws-cdk-lib/aws-ecs"; +import { Construct } from "constructs"; -import { ArkSolisLambdaStack } from "../lib/cdk-solis-db"; -import { ArkSolisEcsStack } from "../lib/cdk-solis-stack"; -import { ArkSolisEfsStack } from "../lib/cdk-stack-efs"; +import { EcsWithEfsConstruct } from "../lib/ecs-with-efs-construct"; +import { EfsConstruct } from "../lib/efs-construct"; -const app = new cdk.App(); +interface ArkSolisEcsStackProps extends cdk.StackProps { + vpcId: string; +} -new ArkSolisEfsStack(app, "ark-solis-efs-stack", { - env: { - account: process.env.CDK_DEFAULT_ACCOUNT, - region: process.env.CDK_DEFAULT_REGION - } -}); +class ArkSolisEcsStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: ArkSolisEcsStackProps) { + super(scope, id, props); -new ArkSolisLambdaStack(app, "ark-solis-db-stack", { - env: { - account: process.env.CDK_DEFAULT_ACCOUNT, - region: process.env.CDK_DEFAULT_REGION - } -}); + const efsConstruct = new EfsConstruct(this, "EfsConstruct", { + vpcId: props.vpcId + }); -new ArkSolisEcsStack(app, "ark-solis-production-stack", { - vpcId: "vpc-0d11f7ec183208e08", - efsFileSystemId: cdk.Fn.importValue("RecordingEFSFileStorageId"), - efsAccessPointId: cdk.Fn.importValue("RecordingEFSFileStorageAccessPointId"), - efsSecurityGroupId: cdk.Fn.importValue( - "RecordingEFSFileStorageSecurityGroupId" - ), - env: { - account: process.env.CDK_DEFAULT_ACCOUNT, - region: process.env.CDK_DEFAULT_REGION + new EcsWithEfsConstruct(this, "EcsWithEfsConstruct", { + vpcId: props.vpcId, + efsFileSystemId: efsConstruct.fileSystemId, + efsAccessPointId: efsConstruct.accessPointId, + efsSecurityGroupId: efsConstruct.securityGroupId, + containerImage: ecs.ContainerImage.fromEcrRepository( + cdk.aws_ecr.Repository.fromRepositoryName( + this, + "ArkProjectRepository", + "ark-project-repo" + ), + "solis-latest" + ), + containerPort: 7777, + domainName: "arkproject.dev", + subdomain: "staging.solis" + }); } -}); +} +const app = new cdk.App(); +new ArkSolisEcsStack(app, "ArkSolisEcsStack", { + vpcId: "vpc-0d11f7ec183208e08" +}); app.synth(); diff --git a/aws/cdk-solis-ecs/lib/cdk-solis-stack.ts b/aws/cdk-solis-ecs/lib/cdk-solis-stack.ts deleted file mode 100644 index d4cb58d50..000000000 --- a/aws/cdk-solis-ecs/lib/cdk-solis-stack.ts +++ /dev/null @@ -1,236 +0,0 @@ -import { Stack, StackProps } from "aws-cdk-lib"; -import * as cdk from "aws-cdk-lib"; -import * as acm from "aws-cdk-lib/aws-certificatemanager"; -import { Port, SecurityGroup, SubnetType, Vpc } from "aws-cdk-lib/aws-ec2"; -import * as ec2 from "aws-cdk-lib/aws-ec2"; -import * as ecs from "aws-cdk-lib/aws-ecs"; -import { - AwsLogDriver, - Cluster, - FargateTaskDefinition -} from "aws-cdk-lib/aws-ecs"; -import * as elbv2 from "aws-cdk-lib/aws-elasticloadbalancingv2"; -import { - Effect, - ManagedPolicy, - PolicyStatement, - Role, - ServicePrincipal -} from "aws-cdk-lib/aws-iam"; -import * as logs from "aws-cdk-lib/aws-logs"; -import * as route53 from "aws-cdk-lib/aws-route53"; -import * as route53Targets from "aws-cdk-lib/aws-route53-targets"; -import { StringParameter } from "aws-cdk-lib/aws-ssm"; -import { Construct } from "constructs"; - -interface ArkSolisEcsStackProps extends StackProps { - vpcId: string; - efsFileSystemId: string; - efsAccessPointId: string; - efsSecurityGroupId: string; -} - -export class ArkSolisEcsStack extends Stack { - constructor(scope: Construct, id: string, props: ArkSolisEcsStackProps) { - super(scope, id, props); - - const vpc = Vpc.fromLookup(this, "Vpc", { - vpcId: props.vpcId - }); - - const cluster = new Cluster(this, "Cluster", { - vpc: vpc - }); - - const containerSG = new SecurityGroup(this, "ContainerSG", { - vpc, - allowAllOutbound: true, - securityGroupName: "ContainerSG" - }); - - const efsSecurityGroup = SecurityGroup.fromSecurityGroupId( - this, - "EfsSecurityGroup", - props.efsSecurityGroupId - ); - - containerSG.connections.allowTo( - efsSecurityGroup, - Port.tcp(2049), - "Allow this container to connect to the EFS" - ); - - const policy = new ManagedPolicy(this, "api-policy", { - managedPolicyName: `api-policy`, - statements: [ - new PolicyStatement({ - effect: Effect.ALLOW, - actions: ["elasticfilesystem:*"], - resources: ["*"] - }) - ] - }); - - const containerTaskRole = new Role(this, "api-task-role", { - roleName: `api-task-role`, - assumedBy: new ServicePrincipal("ecs-tasks.amazonaws.com"), - managedPolicies: [ - ManagedPolicy.fromManagedPolicyArn( - this, - "AmazonECSTaskExecutionRolePolicy", - policy.managedPolicyArn - ) - ] - }); - - const forceNewDeployment = new StringParameter(this, "ForceNewDeployment", { - stringValue: Date.now().toString() // Use the current timestamp - }); - - // Task Definition - const taskDefinition = new FargateTaskDefinition(this, "ArkSolisTaskDef", { - family: "ArkSolisTaskDefFam" + forceNewDeployment.stringValue, - memoryLimitMiB: 8192, - cpu: 4096, - taskRole: containerTaskRole - }); - - // ECR Repository - const ecrRepository = ecs.ContainerImage.fromEcrRepository( - cdk.aws_ecr.Repository.fromRepositoryName( - this, - "ArkProjectRepository", - "ark-project-repo" - ), - "solis-latest" - ); - - // Log Group - const logGroup = new logs.LogGroup(this, "LogGroup", { - retention: logs.RetentionDays.ONE_WEEK - }); - - // Logging - const logging = new AwsLogDriver({ - logGroup, - streamPrefix: "Solis" - }); - - // Container Definition - const container = taskDefinition.addContainer("ArkSolisContainer", { - image: ecrRepository, - environment: { - STARKNET_NODE_URL: process.env.STARKNET_NODE_URL || "default_rpc_url", // Fallback to a default if not set - STARKNET_APPCHAIN_MESSAGING_ADDRESS: - process.env.STARKNET_APPCHAIN_MESSAGING_ADDRESS || - "default_contract_address", - STARKNET_SOLIS_ACCOUNT_ADDRESS: - process.env.STARKNET_SOLIS_ACCOUNT_ADDRESS || - "default_sender_address", - STARKNET_SOLIS_ACCOUNT_PRIVATE_KEY: - process.env.STARKNET_SOLIS_ACCOUNT_PRIVATE_KEY || - "default_private_key", - RPC_USER: process.env.RPC_USER || "default_rpc_user", - RPC_PASSWORD: process.env.RPC_PASSWORD || "default_rpc_password", - DEPLOYMENT_VERSION: Date.now().toString() - }, - memoryLimitMiB: 8192, - logging - }); - - container.addPortMappings({ - containerPort: 7777 - }); - - // Mount the EFS file system - taskDefinition.addVolume({ - name: "efs", - efsVolumeConfiguration: { - authorizationConfig: { - accessPointId: props.efsAccessPointId, - iam: "ENABLED" - }, - fileSystemId: props.efsFileSystemId, - transitEncryption: "ENABLED", - transitEncryptionPort: 2049 - } - }); - - container.addMountPoints({ - readOnly: false, - containerPath: "/efs/mnt", - sourceVolume: "efs" - }); - - // Application Load Balancer - const sg = new SecurityGroup(this, "LoadBalancerSG", { - vpc, - allowAllOutbound: true - }); - sg.addIngressRule( - ec2.Peer.anyIpv4(), - ec2.Port.tcp(443), - "Allow HTTPS traffic" - ); - - const lb = new elbv2.ApplicationLoadBalancer(this, "LB", { - vpc, - internetFacing: true, - securityGroup: sg - }); - - const hostedZone = route53.HostedZone.fromLookup(this, "HostedZone", { - domainName: "arkproject.dev" - }); - - const certificate = new acm.Certificate(this, "Certificate", { - domainName: `staging.solis.arkproject.dev`, - validation: acm.CertificateValidation.fromDns(hostedZone) - }); - - const httpsListener = lb.addListener("HttpsListener", { - port: 443, - certificates: [ - elbv2.ListenerCertificate.fromCertificateManager(certificate) - ] - }); - - // ECS Service - const ecsService = new ecs.FargateService(this, "ark-solis-service", { - cluster, - taskDefinition, - securityGroups: [containerSG], - healthCheckGracePeriod: cdk.Duration.seconds(60) - }); - - httpsListener.addTargets("ECS", { - port: 7777, - targets: [ecsService], - protocol: elbv2.ApplicationProtocol.HTTP, - healthCheck: { - interval: cdk.Duration.seconds(30), - path: "/", - timeout: cdk.Duration.seconds(5), - healthyThresholdCount: 2, - unhealthyThresholdCount: 3, - healthyHttpCodes: "200" - } - }); - - new route53.ARecord(this, "AliasRecord", { - zone: hostedZone, - target: route53.RecordTarget.fromAlias( - new route53Targets.LoadBalancerTarget(lb) - ), - recordName: "staging.solis" - }); - - new cdk.CfnOutput(this, "LoadBalancerDNS", { - value: lb.loadBalancerDnsName, - description: "DNS Name of the Load Balancer" - }); - new cdk.CfnOutput(this, "SubdomainUrl", { - value: `https://staging.solis.arkproject.dev` - }); - } -} diff --git a/aws/cdk-solis-ecs/lib/cdk-stack-efs.ts b/aws/cdk-solis-ecs/lib/cdk-stack-efs.ts deleted file mode 100644 index 27808e403..000000000 --- a/aws/cdk-solis-ecs/lib/cdk-stack-efs.ts +++ /dev/null @@ -1,98 +0,0 @@ -import * as cdk from "aws-cdk-lib"; -import { Stack, StackProps } from "aws-cdk-lib"; -import * as ec2 from "aws-cdk-lib/aws-ec2"; -import * as efs from "aws-cdk-lib/aws-efs"; -import { Construct } from "constructs"; - -export class ArkSolisEfsStack extends Stack { - constructor(scope: Construct, id: string, props?: StackProps) { - super(scope, id, props); - - const vpc = ec2.Vpc.fromLookup(this, "EfsVpc", { - vpcId: "vpc-0d11f7ec183208e08" // Replace with your VPC ID - }); - - const fileSystemName = "RecordingEFSFileStorage"; - - const fileSystem = new efs.CfnFileSystem(this, "RecordingEFSFileStorage", { - performanceMode: "maxIO", - encrypted: true, - fileSystemTags: [ - { - key: "Name", - value: fileSystemName - } - ], - fileSystemPolicy: { - Version: "2012-10-17", - Statement: [ - { - Effect: "Allow", - Principal: { - AWS: "*" - }, - Action: ["elasticfilesystem:ClientMount"] - } - ] - } - }); - - new cdk.CfnOutput(this, "RecordingEFSFileStorageId", { - value: fileSystem.ref, - exportName: "RecordingEFSFileStorageId" - }); - - const securityGroup = new ec2.SecurityGroup( - this, - "RecordingEFSFileStorageSecurityGroup", - { - vpc, - allowAllOutbound: true, - description: "Security group for Recording EFS File Storage", - securityGroupName: "RecordingEFSFileStorageSecurityGroup" - } - ); - - new cdk.CfnOutput(this, "RecordingEFSFileStorageSecurityGroupId", { - value: securityGroup.securityGroupId, - exportName: "RecordingEFSFileStorageSecurityGroupId" - }); - - for (const privateSubnet of vpc.privateSubnets) { - new efs.CfnMountTarget( - this, - `RecordingEFSFileStorageMountTarget-${privateSubnet.node.id}`, - { - fileSystemId: fileSystem.ref, - securityGroups: [securityGroup.securityGroupId], - subnetId: privateSubnet.subnetId - } - ); - } - - const accessPoint = new efs.CfnAccessPoint( - this, - "RecordingEFSFileStorageAccessPoint", - { - fileSystemId: fileSystem.ref, - posixUser: { - uid: "1000", - gid: "1000" - }, - rootDirectory: { - path: "/", - creationInfo: { - ownerGid: "1000", - ownerUid: "1000", - permissions: "755" - } - } - } - ); - - new cdk.CfnOutput(this, "RecordingEFSFileStorageAccessPointId", { - value: accessPoint.ref, - exportName: "RecordingEFSFileStorageAccessPointId" - }); - } -} diff --git a/aws/cdk-solis-ecs/lib/ecs-with-efs-construct.ts b/aws/cdk-solis-ecs/lib/ecs-with-efs-construct.ts new file mode 100644 index 000000000..e27db3b85 --- /dev/null +++ b/aws/cdk-solis-ecs/lib/ecs-with-efs-construct.ts @@ -0,0 +1,131 @@ +import * as cdk from "aws-cdk-lib"; +import * as acm from "aws-cdk-lib/aws-certificatemanager"; +import * as ec2 from "aws-cdk-lib/aws-ec2"; +import * as ecs from "aws-cdk-lib/aws-ecs"; +import * as ecsPatterns from "aws-cdk-lib/aws-ecs-patterns"; +import * as efs from "aws-cdk-lib/aws-efs"; +import * as route53 from "aws-cdk-lib/aws-route53"; +import * as route53Targets from "aws-cdk-lib/aws-route53-targets"; +import { Construct } from "constructs"; + +interface EcsWithEfsProps extends cdk.StackProps { + vpcId: string; + efsFileSystemId: string; + efsAccessPointId: string; + efsSecurityGroupId: string; + containerImage: ecs.ContainerImage; + containerPort: number; + domainName: string; + subdomain: string; +} + +export class EcsWithEfsConstruct extends Construct { + constructor(scope: Construct, id: string, props: EcsWithEfsProps) { + super(scope, id); + + const vpc = ec2.Vpc.fromLookup(this, "Vpc", { + vpcId: props.vpcId + }); + + const cluster = new ecs.Cluster(this, "Cluster", { + vpc + }); + + const containerSG = new ec2.SecurityGroup(this, "ContainerSG", { + vpc, + allowAllOutbound: true, + securityGroupName: "ContainerSG" + }); + + const efsSecurityGroup = ec2.SecurityGroup.fromSecurityGroupId( + this, + "EfsSecurityGroup", + props.efsSecurityGroupId + ); + + containerSG.connections.allowTo( + efsSecurityGroup, + ec2.Port.tcp(2049), + "Allow this container to connect to the EFS" + ); + + const taskDefinition = new ecs.FargateTaskDefinition(this, "TaskDef", { + memoryLimitMiB: 8192, + cpu: 4096 + }); + + const container = taskDefinition.addContainer("AppContainer", { + image: props.containerImage, + memoryLimitMiB: 8192, + logging: new ecs.AwsLogDriver({ + streamPrefix: "EcsWithEfs" + }) + }); + + container.addPortMappings({ + containerPort: props.containerPort + }); + + taskDefinition.addVolume({ + name: "EfsVolume", + efsVolumeConfiguration: { + fileSystemId: props.efsFileSystemId, + transitEncryption: "ENABLED", + authorizationConfig: { + accessPointId: props.efsAccessPointId, + iam: "ENABLED" + } + } + }); + + container.addMountPoints({ + sourceVolume: "EfsVolume", + containerPath: "/mnt/efs", + readOnly: false + }); + + const loadBalancerSG = new ec2.SecurityGroup(this, "LoadBalancerSG", { + vpc, + allowAllOutbound: true + }); + loadBalancerSG.addIngressRule( + ec2.Peer.anyIpv4(), + ec2.Port.tcp(443), + "Allow HTTPS traffic" + ); + + const loadBalancer = new ecsPatterns.ApplicationLoadBalancedFargateService( + this, + "LoadBalancedFargateService", + { + cluster, + taskDefinition, + publicLoadBalancer: true, + securityGroups: [containerSG] + } + ); + + const hostedZone = route53.HostedZone.fromLookup(this, "HostedZone", { + domainName: props.domainName + }); + + const certificate = new acm.Certificate(this, "Certificate", { + domainName: `${props.subdomain}.${props.domainName}`, + validation: acm.CertificateValidation.fromDns(hostedZone) + }); + + loadBalancer.listener.addCertificates("HttpsCertificate", [certificate]); + + new route53.ARecord(this, "AliasRecord", { + zone: hostedZone, + target: route53.RecordTarget.fromAlias( + new route53Targets.LoadBalancerTarget(loadBalancer.loadBalancer) + ), + recordName: props.subdomain + }); + + new cdk.CfnOutput(this, "LoadBalancerDNS", { + value: loadBalancer.loadBalancer.loadBalancerDnsName + }); + } +} diff --git a/aws/cdk-solis-ecs/lib/efs-construct.ts b/aws/cdk-solis-ecs/lib/efs-construct.ts new file mode 100644 index 000000000..8ce7e6128 --- /dev/null +++ b/aws/cdk-solis-ecs/lib/efs-construct.ts @@ -0,0 +1,70 @@ +import * as cdk from "aws-cdk-lib"; +import * as ec2 from "aws-cdk-lib/aws-ec2"; +import * as efs from "aws-cdk-lib/aws-efs"; +import { Construct } from "constructs"; + +interface EfsConstructProps extends cdk.StackProps { + vpcId: string; +} + +export class EfsConstruct extends Construct { + public readonly fileSystemId: string; + public readonly accessPointId: string; + public readonly securityGroupId: string; + + constructor(scope: Construct, id: string, props: EfsConstructProps) { + super(scope, id); + + const vpc = ec2.Vpc.fromLookup(this, "EfsVpc", { + vpcId: props.vpcId + }); + + const fileSystemName = "RecordingEFSFileStorage"; + + const fileSystem = new efs.CfnFileSystem(this, "RecordingEFSFileStorage", { + performanceMode: "maxIO", + encrypted: true, + fileSystemTags: [ + { + key: "Name", + value: fileSystemName + } + ] + }); + + const securityGroup = new ec2.SecurityGroup(this, "EfsSecurityGroup", { + vpc, + allowAllOutbound: true, + description: "Security group for EFS File Storage", + securityGroupName: "EfsSecurityGroup" + }); + + for (const privateSubnet of vpc.privateSubnets) { + new efs.CfnMountTarget(this, `EfsMountTarget-${privateSubnet.node.id}`, { + fileSystemId: fileSystem.ref, + securityGroups: [securityGroup.securityGroupId], + subnetId: privateSubnet.subnetId + }); + } + + const accessPoint = new efs.CfnAccessPoint(this, "EfsAccessPoint", { + fileSystemId: fileSystem.ref, + posixUser: { + uid: "1000", + gid: "1000" + }, + rootDirectory: { + path: "/", + creationInfo: { + ownerGid: "1000", + ownerUid: "1000", + permissions: "755" + } + } + }); + + this.fileSystemId = fileSystem.ref; + this.accessPointId = accessPoint.ref; + this.securityGroupId = securityGroup.securityGroupId; + } +} diff --git a/aws/cdk-solis-ecs/package.json b/aws/cdk-solis-ecs/package.json index 45a95ad95..e243a0038 100644 --- a/aws/cdk-solis-ecs/package.json +++ b/aws/cdk-solis-ecs/package.json @@ -25,6 +25,7 @@ "aws-cdk-lib": "2.100.0", "constructs": "^10.0.0", "dotenv": "^16.3.1", - "source-map-support": "^0.5.21" + "source-map-support": "^0.5.21", + "@aws-cdk-containers/ecs-service-extensions": "2.0.1-alpha.373" } }