|
| 1 | +from troposphere import ( |
| 2 | + AWS_REGION, |
| 3 | + Equals, |
| 4 | + GetAtt, |
| 5 | + If, |
| 6 | + Join, |
| 7 | + Not, |
| 8 | + Output, |
| 9 | + Parameter, |
| 10 | + Ref, |
| 11 | + iam |
| 12 | +) |
| 13 | +from troposphere.cloudfront import ( |
| 14 | + CacheCookiesConfig, |
| 15 | + CacheHeadersConfig, |
| 16 | + CachePolicy, |
| 17 | + CachePolicyConfig, |
| 18 | + CacheQueryStringsConfig, |
| 19 | + CustomOriginConfig, |
| 20 | + DefaultCacheBehavior, |
| 21 | + Distribution, |
| 22 | + DistributionConfig, |
| 23 | + Origin, |
| 24 | + ParametersInCacheKeyAndForwardedToOrigin, |
| 25 | + ViewerCertificate |
| 26 | +) |
| 27 | + |
| 28 | +from .certificates import application as app_certificate |
| 29 | +from .domain import all_domains_list |
| 30 | +from .template import template |
| 31 | + |
| 32 | +origin_domain_name = Ref( |
| 33 | + template.add_parameter( |
| 34 | + Parameter( |
| 35 | + "AppCloudFrontOriginDomainName", |
| 36 | + Description="Domain name of the origin server", |
| 37 | + Type="String", |
| 38 | + Default="", |
| 39 | + ), |
| 40 | + group="Application Server", |
| 41 | + label="CloudFront Origin Domain Name", |
| 42 | + ) |
| 43 | +) |
| 44 | + |
| 45 | +instance_role = Ref( |
| 46 | + template.add_parameter( |
| 47 | + Parameter( |
| 48 | + "AppCloudFrontRoleArn", |
| 49 | + Description="ARN of the role to add IAM permissions for invalidating this distribution", |
| 50 | + Type="String", |
| 51 | + Default="", |
| 52 | + ), |
| 53 | + group="Application Server", |
| 54 | + label="CloudFront Role ARN", |
| 55 | + ) |
| 56 | +) |
| 57 | + |
| 58 | +origin_request_policy_id = Ref( |
| 59 | + template.add_parameter( |
| 60 | + Parameter( |
| 61 | + "AppCloudFrontOriginRequestPolicyId", |
| 62 | + Description="The unique identifier of the origin request policy to attach to the app cache behavior", |
| 63 | + Type="String", |
| 64 | + # https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-managed-origin-request-policies.html#managed-origin-request-policy-all-viewer |
| 65 | + # Recommended for custom origins |
| 66 | + Default="216adef6-5c7f-47e4-b989-5492eafa07d3", |
| 67 | + ), |
| 68 | + group="Application Server", |
| 69 | + label="Origin Request Policy ID", |
| 70 | + ) |
| 71 | +) |
| 72 | + |
| 73 | +app_protocol_policy = template.add_parameter( |
| 74 | + Parameter( |
| 75 | + "AppCloudFrontProtocolPolicy", |
| 76 | + Description="The protocols allowed by the application server's CloudFront distribution. See: " |
| 77 | + "http://docs.aws.amazon.com/cloudfront/latest/APIReference/API_DefaultCacheBehavior.html", |
| 78 | + Type="String", |
| 79 | + AllowedValues=["redirect-to-https", "https-only", "allow-all"], |
| 80 | + Default="redirect-to-https", |
| 81 | + ), |
| 82 | + group="Application Server", |
| 83 | + label="CloudFront Protocol Policy", |
| 84 | +) |
| 85 | + |
| 86 | +app_forwarded_headers = template.add_parameter( |
| 87 | + Parameter( |
| 88 | + "AppCloudFrontForwardedHeaders", |
| 89 | + Description=( |
| 90 | + "The CachePolicy headers that will be forwarded to the origin and used in the cache key. " |
| 91 | + "The 'Host' header is required for SSL on an Elastic Load Balancer, but it " |
| 92 | + "should NOT be passed to a Lambda Function URL." |
| 93 | + ), |
| 94 | + Type="CommaDelimitedList", |
| 95 | + Default="", |
| 96 | + ), |
| 97 | + group="Application Server", |
| 98 | + label="CloudFront Forwarded Headers", |
| 99 | +) |
| 100 | +app_forwarded_headers_condition = "AppCloudFrontForwardedHeadersCondition" |
| 101 | +template.add_condition( |
| 102 | + app_forwarded_headers_condition, |
| 103 | + Not(Equals(Join("", Ref(app_forwarded_headers)), "")), |
| 104 | +) |
| 105 | + |
| 106 | +# Currently, you can specify only certificates that are in the US East (N. Virginia) region. |
| 107 | +# http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distributionconfig-viewercertificate.html |
| 108 | +us_east_1_condition = "UsEast1Condition" |
| 109 | +template.add_condition( |
| 110 | + us_east_1_condition, |
| 111 | + Equals(Ref(AWS_REGION), "us-east-1"), |
| 112 | +) |
| 113 | + |
| 114 | +app_certificate_arn = template.add_parameter( |
| 115 | + Parameter( |
| 116 | + "AppCloudFrontCertArn", |
| 117 | + Description="If your stack is NOT in the us-east-1 you must manually create an ACM certificate for " |
| 118 | + "your application domain in the us-east-1 region and provide its ARN here.", |
| 119 | + Type="String", |
| 120 | + ), |
| 121 | + group="Application Server", |
| 122 | + label="CloudFront SSL Certificate ARN", |
| 123 | +) |
| 124 | +app_certificate_arn_condition = "AppCloudFrontCertArnCondition" |
| 125 | +template.add_condition( |
| 126 | + app_certificate_arn_condition, Not(Equals(Ref(app_certificate_arn), "")) |
| 127 | +) |
| 128 | + |
| 129 | +cache_policy = template.add_resource( |
| 130 | + CachePolicy( |
| 131 | + "AppCloudFrontCachePolicy", |
| 132 | + CachePolicyConfig=CachePolicyConfig( |
| 133 | + Name="AppCachePolicy", |
| 134 | + DefaultTTL=86400, # 1 day |
| 135 | + MaxTTL=31536000, # 1 year |
| 136 | + MinTTL=0, |
| 137 | + ParametersInCacheKeyAndForwardedToOrigin=ParametersInCacheKeyAndForwardedToOrigin( |
| 138 | + CookiesConfig=CacheCookiesConfig( |
| 139 | + CookieBehavior="none", |
| 140 | + ), |
| 141 | + EnableAcceptEncodingGzip=True, |
| 142 | + EnableAcceptEncodingBrotli=True, |
| 143 | + HeadersConfig=If( |
| 144 | + app_forwarded_headers_condition, |
| 145 | + CacheHeadersConfig( |
| 146 | + # Determines whether any HTTP headers are included in the |
| 147 | + # cache key and in requests that CloudFront sends to the |
| 148 | + # origin |
| 149 | + # * whitelist: Only the HTTP headers that are listed in the |
| 150 | + # Headers type are included in the cache key and in |
| 151 | + # requests that CloudFront sends to the origin. |
| 152 | + HeaderBehavior="whitelist", |
| 153 | + Headers=Ref(app_forwarded_headers), |
| 154 | + ), |
| 155 | + CacheHeadersConfig( |
| 156 | + HeaderBehavior="none", |
| 157 | + ), |
| 158 | + ), |
| 159 | + QueryStringsConfig=CacheQueryStringsConfig( |
| 160 | + # Determines whether any URL query strings in viewer |
| 161 | + # requests are included in the cache key and in requests |
| 162 | + # that CloudFront sends to the origin |
| 163 | + QueryStringBehavior="all", |
| 164 | + ), |
| 165 | + ), |
| 166 | + ), |
| 167 | + ) |
| 168 | +) |
| 169 | + |
| 170 | +# Create a CloudFront CDN distribution |
| 171 | +app_distribution = template.add_resource( |
| 172 | + Distribution( |
| 173 | + "AppCloudFrontDistribution", |
| 174 | + DistributionConfig=DistributionConfig( |
| 175 | + Aliases=all_domains_list, |
| 176 | + HttpVersion="http2", |
| 177 | + # If we're in us-east-1, use the application certificate tied to the load balancer, otherwise, |
| 178 | + # use the manually-created cert |
| 179 | + ViewerCertificate=If( |
| 180 | + us_east_1_condition, |
| 181 | + ViewerCertificate( |
| 182 | + AcmCertificateArn=app_certificate, |
| 183 | + SslSupportMethod="sni-only", |
| 184 | + # Default/recommended on the AWS console, as of May, 2023 |
| 185 | + MinimumProtocolVersion="TLSv1.2_2021", |
| 186 | + ), |
| 187 | + If( |
| 188 | + app_certificate_arn_condition, |
| 189 | + ViewerCertificate( |
| 190 | + AcmCertificateArn=Ref(app_certificate_arn), |
| 191 | + SslSupportMethod="sni-only", |
| 192 | + MinimumProtocolVersion="TLSv1.2_2021", |
| 193 | + ), |
| 194 | + Ref("AWS::NoValue"), |
| 195 | + ), |
| 196 | + ), |
| 197 | + Origins=[ |
| 198 | + Origin( |
| 199 | + Id="ApplicationServer", |
| 200 | + DomainName=origin_domain_name, |
| 201 | + CustomOriginConfig=CustomOriginConfig( |
| 202 | + OriginProtocolPolicy="https-only", |
| 203 | + ), |
| 204 | + ) |
| 205 | + ], |
| 206 | + DefaultCacheBehavior=DefaultCacheBehavior( |
| 207 | + TargetOriginId="ApplicationServer", |
| 208 | + Compress="true", |
| 209 | + AllowedMethods=[ |
| 210 | + "DELETE", |
| 211 | + "GET", |
| 212 | + "HEAD", |
| 213 | + "OPTIONS", |
| 214 | + "PATCH", |
| 215 | + "POST", |
| 216 | + "PUT", |
| 217 | + ], |
| 218 | + CachePolicyId=Ref(cache_policy), |
| 219 | + CachedMethods=["HEAD", "GET"], |
| 220 | + OriginRequestPolicyId=origin_request_policy_id, |
| 221 | + ViewerProtocolPolicy=Ref(app_protocol_policy), |
| 222 | + ), |
| 223 | + Enabled=True, |
| 224 | + ), |
| 225 | + ) |
| 226 | +) |
| 227 | + |
| 228 | +invalidation_policy = template.add_resource( |
| 229 | + iam.PolicyType( |
| 230 | + "AppCloudFrontInvalidationPolicy", |
| 231 | + PolicyName="AppCloudFrontInvalidationPolicy", |
| 232 | + PolicyDocument=dict( |
| 233 | + Statement=[ |
| 234 | + dict( |
| 235 | + Effect="Allow", |
| 236 | + Action=[ |
| 237 | + "cloudfront:GetDistribution", |
| 238 | + "cloudfront:GetDistributionConfig", |
| 239 | + "cloudfront:ListDistributions", |
| 240 | + "cloudfront:ListCloudFrontOriginAccessIdentities", |
| 241 | + "cloudfront:CreateInvalidation", |
| 242 | + "cloudfront:GetInvalidation", |
| 243 | + "cloudfront:ListInvalidations", |
| 244 | + ], |
| 245 | + Resource="*", |
| 246 | + # TODO: if/when CloudFront supports resource-level IAM permissions, enable them, e.g.: |
| 247 | + # Resource=Join("", [arn_prefix, ":cloudfront:::distribution/", Ref(app_distribution)]), |
| 248 | + # See: https://stackoverflow.com/a/29563986/166053 |
| 249 | + ), |
| 250 | + ], |
| 251 | + ), |
| 252 | + Roles=[instance_role], |
| 253 | + ) |
| 254 | +) |
| 255 | + |
| 256 | +# Output CloudFront url |
| 257 | +template.add_output( |
| 258 | + Output( |
| 259 | + "AppCloudFrontDomainName", |
| 260 | + Description="The app CDN domain name", |
| 261 | + Value=GetAtt(app_distribution, "DomainName"), |
| 262 | + ) |
| 263 | +) |
0 commit comments