Metadata-Version: 2.1
Name: CfnCustomCFrontDistro
Version: 0.0.1
Summary: AWS cloudformation CloudFront Distributions with OriginGroups support
Home-page: https://github.com/karmafeast/CfnCustomCFrontDistro
Author: karmafeast
Author-email: karmafeast@gmail.com
License: UNKNOWN
Description: # Custom::CFrontDistro
        
        ## USE
        
        Use this custom resource as if a regular AWS::CloudFront::Distribution, with additional syntax available for the configuration and use of origin groups (see below section)
        
        Native AWS::CloudFront::Distribution documentation implies (correctly in this case) that an origin group can be used in cache behaviors / default cache behavior as the target origin.
        
        While this is true in this custom resource, there is no ability to create an origin group in the native resource (10/2019).
        Regardless of the documentation state at AWS - you can do as it says with this resource.
        
        ## Method of Operation
        
        A cloudformation custom resource for CloudFront distributions is lifecycle managed by an associated lambda function in this instance.
        
        This function must exist for the custom resource to be able to be created, and its ARN must be referenced in the custom resource declaration in its 'ServiceToken' property
        
        ```JSON
        {
        ...
            {"MyDistro":
              "Type": "Custom::CFrontDistro",
              {"Properties":
                {
                  "ServiceToken":"ARN_TO_FUNCTION",
                  "DistributionConfig": {"keys": "values"}
                }
              }
            }
        }
        ```
        
        ## New things in the distribution config
        
        You may specify origin groups in a form that should appear familiar to those using cloudformation.
        
        
        ### OriginGroups list (of dict items) --
        A complex type that contains information about origin groups for this distribution.
        
        ### OriginGroups list item(dict) --
        An origin group includes two origins (a primary origin and a second origin to failover to) and a failover criteria that you specify. 
        
        NOTE - the FIRST ITEM in the list of 'Members' will be the primary origin of the pair.
        
        You create an origin group to support origin failover in CloudFront. 
        
        When you create or update a distribution, you can specify the origin group instead of a single origin, and CloudFront will failover 
        from the primary origin to the second origin under the failover conditions that you've chosen.
        
        ### Id (string) -- [REQUIRED]
        The origin group's ID.  This is a string, and one that should match cache behavior entries where this origin group is referenced
        
        ### FailoverCriteria (dict) -- [REQUIRED]
        A complex type that contains information about the failover criteria for an origin group.
        
        ### StatusCodes (list) -- [REQUIRED]
        The status codes that, when returned from the primary origin, will trigger CloudFront to failover to the second origin.
        The StatusCodes list is the FailoverCriteria dict (see examples).  
        these are integer numbers for the 400/500 HTTPStatus codes that invoke failover in the origin group.
        
        ### Members (list) -- [REQUIRED]
        A complex type that contains information about the origins in an origin group.
        
        NOTE - the FIRST ITEM in the list of 'Members' will be the primary origin of the pair.
        
        NOTE - the 'ID' of the member origin group should be used.  These should be specified elsewhere in the distribution config as normal.
        
        ~~~YAML
        AWSTemplateFormatVersion: '2010-09-09'
        Transform: AWS::Serverless-2016-10-31
        Description: Custom Resource for CloudFront Distribution, supporting origin groups
        Resources:
          CustomCFrontDistro:
            Type: Custom::CFrontDistro
            Properties:
              ServiceToken:
                Fn::ImportValue: CustomCFrontDistributionFunctionARN
              DistributionConfig:
        #...
                OriginGroups:
                  - Id: testyorigingroup
                    FailoverCriteria:
                      StatusCodes:
                      - 403
                      - 404
                      - 500
                      - 502
                      - 503
                      - 504
                    Members:
                      - my_fake_origin_1
                      - my_fake_origin_2
                    CustomErrorResponses:
                    - ErrorCode: 403
                      ResponsePagePath: "/some/path/to/here"
                      ResponseCode: 200
                      ErrorCachingMinTTL: 999
        #... rest of distribution config
        ~~~
        
        ~~~JSON
        {
           "OriginGroups":[
              {
                 "Id":"testyorigingroup",
                 "FailoverCriteria":{
                    "StatusCodes":[
                       403,
                       404,
                       500,
                       502,
                       503,
                       504
                    ]
                 },
                 "Members":[
                    "my_fake_origin_1",
                    "my_fake_origin_2"
                 ]
              }
           ],
           "CustomErrorResponses":[
              {
                 "ErrorCode":403,
                 "ResponsePagePath":"/some/path/to/here",
                 "ResponseCode":200,
                 "ErrorCachingMinTTL":999
              }
           ]
        }
        ~~~
        
        ## How to Deploy the Custom Resource Lambda
        
        Your ```Custom::CFrontDistro``` containing template must have a 'ServiceToken' property, which is the ARN to the lambda function which manages
        the custom resource lifecycle.
        
        There are two ways you could achieve this.
        
        ### 1. deploy the function as a separate stack, export its ARN in the template. Import that exported ARN value into template where resource is used.
        
        This method is preferred if you wish to have a single cloudwatch log stream to examine for the resource lambda actions in a given account, rather than
        the second method, which will deploy a lambda function independently in each template where it is used.
        
        You will get better debug using this method - as if there is failure in a template where the function itself is a resource, it will be purged along with
        the rest of the stack if creation fails.
        
        ### 2. deploy the function and the resource together in a single template - elect not to reuse the resource lambda
        
        More portable in some regards, in that you don't end up referencing a cfn export to locate the ARN of the custom resource function
        
        Fragmented logging, overdeployment of lambda functions (per template vs shared per account).
        
        Tighter permission control is possible if you deploy with the resource to manage a sinlge distro, in that you can restrict things like s3 bucket acl get/put to target distro
        access logging bucket.
        
        ### Notes on IAM Permissions for Custom Resource Function
        
        Shown in Examples section below.
        
        Remember, the resource creations are occurring with the lambda execution context - NOT whatever is running the CloudFormation template.
        
        To this end, the lambda execution will require full ability to manage CloudFront resources.
        
        Similarly it will require access to CloudWatch to configure events for its own execution, and for options that might be specified in a resource.
        
        The role will also require ability to list all s3 buckets, and to get/set acls on buckets - to support configuration of resources that elect to include
        logging.
        
        To support polling for long operations in cloudformation associated with cloudfront distros (a create or update can take many minutes to return),
        the lambda function does not attempt to wait for readieness of a managed resource before returning.
        
        Instead it sets up polling of itself and the lambda function handler routes events based on their type from cloudformation (e.g. poll_update).
        There is a return format which allows cloudformation to poll for a resource's change to desired state 'SUCCESS' following a configuration change.
        
        ## Examples
        
        ### Lambda Function for Custom Resource
        
        ~~~YAML
        ---
        AWSTemplateFormatVersion: '2010-09-09'
        Transform: AWS::Serverless-2016-10-31
        Description: Custom Resource for CloudFront Distribution, supporting origin groups
        Resources:
          CustomCFrontDistributionFunction:
            Type: AWS::Serverless::Function
            Properties:
              CodeUri: customCFrontDistro/
              Handler: customCFrontDistro.lambda_handler
              Runtime: python3.7
              Role:
                Fn::GetAtt:
                - IARCustomCFrontDistribution
                - Arn
              Timeout: 300
          IARCustomCFrontDistribution:
            Type: AWS::IAM::Role
            Properties:
              ManagedPolicyArns:
              - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
              - arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess
              - arn:aws:iam::aws:policy/CloudFrontFullAccess
              - arn:aws:iam::aws:policy/CloudWatchEventsFullAccess
              AssumeRolePolicyDocument:
                Version: '2012-10-17'
                Statement:
                - Action:
                  - sts:AssumeRole
                  Effect: Allow
                  Principal:
                    Service:
                    - lambda.amazonaws.com
          IAMPS3Policy:
            Type: AWS::IAM::ManagedPolicy
            Properties:
              PolicyDocument:
                Version: '2012-10-17'
                Statement:
                - Effect: Allow
                  Action:
                  - s3:ListAllMyBuckets
                  - s3:GetBucketAcl
                  - s3:PutBucketAcl
                  Resource: "*"
              Roles:
              - Ref: IARCustomCFrontDistribution
          IAMPLambdaPerms:
            Type: AWS::IAM::ManagedPolicy
            Properties:
              PolicyDocument:
                Version: '2012-10-17'
                Statement:
                - Effect: Allow
                  Action:
                  - lambda:AddPermission
                  - lambda:RemovePermission
                  - events:PutRule
                  - events:DeleteRule
                  - events:PutTargets
                  - events:RemoveTargets
                  Resource:
                    Fn::GetAtt:
                    - CustomCFrontDistributionFunction
                    - Arn
              Roles:
              - Ref: IARCustomCFrontDistribution
        Outputs:
          CustomCFrontDistributionFunctionARN:
            Description: Custom CFront Distribution Lambda Function ARN
            Value:
              Fn::GetAtt:
              - CustomCFrontDistributionFunction
              - Arn
            Export:
              Name: CustomCFrontDistributionFunctionARN
        
        ~~~
        
        ~~~JSON
        {
           "AWSTemplateFormatVersion":"2010-09-09",
           "Transform":"AWS::Serverless-2016-10-31",
           "Description":"Custom Resource for CloudFront Distribution, supporting origin groups",
           "Resources":{
              "CustomCFrontDistributionFunction":{
                 "Type":"AWS::Serverless::Function",
                 "Properties":{
                    "CodeUri":"customCFrontDistro/",
                    "Handler":"customCFrontDistro.lambda_handler",
                    "Runtime":"python3.7",
                    "Role":{
                       "Fn::GetAtt":[
                          "IARCustomCFrontDistribution",
                          "Arn"
                       ]
                    },
                    "Timeout":300
                 }
              },
              "IARCustomCFrontDistribution":{
                 "Type":"AWS::IAM::Role",
                 "Properties":{
                    "ManagedPolicyArns":[
                       "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
                       "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess",
                       "arn:aws:iam::aws:policy/CloudFrontFullAccess",
                       "arn:aws:iam::aws:policy/CloudWatchEventsFullAccess"
                    ],
                    "AssumeRolePolicyDocument":{
                       "Version":"2012-10-17",
                       "Statement":[
                          {
                             "Action":[
                                "sts:AssumeRole"
                             ],
                             "Effect":"Allow",
                             "Principal":{
                                "Service":[
                                   "lambda.amazonaws.com"
                                ]
                             }
                          }
                       ]
                    }
                 }
              },
              "IAMPS3Policy":{
                 "Type":"AWS::IAM::ManagedPolicy",
                 "Properties":{
                    "PolicyDocument":{
                       "Version":"2012-10-17",
                       "Statement":[
                          {
                             "Effect":"Allow",
                             "Action":[
                                "s3:ListAllMyBuckets",
                                "s3:GetBucketAcl",
                                "s3:PutBucketAcl"
                             ],
                             "Resource":"*"
                          }
                       ]
                    },
                    "Roles":[
                       {
                          "Ref":"IARCustomCFrontDistribution"
                       }
                    ]
                 }
              },
              "IAMPLambdaPerms":{
                 "Type":"AWS::IAM::ManagedPolicy",
                 "Properties":{
                    "PolicyDocument":{
                       "Version":"2012-10-17",
                       "Statement":[
                          {
                             "Effect":"Allow",
                             "Action":[
                                "lambda:AddPermission",
                                "lambda:RemovePermission",
                                "events:PutRule",
                                "events:DeleteRule",
                                "events:PutTargets",
                                "events:RemoveTargets"
                             ],
                             "Resource":{
                                "Fn::GetAtt":[
                                   "CustomCFrontDistributionFunction",
                                   "Arn"
                                ]
                             }
                          }
                       ]
                    },
                    "Roles":[
                       {
                          "Ref":"IARCustomCFrontDistribution"
                       }
                    ]
                 }
              }
           },
           "Outputs":{
              "CustomCFrontDistributionFunctionARN":{
                 "Description":"Custom CFront Distribution Lambda Function ARN",
                 "Value":{
                    "Fn::GetAtt":[
                       "CustomCFrontDistributionFunction",
                       "Arn"
                    ]
                 },
                 "Export":{
                    "Name":"CustomCFrontDistributionFunctionARN"
                 }
              }
           }
        }
        ~~~
        
        
        ### Resource use example
        
        ~~~YAML
        ---
        AWSTemplateFormatVersion: '2010-09-09'
        Transform: AWS::Serverless-2016-10-31
        Description: Custom Resource for CloudFront Distribution, supporting origin groups
        Resources:
          CustomCFrontDistro:
            Type: Custom::CFrontDistro
            Properties:
              ServiceToken:
                Fn::ImportValue: CustomCFrontDistributionFunctionARN
              DistributionConfig:
                Comment: i am not real, only testy!
                Logging:
                  Bucket: fake_name_thing.s3.amazonaws.com
                Enabled: true
                Restrictions:
                  GeoRestriction:
                    RestrictionType: none
                    Quantity: 0
                ViewerCertificate:
                  CloudFrontDefaultCertificate: true
                CacheBehaviors:
                - AllowedMethods:
                  - GET
                  - HEAD
                  CachedMethods:
                  - GET
                  - HEAD
                  Compress: false
                  ForwardedValues:
                    QueryString: false
                  PathPattern: "*.hugwolf"
                  TargetOriginId: testyorigingroup
                  ViewerProtocolPolicy: https-only
                - Compress: false
                  DefaultTTL: 600
                  ForwardedValues:
                    QueryString: false
                  PathPattern: "*.fake_name_thing"
                  TargetOriginId: testyorigingroup
                  ViewerProtocolPolicy: https-only
                DefaultCacheBehavior:
                  TargetOriginId: my_fake_origin_1
                  ViewerProtocolPolicy: https-only
                  ForwardedValues:
                    QueryString: false
                HttpVersion: http2
                Origins:
                - Id: my_fake_origin_1
                  DomainName: my_fake_origin_1.s3.amazonaws.com
                  S3OriginConfig:
                    OriginAccessIdentity: origin-access-identity/cloudfront/E_FAKE_ORIGIN_ACCESS_ID_1
                - Id: my_fake_origin_2
                  DomainName: my_fake_origin_2.s3.amazonaws.com
                  S3OriginConfig:
                    OriginAccessIdentity: origin-access-identity/cloudfront/E_FAKE_ORIGIN_ACCESS_ID_2
                OriginGroups:
                - Id: testyorigingroup
                  FailoverCriteria:
                    StatusCodes:
                    - 403
                    - 404
                    - 500
                    - 502
                    - 503
                    - 504
                  Members:
                  - my_fake_origin_1
                  - my_fake_origin_2
                CustomErrorResponses:
                - ErrorCode: 403
                  ResponsePagePath: "/some/path/to/here"
                  ResponseCode: 200
                  ErrorCachingMinTTL: 999
        Outputs:
          customCFrontDistroDomainName:
            Description: cloudfront distribution domain name
            Value:
              Fn::GetAtt:
              - CustomCFrontDistro
              - DomainName
          customCFrontDistroARN:
            Description: cloudfront distribution ARN
            Value:
              Fn::GetAtt:
              - CustomCFrontDistro
              - ARN
        
        ~~~
        
        ~~~JSON
           "Resources":{
              "CustomCFrontDistro":{
                 "Type":"Custom::CFrontDistro",
                 "Properties":{
                    "ServiceToken": {"Fn::ImportValue": "CustomCFrontDistributionFunctionARN"},
                    "DistributionConfig":{
                       "Comment":"i am not real, only testy!",
                       "Logging":{
                          "Bucket":"fake_name_thing.s3.amazonaws.com"
                       },
                       "Enabled":true,
                       "Restrictions":{
                          "GeoRestriction":{
                             "RestrictionType":"none",
                             "Quantity":0
                          }
                       },
                       "ViewerCertificate":{
                          "CloudFrontDefaultCertificate":true
                       },
                       "CacheBehaviors":[
                          {
                             "AllowedMethods":[
                                "GET",
                                "HEAD"
                             ],
                             "CachedMethods":[
                                "GET",
                                "HEAD"
                             ],
                             "Compress":false,
                             "ForwardedValues":{
                                "QueryString":false
                             },
                             "PathPattern":"*.hugwolf",
                             "TargetOriginId":"testyorigingroup",
                             "ViewerProtocolPolicy":"https-only"
                          },
                          {
                             "Compress":false,
                             "DefaultTTL":600,
                             "ForwardedValues":{
                                "QueryString":false
                             },
                             "PathPattern":"*.fake_name_thing",
                             "TargetOriginId":"testyorigingroup",
                             "ViewerProtocolPolicy":"https-only"
                          }
                       ],
                       "DefaultCacheBehavior":{
                          "TargetOriginId":"my_fake_origin_1",
                          "ViewerProtocolPolicy":"https-only",
                          "ForwardedValues":{
                             "QueryString":false
                          }
                       },
                       "HttpVersion":"http2",
                       "Origins":[
                          {
                             "Id":"my_fake_origin_1",
                             "DomainName":"my_fake_origin_1.s3.amazonaws.com",
                             "S3OriginConfig":{
                                "OriginAccessIdentity":"origin-access-identity/cloudfront/E_FAKE_ORIGIN_ACCESS_ID_1"
                             }
                          },
                          {
                             "Id":"my_fake_origin_2",
                             "DomainName":"my_fake_origin_2.s3.amazonaws.com",
                             "S3OriginConfig":{
                                "OriginAccessIdentity":"origin-access-identity/cloudfront/E_FAKE_ORIGIN_ACCESS_ID_2"
                             }
                          }
                       ],
                       "OriginGroups":[
                          {
                             "Id":"testyorigingroup",
                             "FailoverCriteria":{
                                "StatusCodes":[
                                   403,
                                   404,
                                   500,
                                   502,
                                   503,
                                   504
                                ]
                             },
                             "Members":[
                                "my_fake_origin_1",
                                "my_fake_origin_2"
                             ]
                          }
                       ],
                       "CustomErrorResponses":[
                          {
                             "ErrorCode":403,
                             "ResponsePagePath":"/some/path/to/here",
                             "ResponseCode":200,
                             "ErrorCachingMinTTL":999
                          }
                       ]
                    }
                 }
              }
           },
           "Outputs": {
             "customCFrontDistroDomainName": {
               "Description": "cloudfront distribution domain name",
               "Value": {
                 "Fn::GetAtt": [
                   "CustomCFrontDistro",
                   "DomainName"
                 ]
               }
             },
             "customCFrontDistroARN": {
               "Description": "cloudfront distribution ARN",
               "Value": {
                 "Fn::GetAtt": [
                   "CustomCFrontDistro",
                   "ARN"
                 ]
               }
             }
           }
        ~~~
        
        ## ref
        [cfn: AWS::CloudFront::Distribution](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudfront-distribution.html "cfn: AWS::CloudFront::Distribution")
        
        [boto3 docs for CFront](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/cloudfront.html#CloudFront.Client.create_distribution "this is cfn data is translated into and from")
        
Platform: UNKNOWN
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Requires-Python: >=3.7
Description-Content-Type: text/markdown
