def add_security_group_ingress(service_stack: ComposeXStack, db_name: str,
                               sg_id, port):
    """
    Function to add a SecurityGroupIngress rule into the ECS Service template

    :param ecs_composex.ecs.ServicesStack service_stack: The root stack for the services
    :param str db_name: the name of the database to use for imports
    :param sg_id: The security group Id to use for ingress. DB Security group, not service's
    :param port: The port for Ingress to the DB.
    """
    service_template = service_stack.stack_template
    add_resource(
        service_template,
        SecurityGroupIngress(
            f"AllowFrom{service_stack.title}to{db_name}",
            template=service_template,
            GroupId=sg_id,
            FromPort=port,
            ToPort=port,
            Description=Sub(f"Allow FROM {service_stack.title} TO {db_name}"),
            SourceSecurityGroupId=GetAtt(service_template.resources[SG_T],
                                         "GroupId"),
            SourceSecurityGroupOwnerId=Ref("AWS::AccountId"),
            IpProtocol="6",
        ),
    )
Ejemplo n.º 2
0
def add_security_group(family) -> None:
    """
    Creates a new EC2 SecurityGroup and assigns to ecs_service.network_settings
    Adds the security group to the family template resources.

    :param ecs_composex.ecs.ecs_family.ComposeFamily family:
    """
    family.service_networking.security_group = SecurityGroup(
        SG_T,
        GroupDescription=Sub(
            f"SG for ${{{SERVICE_NAME.title}}} - ${{STACK_NAME}}",
            STACK_NAME=define_stack_name(),
        ),
        Tags=Tags({
            "Name":
            Sub(
                f"${{{SERVICE_NAME.title}}}-${{STACK_NAME}}",
                STACK_NAME=define_stack_name(),
            ),
            "StackName":
            StackName,
            "MicroserviceName":
            Ref(SERVICE_NAME),
        }),
        VpcId=Ref(VPC_ID),
    )
    add_resource(family.template, family.service_networking.security_group)
Ejemplo n.º 3
0
def create_alarms(template: Template, new_alarms: list[Alarm]) -> None:
    """
    Main function to create new alarms
    Rules out CompositeAlarms first, creates "Simple" alarms, and then link these to ComopsiteAlarms if so declared.
    """
    for alarm in new_alarms:
        if (alarm.properties and not alarm.parameters or
            (alarm.parameters
             and not keyisset("CompositeExpression", alarm.parameters))):
            try:
                import_record_properties(
                    alarm.properties,
                    CompositeAlarm,
                    ignore_missing_required=False,
                )
            except KeyError:
                props = import_record_properties(alarm.properties, CWAlarm)
                alarm.cfn_resource = CWAlarm(alarm.logical_name, **props)
                if alarm.cfn_resource.title not in template.resources:
                    alarm.init_outputs()
                    alarm.generate_outputs()
                    add_resource(template, alarm.cfn_resource)
                    add_outputs(template, alarm.outputs)
        elif alarm.parameters and keyisset("CompositeExpression",
                                           alarm.parameters):
            continue

    add_composite_alarms(template, new_alarms)
def define_table(table, template):
    """
    Function to create the DynamoDB table resource

    :param table:
    :type table: ecs_composex.common.compose_resources.Table
    """
    table_props = import_record_properties(table.properties, dynamodb.Table)
    table_props.update({
        "Metadata":
        metadata,
        "Tags":
        Tags(
            Name=table.name,
            ResourceName=table.logical_name,
            CreatedByComposex=True,
            RootStackName=Ref(ROOT_STACK_NAME),
        ),
    })
    cfn_table = dynamodb.Table(table.logical_name, **table_props)
    table.cfn_resource = cfn_table
    if table.scaling:
        add_autoscaling(table, template)
    table.init_outputs()
    table.generate_outputs()
    add_resource(template, table.cfn_resource)
    add_outputs(template, table.outputs)
