@medplum/cdk 2.0.7 → 2.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -374,6 +374,11 @@ class BackEnd extends constructs.Construct {
374
374
  desiredCount: config.desiredServerCount,
375
375
  securityGroups: [fargateSecurityGroup],
376
376
  });
377
+ // Add dependencies - make sure Fargate service is created after RDS and Redis
378
+ if (rdsCluster) {
379
+ fargateService.node.addDependency(rdsCluster);
380
+ }
381
+ fargateService.node.addDependency(redisCluster);
377
382
  // Load Balancer Target Group
378
383
  const targetGroup = new awsCdkLib.aws_elasticloadbalancingv2.ApplicationTargetGroup(this, 'TargetGroup', {
379
384
  vpc: vpc,
@@ -433,16 +438,20 @@ class BackEnd extends constructs.Construct {
433
438
  }
434
439
  // Grant Redis access to the fargate group
435
440
  redisSecurityGroup.addIngressRule(fargateSecurityGroup, awsCdkLib.aws_ec2.Port.tcp(6379));
436
- // Route 53
437
- const zone = awsCdkLib.aws_route53.HostedZone.fromLookup(this, 'Zone', {
438
- domainName: config.domainName.split('.').slice(-2).join('.'),
439
- });
440
- // Route53 alias record for the load balancer
441
- const record = new awsCdkLib.aws_route53.ARecord(this, 'LoadBalancerAliasRecord', {
442
- recordName: config.apiDomainName,
443
- target: awsCdkLib.aws_route53.RecordTarget.fromAlias(new awsCdkLib.aws_route53_targets.LoadBalancerTarget(loadBalancer)),
444
- zone: zone,
445
- });
441
+ // DNS
442
+ let record = undefined;
443
+ if (!config.skipDns) {
444
+ // Route 53
445
+ const zone = awsCdkLib.aws_route53.HostedZone.fromLookup(this, 'Zone', {
446
+ domainName: config.domainName.split('.').slice(-2).join('.'),
447
+ });
448
+ // Route53 alias record for the load balancer
449
+ record = new awsCdkLib.aws_route53.ARecord(this, 'LoadBalancerAliasRecord', {
450
+ recordName: config.apiDomainName,
451
+ target: awsCdkLib.aws_route53.RecordTarget.fromAlias(new awsCdkLib.aws_route53_targets.LoadBalancerTarget(loadBalancer)),
452
+ zone: zone,
453
+ });
454
+ }
446
455
  // SSM Parameters
447
456
  const databaseSecrets = new awsCdkLib.aws_ssm.StringParameter(this, 'DatabaseSecretsParameter', {
448
457
  tier: awsCdkLib.aws_ssm.ParameterTier.STANDARD,
@@ -463,7 +472,7 @@ class BackEnd extends constructs.Construct {
463
472
  stringValue: botLambdaRole.roleArn,
464
473
  });
465
474
  // Debug
466
- console.log('ARecord', record.domainName);
475
+ console.log('ARecord', record?.domainName);
467
476
  console.log('DatabaseSecretsParameter', databaseSecrets.parameterArn);
468
477
  console.log('RedisSecretsParameter', redisSecretsParameter.parameterArn);
469
478
  console.log('RedisCluster', redisCluster.attrPrimaryEndPointAddress);
@@ -594,17 +603,21 @@ class FrontEnd extends constructs.Construct {
594
603
  ],
595
604
  webAclId: waf.attrArn,
596
605
  });
597
- const zone = awsCdkLib.aws_route53.HostedZone.fromLookup(this, 'Zone', {
598
- domainName: config.domainName.split('.').slice(-2).join('.'),
599
- });
600
- // Route53 alias record for the CloudFront distribution
601
- const record = new awsCdkLib.aws_route53.ARecord(this, 'AppAliasRecord', {
602
- recordName: config.appDomainName,
603
- target: awsCdkLib.aws_route53.RecordTarget.fromAlias(new awsCdkLib.aws_route53_targets.CloudFrontTarget(distribution)),
604
- zone,
605
- });
606
+ // DNS
607
+ let record = undefined;
608
+ if (!config.skipDns) {
609
+ const zone = awsCdkLib.aws_route53.HostedZone.fromLookup(this, 'Zone', {
610
+ domainName: config.domainName.split('.').slice(-2).join('.'),
611
+ });
612
+ // Route53 alias record for the CloudFront distribution
613
+ record = new awsCdkLib.aws_route53.ARecord(this, 'AppAliasRecord', {
614
+ recordName: config.appDomainName,
615
+ target: awsCdkLib.aws_route53.RecordTarget.fromAlias(new awsCdkLib.aws_route53_targets.CloudFrontTarget(distribution)),
616
+ zone,
617
+ });
618
+ }
606
619
  // Debug
607
- console.log('ARecord', record.domainName);
620
+ console.log('ARecord', record?.domainName);
608
621
  }
609
622
  }
610
623
  }
@@ -702,17 +715,21 @@ class Storage extends constructs.Construct {
702
715
  domainNames: [config.storageDomainName],
703
716
  webAclId: waf.attrArn,
704
717
  });
705
- const zone = awsCdkLib.aws_route53.HostedZone.fromLookup(this, 'Zone', {
706
- domainName: config.domainName.split('.').slice(-2).join('.'),
707
- });
708
- // Route53 alias record for the CloudFront distribution
709
- const record = new awsCdkLib.aws_route53.ARecord(this, 'StorageAliasRecord', {
710
- recordName: config.storageDomainName,
711
- target: awsCdkLib.aws_route53.RecordTarget.fromAlias(new awsCdkLib.aws_route53_targets.CloudFrontTarget(distribution)),
712
- zone,
713
- });
718
+ // DNS
719
+ let record = undefined;
720
+ if (!config.skipDns) {
721
+ const zone = awsCdkLib.aws_route53.HostedZone.fromLookup(this, 'Zone', {
722
+ domainName: config.domainName.split('.').slice(-2).join('.'),
723
+ });
724
+ // Route53 alias record for the CloudFront distribution
725
+ record = new awsCdkLib.aws_route53.ARecord(this, 'StorageAliasRecord', {
726
+ recordName: config.storageDomainName,
727
+ target: awsCdkLib.aws_route53.RecordTarget.fromAlias(new awsCdkLib.aws_route53_targets.CloudFrontTarget(distribution)),
728
+ zone,
729
+ });
730
+ }
714
731
  // Debug
715
- console.log('ARecord', record.domainName);
732
+ console.log('ARecord', record?.domainName);
716
733
  }
717
734
  }
