Skip to content

Commit

Permalink
- Added deployment_branch_name to CodeBuild GitHub source configuration
Browse files Browse the repository at this point in the history
- Added source_security_group_owner to SecurityGroup for cross account access
- Implemented additional IAM Policy conditions on CodeBuild VPCConfig Service Role
- Added CodeBuild support to EventsRules
- Added SubnetId ARNs to segment's CFN outputs
  • Loading branch information
gitwater committed Jan 27, 2022
1 parent 9946ec6 commit 5425837
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 30 deletions.
26 changes: 24 additions & 2 deletions src/paco/cftemplates/codebuild.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from awacs.aws import Allow, Statement, Policy, PolicyDocument, Principal, Action, Condition, StringEquals, StringLike
from awacs.aws import Allow, Statement, Policy, PolicyDocument, Principal, Action, Condition, StringEquals, StringLike, ArnEquals
from awacs.sts import AssumeRole
from paco.cftemplates.cftemplates import StackTemplate
from paco.core.exception import PacoException
Expand Down Expand Up @@ -478,6 +478,8 @@ def create_codebuild_cfn(
project_dict['Source']['Type'] = 'GITHUB'
project_dict['Source']['Location'] = action_config.source.github.location
project_dict['Source']['ReportBuildStatus'] = action_config.source.github.report_build_status
if action_config.source.github.deployment_branch_name != None:
project_dict['SourceVersion'] = action_config.source.github.deployment_branch_name
else:
raise PacoException("CodeBuild source must be configured when Codepipeline is disabled.")

Expand Down Expand Up @@ -513,9 +515,11 @@ def create_codebuild_cfn(
# ref_attribute='id',
# )
subnet_id_list = []
subnet_arn_list = []
az_size = self.env_ctx.netenv[self.account_ctx.name][self.aws_region].network.availability_zones
for segment_ref in vpc_config.segments:
for az_idx in range(1, az_size+1):
# Subnet Ids
segment_name = self.create_cfn_logical_id(f"Segment{segment_ref.split('.')[-1]}AZ{az_idx}")
subnet_id_param = self.create_cfn_parameter(
name=segment_name,
Expand All @@ -524,6 +528,14 @@ def create_codebuild_cfn(
value=segment_ref + f'.az{az_idx}.subnet_id'
)
subnet_id_list.append(troposphere.Ref(subnet_id_param))
# Subnet Arns
subnet_arn_param = self.create_cfn_parameter(
name=segment_name+'Arn',
param_type='String',
description=f'VPC Subnet Id ARN in AZ{az_idx} for CodeBuild VPC Config',
value=segment_ref + f'.az{az_idx}.subnet_id.arn'
)
subnet_arn_list.append(troposphere.Ref(subnet_arn_param))

if len(subnet_id_list) == 0:
raise PacoException("CodeBuild VPC Config must have at least one segment defined.")
Expand Down Expand Up @@ -553,7 +565,17 @@ def create_codebuild_cfn(
Action=[
Action('ec2', 'CreateNetworkInterfacePermission'),
],
Resource=[ f'arn:aws:ec2:{self.aws_region}:{self.account_ctx.name}:network-interface/*' ]
Resource=[ f'arn:aws:ec2:{self.aws_region}:{self.account_ctx.id}:network-interface/*' ],
Condition=Condition(
[
StringEquals({
"ec2:AuthorizedService": "codebuild.amazonaws.com"
}),
ArnEquals({
"ec2:Subnet": subnet_arn_list
})
]
)
)
)

Expand Down
40 changes: 25 additions & 15 deletions src/paco/cftemplates/eventsrule.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ def __init__(
target = eventsrule.targets[index]
# Target Parameters
target_name = 'Target{}'.format(index)

# Target CFN Parameters
self.target_params[target_name + 'Arn'] = self.create_cfn_parameter(
param_type='String',
name=target_name + 'Arn',
Expand All @@ -79,21 +81,8 @@ def __init__(
description=target_name + ' for the Event Rule.',
value=target_name,
)
cfn_export_dict = {
'Arn': troposphere.Ref(self.target_params[target_name + 'Arn']),
'Id': troposphere.Ref(self.target_params[target_name]),
}
if target.input_json != None:
cfn_export_dict['Input'] = target.input_json

# Events Rule Targets
targets.append(
troposphere.events.Target.from_dict(
target_name,
cfn_export_dict
)
)

# IAM Role
# Lambda Policy Actions
target_ref = Reference(target.target)
if target_ref.parts[-1] == 'project' and target_ref.parts[-3] == 'build':
Expand All @@ -102,9 +91,12 @@ def __init__(
else:
target_model_obj = get_model_obj_from_ref(target.target, self.paco_ctx.project)

# IAM Role Polcies by Resource type
if schemas.IDeploymentPipelineBuildCodeBuild.providedBy(target_model_obj):
# CodeBuild Project
target_policy_actions = [awacs.codebuild.StartBuild]
elif schemas.ILambda.providedBy(target_model_obj):
# Lambda Function
target_policy_actions = [awacs.awslambda.InvokeFunction]


Expand Down Expand Up @@ -139,6 +131,23 @@ def __init__(
)
self.template.add_resource(target_invocation_role_resource)

# Create Target CFN Resources
cfn_export_dict = {
'Arn': troposphere.Ref(self.target_params[target_name + 'Arn']),
'Id': troposphere.Ref(self.target_params[target_name]),
'RoleArn': troposphere.GetAtt(target_invocation_role_resource, 'Arn')
}
if target.input_json != None:
cfn_export_dict['Input'] = target.input_json

# Events Rule Targets
targets.append(
troposphere.events.Target.from_dict(
target_name,
cfn_export_dict
)
)

# Events Rule Resource
# The Name is needed so that a Lambda can be created and it's Lambda ARN output
# can be supplied as a Parameter to this Stack and a Lambda Permission can be
Expand All @@ -153,10 +162,11 @@ def __init__(
Name=name,
Description=troposphere.Ref(description_param),
ScheduleExpression=troposphere.Ref(schedule_expression_param),
RoleArn=troposphere.GetAtt(target_invocation_role_resource, 'Arn'),
Targets=targets,
RoleArn=troposphere.Ref(target_invocation_role_resource),
State=enabled_state
)
event_rule_resource.DependsOn = target_invocation_role_resource
self.template.add_resource(event_rule_resource)

# Outputs
Expand Down
63 changes: 50 additions & 13 deletions src/paco/cftemplates/security_groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def __init__(
template_name = 'SG'
self.set_aws_name(template_name, sg_group_id, rules_id)

self.source_group_param_cache = {}
self.troposphere_param_cache = {}

# Troposphere Template Initialization
self.init_template('Security Groups')
Expand Down Expand Up @@ -121,7 +121,7 @@ def create_group_rules(self, sg_group_id, sg_name, sg_config, template):
# Ingress and Egress rules
for sg_rule_config in sg_rule_list:
rule_dict = {
'GroupId': self.create_group_param_ref(sg_group_config_ref, template),
'GroupId': self.create_group_param_ref('Source', sg_group_config_ref),
'IpProtocol': str(sg_rule_config.protocol),
'FromPort': None,
'ToPort': None,
Expand Down Expand Up @@ -150,15 +150,27 @@ def create_group_rules(self, sg_group_id, sg_name, sg_config, template):
elif sg_rule_config.cidr_ip_v6 != '':
rule_dict['CidrIpv6'] = sg_rule_config.cidr_ip_v6
elif getattr(sg_rule_config, 'source_security_group', '') != '':
# Source Security Group Id
if references.is_ref(sg_rule_config.source_security_group):
rule_dict['SourceSecurityGroupId'] = self.create_group_param_ref(
sg_rule_config.source_security_group, template)
'Source',
sg_rule_config.source_security_group)
else:
rule_dict['SourceSecurityGroupId'] = sg_rule_config.source_security_group
# Source Security Group Owner Id
if sg_rule_config.source_security_group_owner != None and getattr(sg_rule_config, 'source_security_group_owner', '') != '':
if references.is_ref(sg_rule_config.source_security_group_owner):
rule_dict['SourceSecurityGroupOwnerId'] = self.create_group_owner_param_ref(
'Source',
sg_rule_config.source_security_group_owner)
else:
rule_dict['SourceSecurityGroupOwnerId'] = sg_rule_config.source_security_group_owner
elif getattr(sg_rule_config, 'destination_security_group', '') != '':
# Destination Security Group Id
if references.is_ref(sg_rule_config.destination_security_group):
rule_dict['DestinationSecurityGroupId'] = self.create_group_param_ref(
sg_rule_config.destination_security_group, template)
'Destination',
sg_rule_config.destination_security_group)
else:
rule_dict['DestinationSecurityGroupId'] = sg_rule_config.destination_security_group
else:
Expand All @@ -169,7 +181,7 @@ def create_group_rules(self, sg_group_id, sg_name, sg_config, template):
template.add_resource(rule_res)


def create_group_param_ref(self, group_ref, template):
def create_param_from_ref(self, group_ref, param_type, param_name, param_description, ref_att):
"""
Creates a Security Group Id parameter and returns a Ref()
to it. It caches the parameter to allow multiple references
Expand All @@ -180,17 +192,42 @@ def create_group_param_ref(self, group_ref, template):
if self.paco_ctx.legacy_flag('aim_name_2019_11_28') == True:
hash_ref = 'aim' + group_ref[4:]
group_ref_hash = utils.md5sum(str_data=hash_ref)
if group_ref_hash in self.source_group_param_cache.keys():
return troposphere.Ref(self.source_group_param_cache[group_ref_hash])
if group_ref_hash in self.troposphere_param_cache.keys():
return troposphere.Ref(self.troposphere_param_cache[group_ref_hash])

source_sg_param = self.create_cfn_parameter(
param_type='AWS::EC2::SecurityGroup::Id',
name='SourceGroupId' + group_ref_hash,
description='Source Security Group - ' + hash_ref,
value=group_ref + '.id',
param_type=param_type,
name=param_name + group_ref_hash,
description=f'{param_description} - {hash_ref}',
value=f'{group_ref}.{ref_att}'
)
self.troposphere_param_cache[group_ref_hash] = source_sg_param

self.source_group_param_cache[group_ref_hash] = source_sg_param
return troposphere.Ref(self.source_group_param_cache[group_ref_hash])
return troposphere.Ref(self.troposphere_param_cache[group_ref_hash])

def create_group_param_ref(self, group_type, group_ref):
"""
Creates a Security Group Id parameter and returns a Ref()
to it. It caches the parameter to allow multiple references
from a single Parameter.
"""
return self.create_param_from_ref(
group_ref,
'AWS::EC2::SecurityGroup::Id',
f'{group_type}GroupId',
f'{group_type} Security Group',
'id')

def create_group_owner_param_ref(self, group_type, group_ref):
"""
Creates a Security Group Id parameter and returns a Ref()
to it. It caches the parameter to allow multiple references
from a single Parameter.
"""
return self.create_param_from_ref(
group_ref,
'String',
f'{group_type}GroupOwnerId',
f'{group_type} Security Group Owner Id',
'id')

13 changes: 13 additions & 0 deletions src/paco/cftemplates/segment.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,16 @@ def __init__(
SubnetIdAZ3:
Condition: AZ3Enabled
Value: !Sub '${{SubnetAZ3}}'
SubnetIdAZ1Arn:
Value: !Sub 'arn:aws:ec2:${{AWS::Region}}:${{AWS::AccountId}}:subnet/${{SubnetAZ1}}'
SubnetIdAZ2Arn:
Condition: AZ2Enabled
Value: !Sub 'arn:aws:ec2:${{AWS::Region}}:${{AWS::AccountId}}:subnet/${{SubnetAZ2}}'
SubnetIdAZ3Arn:
Condition: AZ3Enabled
Value: !Sub 'arn:aws:ec2:${{AWS::Region}}:${{AWS::AccountId}}:subnet/${{SubnetAZ3}}'
AvailabilityZone1:
Value: !GetAtt [SubnetAZ1, AvailabilityZone]
AvailabilityZone2:
Expand Down Expand Up @@ -395,13 +405,16 @@ def __init__(

self.stack.register_stack_output_config(segment_config_ref+'.subnet_id_list', 'SubnetIdList')
self.stack.register_stack_output_config(segment_config_ref+'.az1.subnet_id', 'SubnetIdAZ1')
self.stack.register_stack_output_config(segment_config_ref+'.az1.subnet_id.arn', 'SubnetIdAZ1Arn')
self.stack.register_stack_output_config(segment_config_ref+'.az1.availability_zone', 'AvailabilityZone1')
self.stack.register_stack_output_config(segment_config_ref+'.az1.route_table.id', 'RouteTableIdAZ1')
if availability_zones > 1:
self.stack.register_stack_output_config(segment_config_ref+'.az2.subnet_id', 'SubnetIdAZ2')
self.stack.register_stack_output_config(segment_config_ref+'.az2.subnet_id.arn', 'SubnetIdAZ2Arn')
self.stack.register_stack_output_config(segment_config_ref+'.az2.availability_zone', 'AvailabilityZone2')
self.stack.register_stack_output_config(segment_config_ref+'.az2.route_table.id', 'RouteTableIdAZ2')
if availability_zones > 2:
self.stack.register_stack_output_config(segment_config_ref+'.az3.subnet_id', 'SubnetIdAZ3')
self.stack.register_stack_output_config(segment_config_ref+'.az3.subnet_id.arn', 'SubnetIdAZ3Arn')
self.stack.register_stack_output_config(segment_config_ref+'.az3.availability_zone', 'AvailabilityZone3')
self.stack.register_stack_output_config(segment_config_ref+'.az3.route_table.id', 'RouteTableIdAZ3')

0 comments on commit 5425837

Please sign in to comment.