Ejemplo n.º 5
0
def create_log_group(
    family: ComposeFamily,
    group_name,
    grant_task_role_access: bool = False,
) -> LogGroup:
    """
    Function to create a new Log Group for the services
    :return:
    """
    if LOG_GROUP_T not in family.template.resources:
        svc_log = LogGroup(
            LOG_GROUP_T,
            RetentionInDays=Ref(LOG_GROUP_RETENTION),
            LogGroupName=group_name,
        )
        add_resource(family.template, svc_log)

    else:
        svc_log = family.template.resources[LOG_GROUP_T]
    roles = [family.iam_manager.exec_role.name]
    if grant_task_role_access:
        roles.append(family.iam_manager.task_role.name)
    define_iam_permissions(
        "logs",
        family,
        family.template,
        "CloudWatchLogsAccess",
        LOGGING_IAM_PERMISSIONS_MODEL,
        access_definition="LogGroupOwner",
        resource_arns=[GetAtt(svc_log, "Arn")],
        roles=roles,
    )

    return svc_log
def handle_cross_account_permissions(
    family: ComposeFamily,
    service: ComposeService,
    settings: ComposeXSettings,
    parameter_name: str,
    config_value: str,
):
    """
    Function to automatically add cross-account role access for FireHose to the specified role ARN
    :param family:
    :param service:
    :param settings:
    :param parameter_name:
    :param config_value:
    :return:
    """
    try:
        validate_iam_role_arn(config_value)
    except ValueError:
        LOG.error(
            f"{family.name}.{service.name} - FireLens config for firehose role_arn is invalid"
        )
        raise
    policy_title = (
        f"{family.logical_name}{service.logical_name}LoggingFirehoseCrossAccount"
    )
    if policy_title in family.template.resources:
        policy = family.template.resources[policy_title]
        resource = policy.PolicyDocument["Statement"][0]["Resource"]
        if isinstance(resource, str):
            resource = [resource]
        if config_value not in resource:
            policy.PolicyDocument["Statement"][0]["Resource"].append(
                config_value)
    else:
        policy = PolicyType(
            policy_title,
            PolicyName=Sub(
                f"{family.logical_name}{service.logical_name}FireHoseCrossAccountAccess${{STACK_ID}}",
                STACK_ID=STACK_ID_SHORT,
            ),
            PolicyDocument={
                "Version":
                "2012-10-17",
                "Statement": [{
                    "Sid": "LoggingFirehoseCrossAccount",
                    "Effect": "Allow",
                    "Action": ["sts:AssumeRole"],
                    "Resource": [config_value],
                }],
            },
            Roles=family.iam_manager.task_role.name,
        )
        add_resource(family.template, policy)
    return config_value
Ejemplo n.º 7
0
def add_composite_alarms(template: Template, new_alarms: list[Alarm]) -> None:

    for alarm in new_alarms:
        if not alarm.cfn_resource and ((alarm.parameters and keyisset(
                "CompositeExpression", alarm.parameters)) or alarm.properties):
            alarm.is_composite = True
            create_composite_alarm(alarm, new_alarms)
            add_resource(template, alarm.cfn_resource)
            alarm.init_outputs()
            alarm.generate_outputs()
            add_outputs(template, alarm.outputs)
Ejemplo n.º 8
0
def add_x_resources(settings: ComposeXSettings) -> None:
    """
    Processes the modules / resources that are defining the environment settings
    """
    for name, module in settings.mod_manager.modules.items():
        LOG.info(f"Processing {name}")
        x_stack = module.stack_class(
            module.mapping_key,
            settings=settings,
            module=module,
            Parameters={ROOT_STACK_NAME_T: Ref(AWS_STACK_NAME)},
        )
        if x_stack and x_stack.is_void:
            settings.x_resources_void.append({module.res_key: x_stack})
        elif (x_stack and hasattr(x_stack, "title")
              and hasattr(x_stack, "stack_template") and not x_stack.is_void):
            add_resource(settings.root_stack.stack_template, x_stack)