718
735
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":["../../../src/waf.ts","../../../src/backend.ts","../../../src/frontend.ts","../../../src/storage.ts","../../../src/index.ts"],"sourcesContent":["// Based on https://gist.github.com/statik/f1ac9d6227d98d30c7a7cec0c83f4e64\n\nimport { aws_wafv2 as wafv2 } from 'aws-cdk-lib';\n\nexport const awsManagedRules: wafv2.CfnWebACL.RuleProperty[] = [\n // Common Rule Set aligns with major portions of OWASP Core Rule Set\n // https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-baseline.html\n {\n name: 'AWS-AWSManagedRulesCommonRuleSet',\n priority: 10,\n statement: {\n managedRuleGroupStatement: {\n vendorName: 'AWS',\n name: 'AWSManagedRulesCommonRuleSet',\n // Excluding generic RFI body rule for sns notifications\n // https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-list.html\n excludedRules: [\n { name: 'NoUserAgent_HEADER' },\n { name: 'UserAgent_BadBots_HEADER' },\n { name: 'SizeRestrictions_QUERYSTRING' },\n { name: 'SizeRestrictions_Cookie_HEADER' },\n { name: 'SizeRestrictions_BODY' },\n { name: 'SizeRestrictions_URIPATH' },\n { name: 'EC2MetaDataSSRF_BODY' },\n { name: 'EC2MetaDataSSRF_COOKIE' },\n { name: 'EC2MetaDataSSRF_URIPATH' },\n { name: 'EC2MetaDataSSRF_QUERYARGUMENTS' },\n { name: 'GenericLFI_QUERYARGUMENTS' },\n { name: 'GenericLFI_URIPATH' },\n { name: 'GenericLFI_BODY' },\n { name: 'RestrictedExtensions_URIPATH' },\n { name: 'RestrictedExtensions_QUERYARGUMENTS' },\n { name: 'GenericRFI_QUERYARGUMENTS' },\n { name: 'GenericRFI_BODY' },\n { name: 'GenericRFI_URIPATH' },\n { name: 'CrossSiteScripting_COOKIE' },\n { name: 'CrossSiteScripting_QUERYARGUMENTS' },\n { name: 'CrossSiteScripting_BODY' },\n { name: 'CrossSiteScripting_URIPATH' },\n ],\n },\n },\n overrideAction: {\n count: {},\n },\n visibilityConfig: {\n sampledRequestsEnabled: true,\n cloudWatchMetricsEnabled: true,\n metricName: 'AWS-AWSManagedRulesCommonRuleSet',\n },\n },\n // AWS IP Reputation list includes known malicious actors/bots and is regularly updated\n // https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-ip-rep.html\n {\n name: 'AWS-AWSManagedRulesAmazonIpReputationList',\n priority: 20,\n statement: {\n managedRuleGroupStatement: {\n vendorName: 'AWS',\n name: 'AWSManagedRulesAmazonIpReputationList',\n excludedRules: [{ name: 'AWSManagedIPReputationList' }, { name: 'AWSManagedReconnaissanceList' }],\n },\n },\n overrideAction: {\n count: {},\n },\n visibilityConfig: {\n sampledRequestsEnabled: true,\n cloudWatchMetricsEnabled: true,\n metricName: 'AWSManagedRulesAmazonIpReputationList',\n },\n },\n // Blocks common SQL Injection\n // https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-use-case.html#aws-managed-rule-groups-use-case-sql-db\n {\n name: 'AWSManagedRulesSQLiRuleSet',\n priority: 30,\n visibilityConfig: {\n sampledRequestsEnabled: true,\n cloudWatchMetricsEnabled: true,\n metricName: 'AWSManagedRulesSQLiRuleSet',\n },\n overrideAction: {\n count: {},\n },\n statement: {\n managedRuleGroupStatement: {\n vendorName: 'AWS',\n name: 'AWSManagedRulesSQLiRuleSet',\n excludedRules: [\n { name: 'SQLi_QUERYARGUMENTS' },\n { name: 'SQLiExtendedPatterns_QUERYARGUMENTS' },\n { name: 'SQLi_BODY' },\n { name: 'SQLiExtendedPatterns_BODY' },\n { name: 'SQLi_COOKIE' },\n { name: 'SQLi_URIPATH' },\n ],\n },\n },\n },\n // Blocks attacks targeting LFI(Local File Injection) for linux systems\n // https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-use-case.html#aws-managed-rule-groups-use-case-linux-os\n {\n name: 'AWSManagedRuleLinux',\n priority: 40,\n visibilityConfig: {\n sampledRequestsEnabled: true,\n cloudWatchMetricsEnabled: true,\n metricName: 'AWSManagedRuleLinux',\n },\n overrideAction: {\n count: {},\n },\n statement: {\n managedRuleGroupStatement: {\n vendorName: 'AWS',\n name: 'AWSManagedRulesLinuxRuleSet',\n excludedRules: [{ name: 'LFI_URIPATH' }, { name: 'LFI_QUERYSTRING' }, { name: 'LFI_COOKIE' }],\n },\n },\n },\n];\n","import {\n aws_ec2 as ec2,\n aws_ecs as ecs,\n aws_elasticache as elasticache,\n aws_elasticloadbalancingv2 as elbv2,\n aws_iam as iam,\n aws_logs as logs,\n aws_rds as rds,\n aws_route53 as route53,\n aws_route53_targets as targets,\n aws_s3 as s3,\n aws_secretsmanager as secretsmanager,\n aws_ssm as ssm,\n aws_wafv2 as wafv2,\n Duration,\n RemovalPolicy,\n} from 'aws-cdk-lib';\nimport { Repository } from 'aws-cdk-lib/aws-ecr';\nimport { Construct } from 'constructs';\nimport { MedplumInfraConfig } from './config';\nimport { awsManagedRules } from './waf';\n\n/**\n * Based on: https://github.com/aws-samples/http-api-aws-fargate-cdk/blob/master/cdk/singleAccount/lib/fargate-vpclink-stack.ts\n *\n * RDS config: https://docs.aws.amazon.com/cdk/api/latest/docs/aws-rds-readme.html\n */\nexport class BackEnd extends Construct {\n constructor(scope: Construct, config: MedplumInfraConfig) {\n super(scope, 'BackEnd');\n\n const name = config.name;\n\n // VPC\n let vpc: ec2.IVpc;\n\n if (config.vpcId) {\n // Lookup VPC by ARN\n vpc = ec2.Vpc.fromLookup(this, 'VPC', { vpcId: config.vpcId });\n } else {\n // VPC Flow Logs\n const vpcFlowLogs = new logs.LogGroup(this, 'VpcFlowLogs', {\n logGroupName: '/medplum/flowlogs/' + name,\n removalPolicy: RemovalPolicy.DESTROY,\n });\n\n // Create VPC\n vpc = new ec2.Vpc(this, 'VPC', {\n maxAzs: config.maxAzs,\n flowLogs: {\n cloudwatch: {\n destination: ec2.FlowLogDestination.toCloudWatchLogs(vpcFlowLogs),\n trafficType: ec2.FlowLogTrafficType.ALL,\n },\n },\n });\n }\n\n // Bot Lambda Role\n const botLambdaRole = new iam.Role(this, 'BotLambdaRole', {\n assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),\n });\n\n // RDS\n let rdsCluster = undefined;\n let rdsSecretsArn = config.rdsSecretsArn;\n if (!rdsSecretsArn) {\n rdsCluster = new rds.DatabaseCluster(this, 'DatabaseCluster', {\n engine: rds.DatabaseClusterEngine.auroraPostgres({\n version: rds.AuroraPostgresEngineVersion.VER_12_9,\n }),\n credentials: rds.Credentials.fromGeneratedSecret('clusteradmin'),\n defaultDatabaseName: 'medplum',\n storageEncrypted: true,\n instances: config.rdsInstances,\n instanceProps: {\n vpc: vpc,\n vpcSubnets: {\n subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,\n },\n instanceType: config.rdsInstanceType ? new ec2.InstanceType(config.rdsInstanceType) : undefined,\n enablePerformanceInsights: true,\n },\n backup: {\n retention: Duration.days(7),\n },\n cloudwatchLogsExports: ['postgresql'],\n instanceUpdateBehaviour: rds.InstanceUpdateBehaviour.ROLLING,\n });\n\n rdsSecretsArn = (rdsCluster.secret as secretsmanager.ISecret).secretArn;\n }\n\n // Redis\n // Important: For HIPAA compliance, you must specify TransitEncryptionEnabled as true, an AuthToken, and a CacheSubnetGroup.\n const redisSubnetGroup = new elasticache.CfnSubnetGroup(this, 'RedisSubnetGroup', {\n description: 'Redis Subnet Group',\n subnetIds: vpc.privateSubnets.map((subnet) => subnet.subnetId),\n });\n\n const redisSecurityGroup = new ec2.SecurityGroup(this, 'RedisSecurityGroup', {\n vpc,\n description: 'Redis Security Group',\n allowAllOutbound: false,\n });\n\n const redisPassword = new secretsmanager.Secret(this, 'RedisPassword', {\n generateSecretString: {\n secretStringTemplate: '{}',\n generateStringKey: 'password',\n excludeCharacters: '@%*()_+=`~{}|[]\\\\:\";\\'?,./',\n },\n });\n\n const redisCluster = new elasticache.CfnReplicationGroup(this, 'RedisCluster', {\n engine: 'Redis',\n engineVersion: '6.x',\n cacheNodeType: 'cache.t2.medium',\n replicationGroupDescription: 'RedisReplicationGroup',\n authToken: redisPassword.secretValueFromJson('password').toString(),\n transitEncryptionEnabled: true,\n atRestEncryptionEnabled: true,\n multiAzEnabled: true,\n cacheSubnetGroupName: redisSubnetGroup.ref,\n numNodeGroups: 1,\n replicasPerNodeGroup: 1,\n securityGroupIds: [redisSecurityGroup.securityGroupId],\n });\n redisCluster.node.addDependency(redisPassword);\n\n const redisSecrets = new secretsmanager.Secret(this, 'RedisSecrets', {\n generateSecretString: {\n secretStringTemplate: JSON.stringify({\n host: redisCluster.attrPrimaryEndPointAddress,\n port: redisCluster.attrPrimaryEndPointPort,\n password: redisPassword.secretValueFromJson('password').toString(),\n tls: {},\n }),\n generateStringKey: 'unused',\n },\n });\n redisSecrets.node.addDependency(redisPassword);\n redisSecrets.node.addDependency(redisCluster);\n\n // ECS Cluster\n const cluster = new ecs.Cluster(this, 'Cluster', {\n vpc: vpc,\n });\n\n // Task Policies\n const taskRolePolicies = new iam.PolicyDocument({\n statements: [\n // CloudWatch Logs: Create streams and put events\n new iam.PolicyStatement({\n effect: iam.Effect.ALLOW,\n actions: ['logs:CreateLogStream', 'logs:PutLogEvents'],\n resources: ['arn:aws:logs:*'],\n }),\n\n // Secrets Manager: Read only access to secrets\n // https://docs.aws.amazon.com/mediaconnect/latest/ug/iam-policy-examples-asm-secrets.html\n new iam.PolicyStatement({\n effect: iam.Effect.ALLOW,\n actions: [\n 'secretsmanager:GetResourcePolicy',\n 'secretsmanager:GetSecretValue',\n 'secretsmanager:DescribeSecret',\n 'secretsmanager:ListSecrets',\n 'secretsmanager:ListSecretVersionIds',\n ],\n resources: ['arn:aws:secretsmanager:*'],\n }),\n\n // Parameter Store: Read only access\n // https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-paramstore-access.html\n new iam.PolicyStatement({\n effect: iam.Effect.ALLOW,\n actions: ['ssm:GetParametersByPath', 'ssm:GetParameters', 'ssm:GetParameter', 'ssm:DescribeParameters'],\n resources: ['arn:aws:ssm:*'],\n }),\n\n // SES: Send emails\n // https://docs.aws.amazon.com/ses/latest/dg/sending-authorization-policy-examples.html\n new iam.PolicyStatement({\n effect: iam.Effect.ALLOW,\n actions: ['ses:SendEmail', 'ses:SendRawEmail'],\n resources: ['arn:aws:ses:*'],\n }),\n\n // S3: Read and write access to buckets\n // https://docs.aws.amazon.com/service-authorization/latest/reference/list_amazons3.html\n new iam.PolicyStatement({\n effect: iam.Effect.ALLOW,\n actions: ['s3:ListBucket', 's3:GetObject', 's3:PutObject', 's3:DeleteObject'],\n resources: ['arn:aws:s3:::*'],\n }),\n\n // IAM: Pass role to innvoke lambda functions\n // https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use_passrole.html\n new iam.PolicyStatement({\n effect: iam.Effect.ALLOW,\n actions: ['iam:ListRoles', 'iam:GetRole', 'iam:PassRole'],\n resources: [botLambdaRole.roleArn],\n }),\n\n // Lambda: Create, read, update, delete, and invoke functions\n // https://docs.aws.amazon.com/lambda/latest/dg/access-control-identity-based.html\n new iam.PolicyStatement({\n effect: iam.Effect.ALLOW,\n actions: [\n 'lambda:CreateFunction',\n 'lambda:GetFunction',\n 'lambda:GetFunctionConfiguration',\n 'lambda:UpdateFunctionCode',\n 'lambda:UpdateFunctionConfiguration',\n 'lambda:ListLayerVersions',\n 'lambda:GetLayerVersion',\n 'lambda:InvokeFunction',\n ],\n resources: ['arn:aws:lambda:*'],\n }),\n ],\n });\n\n // Task Role\n const taskRole = new iam.Role(this, 'TaskExecutionRole', {\n assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),\n description: 'Medplum Server Task Execution Role',\n inlinePolicies: {\n TaskExecutionPolicies: taskRolePolicies,\n },\n });\n\n // Task Definitions\n const taskDefinition = new ecs.FargateTaskDefinition(this, 'TaskDefinition', {\n memoryLimitMiB: config.serverMemory,\n cpu: config.serverCpu,\n taskRole: taskRole,\n });\n\n // Log Groups\n const logGroup = new logs.LogGroup(this, 'LogGroup', {\n logGroupName: '/ecs/medplum/' + name,\n removalPolicy: RemovalPolicy.DESTROY,\n });\n\n const logDriver = new ecs.AwsLogDriver({\n logGroup: logGroup,\n streamPrefix: 'Medplum',\n });\n\n // Task Containers\n let serverImage: ecs.ContainerImage | undefined = undefined;\n // Pull out the image name and tag from the image URI if it's an ECR image\n const ecrImageUriRegex = new RegExp(\n `^${config.accountNumber}\\\\.dkr\\\\.ecr\\\\.${config.region}\\\\.amazonaws\\\\.com/(.*)[:@](.*)$`\n );\n const nameTagMatches = config.serverImage.match(ecrImageUriRegex);\n const serverImageName = nameTagMatches?.[1];\n const serverImageTag = nameTagMatches?.[2];\n if (serverImageName && serverImageTag) {\n // Creating an ecr repository image will automatically grant fine-grained permissions to ecs to access the image\n const ecrRepo = Repository.fromRepositoryArn(\n this,\n 'ServerImageRepo',\n `arn:aws:ecr:${config.region}:${config.accountNumber}:repository/${serverImageName}`\n );\n serverImage = ecs.ContainerImage.fromEcrRepository(ecrRepo, serverImageTag);\n } else {\n // Otherwise, use the standard container image\n serverImage = ecs.ContainerImage.fromRegistry(config.serverImage);\n }\n const serviceContainer = taskDefinition.addContainer('MedplumTaskDefinition', {\n image: serverImage,\n command: [config.region === 'us-east-1' ? `aws:/medplum/${name}/` : `aws:${config.region}:/medplum/${name}/`],\n logging: logDriver,\n });\n\n serviceContainer.addPortMappings({\n containerPort: config.apiPort,\n hostPort: config.apiPort,\n });\n\n // Security Groups\n const fargateSecurityGroup = new ec2.SecurityGroup(this, 'ServiceSecurityGroup', {\n allowAllOutbound: true,\n securityGroupName: 'MedplumSecurityGroup',\n vpc: vpc,\n });\n\n // Fargate Services\n const fargateService = new ecs.FargateService(this, 'FargateService', {\n cluster: cluster,\n taskDefinition: taskDefinition,\n assignPublicIp: false,\n vpcSubnets: {\n subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,\n },\n desiredCount: config.desiredServerCount,\n securityGroups: [fargateSecurityGroup],\n });\n\n // Load Balancer Target Group\n const targetGroup = new elbv2.ApplicationTargetGroup(this, 'TargetGroup', {\n vpc: vpc,\n port: config.apiPort,\n protocol: elbv2.ApplicationProtocol.HTTP,\n healthCheck: {\n path: '/healthcheck',\n interval: Duration.seconds(30),\n timeout: Duration.seconds(3),\n healthyThresholdCount: 2,\n unhealthyThresholdCount: 5,\n },\n targets: [fargateService],\n });\n\n // Load Balancer\n const loadBalancer = new elbv2.ApplicationLoadBalancer(this, 'LoadBalancer', {\n vpc: vpc,\n internetFacing: true,\n http2Enabled: true,\n });\n\n if (config.loadBalancerLoggingEnabled) {\n // Load Balancer logging\n loadBalancer.logAccessLogs(\n s3.Bucket.fromBucketName(this, 'LoggingBucket', config.loadBalancerLoggingBucket),\n config.loadBalancerLoggingPrefix\n );\n }\n\n // HTTPS Listener\n // Forward to the target group\n loadBalancer.addListener('HttpsListener', {\n port: 443,\n certificates: [\n {\n certificateArn: config.apiSslCertArn,\n },\n ],\n sslPolicy: elbv2.SslPolicy.FORWARD_SECRECY_TLS12_RES_GCM,\n defaultAction: elbv2.ListenerAction.forward([targetGroup]),\n });\n\n // WAF\n const waf = new wafv2.CfnWebACL(this, 'BackEndWAF', {\n defaultAction: { allow: {} },\n scope: 'REGIONAL',\n name: `${config.stackName}-BackEndWAF`,\n rules: awsManagedRules,\n visibilityConfig: {\n cloudWatchMetricsEnabled: true,\n metricName: `${config.stackName}-BackEndWAF-Metric`,\n sampledRequestsEnabled: false,\n },\n });\n\n // Create an association between the load balancer and the WAF\n const wafAssociation = new wafv2.CfnWebACLAssociation(this, 'LoadBalancerAssociation', {\n resourceArn: loadBalancer.loadBalancerArn,\n webAclArn: waf.attrArn,\n });\n\n // Grant RDS access to the fargate group\n if (rdsCluster) {\n rdsCluster.connections.allowDefaultPortFrom(fargateSecurityGroup);\n }\n\n // Grant Redis access to the fargate group\n redisSecurityGroup.addIngressRule(fargateSecurityGroup, ec2.Port.tcp(6379));\n\n // Route 53\n const zone = route53.HostedZone.fromLookup(this, 'Zone', {\n domainName: config.domainName.split('.').slice(-2).join('.'),\n });\n\n // Route53 alias record for the load balancer\n const record = new route53.ARecord(this, 'LoadBalancerAliasRecord', {\n recordName: config.apiDomainName,\n target: route53.RecordTarget.fromAlias(new targets.LoadBalancerTarget(loadBalancer)),\n zone: zone,\n });\n\n // SSM Parameters\n const databaseSecrets = new ssm.StringParameter(this, 'DatabaseSecretsParameter', {\n tier: ssm.ParameterTier.STANDARD,\n parameterName: `/medplum/${name}/DatabaseSecrets`,\n description: 'Database secrets ARN',\n stringValue: rdsSecretsArn,\n });\n\n const redisSecretsParameter = new ssm.StringParameter(this, 'RedisSecretsParameter', {\n tier: ssm.ParameterTier.STANDARD,\n parameterName: `/medplum/${name}/RedisSecrets`,\n description: 'Redis secrets ARN',\n stringValue: redisSecrets.secretArn,\n });\n\n const botLambdaRoleParameter = new ssm.StringParameter(this, 'BotLambdaRoleParameter', {\n tier: ssm.ParameterTier.STANDARD,\n parameterName: `/medplum/${name}/botLambdaRoleArn`,\n description: 'Bot lambda execution role ARN',\n stringValue: botLambdaRole.roleArn,\n });\n\n // Debug\n console.log('ARecord', record.domainName);\n console.log('DatabaseSecretsParameter', databaseSecrets.parameterArn);\n console.log('RedisSecretsParameter', redisSecretsParameter.parameterArn);\n console.log('RedisCluster', redisCluster.attrPrimaryEndPointAddress);\n console.log('BotLambdaRole', botLambdaRoleParameter.stringValue);\n console.log('WAF', waf.attrArn);\n console.log('WAF Association', wafAssociation.node.id);\n }\n}\n","import {\n aws_certificatemanager as acm,\n aws_cloudfront as cloudfront,\n aws_cloudfront_origins as origins,\n aws_route53 as route53,\n aws_route53_targets as targets,\n aws_s3 as s3,\n aws_wafv2 as wafv2,\n Duration,\n RemovalPolicy,\n} from 'aws-cdk-lib';\nimport { Construct } from 'constructs';\nimport { MedplumInfraConfig } from './config';\nimport { awsManagedRules } from './waf';\n\n/**\n * Static app infrastructure, which deploys app content to an S3 bucket.\n *\n * The app redirects from HTTP to HTTPS, using a CloudFront distribution,\n * Route53 alias record, and ACM certificate.\n */\nexport class FrontEnd extends Construct {\n constructor(parent: Construct, config: MedplumInfraConfig, region: string) {\n super(parent, 'FrontEnd');\n\n let appBucket: s3.IBucket;\n\n if (region === config.region) {\n // S3 bucket\n appBucket = new s3.Bucket(this, 'AppBucket', {\n bucketName: config.appDomainName,\n publicReadAccess: false,\n blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,\n removalPolicy: RemovalPolicy.DESTROY,\n encryption: s3.BucketEncryption.S3_MANAGED,\n enforceSSL: true,\n versioned: false,\n });\n } else {\n // Otherwise, reference the bucket by name and region\n appBucket = s3.Bucket.fromBucketAttributes(this, 'AppBucket', {\n bucketName: config.appDomainName,\n region: config.region,\n });\n }\n\n if (region === 'us-east-1') {\n // HTTP response headers policy\n const responseHeadersPolicy = new cloudfront.ResponseHeadersPolicy(this, 'ResponseHeadersPolicy', {\n securityHeadersBehavior: {\n contentSecurityPolicy: {\n contentSecurityPolicy: [\n `default-src 'none'`,\n `base-uri 'self'`,\n `child-src 'self'`,\n `connect-src 'self' ${config.apiDomainName} *.google.com`,\n `font-src 'self' fonts.gstatic.com`,\n `form-action 'self' *.gstatic.com *.google.com`,\n `frame-ancestors 'none'`,\n `frame-src 'self' *.medplum.com *.gstatic.com *.google.com`,\n `img-src 'self' data: ${config.storageDomainName} *.gstatic.com *.google.com *.googleapis.com`,\n `manifest-src 'self'`,\n `media-src 'self' ${config.storageDomainName}`,\n `script-src 'self' *.medplum.com *.gstatic.com *.google.com`,\n `style-src 'self' 'unsafe-inline' *.medplum.com *.gstatic.com *.google.com`,\n `worker-src 'self' blob: *.gstatic.com *.google.com`,\n `upgrade-insecure-requests`,\n ].join('; '),\n override: true,\n },\n contentTypeOptions: { override: true },\n frameOptions: { frameOption: cloudfront.HeadersFrameOption.DENY, override: true },\n strictTransportSecurity: {\n accessControlMaxAge: Duration.seconds(63072000),\n includeSubdomains: true,\n override: true,\n },\n xssProtection: {\n protection: true,\n modeBlock: true,\n override: true,\n },\n },\n });\n\n // WAF\n const waf = new wafv2.CfnWebACL(this, 'FrontEndWAF', {\n defaultAction: { allow: {} },\n scope: 'CLOUDFRONT',\n name: `${config.stackName}-FrontEndWAF`,\n rules: awsManagedRules,\n visibilityConfig: {\n cloudWatchMetricsEnabled: true,\n metricName: `${config.stackName}-FrontEndWAF-Metric`,\n sampledRequestsEnabled: false,\n },\n });\n\n // API Origin Cache Policy\n const apiOriginCachePolicy = new cloudfront.CachePolicy(this, 'ApiOriginCachePolicy', {\n cachePolicyName: `${config.stackName}-ApiOriginCachePolicy`,\n cookieBehavior: cloudfront.CacheCookieBehavior.all(),\n headerBehavior: cloudfront.CacheHeaderBehavior.allowList(\n 'Authorization',\n 'Content-Encoding',\n 'Content-Type',\n 'If-None-Match',\n 'Origin',\n 'Referer',\n 'User-Agent',\n 'X-Medplum'\n ),\n queryStringBehavior: cloudfront.CacheQueryStringBehavior.all(),\n });\n\n // Origin access identity\n const originAccessIdentity = new cloudfront.OriginAccessIdentity(this, 'OriginAccessIdentity', {});\n appBucket.grantRead(originAccessIdentity);\n\n // CloudFront distribution\n const distribution = new cloudfront.Distribution(this, 'AppDistribution', {\n defaultRootObject: 'index.html',\n defaultBehavior: {\n origin: new origins.S3Origin(appBucket, { originAccessIdentity }),\n responseHeadersPolicy,\n viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,\n },\n additionalBehaviors: {\n '/api/*': {\n origin: new origins.HttpOrigin(config.apiDomainName),\n allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL,\n cachePolicy: apiOriginCachePolicy,\n viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,\n },\n },\n certificate: acm.Certificate.fromCertificateArn(this, 'AppCertificate', config.appSslCertArn),\n domainNames: [config.appDomainName],\n errorResponses: [\n {\n httpStatus: 403,\n responseHttpStatus: 200,\n responsePagePath: '/index.html',\n },\n {\n httpStatus: 404,\n responseHttpStatus: 200,\n responsePagePath: '/index.html',\n },\n ],\n webAclId: waf.attrArn,\n });\n\n const zone = route53.HostedZone.fromLookup(this, 'Zone', {\n domainName: config.domainName.split('.').slice(-2).join('.'),\n });\n\n // Route53 alias record for the CloudFront distribution\n const record = new route53.ARecord(this, 'AppAliasRecord', {\n recordName: config.appDomainName,\n target: route53.RecordTarget.fromAlias(new targets.CloudFrontTarget(distribution)),\n zone,\n });\n\n // Debug\n console.log('ARecord', record.domainName);\n }\n }\n}\n","import {\n aws_certificatemanager as acm,\n aws_cloudfront as cloudfront,\n aws_cloudfront_origins as origins,\n aws_route53 as route53,\n aws_route53_targets as targets,\n aws_s3 as s3,\n aws_wafv2 as wafv2,\n Duration,\n} from 'aws-cdk-lib';\nimport { ServerlessClamscan } from 'cdk-serverless-clamscan';\nimport { Construct } from 'constructs';\nimport { MedplumInfraConfig } from './config';\nimport { awsManagedRules } from './waf';\n\n/**\n * Binary storage bucket and CloudFront distribution.\n */\nexport class Storage extends Construct {\n constructor(parent: Construct, config: MedplumInfraConfig, region: string) {\n super(parent, 'Storage');\n\n let storageBucket: s3.IBucket;\n\n if (region === config.region) {\n // S3 bucket\n storageBucket = new s3.Bucket(this, 'StorageBucket', {\n bucketName: config.storageBucketName,\n publicReadAccess: false,\n blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,\n encryption: s3.BucketEncryption.S3_MANAGED,\n enforceSSL: true,\n versioned: false,\n });\n\n if (config.clamscanEnabled) {\n // ClamAV serverless scan\n const sc = new ServerlessClamscan(this, 'ServerlessClamscan', {\n defsBucketAccessLogsConfig: {\n logsBucket: s3.Bucket.fromBucketName(this, 'LoggingBucket', config.clamscanLoggingBucket),\n logsPrefix: config.clamscanLoggingPrefix,\n },\n });\n sc.addSourceBucket(storageBucket);\n }\n } else {\n // Otherwise, reference the bucket by name\n storageBucket = s3.Bucket.fromBucketAttributes(this, 'StorageBucket', {\n bucketName: config.storageBucketName,\n region: config.region,\n });\n }\n\n if (region === 'us-east-1') {\n // Public key in PEM format\n const publicKey = new cloudfront.PublicKey(this, 'StoragePublicKey', {\n encodedKey: config.storagePublicKey,\n });\n\n // Authorized key group for presigned URLs\n const keyGroup = new cloudfront.KeyGroup(this, 'StorageKeyGroup', {\n items: [publicKey],\n });\n\n // HTTP response headers policy\n const responseHeadersPolicy = new cloudfront.ResponseHeadersPolicy(this, 'ResponseHeadersPolicy', {\n securityHeadersBehavior: {\n contentSecurityPolicy: {\n contentSecurityPolicy:\n \"default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors *.medplum.com;\",\n override: true,\n },\n contentTypeOptions: { override: true },\n frameOptions: { frameOption: cloudfront.HeadersFrameOption.DENY, override: true },\n referrerPolicy: { referrerPolicy: cloudfront.HeadersReferrerPolicy.NO_REFERRER, override: true },\n strictTransportSecurity: {\n accessControlMaxAge: Duration.seconds(63072000),\n includeSubdomains: true,\n override: true,\n },\n xssProtection: {\n protection: true,\n modeBlock: true,\n override: true,\n },\n },\n });\n\n // WAF\n const waf = new wafv2.CfnWebACL(this, 'StorageWAF', {\n defaultAction: { allow: {} },\n scope: 'CLOUDFRONT',\n name: `${config.stackName}-StorageWAF`,\n rules: awsManagedRules,\n visibilityConfig: {\n cloudWatchMetricsEnabled: true,\n metricName: `${config.stackName}-StorageWAF-Metric`,\n sampledRequestsEnabled: false,\n },\n });\n\n // Origin access identity\n const originAccessIdentity = new cloudfront.OriginAccessIdentity(this, 'OriginAccessIdentity', {});\n storageBucket.grantRead(originAccessIdentity);\n\n // CloudFront distribution\n const distribution = new cloudfront.Distribution(this, 'StorageDistribution', {\n defaultBehavior: {\n origin: new origins.S3Origin(storageBucket, { originAccessIdentity }),\n responseHeadersPolicy,\n viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,\n trustedKeyGroups: [keyGroup],\n },\n certificate: acm.Certificate.fromCertificateArn(this, 'StorageCertificate', config.storageSslCertArn),\n domainNames: [config.storageDomainName],\n webAclId: waf.attrArn,\n });\n\n const zone = route53.HostedZone.fromLookup(this, 'Zone', {\n domainName: config.domainName.split('.').slice(-2).join('.'),\n });\n\n // Route53 alias record for the CloudFront distribution\n const record = new route53.ARecord(this, 'StorageAliasRecord', {\n recordName: config.storageDomainName,\n target: route53.RecordTarget.fromAlias(new targets.CloudFrontTarget(distribution)),\n zone,\n });\n\n // Debug\n console.log('ARecord', record.domainName);\n }\n }\n}\n","import { App, Stack } from 'aws-cdk-lib';\nimport { readFileSync } from 'fs';\nimport { resolve } from 'path';\nimport { BackEnd } from './backend';\nimport { MedplumInfraConfig } from './config';\nimport { FrontEnd } from './frontend';\nimport { Storage } from './storage';\n\nclass MedplumStack {\n primaryStack: Stack;\n backEnd: BackEnd;\n frontEnd: FrontEnd;\n storage: Storage;\n\n constructor(scope: App, config: MedplumInfraConfig) {\n this.primaryStack = new Stack(scope, config.stackName, {\n env: {\n region: config.region,\n account: config.accountNumber,\n },\n });\n\n this.backEnd = new BackEnd(this.primaryStack, config);\n this.frontEnd = new FrontEnd(this.primaryStack, config, config.region);\n this.storage = new Storage(this.primaryStack, config, config.region);\n\n if (config.region !== 'us-east-1') {\n // Some resources must be created in us-east-1\n // For example, CloudFront distributions and ACM certificates\n // If the primary region is not us-east-1, create these resources in us-east-1\n const usEast1Stack = new Stack(scope, config.stackName + '-us-east-1', {\n env: {\n region: 'us-east-1',\n account: config.accountNumber,\n },\n });\n\n this.frontEnd = new FrontEnd(usEast1Stack, config, 'us-east-1');\n this.storage = new Storage(usEast1Stack, config, 'us-east-1');\n }\n }\n}\n\nexport function main(context?: Record<string, string>): void {\n const app = new App({ context });\n\n const configFileName = app.node.tryGetContext('config');\n if (!configFileName) {\n console.log('Missing \"config\" context variable');\n console.log('Usage: cdk deploy -c config=my-config.json');\n return;\n }\n\n const config = JSON.parse(readFileSync(resolve(configFileName), 'utf-8')) as MedplumInfraConfig;\n\n const stack = new MedplumStack(app, config);\n\n console.log('Stack', stack.primaryStack.stackId);\n console.log('BackEnd', stack.backEnd.node.id);\n console.log('FrontEnd', stack.frontEnd.node.id);\n console.log('Storage', stack.storage.node.id);\n\n app.synth();\n}\n\nif (require.main === module) {\n main();\n}\n"],"names":["Construct","ec2","logs","RemovalPolicy","iam","rds","Duration","elasticache","secretsmanager","ecs","Repository","elbv2","s3","wafv2","route53","targets","ssm","cloudfront","origins","acm","ServerlessClamscan","Stack","App","readFileSync","resolve"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","sources":["../../../src/waf.ts","../../../src/backend.ts","../../../src/frontend.ts","../../../src/storage.ts","../../../src/index.ts"],"sourcesContent":["// Based on https://gist.github.com/statik/f1ac9d6227d98d30c7a7cec0c83f4e64\n\nimport { aws_wafv2 as wafv2 } from 'aws-cdk-lib';\n\nexport const awsManagedRules: wafv2.CfnWebACL.RuleProperty[] = [\n // Common Rule Set aligns with major portions of OWASP Core Rule Set\n // https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-baseline.html\n {\n name: 'AWS-AWSManagedRulesCommonRuleSet',\n priority: 10,\n statement: {\n managedRuleGroupStatement: {\n vendorName: 'AWS',\n name: 'AWSManagedRulesCommonRuleSet',\n // Excluding generic RFI body rule for sns notifications\n // https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-list.html\n excludedRules: [\n { name: 'NoUserAgent_HEADER' },\n { name: 'UserAgent_BadBots_HEADER' },\n { name: 'SizeRestrictions_QUERYSTRING' },\n { name: 'SizeRestrictions_Cookie_HEADER' },\n { name: 'SizeRestrictions_BODY' },\n { name: 'SizeRestrictions_URIPATH' },\n { name: 'EC2MetaDataSSRF_BODY' },\n { name: 'EC2MetaDataSSRF_COOKIE' },\n { name: 'EC2MetaDataSSRF_URIPATH' },\n { name: 'EC2MetaDataSSRF_QUERYARGUMENTS' },\n { name: 'GenericLFI_QUERYARGUMENTS' },\n { name: 'GenericLFI_URIPATH' },\n { name: 'GenericLFI_BODY' },\n { name: 'RestrictedExtensions_URIPATH' },\n { name: 'RestrictedExtensions_QUERYARGUMENTS' },\n { name: 'GenericRFI_QUERYARGUMENTS' },\n { name: 'GenericRFI_BODY' },\n { name: 'GenericRFI_URIPATH' },\n { name: 'CrossSiteScripting_COOKIE' },\n { name: 'CrossSiteScripting_QUERYARGUMENTS' },\n { name: 'CrossSiteScripting_BODY' },\n { name: 'CrossSiteScripting_URIPATH' },\n ],\n },\n },\n overrideAction: {\n count: {},\n },\n visibilityConfig: {\n sampledRequestsEnabled: true,\n cloudWatchMetricsEnabled: true,\n metricName: 'AWS-AWSManagedRulesCommonRuleSet',\n },\n },\n // AWS IP Reputation list includes known malicious actors/bots and is regularly updated\n // https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-ip-rep.html\n {\n name: 'AWS-AWSManagedRulesAmazonIpReputationList',\n priority: 20,\n statement: {\n managedRuleGroupStatement: {\n vendorName: 'AWS',\n name: 'AWSManagedRulesAmazonIpReputationList',\n excludedRules: [{ name: 'AWSManagedIPReputationList' }, { name: 'AWSManagedReconnaissanceList' }],\n },\n },\n overrideAction: {\n count: {},\n },\n visibilityConfig: {\n sampledRequestsEnabled: true,\n cloudWatchMetricsEnabled: true,\n metricName: 'AWSManagedRulesAmazonIpReputationList',\n },\n },\n // Blocks common SQL Injection\n // https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-use-case.html#aws-managed-rule-groups-use-case-sql-db\n {\n name: 'AWSManagedRulesSQLiRuleSet',\n priority: 30,\n visibilityConfig: {\n sampledRequestsEnabled: true,\n cloudWatchMetricsEnabled: true,\n metricName: 'AWSManagedRulesSQLiRuleSet',\n },\n overrideAction: {\n count: {},\n },\n statement: {\n managedRuleGroupStatement: {\n vendorName: 'AWS',\n name: 'AWSManagedRulesSQLiRuleSet',\n excludedRules: [\n { name: 'SQLi_QUERYARGUMENTS' },\n { name: 'SQLiExtendedPatterns_QUERYARGUMENTS' },\n { name: 'SQLi_BODY' },\n { name: 'SQLiExtendedPatterns_BODY' },\n { name: 'SQLi_COOKIE' },\n { name: 'SQLi_URIPATH' },\n ],\n },\n },\n },\n // Blocks attacks targeting LFI(Local File Injection) for linux systems\n // https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-use-case.html#aws-managed-rule-groups-use-case-linux-os\n {\n name: 'AWSManagedRuleLinux',\n priority: 40,\n visibilityConfig: {\n sampledRequestsEnabled: true,\n cloudWatchMetricsEnabled: true,\n metricName: 'AWSManagedRuleLinux',\n },\n overrideAction: {\n count: {},\n },\n statement: {\n managedRuleGroupStatement: {\n vendorName: 'AWS',\n name: 'AWSManagedRulesLinuxRuleSet',\n excludedRules: [{ name: 'LFI_URIPATH' }, { name: 'LFI_QUERYSTRING' }, { name: 'LFI_COOKIE' }],\n },\n },\n },\n];\n","import {\n aws_ec2 as ec2,\n aws_ecs as ecs,\n aws_elasticache as elasticache,\n aws_elasticloadbalancingv2 as elbv2,\n aws_iam as iam,\n aws_logs as logs,\n aws_rds as rds,\n aws_route53 as route53,\n aws_route53_targets as targets,\n aws_s3 as s3,\n aws_secretsmanager as secretsmanager,\n aws_ssm as ssm,\n aws_wafv2 as wafv2,\n Duration,\n RemovalPolicy,\n} from 'aws-cdk-lib';\nimport { Repository } from 'aws-cdk-lib/aws-ecr';\nimport { Construct } from 'constructs';\nimport { MedplumInfraConfig } from './config';\nimport { awsManagedRules } from './waf';\n\n/**\n * Based on: https://github.com/aws-samples/http-api-aws-fargate-cdk/blob/master/cdk/singleAccount/lib/fargate-vpclink-stack.ts\n *\n * RDS config: https://docs.aws.amazon.com/cdk/api/latest/docs/aws-rds-readme.html\n */\nexport class BackEnd extends Construct {\n constructor(scope: Construct, config: MedplumInfraConfig) {\n super(scope, 'BackEnd');\n\n const name = config.name;\n\n // VPC\n let vpc: ec2.IVpc;\n\n if (config.vpcId) {\n // Lookup VPC by ARN\n vpc = ec2.Vpc.fromLookup(this, 'VPC', { vpcId: config.vpcId });\n } else {\n // VPC Flow Logs\n const vpcFlowLogs = new logs.LogGroup(this, 'VpcFlowLogs', {\n logGroupName: '/medplum/flowlogs/' + name,\n removalPolicy: RemovalPolicy.DESTROY,\n });\n\n // Create VPC\n vpc = new ec2.Vpc(this, 'VPC', {\n maxAzs: config.maxAzs,\n flowLogs: {\n cloudwatch: {\n destination: ec2.FlowLogDestination.toCloudWatchLogs(vpcFlowLogs),\n trafficType: ec2.FlowLogTrafficType.ALL,\n },\n },\n });\n }\n\n // Bot Lambda Role\n const botLambdaRole = new iam.Role(this, 'BotLambdaRole', {\n assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),\n });\n\n // RDS\n let rdsCluster = undefined;\n let rdsSecretsArn = config.rdsSecretsArn;\n if (!rdsSecretsArn) {\n rdsCluster = new rds.DatabaseCluster(this, 'DatabaseCluster', {\n engine: rds.DatabaseClusterEngine.auroraPostgres({\n version: rds.AuroraPostgresEngineVersion.VER_12_9,\n }),\n credentials: rds.Credentials.fromGeneratedSecret('clusteradmin'),\n defaultDatabaseName: 'medplum',\n storageEncrypted: true,\n instances: config.rdsInstances,\n instanceProps: {\n vpc: vpc,\n vpcSubnets: {\n subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,\n },\n instanceType: config.rdsInstanceType ? new ec2.InstanceType(config.rdsInstanceType) : undefined,\n enablePerformanceInsights: true,\n },\n backup: {\n retention: Duration.days(7),\n },\n cloudwatchLogsExports: ['postgresql'],\n instanceUpdateBehaviour: rds.InstanceUpdateBehaviour.ROLLING,\n });\n\n rdsSecretsArn = (rdsCluster.secret as secretsmanager.ISecret).secretArn;\n }\n\n // Redis\n // Important: For HIPAA compliance, you must specify TransitEncryptionEnabled as true, an AuthToken, and a CacheSubnetGroup.\n const redisSubnetGroup = new elasticache.CfnSubnetGroup(this, 'RedisSubnetGroup', {\n description: 'Redis Subnet Group',\n subnetIds: vpc.privateSubnets.map((subnet) => subnet.subnetId),\n });\n\n const redisSecurityGroup = new ec2.SecurityGroup(this, 'RedisSecurityGroup', {\n vpc,\n description: 'Redis Security Group',\n allowAllOutbound: false,\n });\n\n const redisPassword = new secretsmanager.Secret(this, 'RedisPassword', {\n generateSecretString: {\n secretStringTemplate: '{}',\n generateStringKey: 'password',\n excludeCharacters: '@%*()_+=`~{}|[]\\\\:\";\\'?,./',\n },\n });\n\n const redisCluster = new elasticache.CfnReplicationGroup(this, 'RedisCluster', {\n engine: 'Redis',\n engineVersion: '6.x',\n cacheNodeType: 'cache.t2.medium',\n replicationGroupDescription: 'RedisReplicationGroup',\n authToken: redisPassword.secretValueFromJson('password').toString(),\n transitEncryptionEnabled: true,\n atRestEncryptionEnabled: true,\n multiAzEnabled: true,\n cacheSubnetGroupName: redisSubnetGroup.ref,\n numNodeGroups: 1,\n replicasPerNodeGroup: 1,\n securityGroupIds: [redisSecurityGroup.securityGroupId],\n });\n redisCluster.node.addDependency(redisPassword);\n\n const redisSecrets = new secretsmanager.Secret(this, 'RedisSecrets', {\n generateSecretString: {\n secretStringTemplate: JSON.stringify({\n host: redisCluster.attrPrimaryEndPointAddress,\n port: redisCluster.attrPrimaryEndPointPort,\n password: redisPassword.secretValueFromJson('password').toString(),\n tls: {},\n }),\n generateStringKey: 'unused',\n },\n });\n redisSecrets.node.addDependency(redisPassword);\n redisSecrets.node.addDependency(redisCluster);\n\n // ECS Cluster\n const cluster = new ecs.Cluster(this, 'Cluster', {\n vpc: vpc,\n });\n\n // Task Policies\n const taskRolePolicies = new iam.PolicyDocument({\n statements: [\n // CloudWatch Logs: Create streams and put events\n new iam.PolicyStatement({\n effect: iam.Effect.ALLOW,\n actions: ['logs:CreateLogStream', 'logs:PutLogEvents'],\n resources: ['arn:aws:logs:*'],\n }),\n\n // Secrets Manager: Read only access to secrets\n // https://docs.aws.amazon.com/mediaconnect/latest/ug/iam-policy-examples-asm-secrets.html\n new iam.PolicyStatement({\n effect: iam.Effect.ALLOW,\n actions: [\n 'secretsmanager:GetResourcePolicy',\n 'secretsmanager:GetSecretValue',\n 'secretsmanager:DescribeSecret',\n 'secretsmanager:ListSecrets',\n 'secretsmanager:ListSecretVersionIds',\n ],\n resources: ['arn:aws:secretsmanager:*'],\n }),\n\n // Parameter Store: Read only access\n // https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-paramstore-access.html\n new iam.PolicyStatement({\n effect: iam.Effect.ALLOW,\n actions: ['ssm:GetParametersByPath', 'ssm:GetParameters', 'ssm:GetParameter', 'ssm:DescribeParameters'],\n resources: ['arn:aws:ssm:*'],\n }),\n\n // SES: Send emails\n // https://docs.aws.amazon.com/ses/latest/dg/sending-authorization-policy-examples.html\n new iam.PolicyStatement({\n effect: iam.Effect.ALLOW,\n actions: ['ses:SendEmail', 'ses:SendRawEmail'],\n resources: ['arn:aws:ses:*'],\n }),\n\n // S3: Read and write access to buckets\n // https://docs.aws.amazon.com/service-authorization/latest/reference/list_amazons3.html\n new iam.PolicyStatement({\n effect: iam.Effect.ALLOW,\n actions: ['s3:ListBucket', 's3:GetObject', 's3:PutObject', 's3:DeleteObject'],\n resources: ['arn:aws:s3:::*'],\n }),\n\n // IAM: Pass role to innvoke lambda functions\n // https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use_passrole.html\n new iam.PolicyStatement({\n effect: iam.Effect.ALLOW,\n actions: ['iam:ListRoles', 'iam:GetRole', 'iam:PassRole'],\n resources: [botLambdaRole.roleArn],\n }),\n\n // Lambda: Create, read, update, delete, and invoke functions\n // https://docs.aws.amazon.com/lambda/latest/dg/access-control-identity-based.html\n new iam.PolicyStatement({\n effect: iam.Effect.ALLOW,\n actions: [\n 'lambda:CreateFunction',\n 'lambda:GetFunction',\n 'lambda:GetFunctionConfiguration',\n 'lambda:UpdateFunctionCode',\n 'lambda:UpdateFunctionConfiguration',\n 'lambda:ListLayerVersions',\n 'lambda:GetLayerVersion',\n 'lambda:InvokeFunction',\n ],\n resources: ['arn:aws:lambda:*'],\n }),\n ],\n });\n\n // Task Role\n const taskRole = new iam.Role(this, 'TaskExecutionRole', {\n assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),\n description: 'Medplum Server Task Execution Role',\n inlinePolicies: {\n TaskExecutionPolicies: taskRolePolicies,\n },\n });\n\n // Task Definitions\n const taskDefinition = new ecs.FargateTaskDefinition(this, 'TaskDefinition', {\n memoryLimitMiB: config.serverMemory,\n cpu: config.serverCpu,\n taskRole: taskRole,\n });\n\n // Log Groups\n const logGroup = new logs.LogGroup(this, 'LogGroup', {\n logGroupName: '/ecs/medplum/' + name,\n removalPolicy: RemovalPolicy.DESTROY,\n });\n\n const logDriver = new ecs.AwsLogDriver({\n logGroup: logGroup,\n streamPrefix: 'Medplum',\n });\n\n // Task Containers\n let serverImage: ecs.ContainerImage | undefined = undefined;\n // Pull out the image name and tag from the image URI if it's an ECR image\n const ecrImageUriRegex = new RegExp(\n `^${config.accountNumber}\\\\.dkr\\\\.ecr\\\\.${config.region}\\\\.amazonaws\\\\.com/(.*)[:@](.*)$`\n );\n const nameTagMatches = config.serverImage.match(ecrImageUriRegex);\n const serverImageName = nameTagMatches?.[1];\n const serverImageTag = nameTagMatches?.[2];\n if (serverImageName && serverImageTag) {\n // Creating an ecr repository image will automatically grant fine-grained permissions to ecs to access the image\n const ecrRepo = Repository.fromRepositoryArn(\n this,\n 'ServerImageRepo',\n `arn:aws:ecr:${config.region}:${config.accountNumber}:repository/${serverImageName}`\n );\n serverImage = ecs.ContainerImage.fromEcrRepository(ecrRepo, serverImageTag);\n } else {\n // Otherwise, use the standard container image\n serverImage = ecs.ContainerImage.fromRegistry(config.serverImage);\n }\n const serviceContainer = taskDefinition.addContainer('MedplumTaskDefinition', {\n image: serverImage,\n command: [config.region === 'us-east-1' ? `aws:/medplum/${name}/` : `aws:${config.region}:/medplum/${name}/`],\n logging: logDriver,\n });\n\n serviceContainer.addPortMappings({\n containerPort: config.apiPort,\n hostPort: config.apiPort,\n });\n\n // Security Groups\n const fargateSecurityGroup = new ec2.SecurityGroup(this, 'ServiceSecurityGroup', {\n allowAllOutbound: true,\n securityGroupName: 'MedplumSecurityGroup',\n vpc: vpc,\n });\n\n // Fargate Services\n const fargateService = new ecs.FargateService(this, 'FargateService', {\n cluster: cluster,\n taskDefinition: taskDefinition,\n assignPublicIp: false,\n vpcSubnets: {\n subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,\n },\n desiredCount: config.desiredServerCount,\n securityGroups: [fargateSecurityGroup],\n });\n\n // Add dependencies - make sure Fargate service is created after RDS and Redis\n if (rdsCluster) {\n fargateService.node.addDependency(rdsCluster);\n }\n fargateService.node.addDependency(redisCluster);\n\n // Load Balancer Target Group\n const targetGroup = new elbv2.ApplicationTargetGroup(this, 'TargetGroup', {\n vpc: vpc,\n port: config.apiPort,\n protocol: elbv2.ApplicationProtocol.HTTP,\n healthCheck: {\n path: '/healthcheck',\n interval: Duration.seconds(30),\n timeout: Duration.seconds(3),\n healthyThresholdCount: 2,\n unhealthyThresholdCount: 5,\n },\n targets: [fargateService],\n });\n\n // Load Balancer\n const loadBalancer = new elbv2.ApplicationLoadBalancer(this, 'LoadBalancer', {\n vpc: vpc,\n internetFacing: true,\n http2Enabled: true,\n });\n\n if (config.loadBalancerLoggingEnabled) {\n // Load Balancer logging\n loadBalancer.logAccessLogs(\n s3.Bucket.fromBucketName(this, 'LoggingBucket', config.loadBalancerLoggingBucket),\n config.loadBalancerLoggingPrefix\n );\n }\n\n // HTTPS Listener\n // Forward to the target group\n loadBalancer.addListener('HttpsListener', {\n port: 443,\n certificates: [\n {\n certificateArn: config.apiSslCertArn,\n },\n ],\n sslPolicy: elbv2.SslPolicy.FORWARD_SECRECY_TLS12_RES_GCM,\n defaultAction: elbv2.ListenerAction.forward([targetGroup]),\n });\n\n // WAF\n const waf = new wafv2.CfnWebACL(this, 'BackEndWAF', {\n defaultAction: { allow: {} },\n scope: 'REGIONAL',\n name: `${config.stackName}-BackEndWAF`,\n rules: awsManagedRules,\n visibilityConfig: {\n cloudWatchMetricsEnabled: true,\n metricName: `${config.stackName}-BackEndWAF-Metric`,\n sampledRequestsEnabled: false,\n },\n });\n\n // Create an association between the load balancer and the WAF\n const wafAssociation = new wafv2.CfnWebACLAssociation(this, 'LoadBalancerAssociation', {\n resourceArn: loadBalancer.loadBalancerArn,\n webAclArn: waf.attrArn,\n });\n\n // Grant RDS access to the fargate group\n if (rdsCluster) {\n rdsCluster.connections.allowDefaultPortFrom(fargateSecurityGroup);\n }\n\n // Grant Redis access to the fargate group\n redisSecurityGroup.addIngressRule(fargateSecurityGroup, ec2.Port.tcp(6379));\n\n // DNS\n let record = undefined;\n if (!config.skipDns) {\n // Route 53\n const zone = route53.HostedZone.fromLookup(this, 'Zone', {\n domainName: config.domainName.split('.').slice(-2).join('.'),\n });\n\n // Route53 alias record for the load balancer\n record = new route53.ARecord(this, 'LoadBalancerAliasRecord', {\n recordName: config.apiDomainName,\n target: route53.RecordTarget.fromAlias(new targets.LoadBalancerTarget(loadBalancer)),\n zone: zone,\n });\n }\n\n // SSM Parameters\n const databaseSecrets = new ssm.StringParameter(this, 'DatabaseSecretsParameter', {\n tier: ssm.ParameterTier.STANDARD,\n parameterName: `/medplum/${name}/DatabaseSecrets`,\n description: 'Database secrets ARN',\n stringValue: rdsSecretsArn,\n });\n\n const redisSecretsParameter = new ssm.StringParameter(this, 'RedisSecretsParameter', {\n tier: ssm.ParameterTier.STANDARD,\n parameterName: `/medplum/${name}/RedisSecrets`,\n description: 'Redis secrets ARN',\n stringValue: redisSecrets.secretArn,\n });\n\n const botLambdaRoleParameter = new ssm.StringParameter(this, 'BotLambdaRoleParameter', {\n tier: ssm.ParameterTier.STANDARD,\n parameterName: `/medplum/${name}/botLambdaRoleArn`,\n description: 'Bot lambda execution role ARN',\n stringValue: botLambdaRole.roleArn,\n });\n\n // Debug\n console.log('ARecord', record?.domainName);\n console.log('DatabaseSecretsParameter', databaseSecrets.parameterArn);\n console.log('RedisSecretsParameter', redisSecretsParameter.parameterArn);\n console.log('RedisCluster', redisCluster.attrPrimaryEndPointAddress);\n console.log('BotLambdaRole', botLambdaRoleParameter.stringValue);\n console.log('WAF', waf.attrArn);\n console.log('WAF Association', wafAssociation.node.id);\n }\n}\n","import {\n aws_certificatemanager as acm,\n aws_cloudfront as cloudfront,\n aws_cloudfront_origins as origins,\n aws_route53 as route53,\n aws_route53_targets as targets,\n aws_s3 as s3,\n aws_wafv2 as wafv2,\n Duration,\n RemovalPolicy,\n} from 'aws-cdk-lib';\nimport { Construct } from 'constructs';\nimport { MedplumInfraConfig } from './config';\nimport { awsManagedRules } from './waf';\n\n/**\n * Static app infrastructure, which deploys app content to an S3 bucket.\n *\n * The app redirects from HTTP to HTTPS, using a CloudFront distribution,\n * Route53 alias record, and ACM certificate.\n */\nexport class FrontEnd extends Construct {\n constructor(parent: Construct, config: MedplumInfraConfig, region: string) {\n super(parent, 'FrontEnd');\n\n let appBucket: s3.IBucket;\n\n if (region === config.region) {\n // S3 bucket\n appBucket = new s3.Bucket(this, 'AppBucket', {\n bucketName: config.appDomainName,\n publicReadAccess: false,\n blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,\n removalPolicy: RemovalPolicy.DESTROY,\n encryption: s3.BucketEncryption.S3_MANAGED,\n enforceSSL: true,\n versioned: false,\n });\n } else {\n // Otherwise, reference the bucket by name and region\n appBucket = s3.Bucket.fromBucketAttributes(this, 'AppBucket', {\n bucketName: config.appDomainName,\n region: config.region,\n });\n }\n\n if (region === 'us-east-1') {\n // HTTP response headers policy\n const responseHeadersPolicy = new cloudfront.ResponseHeadersPolicy(this, 'ResponseHeadersPolicy', {\n securityHeadersBehavior: {\n contentSecurityPolicy: {\n contentSecurityPolicy: [\n `default-src 'none'`,\n `base-uri 'self'`,\n `child-src 'self'`,\n `connect-src 'self' ${config.apiDomainName} *.google.com`,\n `font-src 'self' fonts.gstatic.com`,\n `form-action 'self' *.gstatic.com *.google.com`,\n `frame-ancestors 'none'`,\n `frame-src 'self' *.medplum.com *.gstatic.com *.google.com`,\n `img-src 'self' data: ${config.storageDomainName} *.gstatic.com *.google.com *.googleapis.com`,\n `manifest-src 'self'`,\n `media-src 'self' ${config.storageDomainName}`,\n `script-src 'self' *.medplum.com *.gstatic.com *.google.com`,\n `style-src 'self' 'unsafe-inline' *.medplum.com *.gstatic.com *.google.com`,\n `worker-src 'self' blob: *.gstatic.com *.google.com`,\n `upgrade-insecure-requests`,\n ].join('; '),\n override: true,\n },\n contentTypeOptions: { override: true },\n frameOptions: { frameOption: cloudfront.HeadersFrameOption.DENY, override: true },\n strictTransportSecurity: {\n accessControlMaxAge: Duration.seconds(63072000),\n includeSubdomains: true,\n override: true,\n },\n xssProtection: {\n protection: true,\n modeBlock: true,\n override: true,\n },\n },\n });\n\n // WAF\n const waf = new wafv2.CfnWebACL(this, 'FrontEndWAF', {\n defaultAction: { allow: {} },\n scope: 'CLOUDFRONT',\n name: `${config.stackName}-FrontEndWAF`,\n rules: awsManagedRules,\n visibilityConfig: {\n cloudWatchMetricsEnabled: true,\n metricName: `${config.stackName}-FrontEndWAF-Metric`,\n sampledRequestsEnabled: false,\n },\n });\n\n // API Origin Cache Policy\n const apiOriginCachePolicy = new cloudfront.CachePolicy(this, 'ApiOriginCachePolicy', {\n cachePolicyName: `${config.stackName}-ApiOriginCachePolicy`,\n cookieBehavior: cloudfront.CacheCookieBehavior.all(),\n headerBehavior: cloudfront.CacheHeaderBehavior.allowList(\n 'Authorization',\n 'Content-Encoding',\n 'Content-Type',\n 'If-None-Match',\n 'Origin',\n 'Referer',\n 'User-Agent',\n 'X-Medplum'\n ),\n queryStringBehavior: cloudfront.CacheQueryStringBehavior.all(),\n });\n\n // Origin access identity\n const originAccessIdentity = new cloudfront.OriginAccessIdentity(this, 'OriginAccessIdentity', {});\n appBucket.grantRead(originAccessIdentity);\n\n // CloudFront distribution\n const distribution = new cloudfront.Distribution(this, 'AppDistribution', {\n defaultRootObject: 'index.html',\n defaultBehavior: {\n origin: new origins.S3Origin(appBucket, { originAccessIdentity }),\n responseHeadersPolicy,\n viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,\n },\n additionalBehaviors: {\n '/api/*': {\n origin: new origins.HttpOrigin(config.apiDomainName),\n allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL,\n cachePolicy: apiOriginCachePolicy,\n viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,\n },\n },\n certificate: acm.Certificate.fromCertificateArn(this, 'AppCertificate', config.appSslCertArn),\n domainNames: [config.appDomainName],\n errorResponses: [\n {\n httpStatus: 403,\n responseHttpStatus: 200,\n responsePagePath: '/index.html',\n },\n {\n httpStatus: 404,\n responseHttpStatus: 200,\n responsePagePath: '/index.html',\n },\n ],\n webAclId: waf.attrArn,\n });\n\n // DNS\n let record = undefined;\n if (!config.skipDns) {\n const zone = route53.HostedZone.fromLookup(this, 'Zone', {\n domainName: config.domainName.split('.').slice(-2).join('.'),\n });\n\n // Route53 alias record for the CloudFront distribution\n record = new route53.ARecord(this, 'AppAliasRecord', {\n recordName: config.appDomainName,\n target: route53.RecordTarget.fromAlias(new targets.CloudFrontTarget(distribution)),\n zone,\n });\n }\n\n // Debug\n console.log('ARecord', record?.domainName);\n }\n }\n}\n","import {\n aws_certificatemanager as acm,\n aws_cloudfront as cloudfront,\n aws_cloudfront_origins as origins,\n aws_route53 as route53,\n aws_route53_targets as targets,\n aws_s3 as s3,\n aws_wafv2 as wafv2,\n Duration,\n} from 'aws-cdk-lib';\nimport { ServerlessClamscan } from 'cdk-serverless-clamscan';\nimport { Construct } from 'constructs';\nimport { MedplumInfraConfig } from './config';\nimport { awsManagedRules } from './waf';\n\n/**\n * Binary storage bucket and CloudFront distribution.\n */\nexport class Storage extends Construct {\n constructor(parent: Construct, config: MedplumInfraConfig, region: string) {\n super(parent, 'Storage');\n\n let storageBucket: s3.IBucket;\n\n if (region === config.region) {\n // S3 bucket\n storageBucket = new s3.Bucket(this, 'StorageBucket', {\n bucketName: config.storageBucketName,\n publicReadAccess: false,\n blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,\n encryption: s3.BucketEncryption.S3_MANAGED,\n enforceSSL: true,\n versioned: false,\n });\n\n if (config.clamscanEnabled) {\n // ClamAV serverless scan\n const sc = new ServerlessClamscan(this, 'ServerlessClamscan', {\n defsBucketAccessLogsConfig: {\n logsBucket: s3.Bucket.fromBucketName(this, 'LoggingBucket', config.clamscanLoggingBucket),\n logsPrefix: config.clamscanLoggingPrefix,\n },\n });\n sc.addSourceBucket(storageBucket);\n }\n } else {\n // Otherwise, reference the bucket by name\n storageBucket = s3.Bucket.fromBucketAttributes(this, 'StorageBucket', {\n bucketName: config.storageBucketName,\n region: config.region,\n });\n }\n\n if (region === 'us-east-1') {\n // Public key in PEM format\n const publicKey = new cloudfront.PublicKey(this, 'StoragePublicKey', {\n encodedKey: config.storagePublicKey,\n });\n\n // Authorized key group for presigned URLs\n const keyGroup = new cloudfront.KeyGroup(this, 'StorageKeyGroup', {\n items: [publicKey],\n });\n\n // HTTP response headers policy\n const responseHeadersPolicy = new cloudfront.ResponseHeadersPolicy(this, 'ResponseHeadersPolicy', {\n securityHeadersBehavior: {\n contentSecurityPolicy: {\n contentSecurityPolicy:\n \"default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors *.medplum.com;\",\n override: true,\n },\n contentTypeOptions: { override: true },\n frameOptions: { frameOption: cloudfront.HeadersFrameOption.DENY, override: true },\n referrerPolicy: { referrerPolicy: cloudfront.HeadersReferrerPolicy.NO_REFERRER, override: true },\n strictTransportSecurity: {\n accessControlMaxAge: Duration.seconds(63072000),\n includeSubdomains: true,\n override: true,\n },\n xssProtection: {\n protection: true,\n modeBlock: true,\n override: true,\n },\n },\n });\n\n // WAF\n const waf = new wafv2.CfnWebACL(this, 'StorageWAF', {\n defaultAction: { allow: {} },\n scope: 'CLOUDFRONT',\n name: `${config.stackName}-StorageWAF`,\n rules: awsManagedRules,\n visibilityConfig: {\n cloudWatchMetricsEnabled: true,\n metricName: `${config.stackName}-StorageWAF-Metric`,\n sampledRequestsEnabled: false,\n },\n });\n\n // Origin access identity\n const originAccessIdentity = new cloudfront.OriginAccessIdentity(this, 'OriginAccessIdentity', {});\n storageBucket.grantRead(originAccessIdentity);\n\n // CloudFront distribution\n const distribution = new cloudfront.Distribution(this, 'StorageDistribution', {\n defaultBehavior: {\n origin: new origins.S3Origin(storageBucket, { originAccessIdentity }),\n responseHeadersPolicy,\n viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,\n trustedKeyGroups: [keyGroup],\n },\n certificate: acm.Certificate.fromCertificateArn(this, 'StorageCertificate', config.storageSslCertArn),\n domainNames: [config.storageDomainName],\n webAclId: waf.attrArn,\n });\n\n // DNS\n let record = undefined;\n if (!config.skipDns) {\n const zone = route53.HostedZone.fromLookup(this, 'Zone', {\n domainName: config.domainName.split('.').slice(-2).join('.'),\n });\n\n // Route53 alias record for the CloudFront distribution\n record = new route53.ARecord(this, 'StorageAliasRecord', {\n recordName: config.storageDomainName,\n target: route53.RecordTarget.fromAlias(new targets.CloudFrontTarget(distribution)),\n zone,\n });\n }\n\n // Debug\n console.log('ARecord', record?.domainName);\n }\n }\n}\n","import { App, Stack } from 'aws-cdk-lib';\nimport { readFileSync } from 'fs';\nimport { resolve } from 'path';\nimport { BackEnd } from './backend';\nimport { MedplumInfraConfig } from './config';\nimport { FrontEnd } from './frontend';\nimport { Storage } from './storage';\n\nclass MedplumStack {\n primaryStack: Stack;\n backEnd: BackEnd;\n frontEnd: FrontEnd;\n storage: Storage;\n\n constructor(scope: App, config: MedplumInfraConfig) {\n this.primaryStack = new Stack(scope, config.stackName, {\n env: {\n region: config.region,\n account: config.accountNumber,\n },\n });\n\n this.backEnd = new BackEnd(this.primaryStack, config);\n this.frontEnd = new FrontEnd(this.primaryStack, config, config.region);\n this.storage = new Storage(this.primaryStack, config, config.region);\n\n if (config.region !== 'us-east-1') {\n // Some resources must be created in us-east-1\n // For example, CloudFront distributions and ACM certificates\n // If the primary region is not us-east-1, create these resources in us-east-1\n const usEast1Stack = new Stack(scope, config.stackName + '-us-east-1', {\n env: {\n region: 'us-east-1',\n account: config.accountNumber,\n },\n });\n\n this.frontEnd = new FrontEnd(usEast1Stack, config, 'us-east-1');\n this.storage = new Storage(usEast1Stack, config, 'us-east-1');\n }\n }\n}\n\nexport function main(context?: Record<string, string>): void {\n const app = new App({ context });\n\n const configFileName = app.node.tryGetContext('config');\n if (!configFileName) {\n console.log('Missing \"config\" context variable');\n console.log('Usage: cdk deploy -c config=my-config.json');\n return;\n }\n\n const config = JSON.parse(readFileSync(resolve(configFileName), 'utf-8')) as MedplumInfraConfig;\n\n const stack = new MedplumStack(app, config);\n\n console.log('Stack', stack.primaryStack.stackId);\n console.log('BackEnd', stack.backEnd.node.id);\n console.log('FrontEnd', stack.frontEnd.node.id);\n console.log('Storage', stack.storage.node.id);\n\n app.synth();\n}\n\nif (require.main === module) {\n main();\n}\n"],"names":["Construct","ec2","logs","RemovalPolicy","iam","rds","Duration","elasticache","secretsmanager","ecs","Repository","elbv2","s3","wafv2","route53","targets","ssm","cloudfront","origins","acm","ServerlessClamscan","Stack","App","readFileSync","resolve"],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ {
2
+ "apiPort": 5000,
3
+ "name": "staging",
4
+ "region": "us-east-1",
5
+ "accountNumber": "647991932601",
6
+ "stackName": "MedplumStagingStack",
7
+ "domainName": "staging.medplum.com",
8
+ "apiDomainName": "api.staging.medplum.com",
9
+ "appDomainName": "app.staging.medplum.com",
10
+ "storageDomainName": "storage.staging.medplum.com",
11
+ "storageBucketName": "medplum-staging-storage",
12
+ "maxAzs": 2,
13
+ "rdsInstances": 1,
14
+ "rdsInstanceType": "t4g.medium",
15
+ "desiredServerCount": 1,
16
+ "serverMemory": 512,
17
+ "serverCpu": 256,
18
+ "serverImage": "codyebberson/medplum-server:staging",
19
+ "storagePublicKey": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3cnmD3HQbJU7WTGT2ZSO\nLt71c+xQ91m5FAzdFagfkQAG0CeyzHq8VzjLPinLDlOWCwQXfunjoBMP7iyVt/pE\n46ngR55In3UlzsMySHpUAi740u6oh0VeJOZA1x/FrVRYsxFx4XFJ92gcs5VvdT66\nwWTX7KznaIrxIvTWz384ogqXfg41QeoIISM2YUjqSMkyx7wY3xGrFvG5UuAAivbr\ni/ZZkkM2q9frpidpJx4evIuaHZS8fstbHFDbbFFqDMyuk7eAJRea1KH5TsjCHvTK\n5ANRyzq+mty47TKrI+2AQsxjH4mel2lC/at3udgtmfz1MTT7daFWfDKsVn8h3DsA\nJwIDAQAB\n-----END PUBLIC KEY-----",
20
+ "apiSslCertArn": "arn:aws:acm:us-east-1:647991932601:certificate/159b257b-a180-49c6-b188-4dc962d8e708",
21
+ "appSslCertArn": "arn:aws:acm:us-east-1:647991932601:certificate/b0d65b27-2ea8-4377-82e1-c41aa067655b",
22
+ "storageSslCertArn": "arn:aws:acm:us-east-1:647991932601:certificate/2205bb8c-7da9-4992-b8ec-c2c79b43b586"
23
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@medplum/cdk",
3
- "version": "2.0.7",
3
+ "version": "2.0.9",
4
4
  "description": "Medplum CDK Infra as Code",
