in source/constructs/lib/portal-stack.ts [55:236]
constructor(scope: cdk.Construct, id: string, props: PortalStackProps) {
super(scope, id);
const website = new CloudFrontToS3(this, 'Web', {
bucketProps: {
versioned: true,
encryption: s3.BucketEncryption.S3_MANAGED,
accessControl: s3.BucketAccessControl.PRIVATE,
enforceSSL: true,
removalPolicy: cdk.RemovalPolicy.RETAIN,
autoDeleteObjects: false,
},
cloudFrontDistributionProps: {
priceClass: cloudfront.PriceClass.PRICE_CLASS_ALL,
minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2019,
enableIpv6: false,
enableLogging: true, //Enable access logging for the distribution.
comment: 'Data Transfer Hub Portal Distribution',
errorResponses: [
{
httpStatus: 403,
responseHttpStatus: 200,
responsePagePath: "/index.html",
}
]
},
insertHttpSecurityHeaders: false,
});
const websiteBucket = website.s3Bucket as s3.Bucket;
const websiteDist = website.cloudFrontWebDistribution.node.defaultChild as cloudfront.CfnDistribution
if (props.auth_type === AuthType.OPENID) {
// Currently, CachePolicy and Cloudfront Function is not available in Cloudfront in China Regions.
// Need to override the default CachePolicy to use ForwardedValues to support both China regions and Global regions.
// This should be updated in the future once the feature is landed in China regions.
websiteDist.addPropertyOverride('DistributionConfig.DefaultCacheBehavior.CachePolicyId', undefined)
websiteDist.addPropertyOverride('DistributionConfig.DefaultCacheBehavior.ForwardedValues', {
"Cookies": {
"Forward": "none"
},
"QueryString": false
})
} else {
const cfFunction = new cloudfront.Function(this, "DataTransferHubSecurityHeader", {
functionName: `DTHSecHdr${cdk.Aws.REGION}${cdk.Aws.STACK_NAME}`,
code: cloudfront.FunctionCode.fromInline(`
function handler(event) {
var response = event.response;
var headers = response.headers;
headers['strict-transport-security'] = { value: 'max-age=63072000; includeSubdomains; preload' };
headers['content-security-policy'] = { value: "default-src 'self'; upgrade-insecure-requests; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self' ${props.aws_appsync_graphqlEndpoint} https://cognito-idp.${cdk.Aws.REGION}.amazonaws.com/" };
headers['x-content-type-options'] = { value: 'nosniff' };
headers['x-frame-options'] = { value: 'DENY' };
headers['x-xss-protection'] = { value: '1; mode=block' };
// Set the cache-control header
headers['cache-control'] = { value: 'public, max-age=604800;' };
return response;
}`
)
});
websiteDist.addPropertyOverride('DistributionConfig.DefaultCacheBehavior.FunctionAssociations', [
{
"EventType": "viewer-response",
"FunctionARN": cfFunction.functionArn,
}
])
}
// CustomResourceRole
const customResourceRole = new iam.Role(this, 'CustomResourceRole', {
assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
path: '/',
// roleName: `${cdk.Aws.STACK_NAME}CustomResourceRole-${cdk.Aws.REGION}`
})
const cfnCustomResourceRole = customResourceRole.node.defaultChild as iam.CfnRole;
cfnCustomResourceRole.overrideLogicalId('CustomResourceRole');
// CustomResourcePolicy
const customResourcePolicy = new iam.Policy(this, 'CustomResourcePolicy', {
policyName: `${cdk.Aws.STACK_NAME}CustomResourcePolicy`,
statements: [
new iam.PolicyStatement({
actions: [
'logs:CreateLogStream',
'logs:CreateLogGroup',
'logs:PutLogEvents'
],
resources: [
`arn:${cdk.Aws.PARTITION}:logs:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:log-group:/aws/lambda/*`
]
}),
new iam.PolicyStatement({
actions: [
's3:GetObject',
's3:PutObject',
's3:ListBucket'
],
resources: [
`arn:${cdk.Aws.PARTITION}:s3:::*`
]
})
]
});
customResourcePolicy.attachToRole(customResourceRole);
const cfnCustomResourcePolicy = customResourcePolicy.node.defaultChild as iam.CfnPolicy;
cfnCustomResourcePolicy.overrideLogicalId('CustomResourcePolicy');
const customResourceFunction = new lambda.Function(this, 'CustomHandler', {
description: 'Data Transfer Hub - Custom resource',
runtime: lambda.Runtime.NODEJS_14_X,
handler: 'index.handler',
timeout: cdk.Duration.seconds(30),
memorySize: 512,
role: customResourceRole,
code: lambda.Code.fromAsset(path.join(__dirname, '../../custom-resource/'),
{
bundling: {
image: lambda.Runtime.NODEJS_14_X.bundlingImage,
command: [
'bash', '-c', [
`cd /asset-output/`,
`cp -r /asset-input/* /asset-output/`,
`cd /asset-output/`,
`npm install`
].join(' && ')
],
user: 'root'
}
}
)
})
const cfnCustomResourceFn = customResourceFunction.node.defaultChild as lambda.CfnFunction
addCfnNagSuppressRules(cfnCustomResourceFn, [
{
id: 'W58',
reason: 'Lambda function already has permission to write CloudWatch Logs'
}
])
new s3Deployment.BucketDeployment(this, 'DeployWebsite', {
sources: [s3Deployment.Source.asset(path.join(__dirname, '../../portal/build'))],
destinationBucket: websiteBucket,
// disable this, otherwise the aws-exports.json will be deleted
prune: false,
})
this.websiteURL = website.cloudFrontWebDistribution.distributionDomainName
// CustomResourceConfig
this.createCustomResource('CustomResourceConfig', customResourceFunction, {
properties: [
{ path: 'Region', value: cdk.Aws.REGION },
{
path: 'configItem', value: {
aws_project_region: cdk.Aws.REGION,
aws_cognito_region: cdk.Aws.REGION,
aws_cloudfront_url: this.websiteURL,
aws_user_pools_id: props.aws_user_pools_id,
aws_user_pools_web_client_id: props.aws_user_pools_web_client_id,
oauth: {},
aws_oidc_customer_domain: props.aws_oidc_customer_domain,
aws_oidc_provider: props.aws_oidc_provider,
aws_oidc_client_id: props.aws_oidc_client_id,
aws_appsync_graphqlEndpoint: props.aws_appsync_graphqlEndpoint,
aws_appsync_region: cdk.Aws.REGION,
aws_appsync_authenticationType: props.auth_type === AuthType.OPENID ? 'OPENID_CONNECT' : 'AMAZON_COGNITO_USER_POOLS',
taskCluster: props.taskCluster
}
},
{ path: 'destS3Bucket', value: websiteBucket.bucketName },
{ path: 'destS3key', value: 'aws-exports.json' },
{ path: 'customAction', value: 'putConfigFile' }
],
dependencies: [cfnCustomResourceRole, cfnCustomResourcePolicy]
});
}