in rdk/rdk.py [0:0]
def deploy(self):
self.__parse_deploy_args()
#get the rule names
rule_names = self.__get_rule_list_for_command()
my_session = self.__get_boto_session()
#run the deploy code
print (f"[{my_session.region_name}]: Running deploy!")
#create custom session based on whatever credentials are available to us
#get accountID
identity_details = self.__get_caller_identity_details(my_session)
account_id = identity_details['account_id']
partition = identity_details['partition']
if self.args.custom_code_bucket:
code_bucket_name = self.args.custom_code_bucket
else:
code_bucket_name = code_bucket_prefix + account_id + "-" + my_session.region_name
#If we're only deploying the Lambda functions (and role + permissions), branch here. Someday the "main" execution path should use the same generated CFN templates for single-account deployment.
if self.args.functions_only:
#Generate the template
function_template = self.__create_function_cloudformation_template()
#Generate CFN parameter json
cfn_params = [
{
'ParameterKey': 'SourceBucket',
'ParameterValue': code_bucket_name,
}
]
#Write template to S3
my_s3_client = my_session.client('s3')
my_s3_client.put_object(
Body=bytes(function_template.encode('utf-8')),
Bucket=code_bucket_name,
Key=self.args.stack_name + ".json"
)
#Package code and push to S3
s3_code_objects = {}
for rule_name in rule_names:
rule_params, cfn_tags = self.__get_rule_parameters(rule_name)
if 'SourceIdentifier' in rule_params:
print(f"[{my_session.region_name}]: Skipping code packaging for Managed Rule.")
else:
s3_dst = self.__upload_function_code(rule_name, rule_params, account_id, my_session, code_bucket_name)
s3_code_objects[rule_name] = s3_dst
my_cfn = my_session.client('cloudformation')
# Generate the template_url regardless of region using the s3 sdk
config = my_s3_client._client_config
config.signature_version = botocore.UNSIGNED
template_url = boto3.client('s3', config=config).generate_presigned_url('get_object', ExpiresIn=0, Params={'Bucket': code_bucket_name, 'Key': self.args.stack_name + ".json"})
# Check if stack exists. If it does, update it. If it doesn't, create it.
try:
my_stack = my_cfn.describe_stacks(StackName=self.args.stack_name)
#If we've gotten here, stack exists and we should update it.
print (f"[{my_session.region_name}]: Updating CloudFormation Stack for Lambda functions.")
try:
cfn_args = {
'StackName': self.args.stack_name,
'TemplateURL': template_url,
'Parameters': cfn_params,
'Capabilities': [ 'CAPABILITY_IAM' ]
}
# If no tags key is specified, or if the tags dict is empty
if cfn_tags is not None:
cfn_args['Tags'] = cfn_tags
response = my_cfn.update_stack(**cfn_args)
#wait for changes to propagate.
self.__wait_for_cfn_stack(my_cfn, self.args.stack_name)
except ClientError as e:
if e.response['Error']['Code'] == 'ValidationError':
if 'No updates are to be performed.' in str(e):
#No changes made to Config rule definition, so CloudFormation won't do anything.
print(f"[{my_session.region_name}]: No changes to Config Rule configurations.")
else:
#Something unexpected has gone wrong. Emit an error and bail.
print(f"[{my_session.region_name}]: {e}")
return 1
else:
raise
#Push lambda code to functions.
for rule_name in rule_names:
rule_params, cfn_tags = self.__get_rule_parameters(rule_name)
my_lambda_arn = self.__get_lambda_arn_for_rule(rule_name, partition, my_session.region_name, account_id, rule_params)
if 'SourceIdentifier' in rule_params:
print(f"[{my_session.region_name}]: Skipping Lambda upload for Managed Rule.")
continue
print(f"[{my_session.region_name}]: Publishing Lambda code...")
my_lambda_client = my_session.client('lambda')
my_lambda_client.update_function_code(
FunctionName=my_lambda_arn,
S3Bucket=code_bucket_name,
S3Key=s3_code_objects[rule_name],
Publish=True
)
print(f"[{my_session.region_name}]: Lambda code updated.")
except ClientError as e:
#If we're in the exception, the stack does not exist and we should create it.
print (f"[{my_session.region_name}]: Creating CloudFormation Stack for Lambda Functions.")
cfn_args = {
'StackName': self.args.stack_name,
'TemplateURL': template_url,
'Parameters': cfn_params,
'Capabilities': ['CAPABILITY_IAM']
}
# If no tags key is specified, or if the tags dict is empty
if cfn_tags is not None:
cfn_args['Tags'] = cfn_tags
response = my_cfn.create_stack(**cfn_args)
#wait for changes to propagate.
self.__wait_for_cfn_stack(my_cfn, self.args.stack_name)
#We're done! Return with great success.
sys.exit(0)
#If we're deploying both the functions and the Config rules, run the following process:
for rule_name in rule_names:
rule_params, cfn_tags = self.__get_rule_parameters(rule_name)
#create CFN Parameters common for Managed and Custom
source_events = "NONE"
if 'SourceEvents' in rule_params:
source_events = rule_params['SourceEvents']
source_periodic = "NONE"
if 'SourcePeriodic' in rule_params:
source_periodic = rule_params['SourcePeriodic']
combined_input_parameters = {}
if 'InputParameters' in rule_params:
combined_input_parameters.update(json.loads(rule_params['InputParameters']))
if 'OptionalParameters' in rule_params:
#Remove empty parameters
keys_to_delete = []
optional_parameters_json = json.loads(rule_params['OptionalParameters'])
for key, value in optional_parameters_json.items():
if not value:
keys_to_delete.append(key)
for key in keys_to_delete:
del optional_parameters_json[key]
combined_input_parameters.update(optional_parameters_json)
if 'SourceIdentifier' in rule_params:
print(f"[{my_session.region_name}]: Found Managed Rule.")
#create CFN Parameters for Managed Rules
try:
rule_description = rule_params["Description"]
except KeyError:
rule_description = rule_name
my_params = [
{
'ParameterKey': 'RuleName',
'ParameterValue': rule_name,
},
{
'ParameterKey': 'Description',
'ParameterValue': rule_description,
},
{
'ParameterKey': 'SourceEvents',
'ParameterValue': source_events,
},
{
'ParameterKey': 'SourcePeriodic',
'ParameterValue': source_periodic,
},
{
'ParameterKey': 'SourceInputParameters',
'ParameterValue': json.dumps(combined_input_parameters),
},
{
'ParameterKey': 'SourceIdentifier',
'ParameterValue': rule_params['SourceIdentifier']
}]
my_cfn = my_session.client('cloudformation')
if "Remediation" in rule_params:
print(f'[{my_session.region_name}]: Build The CFN Template with Remediation Settings')
cfn_body = os.path.join(path.dirname(__file__), 'template', "configManagedRuleWithRemediation.json")
template_body = open(cfn_body, "r").read()
json_body = json.loads(template_body)
remediation = self.__create_remediation_cloudformation_block(rule_params["Remediation"])
json_body["Resources"]["Remediation"] = remediation
if "SSMAutomation" in rule_params:
#Reference the SSM Automation Role Created, if IAM is created
print(f'[{my_session.region_name}]: Building SSM Automation Section')
ssm_automation = self.__create_automation_cloudformation_block(rule_params['SSMAutomation'], self.__get_alphanumeric_rule_name(rule_name))
json_body["Resources"][self.__get_alphanumeric_rule_name(rule_name+'RemediationAction')] = ssm_automation
if "IAM" in rule_params['SSMAutomation']:
print(f'[{my_session.region_name}]: Lets Build IAM Role and Policy')
#TODO Check For IAM Settings
json_body["Resources"]['Remediation']['Properties']['Parameters']['AutomationAssumeRole']['StaticValue']['Values'] = [{"Fn::GetAtt":[self.__get_alphanumeric_rule_name(rule_name+"Role"), "Arn"]}]
ssm_iam_role, ssm_iam_policy = self.__create_automation_iam_cloudformation_block(rule_params['SSMAutomation'], self.__get_alphanumeric_rule_name(rule_name))
json_body["Resources"][self.__get_alphanumeric_rule_name(rule_name+'Role')] = ssm_iam_role
json_body["Resources"][self.__get_alphanumeric_rule_name(rule_name+'Policy')] = ssm_iam_policy
print(f'[{my_session.region_name}]: Build Supporting SSM Resources')
resource_depends_on = ['rdkConfigRule', self.__get_alphanumeric_rule_name(rule_name+"RemediationAction")]
#Builds SSM Document Before Config RUle
json_body["Resources"]["Remediation"]['DependsOn'] = resource_depends_on
json_body["Resources"]["Remediation"]['Properties']['TargetId'] = {'Ref': self.__get_alphanumeric_rule_name(rule_name+"RemediationAction")}
try:
my_stack_name = self.__get_stack_name_from_rule_name(rule_name)
my_stack = my_cfn.describe_stacks(StackName=my_stack_name)
#If we've gotten here, stack exists and we should update it.
print (f"[{my_session.region_name}]: Updating CloudFormation Stack for " + rule_name)
try:
cfn_args = {
'StackName': my_stack_name,
'TemplateBody': json.dumps(json_body,indent=2),
'Parameters': my_params,
'Capabilities': ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM']
}
# If no tags key is specified, or if the tags dict is empty
if cfn_tags is not None:
cfn_args['Tags'] = cfn_tags
response = my_cfn.update_stack(**cfn_args)
except ClientError as e:
if e.response['Error']['Code'] == 'ValidationError':
if 'No updates are to be performed.' in str(e):
#No changes made to Config rule definition, so CloudFormation won't do anything.
print(f"[{my_session.region_name}]: No changes to Config Rule.")
else:
#Something unexpected has gone wrong. Emit an error and bail.
print(f"[{my_session.region_name}]: {e}")
return 1
else:
raise
except ClientError as e:
#If we're in the exception, the stack does not exist and we should create it.
print (f"[{my_session.region_name}]: Creating CloudFormation Stack for " + rule_name)
if "Remediation" in rule_params:
cfn_args = {
'StackName': my_stack_name,
'TemplateBody': json.dumps(json_body,indent=2),
'Parameters': my_params,
'Capabilities': ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM']
}
else:
cfn_args = {
'StackName': my_stack_name,
'TemplateBody': open(cfn_body, "r").read(),
'Parameters': my_params
}
if cfn_tags is not None:
cfn_args['Tags'] = cfn_tags
response = my_cfn.create_stack(**cfn_args)
#wait for changes to propagate.
self.__wait_for_cfn_stack(my_cfn, my_stack_name)
continue
else:
#deploy config rule
cfn_body = os.path.join(path.dirname(__file__), 'template', "configManagedRule.json")
try:
my_stack_name = self.__get_stack_name_from_rule_name(rule_name)
my_stack = my_cfn.describe_stacks(StackName=my_stack_name)
#If we've gotten here, stack exists and we should update it.
print (f"[{my_session.region_name}]: Updating CloudFormation Stack for " + rule_name)
try:
cfn_args = {
'StackName': my_stack_name,
'TemplateBody': open(cfn_body, "r").read(),
'Parameters': my_params
}
# If no tags key is specified, or if the tags dict is empty
if cfn_tags is not None:
cfn_args['Tags'] = cfn_tags
response = my_cfn.update_stack(**cfn_args)
except ClientError as e:
if e.response['Error']['Code'] == 'ValidationError':
if 'No updates are to be performed.' in str(e):
#No changes made to Config rule definition, so CloudFormation won't do anything.
print(f"[{my_session.region_name}]: No changes to Config Rule.")
else:
#Something unexpected has gone wrong. Emit an error and bail.
print(f"[{my_session.region_name}]: {e}")
return 1
else:
raise
except ClientError as e:
#If we're in the exception, the stack does not exist and we should create it.
print (f"[{my_session.region_name}]: Creating CloudFormation Stack for " + rule_name)
cfn_args = {
'StackName': my_stack_name,
'TemplateBody': open(cfn_body, "r").read(),
'Parameters': my_params
}
if cfn_tags is not None:
cfn_args['Tags'] = cfn_tags
response = my_cfn.create_stack(**cfn_args)
#wait for changes to propagate.
self.__wait_for_cfn_stack(my_cfn, my_stack_name)
#Cloudformation is not supporting tagging config rule currently.
if cfn_tags is not None and len(cfn_tags) > 0:
self.__tag_config_rule(rule_name, cfn_tags, my_session)
continue
print(f"[{my_session.region_name}]: Found Custom Rule.")
s3_src = ""
s3_dst = self.__upload_function_code(rule_name, rule_params, account_id, my_session, code_bucket_name)
#create CFN Parameters for Custom Rules
lambdaRoleArn = ""
if self.args.lambda_role_arn:
print (f"[{my_session.region_name}]: Existing IAM Role provided: " + self.args.lambda_role_arn)
lambdaRoleArn = self.args.lambda_role_arn
elif self.args.lambda_role_name:
print (f"[{my_session.region_name}]: Building IAM Role ARN from Name: " + self.args.lambda_role_name)
arn = f"arn:{partition}:iam::{account_id}:role/{self.args.lambda_role_name}"
lambdaRoleArn = arn
if self.args.boundary_policy_arn:
print (f"[{my_session.region_name}]: Boundary Policy provided: " + self.args.boundary_policy_arn)
boundaryPolicyArn = self.args.boundary_policy_arn
else:
boundaryPolicyArn = ""
try:
rule_description = rule_params["Description"]
except KeyError:
rule_description = rule_name
my_params = [
{
'ParameterKey': 'RuleName',
'ParameterValue': rule_name,
},
{
'ParameterKey': 'RuleLambdaName',
'ParameterValue': self.__get_lambda_name(rule_name, rule_params),
},
{
'ParameterKey': 'Description',
'ParameterValue': rule_description,
},
{
'ParameterKey': 'LambdaRoleArn',
'ParameterValue': lambdaRoleArn,
},
{
'ParameterKey': 'BoundaryPolicyArn',
'ParameterValue': boundaryPolicyArn,
},
{
'ParameterKey': 'SourceBucket',
'ParameterValue': code_bucket_name,
},
{
'ParameterKey': 'SourcePath',
'ParameterValue': s3_dst,
},
{
'ParameterKey': 'SourceRuntime',
'ParameterValue': self.__get_runtime_string(rule_params),
},
{
'ParameterKey': 'SourceEvents',
'ParameterValue': source_events,
},
{
'ParameterKey': 'SourcePeriodic',
'ParameterValue': source_periodic,
},
{
'ParameterKey': 'SourceInputParameters',
'ParameterValue': json.dumps(combined_input_parameters),
},
{
'ParameterKey': 'SourceHandler',
'ParameterValue': self.__get_handler(rule_name, rule_params)
},
{
'ParameterKey': 'Timeout',
'ParameterValue': str(self.args.lambda_timeout)
}]
layers = self.__get_lambda_layers(my_session, self.args, rule_params)
if self.args.lambda_layers:
additional_layers = self.args.lambda_layers.split(',')
layers.extend(additional_layers)
if layers:
my_params.append({
'ParameterKey': 'Layers',
'ParameterValue': ",".join(layers)
})
if self.args.lambda_security_groups and self.args.lambda_subnets:
my_params.append({
'ParameterKey': 'SecurityGroupIds',
'ParameterValue': self.args.lambda_security_groups
})
my_params.append({
'ParameterKey': 'SubnetIds',
'ParameterValue': self.args.lambda_subnets
})
#create json of CFN template
cfn_body = os.path.join(path.dirname(__file__), 'template', "configRule.json")
template_body = open(cfn_body, "r").read()
json_body = json.loads(template_body)
remediation = ""
if "Remediation" in rule_params:
remediation = self.__create_remediation_cloudformation_block(rule_params["Remediation"])
json_body["Resources"]["Remediation"] = remediation
if "SSMAutomation" in rule_params:
##AWS needs to build the SSM before the Config Rule
resource_depends_on = ['rdkConfigRule', self.__get_alphanumeric_rule_name(rule_name+"RemediationAction")]
remediation["DependsOn"] = resource_depends_on
#Add JSON Reference to SSM Document { "Ref" : "MyEC2Instance" }
remediation['Properties']['TargetId'] = {"Ref" : self.__get_alphanumeric_rule_name(rule_name+"RemediationAction") }
if "SSMAutomation" in rule_params:
print(f'[{my_session.region_name}]: Building SSM Automation Section')
ssm_automation = self.__create_automation_cloudformation_block(rule_params['SSMAutomation'], rule_name)
json_body["Resources"][self.__get_alphanumeric_rule_name(rule_name+"RemediationAction")] = ssm_automation
if "IAM" in rule_params['SSMAutomation']:
print('Lets Build IAM Role and Policy')
#TODO Check For IAM Settings
json_body["Resources"]['Remediation']['Properties']['Parameters']['AutomationAssumeRole']['StaticValue']['Values'] = [{"Fn::GetAtt":[self.__get_alphanumeric_rule_name(rule_name+"Role"), "Arn"]}]
ssm_iam_role, ssm_iam_policy = self.__create_automation_iam_cloudformation_block(rule_params['SSMAutomation'], rule_name)
json_body["Resources"][self.__get_alphanumeric_rule_name(rule_name+'Role')] = ssm_iam_role
json_body["Resources"][self.__get_alphanumeric_rule_name(rule_name+'Policy')] = ssm_iam_policy
#debugging
# print(json.dumps(json_body, indent=2))
#deploy config rule
my_cfn = my_session.client('cloudformation')
try:
my_stack_name = self.__get_stack_name_from_rule_name(rule_name)
my_stack = my_cfn.describe_stacks(StackName=my_stack_name)
#If we've gotten here, stack exists and we should update it.
print (f"[{my_session.region_name}]: Updating CloudFormation Stack for " + rule_name)
try:
cfn_args = {
'StackName': my_stack_name,
'TemplateBody': json.dumps(json_body,indent=2),
'Parameters': my_params,
'Capabilities': ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM']
}
# If no tags key is specified, or if the tags dict is empty
if cfn_tags is not None:
cfn_args['Tags'] = cfn_tags
response = my_cfn.update_stack(**cfn_args)
except ClientError as e:
if e.response['Error']['Code'] == 'ValidationError':
if 'No updates are to be performed.' in str(e):
#No changes made to Config rule definition, so CloudFormation won't do anything.
print(f"[{my_session.region_name}]: No changes to Config Rule.")
else:
#Something unexpected has gone wrong. Emit an error and bail.
print(f'[{my_session.region_name}]: Validation Error on CFN\n')
print(f'[{my_session.region_name}]: ' + json.dumps(cfn_args)+ "\n")
print(f'[{my_session.region_name}]: {e}\n')
return 1
else:
raise
my_lambda_arn = self.__get_lambda_arn_for_stack(my_stack_name)
print(f"[{my_session.region_name}]: Publishing Lambda code...")
my_lambda_client = my_session.client('lambda')
my_lambda_client.update_function_code(
FunctionName=my_lambda_arn,
S3Bucket=code_bucket_name,
S3Key=s3_dst,
Publish=True
)
print(f"[{my_session.region_name}]: Lambda code updated.")
except ClientError as e:
#If we're in the exception, the stack does not exist and we should create it.
print (f"[{my_session.region_name}]: Creating CloudFormatioon Stack for " + rule_name)
cfn_args = {
'StackName': my_stack_name,
'TemplateBody': json.dumps(json_body,indent=2),
'Parameters': my_params,
'Capabilities': ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM']
}
if cfn_tags is not None:
cfn_args['Tags'] = cfn_tags
response = my_cfn.create_stack(**cfn_args)
#wait for changes to propagate.
self.__wait_for_cfn_stack(my_cfn, my_stack_name)
#Cloudformation is not supporting tagging config rule currently.
if cfn_tags is not None and len(cfn_tags) > 0:
self.__tag_config_rule(rule_name, cfn_tags, my_session)
print(f'[{my_session.region_name}]: Config deploy complete.')
return 0