5
5
  "author": "Medplum <hello@medplum.com>",
6
6
  "license": "Apache-2.0",
package/src/backend.ts CHANGED
@@ -300,6 +300,12 @@ export class BackEnd extends Construct {
300
300
  securityGroups: [fargateSecurityGroup],
301
301
  });
302
302
 
303
+ // Add dependencies - make sure Fargate service is created after RDS and Redis
304
+ if (rdsCluster) {
305
+ fargateService.node.addDependency(rdsCluster);
306
+ }
307
+ fargateService.node.addDependency(redisCluster);
308
+
303
309
  // Load Balancer Target Group
304
310
  const targetGroup = new elbv2.ApplicationTargetGroup(this, 'TargetGroup', {
305
311
  vpc: vpc,
@@ -370,17 +376,21 @@ export class BackEnd extends Construct {
370
376
  // Grant Redis access to the fargate group
371
377
  redisSecurityGroup.addIngressRule(fargateSecurityGroup, ec2.Port.tcp(6379));
372
378
 
373
- // Route 53
374
- const zone = route53.HostedZone.fromLookup(this, 'Zone', {
375
- domainName: config.domainName.split('.').slice(-2).join('.'),
376
- });
379
+ // DNS
380
+ let record = undefined;
381
+ if (!config.skipDns) {
382
+ // Route 53
383
+ const zone = route53.HostedZone.fromLookup(this, 'Zone', {
384
+ domainName: config.domainName.split('.').slice(-2).join('.'),
385
+ });
377
386
 
378
- // Route53 alias record for the load balancer
379
- const record = new route53.ARecord(this, 'LoadBalancerAliasRecord', {
380
- recordName: config.apiDomainName,
381
- target: route53.RecordTarget.fromAlias(new targets.LoadBalancerTarget(loadBalancer)),
382
- zone: zone,
383
- });
387
+ // Route53 alias record for the load balancer
388
+ record = new route53.ARecord(this, 'LoadBalancerAliasRecord', {
389
+ recordName: config.apiDomainName,
390
+ target: route53.RecordTarget.fromAlias(new targets.LoadBalancerTarget(loadBalancer)),
391
+ zone: zone,
392
+ });
393
+ }
384
394
 
385
395
  // SSM Parameters
386
396
  const databaseSecrets = new ssm.StringParameter(this, 'DatabaseSecretsParameter', {
@@ -405,7 +415,7 @@ export class BackEnd extends Construct {
405
415
  });
406
416
 
407
417
  // Debug
408
- console.log('ARecord', record.domainName);
418
+ console.log('ARecord', record?.domainName);
409
419
  console.log('DatabaseSecretsParameter', databaseSecrets.parameterArn);
410
420
  console.log('RedisSecretsParameter', redisSecretsParameter.parameterArn);
411
421
  console.log('RedisCluster', redisCluster.attrPrimaryEndPointAddress);
package/src/config.ts CHANGED
@@ -28,4 +28,5 @@ export interface MedplumInfraConfig {
28
28
  clamscanEnabled: boolean;
29
29
  clamscanLoggingBucket: string;
30
30
  clamscanLoggingPrefix: string;
31
+ skipDns?: boolean;
31
32
  }
package/src/frontend.ts CHANGED
@@ -150,19 +150,23 @@ export class FrontEnd extends Construct {
150
150
  webAclId: waf.attrArn,
151
151
  });
152
152
 
153
- const zone = route53.HostedZone.fromLookup(this, 'Zone', {
154
- domainName: config.domainName.split('.').slice(-2).join('.'),
155
- });
153
+ // DNS
154
+ let record = undefined;
155
+ if (!config.skipDns) {
156
+ const zone = route53.HostedZone.fromLookup(this, 'Zone', {
157
+ domainName: config.domainName.split('.').slice(-2).join('.'),
158
+ });
156
159
 
157
- // Route53 alias record for the CloudFront distribution
158
- const record = new route53.ARecord(this, 'AppAliasRecord', {
159
- recordName: config.appDomainName,
160
- target: route53.RecordTarget.fromAlias(new targets.CloudFrontTarget(distribution)),
161
- zone,
162
- });
160
+ // Route53 alias record for the CloudFront distribution
161
+ record = new route53.ARecord(this, 'AppAliasRecord', {
162
+ recordName: config.appDomainName,
163
+ target: route53.RecordTarget.fromAlias(new targets.CloudFrontTarget(distribution)),
164
+ zone,
165
+ });
166
+ }
163
167
 
164
168
  // Debug
165
- console.log('ARecord', record.domainName);
169
+ console.log('ARecord', record?.domainName);
166
170
  }
167
171
  }
168
172
  }
