Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use cdk metadata to selectively disable a control #202

Open
Jacco opened this issue Oct 21, 2023 · 3 comments
Open

Use cdk metadata to selectively disable a control #202

Jacco opened this issue Oct 21, 2023 · 3 comments

Comments

@Jacco
Copy link

Jacco commented Oct 21, 2023

For making fine graned exceptions:

I saw the usage in the AWS Guard Rules Registry

They just add a condition to the resource selectors:

#
# Select all API GW Method resources from incoming template (payload)
#
let api_gw_resources_type_check = Resources.*[ Type == 'AWS::ApiGateway::DomainName'
  Metadata.guard.SuppressedRules not exists or
  Metadata.guard.SuppressedRules.* != "API_GW_ENDPOINT_TYPE_CHECK"
]

In cloudformation the exception looks like this:

"Resources": {
  "ElasticsearchDomain": {
    "Type": "AWS::Elasticsearch::Domain",
    "Metadata": {
      "guard": {
        "SuppressedRules": ["ELASTICSEARCH_ENCRYPTED_AT_REST"]
      }
    },
    "Properties": {
      "DomainName": "test"
    }
  }
}

In CDK code something like this:

function addExceptionToResource(resource: IConstruct, rulename: string) {
          const resource = resource as CfnResource;
          let metadata = resource.getMetadata('guard') || {};
          metadata['SuppressedRules'] |= []
          metadata['SuppressedRules'].push(rulename)
          resource.addMetadata('guard', metadata);
}

BTW: I have created some python/nodejs scripts that can automatically pull all the data needed for cdk-validator-cfnguard. The sources it uses to match the detailed metadata are: blackbeard service used by controltower console, two javascript files uses by the controltower console containing metadata, the aws documentation website (to get the config rule). I am already parsing the .guard resources to extract the RemediationMessage from the guard file. It will be very easy to add the metadata condition to the guard files while extracting the assets.

!! If you are interested in these scripts let me know. !!

@Jacco
Copy link
Author

Jacco commented Oct 21, 2023

Here is the python code I used to scrape the undocumented BlackBeard service. I hoped it contained all the necessary data but unfortunately more datasources were needed.

This script does not yet place the metadata/guard files in separate directories. That is done in another step that involves javascript from the console page using nodejs. The servicenames are in there that I use to determine the directory names.

I'd be happy to convert this script to TypeScript if you want :-)

import json
import requests
import boto3
from botocore.auth import SigV4Auth
from botocore.awsrequest import AWSRequest
import inspect
import re

def snake_to_camel(snake):
    if '_' in snake:
        return ''.join([part.capitalize() for part in snake.split('_')])
    else:
        return snake

class BlackbeardSession:
    def __init__(self, boto_session):
        self._sigv4 = SigV4Auth(boto_session.get_credentials(), 'controltower', 'eu-west-1')
        self._endpoint = 'https://prod.eu-west-1.blackbeard.aws.a2z.com'

    def do_request(self, method, **kwargs):
        args = { snake_to_camel(k): v for k, v in kwargs.items() }
        data = json.dumps(args)
        headers = {
            'Content-Type': 'application/x-amz-json-1.0',
            "X-Amz-Target": "AWSBlackbeardService." + snake_to_camel(method)
        }
        request = AWSRequest(method='POST', url=self._endpoint, data=data, headers=headers)
        request.context["payload_signing_enabled"] = True # payload signing is not supported
        self._sigv4.add_auth(request)
        prepped = request.prepare()
        response = requests.post(prepped.url, headers=prepped.headers, data=data)
        return json.loads(response.text)

    def describe_guardrail(self, **kwargs): 
        nm = inspect.currentframe().f_code.co_name
        return self.do_request(nm, **kwargs)

    def list_guardrails(self, **kwargs):
        nm = inspect.currentframe().f_code.co_name
        return self.do_request(nm, **kwargs)

def get_remediation_text(template):
    remeds = set()
    for m in re.finditer(r'\[FIX\]:\s+(?P<remed>.*)\n', template, flags=re.MULTILINE):
        remeds.add(m.groupdict()["remed"])
    if len(remeds) > 1:
        print("WRONG", name)
    return list(remeds)[0]

if __name__ == "__main__":
    # you need to set up so these actions are allowed 
    # "controltower:DescribeGuardrail",
    # "controltower:ListGuardrails",
    boto_session = boto3.Session(region_name='us-east-1')

    bb = BlackbeardSession(boto_session)
    nxt = None
    guardrails = []
    while True:
        r = bb.list_guardrails(**({ "NextToken": nxt } if nxt else {}))
        lst = r['GuardrailList']
        names = [guardrail["Name"] for guardrail in lst]
        guardrails.extend(r['GuardrailList'])
        nxt = r.get("NextToken", None)
        if not nxt:
            break
    
    for guardrail in guardrails:
        if not guardrail["Name"].startswith('CT.'):
            continue
        r = bb.describe_guardrail(guardrail_name=guardrail["Name"])
        print(guardrail["Name"])
        if r["ImplementationType"] == "AWS_CLOUDFORMATION_GUARD_RULE":
            template = r["Artifacts"][0]["Content"]
            with open("./guardrails/cfn-guard/" + guardrail["Name"].lower() + ".guard", "w") as f:
                f.write(template)
            del r["Artifacts"]
            r["RegionalPreference"] = guardrail["RegionalPreference"]
            r["RemediationMessage"] = get_remediation_text(template)
            with open("./guardrails/metadata/" + guardrail["Name"].lower() + ".metadata.json", "w") as f:
                f.write(json.dumps(r, indent=2, default=str))

@eppeters
Copy link
Contributor

Hi @Jacco. Thanks for your feedback. Regardless of the implementation mechanism, is it fair to say your goal is to have a more fine-grained way to turn controls on and off?

Regarding the script to pull from the Blackbeard API - I'm not sure how that relates to the above goal, if in fact that is your goal. If you could help me understand what you use the script for, I would appreciate it. If you're just looking for the metadata associated with each Guard control, this directory might already have what you're looking for: https://github.com/cdklabs/cdk-validator-cfnguard/blob/main/rules/control-tower/metadata

@Jacco
Copy link
Author

Jacco commented Nov 21, 2023

The issue is indeed fine grained control :-)

The script does not really relate to the goal, sorry for the confusion. I used this script to pull all the guard rules from ControlTower (I used it to parse the guardrules and inject the metadata conditions for my use case). I was wondering if you construct the metadata directory manually. I (almost) fully automated this using above script plus a few others. If you are doing it manually then I am happy to donate/explain my script :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants