@medplum/cdk 2.0.17 → 2.0.19

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.
@@ -333,23 +333,8 @@ class BackEnd extends constructs.Construct {
333
333
  streamPrefix: 'Medplum',
334
334
  });
335
335
  // Task Containers
336
- let serverImage = undefined;
337
- // Pull out the image name and tag from the image URI if it's an ECR image
338
- const ecrImageUriRegex = new RegExp(`^${config.accountNumber}\\.dkr\\.ecr\\.${config.region}\\.amazonaws\\.com/(.*)[:@](.*)$`);
339
- const nameTagMatches = config.serverImage.match(ecrImageUriRegex);
340
- const serverImageName = nameTagMatches?.[1];
341
- const serverImageTag = nameTagMatches?.[2];
342
- if (serverImageName && serverImageTag) {
343
- // Creating an ecr repository image will automatically grant fine-grained permissions to ecs to access the image
344
- const ecrRepo = awsEcr.Repository.fromRepositoryArn(this, 'ServerImageRepo', `arn:aws:ecr:${config.region}:${config.accountNumber}:repository/${serverImageName}`);
345
- serverImage = awsCdkLib.aws_ecs.ContainerImage.fromEcrRepository(ecrRepo, serverImageTag);
346
- }
347
- else {
348
- // Otherwise, use the standard container image
349
- serverImage = awsCdkLib.aws_ecs.ContainerImage.fromRegistry(config.serverImage);
350
- }
351
336
  const serviceContainer = taskDefinition.addContainer('MedplumTaskDefinition', {
352
- image: serverImage,
337
+ image: this.getContainerImage(config, config.serverImage),
353
338
  command: [config.region === 'us-east-1' ? `aws:/medplum/${name}/` : `aws:${config.region}:/medplum/${name}/`],
354
339
  logging: logDriver,
355
340
  });
@@ -357,6 +342,17 @@ class BackEnd extends constructs.Construct {
357
342
  containerPort: config.apiPort,
358
343
  hostPort: config.apiPort,
359
344
  });
345
+ if (config.additionalContainers) {
346
+ for (const container of config.additionalContainers) {
347
+ taskDefinition.addContainer('AdditionalContainer-' + container.name, {
348
+ containerName: container.name,
349
+ image: this.getContainerImage(config, container.image),
350
+ command: container.command,
351
+ environment: container.environment,
352
+ logging: logDriver,
353
+ });
354
+ }
355
+ }
360
356
  // Security Groups
