def get_lambda_associations( self) -> List[cloudfront.LambdaFunctionAssociation]: """Retrieve any lambda associations from the instance variables.""" # If custom associations defined, use them if self.variables["lambda_function_associations"]: return [ cloudfront.LambdaFunctionAssociation( EventType=x["type"], LambdaFunctionARN=x["arn"]) for x in self.variables["lambda_function_associations"] ] return []
def get_directory_index_lambda_association( # pylint: disable=no-self-use self, lambda_associations, # List[cloudfront.LambdaFunctionAssociation] directory_index_rewrite_version): # awslambda.Version # type: (...) -> List[cloudfront.LambdaFunctionAssociation] """Retrieve the directory index lambda associations with the added rewriter. Args: lambda_associations [List(Any)]: The lambda associations directory_index_rewrite_version [Any]: The directory index rewrite version """ lambda_associations.append( cloudfront.LambdaFunctionAssociation( EventType='origin-request', LambdaFunctionARN=directory_index_rewrite_version.ref())) return lambda_associations
def get_directory_index_lambda_association( lambda_associations: List[cloudfront.LambdaFunctionAssociation], directory_index_rewrite_version: awslambda.Version, ) -> List[cloudfront.LambdaFunctionAssociation]: """Retrieve the directory index lambda associations with the added rewriter. Args: lambda_associations: The lambda associations. directory_index_rewrite_version: The directory index rewrite version. """ lambda_associations.append( cloudfront.LambdaFunctionAssociation( EventType="origin-request", LambdaFunctionARN=directory_index_rewrite_version.ref(), )) return lambda_associations
def get_lambda_associations(self): # type: () -> List[cloudfront.LambdaFunctionAssociation] """Retrieve any lambda associations from the instance variables. Return: List of Lambda Function association variables """ variables = self.get_variables() # If custom associations defined, use them if variables["lambda_function_associations"]: return [ cloudfront.LambdaFunctionAssociation( EventType=x["type"], LambdaFunctionARN=x["arn"]) for x in variables["lambda_function_associations"] ] return []
def distribution(self) -> cloudfront.Distribution: """Return cloudfront distribution with bucket as origin.""" origin = cloudfront.Origin( S3OriginConfig=cloudfront.S3OriginConfig(OriginAccessIdentity=Join( "", [ "origin-access-identity/cloudfront/", Ref(self.origin_access_identity), ], )), DomainName=f"{self.bucket.name}.s3.amazonaws.com", Id="S3Origin", ) cache_params = { "AllowedMethods": ["GET", "HEAD", "OPTIONS"], "CachePolicyId": Ref(self.cache_policy), "TargetOriginId": "S3Origin", "ViewerProtocolPolicy": "redirect-to-https", } if self.lambda_edge_function_arns: cache_params["LambdaFunctionAssociations"] = [ cloudfront.LambdaFunctionAssociation( EventType="viewer-request", LambdaFunctionARN=lambda_arn) for lambda_arn in self.lambda_edge_function_arns ] default_cache_behavior = cloudfront.DefaultCacheBehavior( **cache_params) return cloudfront.Distribution( name_to_id(self.name), DistributionConfig=cloudfront.DistributionConfig( Aliases=self.aliases, DefaultRootObject=self.root_object, DefaultCacheBehavior=default_cache_behavior, Enabled="True", HttpVersion="http2", Origins=[origin], ViewerCertificate=cloudfront.ViewerCertificate( AcmCertificateArn=self.certificate_arn, SslSupportMethod="sni-only", MinimumProtocolVersion="TLSv1.2_2021", ), ), )
def get_lambda_associations(self): # type: () -> List[cloudfront.LambdaFunctionAssociation] """Retrieve any lambda associations from the instance variables. Keyword Args: directory_index_rewrite_version (dict): The directory index rewrite lambda version resource Return: array: Array of lambda function association variables """ variables = self.get_variables() # If custom associations defined, use them if variables['lambda_function_associations']: return [ cloudfront.LambdaFunctionAssociation( EventType=x['type'], LambdaFunctionARN=x['arn']) for x in variables['lambda_function_associations'] ] return []
def get_distribution_options(self, bucket, # type: s3.Bucket oai, # type: cloudfront.CloudFrontOriginAccessIdentity lambda_funcs, # type: List[cloudfront.LambdaFunctionAssociation] check_auth_lambda_version, # type: awslambda.Version http_headers_lambda_version, # type: awslambda.Version parse_auth_lambda_version, # type: awslambda.Version refresh_auth_lambda_version, # type: awslambda.Version sign_out_lambda_version # type: awslambda.Version ): # noqa: E124 # type: (...) -> Dict[str, Any] """Retrieve the options for our CloudFront distribution. Keyword Args: bucket (dict): The bucket resource oai (dict): The origin access identity resource Return: dict: The CloudFront Distribution Options """ variables = self.get_variables() default_cache_behavior_lambdas = lambda_funcs default_cache_behavior_lambdas.append( cloudfront.LambdaFunctionAssociation( EventType='viewer-request', LambdaFunctionARN=check_auth_lambda_version.ref() ) ) default_cache_behavior_lambdas.append( cloudfront.LambdaFunctionAssociation( EventType='origin-response', LambdaFunctionARN=http_headers_lambda_version.ref() ) ) return { 'Aliases': self.add_aliases(), 'Origins': [ cloudfront.Origin( DomainName=Join( '.', [bucket.ref(), 's3.amazonaws.com']), S3OriginConfig=cloudfront.S3OriginConfig( OriginAccessIdentity=Join( '', ['origin-access-identity/cloudfront/', oai.ref()]) ), Id='protected-bucket' ) ], 'CacheBehaviors': [ cloudfront.CacheBehavior( PathPattern=variables['RedirectPathSignIn'], Compress=True, ForwardedValues=cloudfront.ForwardedValues( QueryString=True ), LambdaFunctionAssociations=[ cloudfront.LambdaFunctionAssociation( EventType='viewer-request', LambdaFunctionARN=parse_auth_lambda_version.ref() ) ], TargetOriginId='protected-bucket', ViewerProtocolPolicy="redirect-to-https" ), cloudfront.CacheBehavior( PathPattern=variables['RedirectPathAuthRefresh'], Compress=True, ForwardedValues=cloudfront.ForwardedValues( QueryString=True ), LambdaFunctionAssociations=[ cloudfront.LambdaFunctionAssociation( EventType='viewer-request', LambdaFunctionARN=refresh_auth_lambda_version.ref() ) ], TargetOriginId='protected-bucket', ViewerProtocolPolicy="redirect-to-https" ), cloudfront.CacheBehavior( PathPattern=variables['SignOutUrl'], Compress=True, ForwardedValues=cloudfront.ForwardedValues( QueryString=True ), LambdaFunctionAssociations=[ cloudfront.LambdaFunctionAssociation( EventType='viewer-request', LambdaFunctionARN=sign_out_lambda_version.ref() ) ], TargetOriginId='protected-bucket', ViewerProtocolPolicy="redirect-to-https" ), ], 'DefaultCacheBehavior': cloudfront.DefaultCacheBehavior( AllowedMethods=['GET', 'HEAD'], Compress=True, DefaultTTL='86400', ForwardedValues=cloudfront.ForwardedValues( QueryString=True, ), LambdaFunctionAssociations=default_cache_behavior_lambdas, TargetOriginId='protected-bucket', ViewerProtocolPolicy='redirect-to-https' ), 'DefaultRootObject': 'index.html', 'Logging': self.add_logging_bucket(), 'PriceClass': variables['PriceClass'], 'Enabled': True, 'WebACLId': self.add_web_acl(), 'CustomErrorResponses': self._get_error_responses(), 'ViewerCertificate': self.add_acm_cert() }
def create_template(self): """Create template (main function called by Stacker).""" template = self.template variables = self.get_variables() template.set_version('2010-09-09') template.set_description('Static Website - Bucket and Distribution') # Conditions template.add_condition( 'AcmCertSpecified', And(Not(Equals(variables['AcmCertificateArn'].ref, '')), Not(Equals(variables['AcmCertificateArn'].ref, 'undefined'))) ) template.add_condition( 'AliasesSpecified', And(Not(Equals(Select(0, variables['Aliases'].ref), '')), Not(Equals(Select(0, variables['Aliases'].ref), 'undefined'))) ) template.add_condition( 'CFLoggingEnabled', And(Not(Equals(variables['LogBucketName'].ref, '')), Not(Equals(variables['LogBucketName'].ref, 'undefined'))) ) template.add_condition( 'DirectoryIndexSpecified', And(Not(Equals(variables['RewriteDirectoryIndex'].ref, '')), Not(Equals(variables['RewriteDirectoryIndex'].ref, 'undefined'))) # noqa ) template.add_condition( 'WAFNameSpecified', And(Not(Equals(variables['WAFWebACL'].ref, '')), Not(Equals(variables['WAFWebACL'].ref, 'undefined'))) ) # Resources oai = template.add_resource( cloudfront.CloudFrontOriginAccessIdentity( 'OAI', CloudFrontOriginAccessIdentityConfig=cloudfront.CloudFrontOriginAccessIdentityConfig( # noqa pylint: disable=line-too-long Comment='CF access to website' ) ) ) bucket = template.add_resource( s3.Bucket( 'Bucket', AccessControl=s3.Private, LifecycleConfiguration=s3.LifecycleConfiguration( Rules=[ s3.LifecycleRule( NoncurrentVersionExpirationInDays=90, Status='Enabled' ) ] ), VersioningConfiguration=s3.VersioningConfiguration( Status='Enabled' ), WebsiteConfiguration=s3.WebsiteConfiguration( IndexDocument='index.html', ErrorDocument='error.html' ) ) ) template.add_output(Output( 'BucketName', Description='Name of website bucket', Value=bucket.ref() )) allowcfaccess = template.add_resource( s3.BucketPolicy( 'AllowCFAccess', Bucket=bucket.ref(), PolicyDocument=PolicyDocument( Version='2012-10-17', Statement=[ Statement( Action=[awacs.s3.GetObject], Effect=Allow, Principal=Principal( 'CanonicalUser', oai.get_att('S3CanonicalUserId') ), Resource=[ Join('', [bucket.get_att('Arn'), '/*']) ] ) ] ) ) ) cfdirectoryindexrewriterole = template.add_resource( iam.Role( 'CFDirectoryIndexRewriteRole', Condition='DirectoryIndexSpecified', AssumeRolePolicyDocument=PolicyDocument( Version='2012-10-17', Statement=[ Statement( Effect=Allow, Action=[awacs.sts.AssumeRole], Principal=Principal('Service', ['lambda.amazonaws.com', 'edgelambda.amazonaws.com']) ) ] ), ManagedPolicyArns=[ IAM_ARN_PREFIX + 'AWSLambdaBasicExecutionRole' ] ) ) cfdirectoryindexrewrite = template.add_resource( awslambda.Function( 'CFDirectoryIndexRewrite', Condition='DirectoryIndexSpecified', Code=awslambda.Code( ZipFile=Join( '', ["'use strict';\n", "exports.handler = (event, context, callback) => {\n", "\n", " // Extract the request from the CloudFront event that is sent to Lambda@Edge\n", # noqa pylint: disable=line-too-long " var request = event.Records[0].cf.request;\n", " // Extract the URI from the request\n", " var olduri = request.uri;\n", " // Match any '/' that occurs at the end of a URI. Replace it with a default index\n", # noqa pylint: disable=line-too-long " var newuri = olduri.replace(/\\/$/, '\\/", variables['RewriteDirectoryIndex'].ref, "');\n", # noqa " // Log the URI as received by CloudFront and the new URI to be used to fetch from origin\n", # noqa pylint: disable=line-too-long " console.log(\"Old URI: \" + olduri);\n", " console.log(\"New URI: \" + newuri);\n", " // Replace the received URI with the URI that includes the index page\n", # noqa pylint: disable=line-too-long " request.uri = newuri;\n", " // Return to CloudFront\n", " return callback(null, request);\n", "\n", "};\n"] ) ), Description='Rewrites CF directory HTTP requests to default page', # noqa Handler='index.handler', Role=cfdirectoryindexrewriterole.get_att('Arn'), Runtime='nodejs8.10' ) ) # Generating a unique resource name here for the Lambda version, so it # updates automatically if the lambda code changes code_hash = hashlib.md5( str(cfdirectoryindexrewrite.properties['Code'].properties['ZipFile'].to_dict()).encode() # noqa pylint: disable=line-too-long ).hexdigest() cfdirectoryindexrewritever = template.add_resource( awslambda.Version( 'CFDirectoryIndexRewriteVer' + code_hash, Condition='DirectoryIndexSpecified', FunctionName=cfdirectoryindexrewrite.ref() ) ) # If custom associations defined, use them if variables['lambda_function_associations']: lambda_function_associations = [ cloudfront.LambdaFunctionAssociation( EventType=x['type'], LambdaFunctionARN=x['arn'] ) for x in variables['lambda_function_associations'] ] else: # otherwise fallback to pure CFN condition lambda_function_associations = If( 'DirectoryIndexSpecified', [cloudfront.LambdaFunctionAssociation( EventType='origin-request', LambdaFunctionARN=cfdirectoryindexrewritever.ref() )], NoValue ) cfdistribution = template.add_resource( get_cf_distribution_class()( 'CFDistribution', DependsOn=allowcfaccess.title, DistributionConfig=get_cf_distro_conf_class()( Aliases=If( 'AliasesSpecified', variables['Aliases'].ref, NoValue ), Origins=[ get_cf_origin_class()( DomainName=Join( '.', [bucket.ref(), 's3.amazonaws.com']), S3OriginConfig=get_s3_origin_conf_class()( OriginAccessIdentity=Join( '', ['origin-access-identity/cloudfront/', oai.ref()]) ), Id='S3Origin' ) ], DefaultCacheBehavior=cloudfront.DefaultCacheBehavior( AllowedMethods=['GET', 'HEAD'], Compress=False, DefaultTTL='86400', ForwardedValues=cloudfront.ForwardedValues( Cookies=cloudfront.Cookies(Forward='none'), QueryString=False, ), LambdaFunctionAssociations=lambda_function_associations, # noqa TargetOriginId='S3Origin', ViewerProtocolPolicy='redirect-to-https' ), DefaultRootObject='index.html', Logging=If( 'CFLoggingEnabled', cloudfront.Logging( Bucket=Join('.', [variables['LogBucketName'].ref, 's3.amazonaws.com']) ), NoValue ), PriceClass=variables['PriceClass'].ref, Enabled=True, WebACLId=If( 'WAFNameSpecified', variables['WAFWebACL'].ref, NoValue ), ViewerCertificate=If( 'AcmCertSpecified', cloudfront.ViewerCertificate( AcmCertificateArn=variables['AcmCertificateArn'].ref, # noqa SslSupportMethod='sni-only' ), NoValue ) ) ) ) template.add_output(Output( 'CFDistributionId', Description='CloudFront distribution ID', Value=cfdistribution.ref() )) template.add_output( Output( 'CFDistributionDomainName', Description='CloudFront distribution domain name', Value=cfdistribution.get_att('DomainName') ) )
), ], DefaultRootObject= "index.html", # Needed for this example only, adapt to your requirements CacheBehaviors=[ # If you have additional cache behaviours, # make sure that (at least) the behaviour matching # /auth-89CE3FEF-FCF6-43B3-9DBA-7C410CAAE220/set-cookie # has the Lambda-function associated. ], DefaultCacheBehavior=cloudfront.DefaultCacheBehavior( ViewerProtocolPolicy= 'redirect-to-https', # HTTPS required. Cookies need to be sent securely LambdaFunctionAssociations=[ cloudfront.LambdaFunctionAssociation( EventType='viewer-request', LambdaFunctionARN=Ref(param_authorizer_lae_arn)), ], # Rest of config as per your needs TargetOriginId='ExampleS3', ForwardedValues=cloudfront.ForwardedValues( QueryString=True, Cookies=cloudfront.Cookies( Forward= 'all', # Don't do this. Done here to validate cookie-removal logic ), ), ), ViewerCertificate=cloudfront.ViewerCertificate( AcmCertificateArn=Ref(acm_cert), SslSupportMethod='sni-only',
def get_distribution_options( self, bucket: s3.Bucket, oai: cloudfront.CloudFrontOriginAccessIdentity, lambda_funcs: List[cloudfront.LambdaFunctionAssociation], check_auth_lambda_version: awslambda.Version, http_headers_lambda_version: awslambda.Version, parse_auth_lambda_version: awslambda.Version, refresh_auth_lambda_version: awslambda.Version, sign_out_lambda_version: awslambda.Version, ) -> Dict[str, Any]: """Retrieve the options for our CloudFront distribution. Keyword Args: bucket: The bucket resource. oai: The origin access identity resource. lambda_funcs: List of Lambda Function associations. check_auth_lambda_version: Lambda Function Version to use. http_headers_lambda_version: Lambda Function Version to use. parse_auth_lambda_version: Lambda Function Version to use. refresh_auth_lambda_version: Lambda Function Version to use. sign_out_lambda_version: Lambda Function Version to use. Return: The CloudFront Distribution Options. """ default_cache_behavior_lambdas = lambda_funcs default_cache_behavior_lambdas.append( cloudfront.LambdaFunctionAssociation( EventType="viewer-request", LambdaFunctionARN=check_auth_lambda_version.ref(), )) default_cache_behavior_lambdas.append( cloudfront.LambdaFunctionAssociation( EventType="origin-response", LambdaFunctionARN=http_headers_lambda_version.ref(), )) return { "Aliases": self.add_aliases(), "Origins": [ cloudfront.Origin( DomainName=Join(".", [bucket.ref(), "s3.amazonaws.com"]), S3OriginConfig=cloudfront. S3OriginConfig(OriginAccessIdentity=Join( "", ["origin-access-identity/cloudfront/", oai.ref()])), Id="protected-bucket", ) ], "CacheBehaviors": [ cloudfront.CacheBehavior( PathPattern=self.variables["RedirectPathSignIn"], Compress=True, ForwardedValues=cloudfront.ForwardedValues( QueryString=True), LambdaFunctionAssociations=[ cloudfront.LambdaFunctionAssociation( EventType="viewer-request", LambdaFunctionARN=parse_auth_lambda_version.ref(), ) ], TargetOriginId="protected-bucket", ViewerProtocolPolicy="redirect-to-https", ), cloudfront.CacheBehavior( PathPattern=self.variables["RedirectPathAuthRefresh"], Compress=True, ForwardedValues=cloudfront.ForwardedValues( QueryString=True), LambdaFunctionAssociations=[ cloudfront.LambdaFunctionAssociation( EventType="viewer-request", LambdaFunctionARN=refresh_auth_lambda_version.ref( ), ) ], TargetOriginId="protected-bucket", ViewerProtocolPolicy="redirect-to-https", ), cloudfront.CacheBehavior( PathPattern=self.variables["SignOutUrl"], Compress=True, ForwardedValues=cloudfront.ForwardedValues( QueryString=True), LambdaFunctionAssociations=[ cloudfront.LambdaFunctionAssociation( EventType="viewer-request", LambdaFunctionARN=sign_out_lambda_version.ref(), ) ], TargetOriginId="protected-bucket", ViewerProtocolPolicy="redirect-to-https", ), ], "DefaultCacheBehavior": cloudfront.DefaultCacheBehavior( AllowedMethods=["GET", "HEAD"], Compress=True, DefaultTTL="86400", ForwardedValues=cloudfront.ForwardedValues(QueryString=True), LambdaFunctionAssociations=default_cache_behavior_lambdas, TargetOriginId="protected-bucket", ViewerProtocolPolicy="redirect-to-https", ), "DefaultRootObject": "index.html", "Logging": self.add_logging_bucket(), "PriceClass": self.variables["PriceClass"], "Enabled": True, "WebACLId": self.add_web_acl(), "CustomErrorResponses": self._get_error_responses(), "ViewerCertificate": self.add_acm_cert(), }
def __init__(self, title, key, **kwargs): super().__init__(title, **kwargs) name = self.title if 'AllowedMethods' in key: self.AllowedMethods = get_endvalue(f'{name}AllowedMethods') if 'CachedMethods' in key: self.CachedMethods = get_endvalue(f'{name}CachedMethods') # If not defined default to True if 'Compress' in key: self.Compress = get_endvalue(f'{name}Compress') else: self.Compress = True if 'DefaultTTL' in key: self.DefaultTTL = get_endvalue(f'{name}DefaultTTL') self.ForwardedValues = clf.ForwardedValues() # If not defined default to True if 'QueryString' in key: self.ForwardedValues.QueryString = get_endvalue( f'{name}QueryString') else: self.ForwardedValues.QueryString = True if 'CookiesForward' in key: self.ForwardedValues.Cookies = clf.Cookies( Forward=get_endvalue(f'{name}CookiesForward')) if 'CookiesWhitelistedNames' in key: self.ForwardedValues.Cookies.WhitelistedNames = (get_endvalue( f'{name}CookiesWhitelistedNames', condition=True)) # conditions c_CookiesForward = get_condition( f'{name}CookiesWhitelistedNames', 'equals', 'whitelist', f'{name}CookiesForward') add_obj(c_CookiesForward) # If not defined default to 'Host' if 'Headers' in key: self.ForwardedValues.Headers = get_endvalue(f'{name}Headers') else: self.ForwardedValues.Headers = ['Host'] if 'QueryStringCacheKeys' in key: self.ForwardedValues.QueryStringCacheKeys = get_endvalue( f'{name}QueryStringCacheKeys', condition=True) # conditions c_QueryString = get_condition(f'{name}QueryStringCacheKeys', 'equals', True, f'{name}QueryString') add_obj(c_QueryString) if 'TargetOriginId' in key: self.TargetOriginId = get_endvalue(f'{name}TargetOriginId') else: self.TargetOriginId = If( 'CloudFrontOriginAdHoc', Ref('RecordSetExternal'), Sub('${EnvRole}${RecordSetCloudFrontSuffix}.origin.%s' % cfg.HostedZoneNameEnv)) if 'MaxTTL' in key: self.MaxTTL = get_endvalue(f'{name}MaxTTL') if 'MinTTL' in key: self.MinTTL = get_endvalue(f'{name}MinTTL') if 'LambdaFunctionARN' in key: condname = f'{name}LambdaFunctionARN' eventType = 'origin-request' if 'LambdaEventType' in key: eventType = key['LambdaEventType'] # conditions add_obj(get_condition(condname, 'not_equals', 'None')) self.LambdaFunctionAssociations = [ If( condname, clf.LambdaFunctionAssociation( EventType=eventType, LambdaFunctionARN=get_endvalue(condname)), Ref('AWS::NoValue')) ] if 'ViewerProtocolPolicy' in key: self.ViewerProtocolPolicy = get_endvalue( f'{name}ViewerProtocolPolicy') else: self.ViewerProtocolPolicy = 'redirect-to-https'