def create_function(self): t = self.template variables = self.get_variables() self.function = t.add_resource( awslambda.Function( "Function", Code=self.code(), DeadLetterConfig=self.dead_letter_config(), Description=variables["Description"] or NoValue, Environment=self.environment(), Handler=variables["Handler"], KmsKeyArn=variables["KmsKeyArn"] or NoValue, MemorySize=variables["MemorySize"], Role=self.role_arn, Runtime=variables["Runtime"], Timeout=variables["Timeout"], VpcConfig=self.vpc_config(), ) ) t.add_output( Output("FunctionName", Value=self.function.Ref()) ) t.add_output( Output("FunctionArn", Value=self.function.GetAtt("Arn")) ) self.function_version = t.add_resource( awslambda.Version( "LatestVersion", FunctionName=self.function.Ref() ) ) t.add_output( Output("LatestVersion", Value=self.function_version.GetAtt("Version")) ) t.add_output( Output("LatestVersionArn", Value=self.function_version.Ref()) ) alias_name = variables["AliasName"] if alias_name: self.alias = t.add_resource( awslambda.Alias( "Alias", Name=alias_name, FunctionName=self.function.Ref(), FunctionVersion=variables["AliasVersion"] or "$LATEST", ) ) t.add_output(Output("AliasArn", Value=self.alias.Ref()))
def add_version(self, title: str, lambda_function: awslambda.Function) -> awslambda.Version: """Create a version association with a Lambda@Edge function. In order to ensure different versions of the function are appropriately uploaded a hash based on the code of the lambda is appended to the name. As the code changes so will this hash value. Args: title: The name of the function in PascalCase. lambda_function: The Lambda function. """ s3_key = lambda_function.properties["Code"].to_dict()["S3Key"] code_hash = s3_key.split(".")[0].split("-")[-1] return self.template.add_resource( awslambda.Version(title + "Ver" + code_hash, FunctionName=lambda_function.ref()))
def add_cloudfront_directory_index_rewrite_version( self, directory_index_rewrite): # type: (awslambda.Function) -> awslambda.Version """Add a specific version to the directory index rewrite lambda. Keyword Args: directory_index_rewrite (dict): The directory index rewrite lambda resource Return: dict: The CloudFront directory index rewrite version """ code_hash = hashlib.md5( str(directory_index_rewrite.properties['Code']. properties['ZipFile']).encode() # noqa pylint: disable=line-too-long ).hexdigest() return self.template.add_resource( awslambda.Version('CFDirectoryIndexRewriteVer' + code_hash, FunctionName=directory_index_rewrite.ref()))
def add_version( self, title, # type: str lambda_function # type: awslambda.Function ): # noqa E214 # type: (...) -> awslambda.Version """Create a version association with a Lambda@Edge function. In order to ensure different versions of the function are appropriately uploaded a hash based on the code of the lambda is appended to the name. As the code changes so will this hash value. Args: title (str): The name of the function in PascalCase role (awslambda.Function): The Lambda function """ s3_key = lambda_function.properties['Code'].to_dict()['S3Key'] code_hash = s3_key.split('.')[0].split('-')[-1] return self.template.add_resource( awslambda.Version(title + 'Ver' + code_hash, FunctionName=lambda_function.ref()))
def add_lambda_function(self): role = self.add_resource( iam.Role( 'TerraformRegistryLambdaRole', AssumeRolePolicyDocument=PolicyDocument( Version='2012-10-17', Statement=[ Statement(Effect=Allow, Action=[Action('sts', 'AssumeRole')], Principal=Principal('Service', 'lambda.amazonaws.com')) ]), ManagedPolicyArns=[ 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole' ], Policies=[ iam.Policy( PolicyName='Registry', PolicyDocument=PolicyDocument(Statement=[ Statement(Effect=Allow, Action=[Action('s3', '*')], Resource=[ GetAtt(self._bucket, 'Arn'), Join('', [ GetAtt(self._bucket, 'Arn'), '/*' ]) ]), Statement(Effect=Allow, Action=[Action('dynamodb', '*')], Resource=[ GetAtt(self._api_token_table, 'Arn') ]) ])) ])) lambda_function = self.add_resource( awslambda.Function( 'TerraformRegistry', Runtime='python3.7', Code=awslambda.Code(S3Bucket=LAMBDA_PACKAGE_BUCKET, S3Key=f'{self._build_version}/lambda.zip'), Handler='registry.handler', Timeout=300, Role=GetAtt(role, 'Arn'), Description=Sub('${AWS::StackName} Terraform Registry'), Environment=awslambda.Environment( Variables={ 'TerraformModules': Ref(self._bucket), 'ApiTokens': Ref(self._api_token_table) }))) aws_sha256, hex_sha256 = sha256('build/lambda.zip') version_name = 'TerraformRegistryVersion' + hex_sha256 self._lambda_function = self.add_resource( awslambda.Version(version_name, CodeSha256=aws_sha256, Description=hex_sha256, FunctionName=Ref(lambda_function), DependsOn=[lambda_function], DeletionPolicy=Retain))
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') ) )