Package: accustom
CloudFormation AccustomCloudFormation Accustom is a library for responding to Custom Resources in AWS CloudFormation using the decorator
pattern.This library provides a cfnresponse method, some helper static classes, and some decorator methods to help with the
function.InstallationCloudFormation Accustom can be found under PyPI athttps://pypi.python.org/pypi/accustom.To install:python3-mpipinstallaccustomTo create a Lambda Code Bundle in Zip Format with CloudFormation Accustom and dependencies (includingrequests),
create a directory with only your code in it and run the following. Alternatively you can create a Lambda Layer with
CloudFormation Accustom and dependencies installed and use that as your base layer for custom resources.python3-mpipinstallaccustom-t.
zipcode.zip*-rQuickstartThe quickest way to use this library is to use the standalone decorator@accustom.sdecorator, in a Lambda function.importaccustom@accustom.sdecorator(expectedProperties=['key1','key2'])defresource_handler(event,context):result=(float(event['ResourceProperties']['key1'])+float(event['ResourceProperties']['key2']))return{'sum':result}In this configuration, the decorator will check to make sure the propertieskey1andkey2have been passed by the
user, and automatically send a response back to CloudFormation based upon theeventobject.As you can see, this greatly simplifies the developer effort required to get a working custom resource that will
correctly respond to CloudFormation Custom Resource Requests.The Decorator PatternsThe most important part of this library are the Decorator patterns. These provide Python decorators that can be put
around handler functions, or resource specific functions, that prepare the data for ease of usage. These decorators will
also handle exceptions for you.@accustom.decorator()This is the primary decorator in the library. The purpose of this decorator is to take the return value of the handler
function, and respond back to CloudFormation based upon the inputeventautomatically.It takes the following options:enforceUseOfClass(Boolean) : When this is set toTrue, you must use aResponseObject. This is implicitly set
to true if no Lambda Context is provided.hideResourceDeleteFailure(Boolean) : When this is set toTruethe function will returnSUCCESSeven on getting
an Exception forDeleterequests.redactConfig(accustom.RedactionConfig) : For more details on how this works please see "Redacting Confidential
Information From Logs"timeoutFunction(Boolean): Will automatically send a failure signal to CloudFormation before Lambda timeout
provided that this function is executed in Lambda.Without aResponseObjectthe decorator will make the following assumptions:if a Lambda Context is not passed, the function will returnFAILEDif a dictionary is passed back, this will be used for the Data to be returned to CloudFormation and the function will
returnSUCCESS.if a string is passed back, this will be put in the return attributeReturnand the function will returnSUCCESS.ifNoneorTrueis passed back, the function will returnSUCCESSifFalseis passed back, the function will returnFAILED@accustom.rdecorator()This decorator, known as the "Resource Decorator" is used when you break the function into different resources, e.g.
by making a decision based upon whichResourceTypewas passed to the handler and calling a function related to that
resource.It takes the following option:decoratorHandleDelete(Boolean) : When set toTrue, if aDeleterequest is made ineventthe decorator will
return aResponseObjectwith a withSUCCESSwithout actually executing the decorated function.genUUID(Boolean) : When set toTrue, if thePhysicalResourceIdin theeventis not set, automatically
generate a UUID4 and put it in thePhysicalResoruceIdfield.expectedProperties(Array or Tuple) : Pass in a list or tuple of properties that you want to check for before
running the decorated function. If any are missing, returnFAILED.The most useful of these options isexpectedProperties. With it is possible to quickly define mandatory properties
for your resource and fail if they are not included.@accustom.sdecorator()This decorator is just a combination of@accustom.decorator()and@accustom.rdecorator(). This allows you to have a
single, stand-alone resource handler that has some defined properties and can automatically handle delete. The options
available to it is the combination of both of the options available to the other two Decorators, except forredactPropertieswhich takes an accustom.StandaloneRedactionConfig object instead of an accustom.RedactionConfig
object. For more information onredactPropertiessee "Redacting Confidential Information From Logs".The other important note about combining these two decorators is thathideResourceDeleteFailurebecomes redundant ifdecoratorHandleDeleteis set toTrue.Response Function and ObjectThecfnresponse()function and theResponseObjectare convenience function for interacting with CloudFormation.cfnresponse()cfnresponse()is a traditional function. At the very minimum it needs to take in theeventand a status,SUCCESSorFAILED. In practice this function will likely not be used very often outside the library, but it is included for
completeness. For more details look directly at the source code for this function.ResponseObjectTheResponseObjectallows you to define a message to be sent to CloudFormation. It only has one method,send(),
which uses thecfnresponse()function under the hood to fire the event. A response object can be initialised and
fired with:importaccustomdefhandler(event,context):r=accustom.ResponseObject()r.send(event)If you are using the decorator pattern it is strongly recommended that you do not invoke thesend()method, and
instead allow the decorator to process the sending of the events for you by returning from your function.To construct a response object you can provide the following optional parameters:data(Dictionary) : data to be passed in the response. Must be a dict if usedphysicalResourceId(String) : Physical resource ID to be used in the responsereason(String) : Reason to pass back to CloudFormation in the response ObjectresponseStatus(accustom.Status): response Status to use in the response Object, defaults toSUCCESSsquashPrintResponse(Boolean) : InDEBUGlogging the function will often print out theDatasection of the
response. If theDatacontains confidential information you'll want to squash this output. This option, when set toTrue, will squash the output.Logging RecommendationsThe decorators utilise thelogginglibrary for logging. It is strongly
recommended that your function does the same, and sets the logging level to at leastINFO. Ensure the log level is setbeforeimporting Accustom.importlogginglogger=logging.getLogger(__name__)logging.getLogger().setLevel(logging.INFO)importaccustomRedacting Confidential Information FromDEBUGLogsIf you often pass confidential information like passwords and secrets in properties to Custom Resources, you may want to
prevent certain properties from being printed to debug logs. To help with this we provide a functionality to either
blocklist or allowlist Resource Properties based upon provided regular expressions.To utilise this functionality you must initialise and include aRedactionConfig. ARedactionConfigconsists of some
flags to define the redaction mode and if the response URL should be redacted, as well as a series ofRedactionRuleSetobjects that define what to redact based upon regular expressions. There is a special case ofRedactionConfigcalled aStandaloneRedactionConfigthat has one, and only one,RedactionRuleSetthat is provided at initialisation.EachRedactionRuleSetdefines a single regex that defines which ResourceTypes this rule set should be applied too. You
can then apply any number of rules, based upon an explicit property name, or a regex. Please see the definitions, and an
example below.RedactionRuleSetTheRedactionRuleSetobject allows you to define a series of properties or regexes which to allowlist or blocklist for
a given resource type regex. It is initialised with the following:resourceRegex(String) : The regex used to work out what resources to apply this too.add_property_regex(propertiesRegex)propertiesRegex(String) : The regex used to work out what properties to allowlist/blocklistadd_property(propertyName)propertyName(String) : The name of the property to allowlist/blocklistRedactionConfigTheRedactionConfigobject allows you to create a collection ofRedactionRuleSetobjects as well as define what mode
(allowlist/blocklist) to use, and if the presigned URL provided by CloudFormation should be redacted from the logs.redactMode(accustom.RedactMode) : What redaction mode should be used, if it should be a blocklist or allowlistredactResponseURL(Boolean) : If the response URL should be not be logged.add_rule_set(ruleSet)ruleSet(accustom.RedactionRuleSet) : The rule set to be added to the RedactionConfigStandaloneRedactionConfigTheStandaloneRedactionConfigobject allows you to apply a singleRedactionRuleSetobject as well as define what
mode (allowlist/blocklist) to use, and if the presigned URL provided by CloudFormation should be redacted from the logs.redactMode(accustom.RedactMode) : What redaction mode should be used, if it should be a blocklist or allowlistredactResponseURL(Boolean) : If the response URL should be not be logged.ruleSet(accustom.RedactionRuleSet) : The rule set to be added to the RedactionConfigExample of RedactionThe below example takes in two rule sets. The first ruleset applies to all resources types, and the second ruleset
applies only to theCustom::Testresource type.All resources will have properties calledTestandExampleredacted and replaced with[REDATED]. TheCustom::Testresource will also additionally redact properties calledCustomand those thatstart withDeleteMe.Finally, asredactResponseURLis set toTrue, the response URL will not be printed in the debug logs.fromaccustomimportRedactionRuleSet,RedactionConfig,decoratorruleSetDefault=RedactionRuleSet()ruleSetDefault.add_property_regex('^Test$')ruleSetDefault.add_property('Example')ruleSetCustom=RedactionRuleSet('^Custom::Test$')ruleSetCustom.add_property('Custom')ruleSetCustom.add_property_regex('^DeleteMe.*$')rc=RedactionConfig(redactResponseURL=True)rc.add_rule_set(ruleSetDefault)rc.add_rule_set(ruleSetCustom)@decorator(redactConfig=rc)defresource_handler(event,context):result=(float(event['ResourceProperties']['Test'])+float(event['ResourceProperties']['Example']))return{'sum':result}Note on Timeouts and PermissionsThe timeout is implemented using asynchronous chained invocationof your Lambda function. For this reason, please be
aware of the following limitations:The function must have access to the Lambda API Endpoints in order to self invoke.The function must have permission to self invoke (i.e. lambda:InvokeFunction permission).If your requirements violate any of these conditions, set thetimeoutFunctionoption toFalse. Please also note that
this willdoublethe invocations per request, so if you're not in the free tier for Lambda make sure you are aware of
this as it may increase costs.ConstantsWe provide three constants for ease of use:Static value : how to accessStatusSUCCESS:accustom.Status.SUCCESSFAILED:accustom.Status.FAILEDRequestTypeCreate:accustom.RequestType.CREATEUpdate:accustom.RequestType.UPDATEDelete:accustom.RequestType.DELETERedactModeBlocklisting :accustom.RedactMode.BLOCKLISTAllowlisting :accustom.RedactMode.ALLOWLISTHow to ContributeFeel free to open issues, fork, or submit a pull request:Issue Tracker:https://github.com/awslabs/cloudformation-accustom/issuesSource Code:https://github.com/awslabs/cloudformation-accustom