package/src/index.test.ts CHANGED
@@ -229,4 +229,39 @@ describe('Infra', () => {
229
229
  expect(() => main({ config: filename })).not.toThrow();
230
230
  unlinkSync(filename);
231
231
  });
232
+
233
+ test('Skip DNS', () => {
234
+ // Create a temp config file
235
+ const filename = resolve('./medplum.skipDns.config.json');
236
+ writeFileSync(
237
+ filename,
238
+ JSON.stringify({
239
+ name: 'skipDns',
240
+ stackName: 'MedplumSkipDnsStack',
241
+ accountNumber: '647991932601',
242
+ region: 'us-east-1',
243
+ domainName: 'medplum.com',
244
+ apiPort: 8103,
245
+ apiDomainName: 'api.medplum.com',
246
+ apiSslCertArn: 'arn:aws:acm:us-east-1:647991932601:certificate/08bf1daf-3a2b-4cbe-91a0-739b4364a1ec',
247
+ appDomainName: 'app.medplum.com',
248
+ appSslCertArn: 'arn:aws:acm:us-east-1:647991932601:certificate/fd21b628-b2c0-4a5d-b4f5-b5c9a6d63b1a',
249
+ storageBucketName: 'medplum-storage',
250
+ storageDomainName: 'storage.medplum.com',
251
+ storageSslCertArn: 'arn:aws:acm:us-east-1:647991932601:certificate/19d85245-0a1d-4bf5-9789-23082b1a15fc',
252
+ storagePublicKey: '-----BEGIN PUBLIC KEY-----\n-----END PUBLIC KEY-----',
253
+ maxAzs: 2,
254
+ rdsInstances: 1,
255
+ desiredServerCount: 1,
256
+ serverImage: 'medplum/medplum-server:latest',
257
+ serverMemory: 512,
258
+ serverCpu: 256,
259
+ skipDns: true,
260
+ }),
261
+ { encoding: 'utf-8' }
262
+ );
263
+
264
+ expect(() => main({ config: filename })).not.toThrow();
265
+ unlinkSync(filename);
266
+ });
232
267
  });