361
357
  const fargateSecurityGroup = new awsCdkLib.aws_ec2.SecurityGroup(this, 'ServiceSecurityGroup', {
362
358
  allowAllOutbound: true,
@@ -396,7 +392,7 @@ class BackEnd extends constructs.Construct {
396
392
  // Load Balancer
397
393
  const loadBalancer = new awsCdkLib.aws_elasticloadbalancingv2.ApplicationLoadBalancer(this, 'LoadBalancer', {
398
394
  vpc: vpc,
399
- internetFacing: true,
395
+ internetFacing: config.apiInternetFacing !== false,
400
396
  http2Enabled: true,
401
397
  });
402
398
  if (config.loadBalancerLoggingEnabled) {
@@ -480,6 +476,28 @@ class BackEnd extends constructs.Construct {
480
476
  console.log('WAF', waf.attrArn);
481
477
  console.log('WAF Association', wafAssociation.node.id);
482
478
  }
479
+ /**
480
+ * Returns a container image for the given image name.
481
+ * If the image name is an ECR image, then the image will be pulled from ECR.
482
+ * Otherwise, the image name is assumed to be a Docker Hub image.
483
+ * @param config The config settings (account number and region).
484
+ * @param imageName The image name.
485
+ * @returns The container image.
486
+ */
487
+ getContainerImage(config, imageName) {
488
+ // Pull out the image name and tag from the image URI if it's an ECR image
489
+ const ecrImageUriRegex = new RegExp(`^${config.accountNumber}\\.dkr\\.ecr\\.${config.region}\\.amazonaws\\.com/(.*)[:@](.*)$`);
490
+ const nameTagMatches = imageName.match(ecrImageUriRegex);
491
+ const serverImageName = nameTagMatches?.[1];
492
+ const serverImageTag = nameTagMatches?.[2];
493
+ if (serverImageName && serverImageTag) {
494
+ // Creating an ecr repository image will automatically grant fine-grained permissions to ecs to access the image
495
+ const ecrRepo = awsEcr.Repository.fromRepositoryArn(this, 'ServerImageRepo', `arn:aws:ecr:${config.region}:${config.accountNumber}:repository/${serverImageName}`);
496
+ return awsCdkLib.aws_ecs.ContainerImage.fromEcrRepository(ecrRepo, serverImageTag);
497
+ }
498
+ // Otherwise, use the standard container image
499
+ return awsCdkLib.aws_ecs.ContainerImage.fromRegistry(imageName);
500
+ }
483
501
  }
484
502
 
485
503
  /**
@@ -494,11 +512,10 @@ class BackEnd extends constructs.Construct {
494
512
  *
495
513
  * See: https://stackoverflow.com/a/60917015
496
514
  *
497
- * @param scope The CDK construct scope.
498
515
  * @param bucket The S3 bucket.
499
516
  * @param identity The CloudFront Origin Access Identity.
500
517
  */
501
- function grantBucketAccessToOriginAccessIdentity(scope, bucket, identity) {
518
+ function grantBucketAccessToOriginAccessIdentity(bucket, identity) {
502
519
  const policyStatement = new awsCdkLib.aws_iam.PolicyStatement();
503
520
  policyStatement.addActions('s3:GetObject*');
504
521
  policyStatement.addActions('s3:GetBucket*');
@@ -506,11 +523,7 @@ function grantBucketAccessToOriginAccessIdentity(scope, bucket, identity) {
506
523
  policyStatement.addResources(bucket.bucketArn);
507
524
  policyStatement.addResources(`${bucket.bucketArn}/*`);
508
525
  policyStatement.addCanonicalUserPrincipal(identity.cloudFrontOriginAccessIdentityS3CanonicalUserId);
509
- let policy = bucket.policy;
510
- if (!policy) {
511
- policy = new awsCdkLib.aws_s3.BucketPolicy(scope, 'Policy', { bucket });
512
- }
513
- policy.document.addStatements(policyStatement);
526
+ bucket.addToResourcePolicy(policyStatement);
514
527
  }
515
528
 
516
529
  /**
@@ -601,7 +614,7 @@ class FrontEnd extends constructs.Construct {
601
614
  });
602
615
  // Origin access identity
603
616
  const originAccessIdentity = new awsCdkLib.aws_cloudfront.OriginAccessIdentity(this, 'OriginAccessIdentity', {});
604
- grantBucketAccessToOriginAccessIdentity(this, appBucket, originAccessIdentity);
617
+ grantBucketAccessToOriginAccessIdentity(appBucket, originAccessIdentity);
605
618
  // CloudFront distribution
606
619
  const distribution = new awsCdkLib.aws_cloudfront.Distribution(this, 'AppDistribution', {
607
620
  defaultRootObject: 'index.html',
@@ -610,14 +623,16 @@ class FrontEnd extends constructs.Construct {
610
623
  responseHeadersPolicy,
611
624
  viewerProtocolPolicy: awsCdkLib.aws_cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
612
625
  },
613
- additionalBehaviors: {
614
- '/api/*': {
615
- origin: new awsCdkLib.aws_cloudfront_origins.HttpOrigin(config.apiDomainName),
616
- allowedMethods: awsCdkLib.aws_cloudfront.AllowedMethods.ALLOW_ALL,
617
- cachePolicy: apiOriginCachePolicy,
618
- viewerProtocolPolicy: awsCdkLib.aws_cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
619
- },
620
- },
626
+ additionalBehaviors: config.appApiProxy
627
+ ? {
628
+ '/api/*': {
629
+ origin: new awsCdkLib.aws_cloudfront_origins.HttpOrigin(config.apiDomainName),
630
+ allowedMethods: awsCdkLib.aws_cloudfront.AllowedMethods.ALLOW_ALL,
631
+ cachePolicy: apiOriginCachePolicy,
632
+ viewerProtocolPolicy: awsCdkLib.aws_cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
633
+ },
634
+ }
635
+ : undefined,
621
636
  certificate: awsCdkLib.aws_certificatemanager.Certificate.fromCertificateArn(this, 'AppCertificate', config.appSslCertArn),
622
637
  domainNames: [config.appDomainName],
623
638
  errorResponses: [
@@ -733,7 +748,7 @@ class Storage extends constructs.Construct {
733
748
  });
734
749
  // Origin access identity
735
750
  const originAccessIdentity = new awsCdkLib.aws_cloudfront.OriginAccessIdentity(this, 'OriginAccessIdentity', {});
736
- grantBucketAccessToOriginAccessIdentity(this, storageBucket, originAccessIdentity);
751
+ grantBucketAccessToOriginAccessIdentity(storageBucket, originAccessIdentity);
737
752
  // CloudFront distribution
738
753
  const distribution = new awsCdkLib.aws_cloudfront.Distribution(this, 'StorageDistribution', {
739
754
  defaultBehavior: {
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":["../../../src/waf.ts","../../../src/backend.ts","../../../src/oai.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 { aws_cloudfront as cloudfront, aws_iam as iam, aws_s3 as s3 } from 'aws-cdk-lib';\nimport { Construct } from 'constructs';\n\n/**\n * Grants S3 bucket read access to the CloudFront Origin Access Identity (OAI).\n *\n * Under normal circumstances, where CDK creates both the S3 bucket and the OAI,\n * you can achieve this same behavior by simply calling:\n *\n * bucket.grantRead(identity);\n *\n * However, if importing an S3 bucket via `s3.Bucket.fromBucketAttributes()`, that does not work.\n *\n * See: https://stackoverflow.com/a/60917015\n *\n * @param scope The CDK construct scope.\n * @param bucket The S3 bucket.\n * @param identity The CloudFront Origin Access Identity.\n */\nexport function grantBucketAccessToOriginAccessIdentity(\n scope: Construct,\n bucket: s3.IBucket,\n identity: cloudfront.OriginAccessIdentity\n): void {\n const policyStatement = new iam.PolicyStatement();\n policyStatement.addActions('s3:GetObject*');\n policyStatement.addActions('s3:GetBucket*');\n policyStatement.addActions('s3:List*');\n policyStatement.addResources(bucket.bucketArn);\n policyStatement.addResources(`${bucket.bucketArn}/*`);\n policyStatement.addCanonicalUserPrincipal(identity.cloudFrontOriginAccessIdentityS3CanonicalUserId);\n\n let policy = bucket.policy;\n if (!policy) {\n policy = new s3.BucketPolicy(scope, 'Policy', { bucket });\n }\n\n policy.document.addStatements(policyStatement);\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 { grantBucketAccessToOriginAccessIdentity } from './oai';\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 grantBucketAccessToOriginAccessIdentity(this, appBucket, 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 { grantBucketAccessToOriginAccessIdentity } from './oai';\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 grantBucketAccessToOriginAccessIdentity(this, storageBucket, 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, Tags } 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 Tags.of(this.primaryStack).add('medplum:environment', config.name);\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 Tags.of(usEast1Stack).add('medplum:environment', config.name);\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","Tags","App","readFileSync","resolve"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","sources":["../../../src/waf.ts","../../../src/backend.ts","../../../src/oai.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 Duration,\n RemovalPolicy,\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_s3 as s3,\n aws_secretsmanager as secretsmanager,\n aws_ssm as ssm,\n aws_route53_targets as targets,\n aws_wafv2 as wafv2,\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 const serviceContainer = taskDefinition.addContainer('MedplumTaskDefinition', {\n image: this.getContainerImage(config, config.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 if (config.additionalContainers) {\n for (const container of config.additionalContainers) {\n taskDefinition.addContainer('AdditionalContainer-' + container.name, {\n containerName: container.name,\n image: this.getContainerImage(config, container.image),\n command: container.command,\n environment: container.environment,\n logging: logDriver,\n });\n }\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: config.apiInternetFacing !== false, // default 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 /**\n * Returns a container image for the given image name.\n * If the image name is an ECR image, then the image will be pulled from ECR.\n * Otherwise, the image name is assumed to be a Docker Hub image.\n * @param config The config settings (account number and region).\n * @param imageName The image name.\n * @returns The container image.\n */\n private getContainerImage(config: MedplumInfraConfig, imageName: string): ecs.ContainerImage {\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 = imageName.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 return ecs.ContainerImage.fromEcrRepository(ecrRepo, serverImageTag);\n }\n\n // Otherwise, use the standard container image\n return ecs.ContainerImage.fromRegistry(imageName);\n }\n}\n","import { aws_cloudfront as cloudfront, aws_iam as iam, aws_s3 as s3 } from 'aws-cdk-lib';\n\n/**\n * Grants S3 bucket read access to the CloudFront Origin Access Identity (OAI).\n *\n * Under normal circumstances, where CDK creates both the S3 bucket and the OAI,\n * you can achieve this same behavior by simply calling:\n *\n * bucket.grantRead(identity);\n *\n * However, if importing an S3 bucket via `s3.Bucket.fromBucketAttributes()`, that does not work.\n *\n * See: https://stackoverflow.com/a/60917015\n *\n * @param bucket The S3 bucket.\n * @param identity The CloudFront Origin Access Identity.\n */\nexport function grantBucketAccessToOriginAccessIdentity(\n bucket: s3.IBucket,\n identity: cloudfront.OriginAccessIdentity\n): void {\n const policyStatement = new iam.PolicyStatement();\n policyStatement.addActions('s3:GetObject*');\n policyStatement.addActions('s3:GetBucket*');\n policyStatement.addActions('s3:List*');\n policyStatement.addResources(bucket.bucketArn);\n policyStatement.addResources(`${bucket.bucketArn}/*`);\n policyStatement.addCanonicalUserPrincipal(identity.cloudFrontOriginAccessIdentityS3CanonicalUserId);\n bucket.addToResourcePolicy(policyStatement);\n}\n","import {\n Duration,\n RemovalPolicy,\n aws_certificatemanager as acm,\n aws_cloudfront as cloudfront,\n aws_cloudfront_origins as origins,\n aws_route53 as route53,\n aws_s3 as s3,\n aws_route53_targets as targets,\n aws_wafv2 as wafv2,\n} from 'aws-cdk-lib';\nimport { Construct } from 'constructs';\nimport { MedplumInfraConfig } from './config';\nimport { grantBucketAccessToOriginAccessIdentity } from './oai';\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 grantBucketAccessToOriginAccessIdentity(appBucket, 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: config.appApiProxy\n ? {\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 : undefined,\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 Duration,\n aws_certificatemanager as acm,\n aws_cloudfront as cloudfront,\n aws_cloudfront_origins as origins,\n aws_route53 as route53,\n aws_s3 as s3,\n aws_route53_targets as targets,\n aws_wafv2 as wafv2,\n} from 'aws-cdk-lib';\nimport { ServerlessClamscan } from 'cdk-serverless-clamscan';\nimport { Construct } from 'constructs';\nimport { MedplumInfraConfig } from './config';\nimport { grantBucketAccessToOriginAccessIdentity } from './oai';\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 grantBucketAccessToOriginAccessIdentity(storageBucket, 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, Tags } 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 Tags.of(this.primaryStack).add('medplum:environment', config.name);\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 Tags.of(usEast1Stack).add('medplum:environment', config.name);\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","elbv2","s3","wafv2","route53","targets","ssm","Repository","cloudfront","origins","acm","ServerlessClamscan","Stack","Tags","App","readFileSync","resolve"],"mappings":"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"}
@@ -1,11 +1,12 @@
1
1
  {
2
- "apiPort": 5000,
2
+ "apiPort": 8103,
3
3
  "name": "staging",
4
4
  "region": "us-east-1",
5
5
  "accountNumber": "647991932601",
6
6
  "stackName": "MedplumStagingStack",
7
7
  "domainName": "staging.medplum.com",
8
8
  "apiDomainName": "api.staging.medplum.com",
9
+ "apiInternetFacing": false,
9
10
  "appDomainName": "app.staging.medplum.com",
10
11
  "storageDomainName": "storage.staging.medplum.com",
11
12
  "storageBucketName": "medplum-staging-storage",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@medplum/cdk",
3
- "version": "2.0.17",
3
+ "version": "2.0.19",
4
4
  "description": "Medplum CDK Infra as Code",
5
5
  "author": "Medplum <hello@medplum.com>",
6
6
  "license": "Apache-2.0",
@@ -18,14 +18,14 @@
18
18
  "test": "jest --runInBand"
19
19
  },
20
20
  "dependencies": {
21
- "@aws-sdk/client-acm": "3.319.0",
22
- "@aws-sdk/client-ssm": "3.319.0",
23
- "@aws-sdk/client-sts": "3.319.0",
24
- "aws-cdk-lib": "2.76.0",
21
+ "@aws-sdk/client-acm": "3.332.0",
22
+ "@aws-sdk/client-ssm": "3.332.0",
23
+ "@aws-sdk/client-sts": "3.332.0",
24
+ "aws-cdk-lib": "2.79.1",
25
25
  "cdk": "2.72.1",
26
- "cdk-nag": "2.26.4",
27
- "cdk-serverless-clamscan": "2.4.215",
28
- "constructs": "10.2.5"
26
+ "cdk-nag": "2.27.2",
27
+ "cdk-serverless-clamscan": "2.4.233",
28
+ "constructs": "10.2.20"
29
29
  },
30
30
  "bin": {
31
31
  "medplum-cdk-init": "./dist/cjs/init.cjs"
package/src/backend.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import {
2
+ Duration,
3
+ RemovalPolicy,
2
4
  aws_ec2 as ec2,
3
5
  aws_ecs as ecs,
4
6
  aws_elasticache as elasticache,
@@ -7,13 +9,11 @@ import {
7
9
  aws_logs as logs,
8
10
  aws_rds as rds,
9
11
  aws_route53 as route53,
10
- aws_route53_targets as targets,
11
12
  aws_s3 as s3,
12
13
  aws_secretsmanager as secretsmanager,
13
14
  aws_ssm as ssm,
15
+ aws_route53_targets as targets,
14
16
  aws_wafv2 as wafv2,
15
- Duration,
16
- RemovalPolicy,
17
17
  } from 'aws-cdk-lib';
18
18
  import { Repository } from 'aws-cdk-lib/aws-ecr';
19
19
  import { Construct } from 'constructs';
@@ -250,28 +250,8 @@ export class BackEnd extends Construct {
250
250
  });
251
251
 
252
252
  // Task Containers
253
- let serverImage: ecs.ContainerImage | undefined = undefined;
254
- // Pull out the image name and tag from the image URI if it's an ECR image
255
- const ecrImageUriRegex = new RegExp(
256
- `^${config.accountNumber}\\.dkr\\.ecr\\.${config.region}\\.amazonaws\\.com/(.*)[:@](.*)$`
257
- );
258
- const nameTagMatches = config.serverImage.match(ecrImageUriRegex);
259
- const serverImageName = nameTagMatches?.[1];
260
- const serverImageTag = nameTagMatches?.[2];
261
- if (serverImageName && serverImageTag) {
262
- // Creating an ecr repository image will automatically grant fine-grained permissions to ecs to access the image
263
- const ecrRepo = Repository.fromRepositoryArn(
264
- this,
265
- 'ServerImageRepo',
266
- `arn:aws:ecr:${config.region}:${config.accountNumber}:repository/${serverImageName}`
267
- );
268
- serverImage = ecs.ContainerImage.fromEcrRepository(ecrRepo, serverImageTag);
269
- } else {
270
- // Otherwise, use the standard container image
271
- serverImage = ecs.ContainerImage.fromRegistry(config.serverImage);
272
- }
273
253
  const serviceContainer = taskDefinition.addContainer('MedplumTaskDefinition', {
274
- image: serverImage,
254
+ image: this.getContainerImage(config, config.serverImage),
275
255
  command: [config.region === 'us-east-1' ? `aws:/medplum/${name}/` : `aws:${config.region}:/medplum/${name}/`],
276
256
  logging: logDriver,
277
257
  });
@@ -281,6 +261,18 @@ export class BackEnd extends Construct {
281
261
  hostPort: config.apiPort,
282
262
  });
283
263
 
264
+ if (config.additionalContainers) {
265
+ for (const container of config.additionalContainers) {
266
+ taskDefinition.addContainer('AdditionalContainer-' + container.name, {
267
+ containerName: container.name,
268
+ image: this.getContainerImage(config, container.image),
269
+ command: container.command,
270
+ environment: container.environment,
271
+ logging: logDriver,
272
+ });
273
+ }
274
+ }
275
+
284
276
  // Security Groups
285
277
  const fargateSecurityGroup = new ec2.SecurityGroup(this, 'ServiceSecurityGroup', {
286
278
  allowAllOutbound: true,
@@ -324,7 +316,7 @@ export class BackEnd extends Construct {
324
316
  // Load Balancer
325
317
  const loadBalancer = new elbv2.ApplicationLoadBalancer(this, 'LoadBalancer', {
326
318
  vpc: vpc,
327
- internetFacing: true,
319
+ internetFacing: config.apiInternetFacing !== false, // default true
328
320
  http2Enabled: true,
329
321
  });
330
322
 
@@ -423,4 +415,34 @@ export class BackEnd extends Construct {
423
415
  console.log('WAF', waf.attrArn);
424
416
  console.log('WAF Association', wafAssociation.node.id);
425
417
  }
418
+
419
+ /**
420
+ * Returns a container image for the given image name.
421
+ * If the image name is an ECR image, then the image will be pulled from ECR.
422
+ * Otherwise, the image name is assumed to be a Docker Hub image.
423
+ * @param config The config settings (account number and region).
424
+ * @param imageName The image name.
425
+ * @returns The container image.
426
+ */
427
+ private getContainerImage(config: MedplumInfraConfig, imageName: string): ecs.ContainerImage {
428
+ // Pull out the image name and tag from the image URI if it's an ECR image
429
+ const ecrImageUriRegex = new RegExp(
430
+ `^${config.accountNumber}\\.dkr\\.ecr\\.${config.region}\\.amazonaws\\.com/(.*)[:@](.*)$`
431
+ );
432
+ const nameTagMatches = imageName.match(ecrImageUriRegex);
433
+ const serverImageName = nameTagMatches?.[1];
434
+ const serverImageTag = nameTagMatches?.[2];
435
+ if (serverImageName && serverImageTag) {
436
+ // Creating an ecr repository image will automatically grant fine-grained permissions to ecs to access the image
437
+ const ecrRepo = Repository.fromRepositoryArn(
438
+ this,
439
+ 'ServerImageRepo',
440
+ `arn:aws:ecr:${config.region}:${config.accountNumber}:repository/${serverImageName}`
441
+ );
442
+ return ecs.ContainerImage.fromEcrRepository(ecrRepo, serverImageTag);
443
+ }
444
+
445
+ // Otherwise, use the standard container image
446
+ return ecs.ContainerImage.fromRegistry(imageName);
447
+ }
426
448
  }
package/src/config.ts CHANGED
@@ -8,8 +8,10 @@ export interface MedplumInfraConfig {
8
8
  apiPort: number;
9
9
  apiDomainName: string;
10
10
  apiSslCertArn: string;
11
+ apiInternetFacing?: boolean;
11
12
  appDomainName: string;
12
13
  appSslCertArn: string;
14
+ appApiProxy?: boolean;
13
15
  storageBucketName: string;
14
16
  storageDomainName: string;
15
17
  storageSslCertArn: string;
@@ -29,4 +31,15 @@ export interface MedplumInfraConfig {
29
31
  clamscanLoggingBucket: string;
30
32
  clamscanLoggingPrefix: string;
31
33
  skipDns?: boolean;
34
+ additionalContainers?: {
35
+ name: string;
36
+ image: string;
37
+ cpu?: number;
38
+ memory?: number;
39
+ essential?: boolean;
40
+ command?: string[];
41
+ environment?: {
42
+ [key: string]: string;
43
+ };
44
+ }[];
32
45
  }
package/src/frontend.ts CHANGED
@@ -1,13 +1,13 @@
1
1
  import {
2
+ Duration,
3
+ RemovalPolicy,
2
4
  aws_certificatemanager as acm,
3
5
  aws_cloudfront as cloudfront,
4
6
  aws_cloudfront_origins as origins,
5
7
  aws_route53 as route53,
6
- aws_route53_targets as targets,
7
8
  aws_s3 as s3,
9
+ aws_route53_targets as targets,
8
10
  aws_wafv2 as wafv2,
9
- Duration,
10
- RemovalPolicy,
11
11
  } from 'aws-cdk-lib';
12
12
  import { Construct } from 'constructs';
13
13
  import { MedplumInfraConfig } from './config';
@@ -116,7 +116,7 @@ export class FrontEnd extends Construct {
116
116
 
117
117
  // Origin access identity
118
118
  const originAccessIdentity = new cloudfront.OriginAccessIdentity(this, 'OriginAccessIdentity', {});
119
- grantBucketAccessToOriginAccessIdentity(this, appBucket, originAccessIdentity);
119
+ grantBucketAccessToOriginAccessIdentity(appBucket, originAccessIdentity);
120
120
 
121
121
  // CloudFront distribution
122
122
  const distribution = new cloudfront.Distribution(this, 'AppDistribution', {
@@ -126,14 +126,16 @@ export class FrontEnd extends Construct {
126
126
  responseHeadersPolicy,
127
127
  viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
128
128
  },
129
- additionalBehaviors: {
130
- '/api/*': {
131
- origin: new origins.HttpOrigin(config.apiDomainName),
132
- allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL,
133
- cachePolicy: apiOriginCachePolicy,
134
- viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
135
- },
136
- },
129
+ additionalBehaviors: config.appApiProxy
130
+ ? {
131
+ '/api/*': {
132
+ origin: new origins.HttpOrigin(config.apiDomainName),
133
+ allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL,
134
+ cachePolicy: apiOriginCachePolicy,
135
+ viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
136
+ },
137
+ }
138
+ : undefined,
137
139
  certificate: acm.Certificate.fromCertificateArn(this, 'AppCertificate', config.appSslCertArn),
138
140
  domainNames: [config.appDomainName],
139
141
  errorResponses: [
package/src/index.test.ts CHANGED
@@ -264,4 +264,130 @@ describe('Infra', () => {
264
264
  expect(() => main({ config: filename })).not.toThrow();
265
265
  unlinkSync(filename);
266
266
  });
267
+
268
+ test('Add DataDog container', () => {
269
+ // Create a temp config file
270
+ const filename = resolve('./medplum.datadog.config.json');
271
+ writeFileSync(
272
+ filename,
273
+ JSON.stringify({
274
+ name: 'datadog',
275
+ stackName: 'MedplumDataDogStack',
276
+ accountNumber: '647991932601',
277
+ region: 'us-east-1',
278
+ domainName: 'medplum.com',
279
+ apiPort: 8103,
280
+ apiDomainName: 'api.medplum.com',
281
+ apiSslCertArn: 'arn:aws:acm:us-east-1:647991932601:certificate/08bf1daf-3a2b-4cbe-91a0-739b4364a1ec',
282
+ appDomainName: 'app.medplum.com',
283
+ appSslCertArn: 'arn:aws:acm:us-east-1:647991932601:certificate/fd21b628-b2c0-4a5d-b4f5-b5c9a6d63b1a',
284
+ storageBucketName: 'medplum-storage',
285
+ storageDomainName: 'storage.medplum.com',
286
+ storageSslCertArn: 'arn:aws:acm:us-east-1:647991932601:certificate/19d85245-0a1d-4bf5-9789-23082b1a15fc',
287
+ storagePublicKey: '-----BEGIN PUBLIC KEY-----\n-----END PUBLIC KEY-----',
288
+ maxAzs: 2,
289
+ rdsInstances: 1,
290
+ desiredServerCount: 1,
291
+ serverImage: 'medplum/medplum-server:latest',
292
+ serverMemory: 512,
293
+ serverCpu: 256,
294
+ additionalContainers: [
295
+ {
296
+ name: 'datadog-agent',
297
+ image: 'datadog/agent:latest',
298
+ environment: {
299
+ DD_SITE: 'datadoghq.com',
300
+ DD_API_KEY: 'YOUR_DATADOG_API_KEY',
301
+ },
302
+ },
303
+ ],
304
+ }),
305
+ { encoding: 'utf-8' }
306
+ );
307
+
308
+ expect(() => main({ config: filename })).not.toThrow();
309
+ unlinkSync(filename);
310
+ });
311
+
312
+ test('API in private subnet', () => {
313
+ // Create a temp config file
314
+ const filename = resolve('./medplum.test.config.json');
315
+ writeFileSync(
316
+ filename,
317
+ JSON.stringify({
318
+ name: 'unittest',
319
+ stackName: 'MedplumUnitTestStack',
320
+ accountNumber: '647991932601',
321
+ region: 'us-east-1',
322
+ domainName: 'medplum.com',
323
+ apiPort: 8103,
324
+ apiDomainName: 'api.medplum.com',
325
+ apiSslCertArn: 'arn:aws:acm:us-east-1:647991932601:certificate/08bf1daf-3a2b-4cbe-91a0-739b4364a1ec',
326
+ apiInternetFacing: false,
327
+ appDomainName: 'app.medplum.com',
328
+ appSslCertArn: 'arn:aws:acm:us-east-1:647991932601:certificate/fd21b628-b2c0-4a5d-b4f5-b5c9a6d63b1a',
329
+ storageBucketName: 'medplum-storage',
330
+ storageDomainName: 'storage.medplum.com',
331
+ storageSslCertArn: 'arn:aws:acm:us-east-1:647991932601:certificate/19d85245-0a1d-4bf5-9789-23082b1a15fc',
332
+ storagePublicKey: '-----BEGIN PUBLIC KEY-----\n-----END PUBLIC KEY-----',
333
+ maxAzs: 2,
334
+ rdsInstances: 1,
335
+ desiredServerCount: 1,
336
+ serverImage: 'medplum/medplum-server:staging',
337
+ serverMemory: 512,
338
+ serverCpu: 256,
339
+ loadBalancerLoggingEnabled: true,
340
+ loadBalancerLoggingBucket: 'medplum-logs-us-east-1',
341
+ loadBalancerLoggingPrefix: 'elb',
342
+ clamscanEnabled: true,
343
+ clamscanLoggingBucket: 'medplum-logs-us-east-1',
344
+ clamscanLoggingPrefix: 'clamscan',
345
+ }),
346
+ { encoding: 'utf-8' }
347
+ );
348
+
349
+ expect(() => main({ config: filename })).not.toThrow();
350
+ unlinkSync(filename);
351
+ });
352
+
353
+ test('Disable app-api proxy', () => {
354
+ // Create a temp config file
355
+ const filename = resolve('./medplum.test.config.json');
356
+ writeFileSync(
357
+ filename,
358
+ JSON.stringify({
359
+ name: 'unittest',
360
+ stackName: 'MedplumUnitTestStack',
361
+ accountNumber: '647991932601',
362
+ region: 'us-east-1',
363
+ domainName: 'medplum.com',
364
+ apiPort: 8103,
365
+ apiDomainName: 'api.medplum.com',
366
+ apiSslCertArn: 'arn:aws:acm:us-east-1:647991932601:certificate/08bf1daf-3a2b-4cbe-91a0-739b4364a1ec',
367
+ appDomainName: 'app.medplum.com',
368
+ appSslCertArn: 'arn:aws:acm:us-east-1:647991932601:certificate/fd21b628-b2c0-4a5d-b4f5-b5c9a6d63b1a',
369
+ appApiProxy: false,
370
+ storageBucketName: 'medplum-storage',
371
+ storageDomainName: 'storage.medplum.com',
372
+ storageSslCertArn: 'arn:aws:acm:us-east-1:647991932601:certificate/19d85245-0a1d-4bf5-9789-23082b1a15fc',
373
+ storagePublicKey: '-----BEGIN PUBLIC KEY-----\n-----END PUBLIC KEY-----',
374
+ maxAzs: 2,
375
+ rdsInstances: 1,
376
+ desiredServerCount: 1,
377
+ serverImage: 'medplum/medplum-server:staging',
378
+ serverMemory: 512,
379
+ serverCpu: 256,
380
+ loadBalancerLoggingEnabled: true,
381
+ loadBalancerLoggingBucket: 'medplum-logs-us-east-1',
382
+ loadBalancerLoggingPrefix: 'elb',
383
+ clamscanEnabled: true,
384
+ clamscanLoggingBucket: 'medplum-logs-us-east-1',
385
+ clamscanLoggingPrefix: 'clamscan',
386
+ }),
387
+ { encoding: 'utf-8' }
388
+ );
389
+
390
+ expect(() => main({ config: filename })).not.toThrow();
391
+ unlinkSync(filename);
392
+ });
267
393
  });
package/src/oai.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { aws_cloudfront as cloudfront, aws_iam as iam, aws_s3 as s3 } from 'aws-cdk-lib';
2
- import { Construct } from 'constructs';
3
2
 
4
3
  /**
5
4
  * Grants S3 bucket read access to the CloudFront Origin Access Identity (OAI).
@@ -13,12 +12,10 @@ import { Construct } from 'constructs';
13
12
  *
14
13
  * See: https://stackoverflow.com/a/60917015
15
14
  *
16
- * @param scope The CDK construct scope.
17
15
  * @param bucket The S3 bucket.
18
16
  * @param identity The CloudFront Origin Access Identity.
19
17
  */
20
18
  export function grantBucketAccessToOriginAccessIdentity(
21
- scope: Construct,
22
19
  bucket: s3.IBucket,
23
20
  identity: cloudfront.OriginAccessIdentity
24
21
  ): void {
@@ -29,11 +26,5 @@ export function grantBucketAccessToOriginAccessIdentity(
29
26
  policyStatement.addResources(bucket.bucketArn);
30
27
  policyStatement.addResources(`${bucket.bucketArn}/*`);
31
28
  policyStatement.addCanonicalUserPrincipal(identity.cloudFrontOriginAccessIdentityS3CanonicalUserId);
32
-
33
- let policy = bucket.policy;
34
- if (!policy) {
35
- policy = new s3.BucketPolicy(scope, 'Policy', { bucket });
36
- }
37
-
38
- policy.document.addStatements(policyStatement);
29
+ bucket.addToResourcePolicy(policyStatement);
39
30
  }
package/src/storage.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  import {
2
+ Duration,
2
3
  aws_certificatemanager as acm,
3
4
  aws_cloudfront as cloudfront,
4
5
  aws_cloudfront_origins as origins,
5
6
  aws_route53 as route53,
6
- aws_route53_targets as targets,
7
7
  aws_s3 as s3,
8
+ aws_route53_targets as targets,
8
9
  aws_wafv2 as wafv2,
9
- Duration,
10
10
  } from 'aws-cdk-lib';
11
11
  import { ServerlessClamscan } from 'cdk-serverless-clamscan';
12
12
  import { Construct } from 'constructs';
@@ -102,7 +102,7 @@ export class Storage extends Construct {
102
102
 
103
103
  // Origin access identity
104
104
  const originAccessIdentity = new cloudfront.OriginAccessIdentity(this, 'OriginAccessIdentity', {});
105
- grantBucketAccessToOriginAccessIdentity(this, storageBucket, originAccessIdentity);
105
+ grantBucketAccessToOriginAccessIdentity(storageBucket, originAccessIdentity);
106
106
 
107
107
  // CloudFront distribution
108
108
  const distribution = new cloudfront.Distribution(this, 'StorageDistribution', {