Open Jacco opened 1 year ago
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))
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
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 :-)
For making fine graned exceptions:
I saw the usage in the AWS Guard Rules Registry
They just add a condition to the resource selectors:
In cloudformation the exception looks like this:
In CDK code something like this:
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. !!