Skip to main content
Glama
storage.ts6.7 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import type { MedplumInfraConfig } from '@medplum/core'; import type { aws_iam as iam, aws_wafv2 as wafv2 } from 'aws-cdk-lib'; import { aws_certificatemanager as acm, aws_cloudfront as cloudfront, Duration, aws_cloudfront_origins as origins, aws_route53 as route53, aws_s3 as s3, aws_route53_targets as targets, } from 'aws-cdk-lib'; import { ServerlessClamscan } from 'cdk-serverless-clamscan'; import { Construct } from 'constructs'; import { grantBucketAccessToOriginAccessIdentity } from './oai'; import { buildWaf } from './waf'; /** * Binary storage bucket and CloudFront distribution. */ export class Storage extends Construct { storageBucket: s3.IBucket; keyGroup?: cloudfront.IKeyGroup; responseHeadersPolicy?: cloudfront.IResponseHeadersPolicy; waf?: wafv2.CfnWebACL; originAccessIdentity?: cloudfront.OriginAccessIdentity; originAccessPolicyStatement?: iam.PolicyStatement; distribution?: cloudfront.IDistribution; dnsRecord?: route53.IRecordSet; constructor(parent: Construct, config: MedplumInfraConfig, region: string) { super(parent, 'Storage'); if (region === config.region) { // S3 bucket this.storageBucket = new s3.Bucket(this, 'StorageBucket', { bucketName: config.storageBucketName, publicReadAccess: false, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, encryption: s3.BucketEncryption.S3_MANAGED, enforceSSL: true, versioned: true, }); if (config.clamscanEnabled) { // ClamAV serverless scan const sc = new ServerlessClamscan(this, 'ServerlessClamscan', { defsBucketAccessLogsConfig: { logsBucket: s3.Bucket.fromBucketName(this, 'LoggingBucket', config.clamscanLoggingBucket), logsPrefix: config.clamscanLoggingPrefix, }, }); sc.addSourceBucket(this.storageBucket); } } else { // Otherwise, reference the bucket by name and region this.storageBucket = s3.Bucket.fromBucketAttributes(this, 'StorageBucket', { bucketName: config.storageBucketName, region: config.region, }); } if (region === 'us-east-1') { // Public key in PEM format let publicKey: cloudfront.IPublicKey; if (config.signingKeyId) { publicKey = cloudfront.PublicKey.fromPublicKeyId(this, 'StoragePublicKey', config.signingKeyId); } else { publicKey = new cloudfront.PublicKey(this, 'StoragePublicKey', { encodedKey: config.storagePublicKey, }); } // Authorized key group for presigned URLs this.keyGroup = new cloudfront.KeyGroup(this, 'StorageKeyGroup', { items: [publicKey], }); // HTTP response headers policy this.responseHeadersPolicy = new cloudfront.ResponseHeadersPolicy(this, 'ResponseHeadersPolicy', { customHeadersBehavior: { customHeaders: [ { header: 'Permissions-Policy', value: 'accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=(), interest-cohort=()', override: true, }, ], }, corsBehavior: { accessControlAllowCredentials: false, accessControlAllowOrigins: [config.appDomainName, 'https://ccda.medplum.com'], accessControlAllowHeaders: ['*'], accessControlAllowMethods: ['GET', 'HEAD', 'OPTIONS'], accessControlMaxAge: Duration.seconds(600), originOverride: false, }, securityHeadersBehavior: { contentSecurityPolicy: { contentSecurityPolicy: "default-src 'none'; connect-src https://ccda.medplum.com; base-uri 'none'; form-action 'none'; frame-ancestors *;", override: true, }, contentTypeOptions: { override: true }, frameOptions: { frameOption: cloudfront.HeadersFrameOption.DENY, override: true, }, referrerPolicy: { referrerPolicy: cloudfront.HeadersReferrerPolicy.NO_REFERRER, override: true, }, strictTransportSecurity: { accessControlMaxAge: Duration.seconds(63072000), includeSubdomains: true, preload: true, override: true, }, xssProtection: { protection: true, modeBlock: true, override: true, }, }, }); // WAF this.waf = buildWaf( this, 'StorageWAF', `${config.stackName}-StorageWAF`, 'CLOUDFRONT', config.storageWafIpSetArn, config.wafLogGroupName, config.wafLogGroupCreate ); // Origin access identity this.originAccessIdentity = new cloudfront.OriginAccessIdentity(this, 'OriginAccessIdentity', {}); this.originAccessPolicyStatement = grantBucketAccessToOriginAccessIdentity( this.storageBucket, this.originAccessIdentity ); // CloudFront distribution this.distribution = new cloudfront.Distribution(this, 'StorageDistribution', { defaultBehavior: { origin: origins.S3BucketOrigin.withOriginAccessIdentity(this.storageBucket, { originAccessIdentity: this.originAccessIdentity, }), responseHeadersPolicy: this.responseHeadersPolicy, viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, trustedKeyGroups: [this.keyGroup], }, certificate: acm.Certificate.fromCertificateArn(this, 'StorageCertificate', config.storageSslCertArn), domainNames: [config.storageDomainName], webAclId: this.waf.attrArn, logBucket: config.storageLoggingBucket ? s3.Bucket.fromBucketName(this, 'LoggingBucket', config.storageLoggingBucket) : undefined, logFilePrefix: config.storageLoggingPrefix, }); // DNS if (!config.skipDns) { const hostedZoneName = config.hostedZoneName ?? config.domainName.split('.').slice(-2).join('.'); const zone = route53.HostedZone.fromLookup(this, 'Zone', { domainName: hostedZoneName }); // Route53 alias record for the CloudFront distribution this.dnsRecord = new route53.ARecord(this, 'StorageAliasRecord', { recordName: config.storageDomainName, target: route53.RecordTarget.fromAlias(new targets.CloudFrontTarget(this.distribution)), zone, }); } } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/medplum/medplum'

If you have feedback or need assistance with the MCP directory API, please join our Discord server