diff --git a/packages/graphql-mesh-server/lib/fargate.ts b/packages/graphql-mesh-server/lib/fargate.ts index 10081be1..b4a13e25 100644 --- a/packages/graphql-mesh-server/lib/fargate.ts +++ b/packages/graphql-mesh-server/lib/fargate.ts @@ -1,220 +1,221 @@ -import { Construct } from 'constructs'; -import { Duration, Token } from 'aws-cdk-lib'; -import { RemovalPolicy } from 'aws-cdk-lib'; -import * as acm from 'aws-cdk-lib/aws-certificatemanager'; -import * as ecs from 'aws-cdk-lib/aws-ecs'; -import * as ecr from 'aws-cdk-lib/aws-ecr'; -import * as ecsPatterns from 'aws-cdk-lib/aws-ecs-patterns'; -import * as iam from 'aws-cdk-lib/aws-iam'; -import * as ssm from 'aws-cdk-lib/aws-ssm'; -import * as auto_scaling from 'aws-cdk-lib/aws-autoscaling'; -import { Port, SecurityGroup, Vpc } from 'aws-cdk-lib/aws-ec2'; -import { RedisService } from './redis-construct'; -import { ManagedRule, Scope, WebApplicationFirewall } from './web-application-firewall'; +import { Construct } from "constructs"; +import { Duration, Token } from "aws-cdk-lib"; +import { RemovalPolicy } from "aws-cdk-lib"; +import * as acm from "aws-cdk-lib/aws-certificatemanager"; +import * as ecs from "aws-cdk-lib/aws-ecs"; +import * as ecr from "aws-cdk-lib/aws-ecr"; +import * as ecsPatterns from "aws-cdk-lib/aws-ecs-patterns"; +import * as iam from "aws-cdk-lib/aws-iam"; +import * as ssm from "aws-cdk-lib/aws-ssm"; +import * as auto_scaling from "aws-cdk-lib/aws-autoscaling"; +import { Port, SecurityGroup, Vpc } from "aws-cdk-lib/aws-ec2"; +import { RedisService } from "./redis-construct"; +import { + ManagedRule, + Scope, + WebApplicationFirewall, +} from "./web-application-firewall"; export interface MeshServiceProps { - /** - * VPC to attach Redis instance to - */ - vpc?: Vpc; - /** - * Repository to pull the container image from - */ - repository?: ecr.Repository; - /** - * ARN of the certificate to add to the load balancer - */ - certificateArn: string; - /** - * Minimum number of Fargate instances - */ - minCapacity?: number; - /** - * Maximum number of Fargate instances - */ - maxCapacity?: number; - /** - * Amount of vCPU per instance (default: 512) - */ - cpu?: number; - /** - * Amount of memory per instance (default: 1024) - */ - memory?: number; - /** - * Redis instance to use for mesh caching - */ - redis: RedisService; - /** - * SSM values to pass through to the container as secrets - */ - secrets?: {[key: string]: ssm.IStringParameter | ssm.IStringListParameter}; + /** + * VPC to attach Redis instance to + */ + vpc?: Vpc; + /** + * Repository to pull the container image from + */ + repository?: ecr.Repository; + /** + * ARN of the certificate to add to the load balancer + */ + certificateArn: string; + /** + * Minimum number of Fargate instances + */ + minCapacity?: number; + /** + * Maximum number of Fargate instances + */ + maxCapacity?: number; + /** + * Amount of vCPU per instance (default: 512) + */ + cpu?: number; + /** + * Amount of memory per instance (default: 1024) + */ + memory?: number; + /** + * Redis instance to use for mesh caching + */ + redis: RedisService; + /** + * SSM values to pass through to the container as secrets + */ + secrets?: { [key: string]: ssm.IStringParameter | ssm.IStringListParameter }; } export class MeshService extends Construct { - public readonly vpc: Vpc; - public readonly repository: ecr.Repository; - public readonly service: ecs.FargateService; - public readonly firewall: WebApplicationFirewall; - - constructor(scope: Construct, id: string, props: MeshServiceProps) { - super(scope, id); - - const certificate = acm.Certificate.fromCertificateArn( - this, - `certificate`, - props.certificateArn - ); - - this.vpc = - props.vpc || - new Vpc(this, 'vpc', { - natGateways: 1, - }); - - this.repository = - props.repository || - new ecr.Repository(this, 'repo', { - removalPolicy: RemovalPolicy.DESTROY, - autoDeleteImages: true, - }); - - if (!props.repository) { - // Delete all images older than 90 days BUT keep 10 from the latest tag - this.repository.addLifecycleRule({ - tagPrefixList: ['latest'], - maxImageCount: 10, - }); - this.repository.addLifecycleRule({ - maxImageAge: Duration.days(90), - }); - } - - // Create a deploy user to push images to ECR - const deployUser = new iam.User(this, 'deploy-user'); - - const deployPolicy = new iam.Policy(this, 'deploy-policy'); - deployPolicy.addStatements( - new iam.PolicyStatement({ - effect: iam.Effect.ALLOW, - actions: [ - 'ecr:CompleteLayerUpload', - 'ecr:UploadLayerPart', - 'ecr:InitiateLayerUpload', - 'ecr:BatchCheckLayerAvailability', - 'ecr:PutImage', - ], - resources: [this.repository.repositoryArn], - }), - new iam.PolicyStatement({ - effect: iam.Effect.ALLOW, - actions: ['ecr:GetAuthorizationToken'], - resources: ['*'], - }) - ); - - deployUser.attachInlinePolicy(deployPolicy); - - const securityGroup = new SecurityGroup(this, 'security-group', { - vpc: this.vpc, - }); - - const cluster = new ecs.Cluster(this, `cluster`, { - vpc: this.vpc, - }); - - const environment: { [key: string]: string } = {}; - - // If using Redis configure security group and pass connection string to container - if (props.redis) { - props.redis.securityGroup.addIngressRule( - securityGroup, - Port.tcp(props.redis.connectionPort) - ); - - environment['REDIS_ENDPOINT'] = props.redis.connectionEndPoint; - environment['REDIS_PORT'] = props.redis.connectionPort.toString(); - } - - // Construct secrets from provided ssm values - const secrets: {[key: string]: ecs.Secret} = {}; - props.secrets = props.secrets || {}; - for (const [key, ssm] of Object.entries(props.secrets)) { - secrets[key] = ecs.Secret.fromSsmParameter(ssm); - } - // Create a load-balanced Fargate service and make it public - const fargateService = - new ecsPatterns.ApplicationLoadBalancedFargateService( - this, - `fargate`, - { - cluster, - certificate, - enableExecuteCommand: true, - cpu: props.cpu || 512, // 0.5 vCPU - memoryLimitMiB: props.memory || 1024, // 1 GB - taskImageOptions: { - image: ecs.ContainerImage.fromEcrRepository( - this.repository - ), - enableLogging: true, // default - containerPort: 4000, // graphql mesh gateway port - secrets: secrets, - environment: environment, - }, - publicLoadBalancer: true, // default, - taskSubnets: { - subnets: [...this.vpc.privateSubnets], - }, - securityGroups: [securityGroup], - } - ); - - this.service = fargateService.service; - - this.firewall = new WebApplicationFirewall(this, 'waf', { - scope: Scope.REGIONAL, - visibilityConfig: { - cloudWatchMetricsEnabled: true, - metricName: "firewall-request", - sampledRequestsEnabled: true - }, - managedRules: [ - { - name: ManagedRule.COMMON_RULE_SET, - excludedRules: [ - { - name: 'SizeRestrictions_QUERYSTRING' - } - ] - }, - { - name: ManagedRule.KNOWN_BAD_INPUTS_RULE_SET, - } - ] - }); - - this.firewall.addAssociation('loadbalancer-association', fargateService.loadBalancer.loadBalancerArn); - - fargateService.targetGroup.configureHealthCheck({ - path: '/healthcheck', - }); - - // Setup auto scaling policy - const scaling = fargateService.service.autoScaleTaskCount({ - minCapacity: props.minCapacity || 1, - maxCapacity: props.maxCapacity || 5, - }); - - const cpuUtilization = fargateService.service.metricCpuUtilization(); - scaling.scaleOnMetric('auto-scale-cpu', { - metric: cpuUtilization, - scalingSteps: [ - { upper: 30, change: -1 }, - { lower: 50, change: +1 }, - { lower: 85, change: +3 }, - ], - adjustmentType: auto_scaling.AdjustmentType.CHANGE_IN_CAPACITY, - }); + public readonly vpc: Vpc; + public readonly repository: ecr.Repository; + public readonly service: ecs.FargateService; + public readonly firewall: WebApplicationFirewall; + + constructor(scope: Construct, id: string, props: MeshServiceProps) { + super(scope, id); + + const certificate = acm.Certificate.fromCertificateArn( + this, + `certificate`, + props.certificateArn + ); + + this.vpc = + props.vpc || + new Vpc(this, "vpc", { + natGateways: 1, + }); + + this.repository = + props.repository || + new ecr.Repository(this, "repo", { + removalPolicy: RemovalPolicy.DESTROY, + autoDeleteImages: true, + }); + + if (!props.repository) { + // Delete all images older than 90 days BUT keep 10 from the latest tag + this.repository.addLifecycleRule({ + tagPrefixList: ["latest"], + maxImageCount: 10, + }); + this.repository.addLifecycleRule({ + maxImageAge: Duration.days(90), + }); + } + + // Create a deploy user to push images to ECR + const deployUser = new iam.User(this, "deploy-user"); + + const deployPolicy = new iam.Policy(this, "deploy-policy"); + deployPolicy.addStatements( + new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: [ + "ecr:CompleteLayerUpload", + "ecr:UploadLayerPart", + "ecr:InitiateLayerUpload", + "ecr:BatchCheckLayerAvailability", + "ecr:PutImage", + ], + resources: [this.repository.repositoryArn], + }), + new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ["ecr:GetAuthorizationToken"], + resources: ["*"], + }) + ); + + deployUser.attachInlinePolicy(deployPolicy); + + const securityGroup = new SecurityGroup(this, "security-group", { + vpc: this.vpc, + }); + + const cluster = new ecs.Cluster(this, `cluster`, { + vpc: this.vpc, + }); + + const environment: { [key: string]: string } = {}; + + // If using Redis configure security group and pass connection string to container + if (props.redis) { + props.redis.securityGroup.addIngressRule( + securityGroup, + Port.tcp(props.redis.connectionPort) + ); + + environment["REDIS_ENDPOINT"] = props.redis.connectionEndPoint; + environment["REDIS_PORT"] = props.redis.connectionPort.toString(); } + + // Construct secrets from provided ssm values + const secrets: { [key: string]: ecs.Secret } = {}; + props.secrets = props.secrets || {}; + for (const [key, ssm] of Object.entries(props.secrets)) { + secrets[key] = ecs.Secret.fromSsmParameter(ssm); + } + // Create a load-balanced Fargate service and make it public + const fargateService = + new ecsPatterns.ApplicationLoadBalancedFargateService(this, `fargate`, { + cluster, + certificate, + enableExecuteCommand: true, + cpu: props.cpu || 512, // 0.5 vCPU + memoryLimitMiB: props.memory || 1024, // 1 GB + taskImageOptions: { + image: ecs.ContainerImage.fromEcrRepository(this.repository), + enableLogging: true, // default + containerPort: 4000, // graphql mesh gateway port + secrets: secrets, + environment: environment, + }, + publicLoadBalancer: true, // default, + taskSubnets: { + subnets: [...this.vpc.privateSubnets], + }, + securityGroups: [securityGroup], + }); + + this.service = fargateService.service; + + this.firewall = new WebApplicationFirewall(this, "waf", { + scope: Scope.REGIONAL, + visibilityConfig: { + cloudWatchMetricsEnabled: true, + metricName: "firewall-request", + sampledRequestsEnabled: true, + }, + managedRules: [ + { + name: ManagedRule.COMMON_RULE_SET, + excludedRules: [ + { + name: "SizeRestrictions_QUERYSTRING", + }, + ], + }, + { + name: ManagedRule.KNOWN_BAD_INPUTS_RULE_SET, + }, + ], + }); + + this.firewall.addAssociation( + "loadbalancer-association", + fargateService.loadBalancer.loadBalancerArn + ); + + fargateService.targetGroup.configureHealthCheck({ + path: "/healthcheck", + }); + + // Setup auto scaling policy + const scaling = fargateService.service.autoScaleTaskCount({ + minCapacity: props.minCapacity || 1, + maxCapacity: props.maxCapacity || 5, + }); + + const cpuUtilization = fargateService.service.metricCpuUtilization(); + scaling.scaleOnMetric("auto-scale-cpu", { + metric: cpuUtilization, + scalingSteps: [ + { upper: 30, change: -1 }, + { lower: 50, change: +1 }, + { lower: 85, change: +3 }, + ], + adjustmentType: auto_scaling.AdjustmentType.CHANGE_IN_CAPACITY, + }); + } } diff --git a/packages/graphql-mesh-server/lib/graphql-mesh-server.ts b/packages/graphql-mesh-server/lib/graphql-mesh-server.ts index cec04774..399c2457 100644 --- a/packages/graphql-mesh-server/lib/graphql-mesh-server.ts +++ b/packages/graphql-mesh-server/lib/graphql-mesh-server.ts @@ -1,95 +1,99 @@ -import { Construct } from 'constructs'; -import { MeshService, MeshServiceProps } from './fargate'; -import { RedisService, RedisServiceProps } from './redis-construct'; -import { CodePipelineService } from './pipeline'; -import { SecurityGroup, Vpc } from 'aws-cdk-lib/aws-ec2'; -import { Repository } from 'aws-cdk-lib/aws-ecr'; -import { FargateService } from 'aws-cdk-lib/aws-ecs'; -import { CfnCacheCluster } from 'aws-cdk-lib/aws-elasticache'; -import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { Construct } from "constructs"; +import { MeshService, MeshServiceProps } from "./fargate"; +import { RedisService, RedisServiceProps } from "./redis-construct"; +import { CodePipelineService } from "./pipeline"; +import { SecurityGroup, Vpc } from "aws-cdk-lib/aws-ec2"; +import { Repository } from "aws-cdk-lib/aws-ecr"; +import { FargateService } from "aws-cdk-lib/aws-ecs"; +import { CfnCacheCluster } from "aws-cdk-lib/aws-elasticache"; +import * as ssm from "aws-cdk-lib/aws-ssm"; export type MeshHostingProps = { - /** - * VPC to attach Redis and Fargate instances to (default: create a vpc) - */ - vpc?: Vpc; - /** - * If no VPC is provided create one with this name (default: 'graphql-server-vpc') - */ - vpcName?: string; - /** - * Cache node type (default: 'cache.t2.micro') - */ - cacheNodeType?: string; - /** - * Repository to pull the container image from - */ - repository?: Repository; - /** - * ARN of the certificate to add to the load balancer - */ - certificateArn: string; - /** - * Minimum number of Fargate instances - */ - minCapacity?: number; - /** - * Maximum number of Fargate instances - */ - maxCapacity?: number; - /** - * Amount of vCPU per Fargate instance (default: 512) - */ - cpu?: number; - /** - * Amount of memory per Fargate instance (default: 1024) - */ - memory?: number; - /** - * Redis instance to use for mesh caching - */ - redis?: RedisService; - /** - * SSM values to pass through to the container as secrets - */ - secrets?: {[key: string]: ssm.IStringParameter | ssm.IStringListParameter}; + /** + * VPC to attach Redis and Fargate instances to (default: create a vpc) + */ + vpc?: Vpc; + /** + * If no VPC is provided create one with this name (default: 'graphql-server-vpc') + */ + vpcName?: string; + /** + * Cache node type (default: 'cache.t2.micro') + */ + cacheNodeType?: string; + /** + * Repository to pull the container image from + */ + repository?: Repository; + /** + * ARN of the certificate to add to the load balancer + */ + certificateArn: string; + /** + * Minimum number of Fargate instances + */ + minCapacity?: number; + /** + * Maximum number of Fargate instances + */ + maxCapacity?: number; + /** + * Amount of vCPU per Fargate instance (default: 512) + */ + cpu?: number; + /** + * Amount of memory per Fargate instance (default: 1024) + */ + memory?: number; + /** + * Redis instance to use for mesh caching + */ + redis?: RedisService; + /** + * SSM values to pass through to the container as secrets + */ + secrets?: { [key: string]: ssm.IStringParameter | ssm.IStringListParameter }; }; export class MeshHosting extends Construct { - public readonly vpc: Vpc; - public readonly repository: Repository; - public readonly service: FargateService; - public readonly cacheCluster: CfnCacheCluster; - public readonly securityGroup: SecurityGroup; + public readonly vpc: Vpc; + public readonly repository: Repository; + public readonly service: FargateService; + public readonly cacheCluster: CfnCacheCluster; + public readonly securityGroup: SecurityGroup; - constructor(scope: Construct, id: string, props: MeshHostingProps) { - super(scope, id); + constructor(scope: Construct, id: string, props: MeshHostingProps) { + super(scope, id); - this.vpc = props.vpc || new Vpc(this, 'graphql-server-vpc', { - vpcName: props.vpcName || 'graphql-server-vpc', - natGateways: 1 - }); + this.vpc = + props.vpc || + new Vpc(this, "graphql-server-vpc", { + vpcName: props.vpcName || "graphql-server-vpc", + natGateways: 1, + }); - const redis = props.redis || new RedisService(this, 'redis', { - ...props, - vpc: this.vpc - }); + const redis = + props.redis || + new RedisService(this, "redis", { + ...props, + vpc: this.vpc, + }); - this.cacheCluster = redis.cacheCluster; - this.securityGroup = redis.securityGroup; + this.cacheCluster = redis.cacheCluster; + this.securityGroup = redis.securityGroup; - const mesh = new MeshService(this, 'mesh', { - ...props, - vpc: this.vpc, - redis, - }); + const mesh = new MeshService(this, "mesh", { + ...props, + vpc: this.vpc, + redis, + }); - this.service = mesh.service; - this.repository = mesh.repository; + this.service = mesh.service; + this.repository = mesh.repository; - new CodePipelineService(this, 'pipeline', { - repository: this.repository, - service: this.service, - }); - } + new CodePipelineService(this, "pipeline", { + repository: this.repository, + service: this.service, + }); + } } diff --git a/packages/graphql-mesh-server/lib/pipeline.ts b/packages/graphql-mesh-server/lib/pipeline.ts index a6189049..a4e7c7c6 100644 --- a/packages/graphql-mesh-server/lib/pipeline.ts +++ b/packages/graphql-mesh-server/lib/pipeline.ts @@ -1,91 +1,94 @@ -import { Duration } from 'aws-cdk-lib'; -import { Artifact, Pipeline } from 'aws-cdk-lib/aws-codepipeline'; -import { Repository } from 'aws-cdk-lib/aws-ecr'; -import { FargateService } from 'aws-cdk-lib/aws-ecs'; -import * as pipe_actions from 'aws-cdk-lib/aws-codepipeline-actions'; -import * as codebuild from 'aws-cdk-lib/aws-codebuild'; -import { Construct } from 'constructs'; -import * as fs from 'fs'; -import * as path from 'path'; -import * as YAML from 'yaml'; +import { Duration } from "aws-cdk-lib"; +import { Artifact, Pipeline } from "aws-cdk-lib/aws-codepipeline"; +import { Repository } from "aws-cdk-lib/aws-ecr"; +import { FargateService } from "aws-cdk-lib/aws-ecs"; +import * as pipe_actions from "aws-cdk-lib/aws-codepipeline-actions"; +import * as codebuild from "aws-cdk-lib/aws-codebuild"; +import { Construct } from "constructs"; +import * as fs from "fs"; +import * as path from "path"; +import * as YAML from "yaml"; export interface CodePipelineServiceProps { - /** - * Repository the code container is pushed too - */ - repository: Repository; + /** + * Repository the code container is pushed too + */ + repository: Repository; - /** - * Services to deploy Code container updates to - */ - service: FargateService; + /** + * Services to deploy Code container updates to + */ + service: FargateService; - /** - * Path to buildspec.yml (default: '../assets/buildspec.yml') - */ - buildspecPath?: string; + /** + * Path to buildspec.yml (default: '../assets/buildspec.yml') + */ + buildspecPath?: string; } export class CodePipelineService extends Construct { - public readonly pipeline: Pipeline; + public readonly pipeline: Pipeline; - constructor(scope: Construct, id: string, props: CodePipelineServiceProps) { - super(scope, id); + constructor(scope: Construct, id: string, props: CodePipelineServiceProps) { + super(scope, id); - this.pipeline = new Pipeline(this, 'deploy-pipeline'); + this.pipeline = new Pipeline(this, "deploy-pipeline"); - const sourceOutput = new Artifact(); - const sourceAction = new pipe_actions.EcrSourceAction({ - actionName: 'ECR', - repository: props.repository, - output: sourceOutput, - }); + const sourceOutput = new Artifact(); + const sourceAction = new pipe_actions.EcrSourceAction({ + actionName: "ECR", + repository: props.repository, + output: sourceOutput, + }); - this.pipeline.addStage({ - stageName: 'Source', - actions: [sourceAction], - }); + this.pipeline.addStage({ + stageName: "Source", + actions: [sourceAction], + }); - const file = fs.readFileSync( - path.resolve(__dirname, props.buildspecPath || '../assets/buildspec.yml'), - 'utf8' - ); - const project: codebuild.PipelineProject = - new codebuild.PipelineProject(this, 'project', { - buildSpec: codebuild.BuildSpec.fromObject(YAML.parse(file)), - }); + const file = fs.readFileSync( + path.resolve(__dirname, props.buildspecPath || "../assets/buildspec.yml"), + "utf8" + ); + const project: codebuild.PipelineProject = new codebuild.PipelineProject( + this, + "project", + { + buildSpec: codebuild.BuildSpec.fromObject(YAML.parse(file)), + } + ); - const buildOutput = new Artifact(); - this.pipeline.addStage({ - stageName: 'Build', - actions: [ - new pipe_actions.CodeBuildAction({ - actionName: 'CodeBuild', - project, - input: sourceOutput, - outputs: [buildOutput], - environmentVariables: { - IMAGE_URI: { - value: sourceAction.variables.imageUri, - }, - CONTAINER_NAME: { - value: props.service.taskDefinition.defaultContainer - ?.containerName, - }, - }, - }), - ], - }); - this.pipeline.addStage({ - stageName: 'Deploy', - actions: [ - new pipe_actions.EcsDeployAction({ - actionName: 'DeployAction', - service: props.service, - input: buildOutput, - deploymentTimeout: Duration.minutes(10), - }), - ], - }); - } + const buildOutput = new Artifact(); + this.pipeline.addStage({ + stageName: "Build", + actions: [ + new pipe_actions.CodeBuildAction({ + actionName: "CodeBuild", + project, + input: sourceOutput, + outputs: [buildOutput], + environmentVariables: { + IMAGE_URI: { + value: sourceAction.variables.imageUri, + }, + CONTAINER_NAME: { + value: + props.service.taskDefinition.defaultContainer?.containerName, + }, + }, + }), + ], + }); + this.pipeline.addStage({ + stageName: "Deploy", + actions: [ + new pipe_actions.EcsDeployAction({ + actionName: "DeployAction", + service: props.service, + input: buildOutput, + deploymentTimeout: Duration.minutes(10), + }), + ], + }); + } } diff --git a/packages/graphql-mesh-server/lib/redis-construct.ts b/packages/graphql-mesh-server/lib/redis-construct.ts index f88526b0..5a9ba5b0 100644 --- a/packages/graphql-mesh-server/lib/redis-construct.ts +++ b/packages/graphql-mesh-server/lib/redis-construct.ts @@ -1,92 +1,90 @@ -import { SecurityGroup, Vpc } from 'aws-cdk-lib/aws-ec2'; +import { SecurityGroup, Vpc } from "aws-cdk-lib/aws-ec2"; import { - CfnCacheCluster, - CfnSubnetGroup, - CfnParameterGroup, -} from 'aws-cdk-lib/aws-elasticache'; -import { CfnOutput, Reference, Token } from 'aws-cdk-lib'; -import { Construct } from 'constructs'; + CfnCacheCluster, + CfnSubnetGroup, + CfnParameterGroup, +} from "aws-cdk-lib/aws-elasticache"; +import { CfnOutput, Reference, Token } from "aws-cdk-lib"; +import { Construct } from "constructs"; export interface RedisServiceProps { - /** - * VPC to attach Redis instance to - */ - vpc: Vpc; - /** - * Cache node type (default: 'cache.t2.micro') - */ - cacheNodeType?: string; + /** + * VPC to attach Redis instance to + */ + vpc: Vpc; + /** + * Cache node type (default: 'cache.t2.micro') + */ + cacheNodeType?: string; } export class RedisService extends Construct { - public readonly cacheCluster: CfnCacheCluster; - public readonly vpc: Vpc; - public readonly securityGroup: SecurityGroup; + public readonly cacheCluster: CfnCacheCluster; + public readonly vpc: Vpc; + public readonly securityGroup: SecurityGroup; - constructor(scope: Construct, id: string, props: RedisServiceProps) { - super(scope, id); + constructor(scope: Construct, id: string, props: RedisServiceProps) { + super(scope, id); - this.vpc = props.vpc; + this.vpc = props.vpc; - this.securityGroup = new SecurityGroup(this, 'RedisSecurityGroup', { - vpc: this.vpc, - }); + this.securityGroup = new SecurityGroup(this, "RedisSecurityGroup", { + vpc: this.vpc, + }); - const privateSubnets: string[] = this.vpc.privateSubnets.map( - (subnet) => { - return subnet.subnetId; - } - ); + const privateSubnets: string[] = this.vpc.privateSubnets.map(subnet => { + return subnet.subnetId; + }); - const cacheSubnetGroup = new CfnSubnetGroup(this, 'CacheSubnetGroup', { - description: 'Subnet Group for Mesh Cache', - subnetIds: privateSubnets, - }); + const cacheSubnetGroup = new CfnSubnetGroup(this, "CacheSubnetGroup", { + description: "Subnet Group for Mesh Cache", + subnetIds: privateSubnets, + }); - const cacheParameterGroup = new CfnParameterGroup( - this, - 'CacheParameterGroup', - { - cacheParameterGroupFamily: 'redis7', - description: 'Parameter Group for Mesh Cache', - properties: { - 'maxmemory-policy': 'allkeys-lru', - }, - } - ); + const cacheParameterGroup = new CfnParameterGroup( + this, + "CacheParameterGroup", + { + cacheParameterGroupFamily: "redis7", + description: "Parameter Group for Mesh Cache", + properties: { + "maxmemory-policy": "allkeys-lru", + }, + } + ); - this.cacheCluster = new CfnCacheCluster(this, 'cache-cluster', { - cacheNodeType: props.cacheNodeType || 'cache.t2.micro', - engine: 'redis', - numCacheNodes: 1, - autoMinorVersionUpgrade: true, - vpcSecurityGroupIds: [this.securityGroup.securityGroupId], - cacheSubnetGroupName: cacheSubnetGroup.ref, - cacheParameterGroupName: cacheParameterGroup.ref, - }); + this.cacheCluster = new CfnCacheCluster(this, "cache-cluster", { + cacheNodeType: props.cacheNodeType || "cache.t2.micro", + engine: "redis", + numCacheNodes: 1, + autoMinorVersionUpgrade: true, + vpcSecurityGroupIds: [this.securityGroup.securityGroupId], + cacheSubnetGroupName: cacheSubnetGroup.ref, + cacheParameterGroupName: cacheParameterGroup.ref, + }); - this.cacheCluster.addDependency(cacheParameterGroup); - this.cacheCluster.addDependency(cacheSubnetGroup); + this.cacheCluster.addDependency(cacheParameterGroup); + this.cacheCluster.addDependency(cacheSubnetGroup); - new CfnOutput(this, 'RedisConnectionString', { - description: 'RedisConnectionString', - value: this.cacheConnectionString, - }); - } + new CfnOutput(this, "RedisConnectionString", { + description: "RedisConnectionString", + value: this.cacheConnectionString, + }); + } - public get cacheConnectionString(): string { - return `redis://${this.cacheCluster - .getAtt('RedisEndpoint.Address') - .toString()}:${this.cacheCluster - .getAtt('RedisEndpoint.Port') - .toString()}`; - } + public get cacheConnectionString(): string { + return `redis://${this.cacheCluster + .getAtt("RedisEndpoint.Address") + .toString()}:${this.cacheCluster + .getAtt("RedisEndpoint.Port") + .toString()}`; + } - public get connectionEndPoint(): string { - return Token.asString(this.cacheCluster.getAtt('RedisEndpoint.Address')) - } + public get connectionEndPoint(): string { + return Token.asString(this.cacheCluster.getAtt("RedisEndpoint.Address")); + } - public get connectionPort(): number { - return Token.asNumber(this.cacheCluster.getAtt('RedisEndpoint.Port')); - } + public get connectionPort(): number { + return Token.asNumber(this.cacheCluster.getAtt("RedisEndpoint.Port")); + } } diff --git a/packages/graphql-mesh-server/lib/web-application-firewall.ts b/packages/graphql-mesh-server/lib/web-application-firewall.ts index 57e3c6ce..98837990 100644 --- a/packages/graphql-mesh-server/lib/web-application-firewall.ts +++ b/packages/graphql-mesh-server/lib/web-application-firewall.ts @@ -2,163 +2,167 @@ import { CfnWebACL, CfnWebACLAssociation } from "aws-cdk-lib/aws-wafv2"; import { Construct } from "constructs"; export enum Action { - BLOCK = 'BLOCK', - ALLOW = 'ALLOW', + BLOCK = "BLOCK", + ALLOW = "ALLOW", } export enum Scope { - CLOUDFRONT = 'CLOUDFRONT', - REGIONAL = 'REGIONAL', + CLOUDFRONT = "CLOUDFRONT", + REGIONAL = "REGIONAL", } export enum ManagedRule { - BOT_CONTROL_RULE_SET = "AWSManagedRulesBotControlRuleSet", - KNOWN_BAD_INPUTS_RULE_SET = "AWSManagedRulesKnownBadInputsRuleSet", - COMMON_RULE_SET = "AWSManagedRulesCommonRuleSet", - ANNONYMOUS_IP_LIST = "AWSManagedRulesAnonymousIpList", - AMAZON_IP_REPUTATION_LIST = "AWSManagedRulesAmazonIpReputationList", - ADMIN_PROTECTION_RULE_SET = "AWSManagedRulesAdminProtectionRuleSet", - SQLI_RULE_SET = "AWSManagedRulesSQLiRuleSet", - PHP_RULE_SET = "AWSManagedRulesPHPRuleSet" + BOT_CONTROL_RULE_SET = "AWSManagedRulesBotControlRuleSet", + KNOWN_BAD_INPUTS_RULE_SET = "AWSManagedRulesKnownBadInputsRuleSet", + COMMON_RULE_SET = "AWSManagedRulesCommonRuleSet", + ANNONYMOUS_IP_LIST = "AWSManagedRulesAnonymousIpList", + AMAZON_IP_REPUTATION_LIST = "AWSManagedRulesAmazonIpReputationList", + ADMIN_PROTECTION_RULE_SET = "AWSManagedRulesAdminProtectionRuleSet", + SQLI_RULE_SET = "AWSManagedRulesSQLiRuleSet", + PHP_RULE_SET = "AWSManagedRulesPHPRuleSet", } export interface VisibilityConfig { - /** - * Whether cloudwatch metrics are enabled or nor - */ - cloudWatchMetricsEnabled: boolean, - - /** - * Name of the metric in cloudwatch - */ - metricName: string, - - /** - * Whether to keep samples of blocked requests - */ - sampledRequestsEnabled: boolean + /** + * Whether cloudwatch metrics are enabled or nor + */ + cloudWatchMetricsEnabled: boolean; + + /** + * Name of the metric in cloudwatch + */ + metricName: string; + + /** + * Whether to keep samples of blocked requests + */ + sampledRequestsEnabled: boolean; } export interface AWSManagedRule { - /** - * Which AWS Rule to add - */ - name: ManagedRule, - - /** - * @default to the name property - */ - metricName?: string, - - /** - * @default false - */ - sampledRequestsEnabled?: boolean, - - /** - * Any rules from this ruleset you wish to disable/exclude - */ - excludedRules?: Array<{ - name: string - }>, - - /** - * Whether to override the default action to COUNT - */ - count?: boolean + /** + * Which AWS Rule to add + */ + name: ManagedRule; + + /** + * @default to the name property + */ + metricName?: string; + + /** + * @default false + */ + sampledRequestsEnabled?: boolean; + + /** + * Any rules from this ruleset you wish to disable/exclude + */ + excludedRules?: Array<{ + name: string; + }>; + + /** + * Whether to override the default action to COUNT + */ + count?: boolean; } export interface WebApplicationFirewallProps { - /** - * Name of the WAF - */ - name?: string, - - /** - * The action to perform if none of the `Rules` contained in the `WebACL` match. - * @default Action.ALLOW - */ - defaultAction?: Action, - - /** - * Specifies whether this is for an Amazon CloudFront distribution or for a regional application. - * @default Scope.REGIONAL - */ - scope?: Scope - - /** - * Default visibility configuration - */ - visibilityConfig: VisibilityConfig, - - /** - * List of AWS Managed rules to add to the WAF - */ - managedRules?: AWSManagedRule[], - - /** - * List of custom rules - */ - rules?: CfnWebACL.RuleProperty[] + /** + * Name of the WAF + */ + name?: string; + + /** + * The action to perform if none of the `Rules` contained in the `WebACL` match. + * @default Action.ALLOW + */ + defaultAction?: Action; + + /** + * Specifies whether this is for an Amazon CloudFront distribution or for a regional application. + * @default Scope.REGIONAL + */ + scope?: Scope; + + /** + * Default visibility configuration + */ + visibilityConfig: VisibilityConfig; + + /** + * List of AWS Managed rules to add to the WAF + */ + managedRules?: AWSManagedRule[]; + + /** + * List of custom rules + */ + rules?: CfnWebACL.RuleProperty[]; } export class WebApplicationFirewall extends Construct { - readonly acl: CfnWebACL; - readonly associations: CfnWebACLAssociation[]; - - constructor(scope: Construct, id: string, props: WebApplicationFirewallProps) { - super(scope, id); - - let defaultAction: CfnWebACL.DefaultActionProperty = { allow: {} }; - - if (props.defaultAction == Action.BLOCK) { - defaultAction = { block: {} }; - } - - this.associations = []; - - const rules: CfnWebACL.RuleProperty[] = props.rules || []; - - // Convert from our AWSManagedRule type to a CfnWebACL.RuleProperty - if (props.managedRules) { - props.managedRules.forEach((rule, index) => { - rules.push({ - name: rule.name, - priority: index, - visibilityConfig: { - // if no metric name is passed then don't enable metrics - cloudWatchMetricsEnabled: rule.metricName ? true : false, - // Default to the rule name if a metric name isn't passed - metricName: rule.metricName || rule.name, - sampledRequestsEnabled: rule.sampledRequestsEnabled || false - }, - statement: { - managedRuleGroupStatement: { - name: rule.name, - vendorName: "AWS", - excludedRules: rule.excludedRules || [], - } - }, - overrideAction: rule.count ? { count: {} } : { none: {} } - }) - }); - } - - this.acl = new CfnWebACL(this, "WebAcl", { - name: props.name, - defaultAction, - scope: props.scope || Scope.REGIONAL, - visibilityConfig: props.visibilityConfig, - rules: rules - }); + readonly acl: CfnWebACL; + readonly associations: CfnWebACLAssociation[]; + + constructor( + scope: Construct, + id: string, + props: WebApplicationFirewallProps + ) { + super(scope, id); + + let defaultAction: CfnWebACL.DefaultActionProperty = { allow: {} }; + + if (props.defaultAction == Action.BLOCK) { + defaultAction = { block: {} }; } - public addAssociation(id: string, resourceArn: string) { - this.associations.push( - new CfnWebACLAssociation(this, id, { - webAclArn: this.acl.attrArn, - resourceArn - }) - ); + this.associations = []; + + const rules: CfnWebACL.RuleProperty[] = props.rules || []; + + // Convert from our AWSManagedRule type to a CfnWebACL.RuleProperty + if (props.managedRules) { + props.managedRules.forEach((rule, index) => { + rules.push({ + name: rule.name, + priority: index, + visibilityConfig: { + // if no metric name is passed then don't enable metrics + cloudWatchMetricsEnabled: rule.metricName ? true : false, + // Default to the rule name if a metric name isn't passed + metricName: rule.metricName || rule.name, + sampledRequestsEnabled: rule.sampledRequestsEnabled || false, + }, + statement: { + managedRuleGroupStatement: { + name: rule.name, + vendorName: "AWS", + excludedRules: rule.excludedRules || [], + }, + }, + overrideAction: rule.count ? { count: {} } : { none: {} }, + }); + }); } -} \ No newline at end of file + + this.acl = new CfnWebACL(this, "WebAcl", { + name: props.name, + defaultAction, + scope: props.scope || Scope.REGIONAL, + visibilityConfig: props.visibilityConfig, + rules: rules, + }); + } + + public addAssociation(id: string, resourceArn: string) { + this.associations.push( + new CfnWebACLAssociation(this, id, { + webAclArn: this.acl.attrArn, + resourceArn, + }) + ); + } +}