troposphere icon indicating copy to clipboard operation
troposphere copied to clipboard

AWS FMS: SecurityServicePolicyData rendering entirely as JSON

Open ev3rl0ng opened this issue 5 years ago • 2 comments

By AWS CloudFormation documentation, the YAML representation of the SecurityServicePolicyData has two child nodes... Type and ManagedServiceData. They are represented in YAML as follows:

PolicyWAFv2:
    Type: AWS::FMS::Policy
    Properties:
      ExcludeResourceTags: false
      PolicyName: Policy
      RemediationEnabled: false 
      ResourceType: AWS::ElasticLoadBalancingV2::LoadBalancer 
      SecurityServicePolicyData: 
        Type: WAFV2
        ManagedServiceData: !Sub '{"type":"WAFV2", 
                                  "preProcessRuleGroups":[{ 
                                  "ruleGroupType":"RuleGroup",
                                  "ruleGroupArn":"${RuleGroup.Arn}",
                                  "overrideAction":{"type":"NONE"}}],
                                  "postProcessRuleGroups":[],
                                  "defaultAction":{"type":"BLOCK"}}' 

The ManagedServiceData property is the only item of the node that is supposed to be JSON based, but in the FMS::Policy troposphere object, the entire thing is coded/output as JSON, which results in outputs like this:

  BaseProtectionsIntPolicy:
    Properties:
      DeleteAllPolicyResources: true
      ExcludeResourceTags: false
      IncludeMap:
        ACCOUNT:
          - '123456789012'
        ORGUNIT: []
      PolicyName: Base-Protections-Int-Policy
      RemediationEnabled: false
      ResourceType: ResourceTypeList
      ResourceTypeList:
        - AWS::ElasticLoadBalancingV2::LoadBalancer
        - AWS::ApiGateway::Stage
      SecurityServicePolicyData: >-
        {"type": "WAFV2", "ManagedServiceData": "{\"type\": \"WAFV2\", \"preProcessRuleGroups\":
        [{\"ruleGroupArn\": null, \"overrideAction\": {\"type\": \"COUNT\"}, \"managedRuleGroupIdentifier\":
        {\"version\": null, \"vendorName\": \"AWS\", \"managedRuleGroupName\": \"AWSManagedRulesCommonRuleSet\"},
        \"ruleGroupType\": \"ManagedRuleGroup\"}, {\"ruleGroupArn\": null, \"overrideAction\":
        {\"type\": \"COUNT\"}, \"managedRuleGroupIdentifier\": {\"version\": null,
        \"vendorName\": \"AWS\", \"managedRuleGroupName\": \"AWSManagedRulesAmazonIpReputationList\"},
        \"ruleGroupType\": \"ManagedRuleGroup\"}, {\"ruleGroupArn\": \"${BaseIntRuleGroup.Arn}\",
        \"overrideAction\": {\"type\": \"NONE\"}, \"ruleGroupType\": \"RuleGroup\"}],
        \"postProcessRuleGroups\": [], \"defaultAction\": {\"type\": \"ALLOW\"}}"}
    Type: AWS::FMS::Policy

Furthermore, attempting to add a troposphere Sub() object around the ManagedServiceData string results in an error that the Sub() object is not JSON Serializable.

I propose that an additional helper object be defined in FMS.py that allows further breakout of these items and moves the json_checker validation to the ManagedServiceData definition under the new object.

I'm curious to know if anyone has this working as is.

Thanks!

ev3rl0ng avatar Nov 17 '20 19:11 ev3rl0ng

Not sure how to tie json_checker validation into the ManagedServiceData property and still allow Sub() usage. Open for input on that.

ev3rl0ng avatar Nov 17 '20 22:11 ev3rl0ng

The attached pull request results in the format covered in the AWS Documentation:

  BaseProtectionsIntPolicy:
    Properties:
      DeleteAllPolicyResources: true
      ExcludeResourceTags: false
      IncludeMap:
        ACCOUNT:
          - '123456789012'
        ORGUNIT: []
      PolicyName: Base-Protections-Int-Policy
      RemediationEnabled: false
      ResourceType: ResourceTypeList
      ResourceTypeList:
        - AWS::ElasticLoadBalancingV2::LoadBalancer
        - AWS::ApiGateway::Stage
      SecurityServicePolicyData:
        ManagedServiceData: !Sub '{"type": "WAFV2", "preProcessRuleGroups": [{"ruleGroupArn":
          null, "overrideAction": {"type": "COUNT"}, "managedRuleGroupIdentifier":
          {"version": null, "vendorName": "AWS", "managedRuleGroupName": "AWSManagedRulesCommonRuleSet"},
          "ruleGroupType": "ManagedRuleGroup"}, {"ruleGroupArn": null, "overrideAction":
          {"type": "COUNT"}, "managedRuleGroupIdentifier": {"version": null, "vendorName":
          "AWS", "managedRuleGroupName": "AWSManagedRulesAmazonIpReputationList"},
          "ruleGroupType": "ManagedRuleGroup"}, {"ruleGroupArn": "${BaseIntRuleGroup.Arn}",
          "overrideAction": {"type": "NONE"}, "ruleGroupType": "RuleGroup"}], "postProcessRuleGroups":
          [], "defaultAction": {"type": "ALLOW"}}'
        Type: WAFV2
    Type: AWS::FMS::Policy

When used as follows:

from troposphere import fms, Sub

# ... redacted ...

policy_obj = fms.Policy(
    title="BaseProtectionsIntPolicy",
    PolicyName="Base-Protections-Int-Policy",
    DeleteAllPolicyResources=True,
    ExcludeResourceTags=False,
    RemediationEnabled=False,
    ResourceType="ResourceTypeList",
    ResourceTypeList=["AWS::ElasticLoadBalancingV2::LoadBalancer", "AWS::ApiGateway::Stage"],
    IncludeMap=fms.IEMap(
        ACCOUNT=[ "123456789012" ],
        ORGUNIT=[]
    ),
    SecurityServicePolicyData=fms.SecurityServicePolicyData(
        Type="WAFV2",
        ManagedServiceData=Sub(generated_managed_service_data)
    )
)

ev3rl0ng avatar Nov 18 '20 13:11 ev3rl0ng