package/src/storage.ts CHANGED
@@ -116,19 +116,23 @@ export class Storage extends Construct {
116
116
  webAclId: waf.attrArn,
117
117
  });
118
118
 
119
- const zone = route53.HostedZone.fromLookup(this, 'Zone', {
120
- domainName: config.domainName.split('.').slice(-2).join('.'),
121
- });
119
+ // DNS
120
+ let record = undefined;
121
+ if (!config.skipDns) {
122
+ const zone = route53.HostedZone.fromLookup(this, 'Zone', {
123
+ domainName: config.domainName.split('.').slice(-2).join('.'),
124
+ });
122
125
 
123
- // Route53 alias record for the CloudFront distribution
124
- const record = new route53.ARecord(this, 'StorageAliasRecord', {
125
- recordName: config.storageDomainName,
126
- target: route53.RecordTarget.fromAlias(new targets.CloudFrontTarget(distribution)),
127
- zone,
128
- });
126
+ // Route53 alias record for the CloudFront distribution
127
+ record = new route53.ARecord(this, 'StorageAliasRecord', {
128
+ recordName: config.storageDomainName,
129
+ target: route53.RecordTarget.fromAlias(new targets.CloudFrontTarget(distribution)),
130
+ zone,
131
+ });
132
+ }
129
133
 
130
134
  // Debug
131
- console.log('ARecord', record.domainName);
135
+ console.log('ARecord', record?.domainName);
132
136
  }
133
137
  }
134
138
  }