Ejemplo n.º 9
0
def create_registry(family, namespace, port_config, settings):
    """
    Creates the settings for the ECS Service Registries and adds the resources to the appropriate template

    :param ecs_composex.ecs.ecs_family.ComposeFamily family:
    :param ecs_composex.cloudmap.cloudmap_stack.PrivateNamespace namespace:
    :param dict port_config:
    :param ecs_composex.common.settings.ComposeXSettings settings:
    """
    if family.ecs_service.registries:
        LOG.warn(f"{family.name} already has a CloudMap mapping. "
                 f"Only one can be set. Ignoring mapping to {namespace.name}")
        return
    if namespace.cfn_resource:
        add_parameters(
            family.template,
            [
                namespace.attributes_outputs[PRIVATE_NAMESPACE_ID]
                ["ImportParameter"]
            ],
        )
        family.stack.Parameters.update({
            namespace.attributes_outputs[PRIVATE_NAMESPACE_ID]["ImportParameter"].title:
            namespace.attributes_outputs[PRIVATE_NAMESPACE_ID]["ImportValue"]
        })
        namespace_id = Ref(namespace.attributes_outputs[PRIVATE_NAMESPACE_ID]
                           ["ImportParameter"])
    elif namespace.lookup_properties:
        add_update_mapping(
            family.template,
            namespace.module.mapping_key,
            settings.mappings[namespace.module.mapping_key],
        )
        namespace_id = namespace.attributes_outputs[PRIVATE_NAMESPACE_ID][
            "ImportValue"]
    else:
        raise AttributeError(
            f"{namespace.module.res_key}.{namespace.name} - Cannot define if new or lookup !?"
        )

    sd_service = SdService(
        f"{namespace.logical_name}EcsServiceDiscovery{family.logical_name}",
        Description=Sub(f"{family.name} service"),
        NamespaceId=namespace_id,
        HealthCheckCustomConfig=HealthCheckCustomConfig(FailureThreshold=1.0),
        DnsConfig=DnsConfig(
            RoutingPolicy="MULTIVALUE",
            NamespaceId=Ref(AWS_NO_VALUE),
            DnsRecords=[
                DnsRecord(TTL="15", Type="A"),
                DnsRecord(TTL="15", Type="SRV"),
            ],
        ),
        Name=family.family_hostname,
    )
    service_registry = ServiceRegistry(
        f"ServiceRegistry{port_config['target']}",
        RegistryArn=GetAtt(sd_service, "Arn"),
        Port=int(port_config["target"]),
    )
    add_resource(family.template, sd_service)
    family.ecs_service.registries.append(service_registry)
