Example #1
0
    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()))
Example #2
0
    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()))
Example #3
0
    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()))
Example #4
0
    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()))
Example #5
0
    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))
Example #6
0
    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')
            )
        )