-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSite.scala
More file actions
227 lines (207 loc) · 9.26 KB
/
Copy pathSite.scala
File metadata and controls
227 lines (207 loc) · 9.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
import Common.awsUsEast1Provider
import typings.pulumiAws.bucketMod.BucketArgs
import typings.pulumiAws.bucketPolicyMod.BucketPolicyArgs
import typings.pulumiAws.bucketPublicAccessBlockMod.BucketPublicAccessBlockArgs
import typings.pulumiAws.cannedAclMod.CannedAcl
import typings.pulumiAws.certificateMod.CertificateArgs
import typings.pulumiAws.certificateValidationMod.CertificateValidationArgs
import typings.pulumiAws.distributionMod.DistributionArgs
import typings.pulumiAws.documentsMod.{AWSPrincipal, PolicyDocument, PolicyStatement}
import typings.pulumiAws.getZoneMod.GetZoneArgs
import typings.pulumiAws.inputMod.cloudfront._
import typings.pulumiAws.inputMod.route53.RecordAlias
import typings.pulumiAws.inputMod.s3.{BucketServerSideEncryptionConfiguration, BucketServerSideEncryptionConfigurationRule, BucketServerSideEncryptionConfigurationRuleApplyServerSideEncryptionByDefault, BucketWebsite}
import typings.pulumiAws.mod.route53.Record
import typings.pulumiAws.originAccessIdentityMod.OriginAccessIdentityArgs
import typings.pulumiAws.recordMod.RecordArgs
import typings.pulumiAws.recordTypeMod.RecordType
import typings.pulumiAws.{pulumiAwsStrings, mod => aws}
import typings.pulumiPulumi.invokeMod.InvokeOptions
import typings.pulumiPulumi.outputMod.Input
import typings.pulumiPulumi.{mod => pulumi}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration.DurationInt
import scala.scalajs.js
import scala.scalajs.js.JSConverters._
import scala.scalajs.js.|
object Site {
// Load the Pulumi program configuration. These act as the "parameters" to the Pulumi program,
// so that different Pulumi Stacks can be brought up using the same code.
val stackConfig = new pulumi.Config("static-website")
// domainName is the domain/host to serve content at.
val domainName = stackConfig.require[String]("domainName")
// // contentBucket is the S3 bucket that the website's contents will be stored in.
val contentBucket = new aws.s3.Bucket("contentBucket",
BucketArgs()
.setBucket(domainName)
.setAcl(CannedAcl.`private`)
.setServerSideEncryptionConfiguration(
BucketServerSideEncryptionConfiguration(
BucketServerSideEncryptionConfigurationRule(
BucketServerSideEncryptionConfigurationRuleApplyServerSideEncryptionByDefault("AES256")
)
)
)
// Configure S3 to serve bucket contents as a website. This way S3 will automatically convert
// requests for "foo/" to "foo/index.html".
.setWebsite(
BucketWebsite()
.setIndexDocument("index.html")
.setErrorDocument("404.html")
)
)
val bucketPublicAccessBlock = new aws.s3.BucketPublicAccessBlock("contentBucketPublicAccessBlock",
BucketPublicAccessBlockArgs(
bucket = contentBucket.id
)
.setBlockPublicAcls(true)
.setBlockPublicPolicy(true)
.setIgnorePublicAcls(true)
.setRestrictPublicBuckets(true)
)
val certificate = new aws.acm.Certificate(s"$domainName-certificate",
CertificateArgs()
.setDomainName(domainName)
.setSubjectAlternativeNamesVarargs(s"www.$domainName")
.setValidationMethod("DNS"),
awsUsEast1Provider
)
val topLevelDomain = domainName.split('.').takeRight(2).mkString(".")
val hostedZoneId = aws.route53.getZone(
GetZoneArgs()
.setName(topLevelDomain),
InvokeOptions()
.setAsync(true)
).toFuture.map(_.zoneId).toJSPromise
val tenMinutes = 10.minutes.toSeconds.toDouble
/**
* Create a DNS record to prove that we _own_ the domain we're requesting a certificate for.
* See https://docs.aws.amazon.com/acm/latest/userguide/gs-acm-validate-dns.html for more info.
*/
val certificateValidationDomains: Input[js.Array[Input[String]]] = certificate.domainValidationOptions.apply[js.Array[Input[String]]](_.map { domainValidationOption =>
val record = new Record(s"${domainValidationOption.domainName}-validation",
RecordArgs(
domainValidationOption.resourceRecordName,
domainValidationOption.resourceRecordType,
hostedZoneId
)
.setRecordsVarargs(
domainValidationOption.resourceRecordValue,
)
.setTtl(tenMinutes)
)
record.fqdn
}.asInstanceOf[js.Array[Input[String]]])
val originAccessIdentity = new aws.cloudfront.OriginAccessIdentity(
"cloudFrontOriginAccessIdentity",
OriginAccessIdentityArgs()
.setComment(contentBucket.bucket)
)
val policyStatement: js.Array[Input[PolicyStatement]] = js.Array(
PolicyStatement(pulumiAwsStrings.Allow)
.setActionVarargs("s3:GetObject")
.setResourceVarargs(contentBucket.arn[String](arn => arn + "/*"))
.setPrincipal(
AWSPrincipal(js.Array(originAccessIdentity.iamArn).asInstanceOf[Input[js.Array[Input[String]] | String]])
)
)
val readPolicy = new aws.s3.BucketPolicy("readPolicy",
BucketPolicyArgs(contentBucket.id,
PolicyDocument(
policyStatement,
pulumiAwsStrings.`2012-10-17`
)
)
)
/**
* This is a _special_ resource that waits for ACM to complete validation via the DNS record
* checking for a status of "ISSUED" on the certificate itself. No actual resources are
* created (or updated or deleted).
*
* See https://www.terraform.io/docs/providers/aws/r/acm_certificate_validation.html for slightly more detail
* and https://github.com/terraform-providers/terraform-provider-aws/blob/master/aws/resource_aws_acm_certificate_validation.go
* for the actual implementation.
*/
val certificateValidation = new aws.acm.CertificateValidation("certificateValidation",
CertificateValidationArgs(certificate.arn)
.setValidationRecordFqdns(certificateValidationDomains),
awsUsEast1Provider
)
val methods: js.Array[Input[String]] = js.Array("GET", "HEAD")
val origin: js.Array[Input[DistributionOrigin]] = js.Array(DistributionOrigin(contentBucket.bucketDomainName, s"s3-origin-$domainName")
.setS3OriginConfig(
DistributionOriginS3OriginConfig(originAccessIdentity.cloudfrontAccessIdentityPath)
)
)
// distributionArgs configures the CloudFront distribution. Relevant documentation:
// https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/distribution-web-values-specify.html
// https://www.terraform.io/docs/providers/aws/r/cloudfront_distribution.html
val distributionArgs = DistributionArgs(
// A CloudFront distribution can configure different cache behaviors based on the request path.
// Here we just specify a single, default cache behavior which is just read-only requests to S3.
defaultCacheBehavior = DistributionDefaultCacheBehavior(
methods,
methods,
DistributionDefaultCacheBehaviorForwardedValues(
DistributionDefaultCacheBehaviorForwardedValuesCookies("none"),
false
).setHeadersVarargs("Origin"),
s"s3-origin-$domainName",
"redirect-to-https"
)
.setCompress(true)
.setMaxTtl(365.days.toSeconds.toDouble)
.setMinTtl(30.days.toSeconds.toDouble)
.setDefaultTtl(30.days.toSeconds.toDouble)
.setLambdaFunctionAssociationsVarargs(
DistributionDefaultCacheBehaviorLambdaFunctionAssociation(
"origin-request",
PathRewriter.pathRewriterArn
)
)
,
enabled = true,
// We only specify one origin for this distribution, the S3 content bucket.
origins = origin,
restrictions = DistributionRestrictions(DistributionRestrictionsGeoRestriction("none")),
viewerCertificate = DistributionViewerCertificate()
.setAcmCertificateArn(certificate.arn) // Per AWS, ACM certificate must be in the us-east-1 region.
.setSslSupportMethod("sni-only")
)
// Alternate aliases the CloudFront distribution can be reached at, in addition to https://xxxx.cloudfront.net.
// Required if you want to access the distribution via config.targetDomain as well.
.setAliasesVarargs(domainName, s"www.$domainName")
.setHttpVersion("http2")
// You can customize error responses. When CloudFront receives an error from the origin (e.g. S3 or some other
// web service) it can return a different error code, and return the response for a different resource.
.setCustomErrorResponsesVarargs(
DistributionCustomErrorResponse(403)
.setResponseCode(200)
.setResponsePagePath("/404.html")
)
// "All" is the most broad distribution, and also the most expensive.
// "100" is the least broad, and also the least expensive.
.setPriceClass("PriceClass_All")
val distribution = new aws.cloudfront.Distribution("cdn", distributionArgs)
// val babber = PathRewriter.pathRewriterArn
// Creates a new Route53 DNS record pointing the domain to the CloudFront distribution.
val route = cloudfrontAliasRecord(domainName, distribution)
val wwwRoute = cloudfrontAliasRecord(s"www.$domainName", distribution)
private def cloudfrontAliasRecord(domain: String, distribution: aws.cloudfront.Distribution) = {
new Record(
domain,
RecordArgs(
name = domain,
`type` = RecordType.A,
zoneId = hostedZoneId
).setAliasesVarargs(
RecordAlias(
evaluateTargetHealth = true,
name = distribution.domainName,
//ZoneId is always 'Z2FDTNDATAQYW2' for CloudFront
zoneId = distribution.hostedZoneId
)
)
)
}
}