Ejemplo n.º 10
0
def add_managed_ssm_parameter(family: ComposeFamily,
                              settings: ComposeXSettings,
                              content: dict) -> SsmParameter:
    """
    Handles x-logging.FireLens.Advanced.Rendered

    :param family:
    :param settings:
    :param content:
    """

    ssm_parameter_title = f"{family.logical_name}FireLensConfigurationSsm"
    ssm_parameter_definition = {
        "Properties": {
            "DataType": "text",
            "Type": "String",
            "Value": dumps(content),
        },
        "MacroParameters": {
            "EncodeToBase64": True
        },
        "Services": {
            family.name: {
                "Access": "RO"
            }
        },
    }

    if "x-ssm_parameter" not in settings.mod_manager.modules:
        ssm_module = settings.mod_manager.add_module("x-ssm_parameter")
        settings.compose_content[ssm_module.res_key]: dict = {
            ssm_parameter_title: ssm_parameter_definition
        }
    else:
        ssm_module = settings.mod_manager.modules["x-ssm_parameter"]
        settings.compose_content[ssm_module.res_key].update(
            {ssm_parameter_title: ssm_parameter_definition})
    if not ssm_module:
        raise LookupError("Failed to import x-ssm_parameter module!")

    if ssm_module.mapping_key not in settings.stacks:
        ssm_stack = ssm_module.stack_class(ssm_module.mod_key, settings,
                                           ssm_module)
        settings.stacks[ssm_module.mapping_key] = ssm_stack
        add_resource(settings.root_stack.stack_template, ssm_stack)
        ssm_parameter = settings.compose_content[
            ssm_module.res_key][ssm_parameter_title]
    else:
        ssm_stack = settings.stacks[ssm_module.mapping_key]
        ssm_parameter = SsmParameter(ssm_parameter_title,
                                     ssm_parameter_definition, ssm_module,
                                     settings)
        render_new_parameters([ssm_parameter], ssm_stack)
        ssm_parameter.stack = ssm_stack
        ssm_parameter.init_outputs()
        ssm_parameter.generate_outputs()
        add_resource(ssm_parameter.stack.stack_template,
                     ssm_parameter.cfn_resource)
        ssm_parameter.to_ecs(settings, settings.mod_manager)
        settings.compose_content[ssm_module.res_key][
            ssm_parameter.name] = ssm_parameter

    return ssm_parameter
def create_autoscaling_target_and_policy(
    table: Table,
    template: Template,
    scalable_property: str,
    scale_definition: dict,
    index: str = None,
) -> tuple:
    """
    Defines the autoscaling target and policy for the a given resource and dimension.

    :param Table table:
    :param Template template:
    :param str scalable_property:
    :param dict scale_definition:
    :param str index:
    :return: The target and the associated policy
    """
    property_mapping: dict = {
        "WriteCapacityUnits": {
            "PredefinedMetricType": "DynamoDBWriteCapacityUtilization"
        },
        "ReadCapacityUnits": {
            "PredefinedMetricType": "DynamoDBReadCapacityUtilization"
        },
    }
    scablable_resource = (
        Sub(f"table/${{{table.cfn_resource.title}}}")
        if not index
        else Sub(f"table/${{{table.cfn_resource.title}}}/index/{index}")
    )
    target_title = (
        f"{table.logical_name}{scalable_property}ScalableTarget"
        if not index
        else f"{table.logical_name}{scalable_property}Index{index}ScalableTarget"
    )
    scaling_target = ScalableTarget(
        target_title,
        MinCapacity=scale_definition["MinCapacity"],
        MaxCapacity=scale_definition["MaxCapacity"],
        ServiceNamespace="dynamodb",
        ScalableDimension=f"dynamodb:table:{scalable_property}"
        if not index
        else f"dynamodb:index:{scalable_property}",
        RoleARN=Sub(
            "arn:aws:iam::${AWS::AccountId}:role/aws-service-role/"
            "dynamodb.application-autoscaling.${AWS::URLSuffix}/"
            "AWSServiceRoleForApplicationAutoScaling_DynamoDBTable"
        ),
        ResourceId=scablable_resource,
    )
    scaling_policy = ScalingPolicy(
        f"{scaling_target.title}ScalingPolicy",
        DependsOn=[scaling_target.title],
        PolicyName=f"{scalable_property}AutoScalingPolicy",
        PolicyType="TargetTrackingScaling",
        ScalingTargetId=Ref(scaling_target),
        TargetTrackingScalingPolicyConfiguration=TargetTrackingScalingPolicyConfiguration(
            TargetValue=scale_definition["TargetValue"],
            ScaleInCooldown=set_else_none(
                "ScaleInCooldown", scale_definition, alt_value=60
            ),
            ScaleOutCooldown=set_else_none(
                "ScaleOutCooldown", scale_definition, alt_value=60
            ),
            PredefinedMetricSpecification=PredefinedMetricSpecification(
                PredefinedMetricType=property_mapping[scalable_property][
                    "PredefinedMetricType"
                ]
            ),
        ),
    )
    target = add_resource(template, scaling_target)
    policy = add_resource(template, scaling_policy)
    return target, policy
Ejemplo n.º 12
0
def set_ecs_cw_policy(
    family: ComposeFamily,
    prometheus_parameter: Parameter,
    cw_config_parameter: Parameter,
) -> None:
    """
    Renders the IAM policy to grant the TaskRole access to CW, ECS and SSM Parameters

    :param family: The Service family
    :param troposphere.ssm.Parameter prometheus_parameter:
    :param troposphere.ssm.Parameter cw_config_parameter:
    """
    ecs_sd_policy = PolicyType(
        "CWAgentAccessForPrometheusScraping",
        PolicyName="CWAgentAccessForPrometheusScraping",
        PolicyDocument={
            "Version":
            "2012-10-17",
            "Statement": [
                {
                    "Sid":
                    "EnableCreationAndManagementOfPrometheusLogEvents",
                    "Effect":
                    "Allow",
                    "Action": ["logs:GetLogEvents", "logs:PutLogEvents"],
                    "Resource":
                    Sub(f"arn:${{{AWS_PARTITION}}}:logs:*:${{{AWS_ACCOUNT_ID}}}:"
                        "log-group:/aws/ecs/containerinsights/*:log-stream:*"),
                },
                {
                    "Sid":
                    "EnableCreationAndManagementOfPrometheusCloudwatchLogGroupsAndStreams",
                    "Effect":
                    "Allow",
                    "Action": [
                        "logs:CreateLogStream",
                        "logs:DescribeLogStreams",
                        "logs:PutRetentionPolicy",
                        "logs:CreateLogGroup",
                    ],
                    "Resource":
                    Sub(f"arn:${{{AWS_PARTITION}}}:logs:*:${{{AWS_ACCOUNT_ID}}}:"
                        "log-group:/aws/ecs/containerinsights/*"),
                },
                {
                    "Sid": "ECSTaskDefinitionsAccess",
                    "Effect": "Allow",
                    "Action": ["ecs:DescribeTaskDefinition"],
                    "Resource": "*",
                },
                {
                    "Sid":
                    "ServiceDiscoveryAccess",
                    "Effect":
                    "Allow",
                    "Action": [
                        "ecs:DescribeTasks",
                        "ecs:ListTasks",
                        "ecs:DescribeContainerInstances",
                        "ecs:DescribeServices",
                        "ecs:ListServices",
                    ],
                    "Resource":
                    "*",
                    "Condition": {
                        "ArnEquals": {
                            "ecs:cluster":
                            Sub(f"arn:${{{AWS_PARTITION}}}:ecs:${{{AWS_REGION}}}:${{{AWS_ACCOUNT_ID}}}"
                                f":cluster/${{{ecs_params.CLUSTER_NAME.title}}}"
                                )
                        }
                    },
                },
                {
                    "Sid":
                    "ExtractFromCloudWatchAgentServerPolicy",
                    "Effect":
                    "Allow",
                    "Action": ["ssm:GetParameter*"],
                    "Resource": [
                        Sub("arn:aws:ssm:*:${AWS::AccountId}:parameter/AmazonCloudWatch-*"
                            ),
                        Sub(f"arn:${{{AWS_PARTITION}}}:ssm:${{{AWS_REGION}}}:${{{AWS_ACCOUNT_ID}}}"
                            f":parameter${{{prometheus_parameter.title}}}"),
                        Sub(f"arn:${{{AWS_PARTITION}}}:ssm:${{{AWS_REGION}}}:${{{AWS_ACCOUNT_ID}}}"
                            f":parameter${{{cw_config_parameter.title}}}"),
                    ],
                },
            ],
        },
        Roles=[
            family.iam_manager.exec_role.name,
            family.iam_manager.task_role.name,
        ],
    )
    add_resource(family.template, ecs_sd_policy)