Skip to content

Commit

Permalink
feat: Limiting read-only access to root file systems in ECS (#523)
Browse files Browse the repository at this point in the history
Limiting read-only access for ECS tasks deployed by data.all, reasoning
detailed in: #426

Out of the 7 ECS tasks that gets deployed, only CDKProxy performs
multiple write operations to the root filesystem.
The workaround is to mount [bind
volumes](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/bind-mounts.html)
to the proper paths in the filesystem:

- **/dataall:** required for cdk deploy write operations (cdk.out,
cdk.context.json) and further file write operations invoked through
dataa.all business logic like archiving objects for the Glue profiling
job
- **/tmp:** required since by upon importing aws_cdk libraries a write
operation happens to the /tmp folder


Since the [currently used CDK
class](https://docs.aws.amazon.com/cdk/api/v1/python/aws_cdk.aws_ecs/FargateTaskDefinition.html)
for the Fargate task definition doesn't allow the definition of mount
points, I had to replace the it with the [CFN-style
class.](https://docs.aws.amazon.com/cdk/api/v2/python/aws_cdk.aws_ecs/CfnTaskDefinition.html)


**[Testing]**
I've created 2 environments and a dataset, and performed the sharing of
the dataset between the 2 environments.
I've verified, that:

- the newly created CDKProxy task definition has the same attributes as
the old one (with the further addition of the
ReadOnlyRootFileSystem=True flag and the 2 new bind volumes)
- the other 6 task definitions have ReadOnlyRootFileSystem=True enabled
- all 7 tasks were executed without failure with the new setting
- the security alert in security hub got archived



By submitting this pull request, I confirm that my contribution is made
under the terms of the Apache 2.0 license.
  • Loading branch information
dbalintx authored Jul 4, 2023
1 parent dfbee81 commit a3a6bde
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 44 deletions.
3 changes: 2 additions & 1 deletion backend/docker/prod/ecs/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,13 @@ RUN /bin/bash -c "pip3.8 install -r /dh.requirements.txt"
RUN /bin/bash -c "pip3.8 install -r /cdk.requirements.txt"

ADD backend/dataall /dataall
VOLUME ["/dataall"]
ADD backend/blueprints /blueprints
ADD backend/cdkproxymain.py /cdkproxymain.py

RUN mkdir -p dataall/cdkproxy/assets/glueprofilingjob/jars
RUN mkdir -p blueprints/ml_data_pipeline/engine/glue/jars
RUN curl https://repo1.maven.org/maven2/com/amazon/deequ/deequ/$DEEQU_VERSION/deequ-$DEEQU_VERSION.jar --output dataall/cdkproxy/assets/glueprofilingjob/jars/deequ-$DEEQU_VERSION.jar
ADD https://repo1.maven.org/maven2/com/amazon/deequ/deequ/$DEEQU_VERSION/deequ-$DEEQU_VERSION.jar /dataall/cdkproxy/assets/glueprofilingjob/jars/
RUN cp -f dataall/cdkproxy/assets/glueprofilingjob/jars/deequ-$DEEQU_VERSION.jar blueprints/ml_data_pipeline/engine/glue/jars/deequ-$DEEQU_VERSION.jar

WORKDIR /
Expand Down
2 changes: 1 addition & 1 deletion deploy/stacks/backend_stack.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ def __init__(
],
database=aurora_stack.cluster.cluster_identifier,
ecs_cluster=self.ecs_stack.ecs_cluster,
ecs_task_definitions=self.ecs_stack.ecs_task_definitions,
ecs_task_definitions_families=self.ecs_stack.ecs_task_definitions_families,
backend_api=self.lambda_api_stack.backend_api_name,
queue_name=sqs_stack.queue.queue_name,
**kwargs,
Expand Down
108 changes: 75 additions & 33 deletions deploy/stacks/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,48 +49,88 @@ def __init__(
self.task_role = self.create_task_role(envname, resource_prefix, pivot_role_name)
self.cicd_stacks_updater_role = self.create_cicd_stacks_updater_role(envname, resource_prefix, tooling_account_id)

cdkproxy_task_definition = ecs.FargateTaskDefinition(
cdkproxy_container_name = f'container'
cdkproxy_log_group = self.create_log_group(
envname, resource_prefix, log_group_name='cdkproxy'
)
cdkproxy_image = ecs.ContainerImage.from_ecr_repository(
repository=ecr_repository,
tag=cdkproxy_image_tag
)

cdkproxy_task_definition = ecs.CfnTaskDefinition(
self,
f'{resource_prefix}-{envname}-cdkproxy',
cpu=1024,
memory_limit_mib=2048,
task_role=self.task_role,
execution_role=self.task_role,
container_definitions=[ecs.CfnTaskDefinition.ContainerDefinitionProperty(
image=cdkproxy_image.image_name,
name=cdkproxy_container_name,
command=['python3.8', '-m', 'dataall.tasks.cdkproxy'],
environment=[
ecs.CfnTaskDefinition.KeyValuePairProperty(
name="AWS_REGION",
value=self.region
),
ecs.CfnTaskDefinition.KeyValuePairProperty(
name="envname",
value=envname
),
ecs.CfnTaskDefinition.KeyValuePairProperty(
name="LOGLEVEL",
value="DEBUG"
),
],
essential=True,
log_configuration=ecs.CfnTaskDefinition.LogConfigurationProperty(
log_driver="awslogs",
options={
"awslogs-group": cdkproxy_log_group.log_group_name,
"awslogs-region": self.region,
"awslogs-stream-prefix": "task"
},
),
mount_points=[
ecs.CfnTaskDefinition.MountPointProperty(
container_path="/dataall",
read_only=False,
source_volume="dataall_scratch"
),
ecs.CfnTaskDefinition.MountPointProperty(
container_path="/tmp",
read_only=False,
source_volume="dataall_tmp_scratch"
)
],
readonly_root_filesystem=True,
)],
cpu="1024",
memory="2048",
execution_role_arn=self.task_role.role_arn,
family=f'{resource_prefix}-{envname}-cdkproxy',
)

cdkproxy_container = cdkproxy_task_definition.add_container(
f'ShareManagementTaskContainer{envname}',
container_name=f'container',
image=ecs.ContainerImage.from_ecr_repository(
repository=ecr_repository, tag=cdkproxy_image_tag
),
environment={
'AWS_REGION': self.region,
'envname': envname,
'LOGLEVEL': 'DEBUG',
},
command=['python3.8', '-m', 'dataall.tasks.cdkproxy'],
logging=ecs.LogDriver.aws_logs(
stream_prefix='task',
log_group=self.create_log_group(
envname, resource_prefix, log_group_name='cdkproxy'
requires_compatibilities=[ecs.Compatibility.FARGATE.name],
task_role_arn=self.task_role.role_arn,
network_mode="awsvpc",
volumes=[
ecs.CfnTaskDefinition.VolumeProperty(
name="dataall_scratch"
),
),
ecs.CfnTaskDefinition.VolumeProperty(
name="dataall_tmp_scratch"
)
]
)

ssm.StringParameter(
self,
f'CDKProxyTaskDefParam{envname}',
parameter_name=f'/dataall/{envname}/ecs/task_def_arn/cdkproxy',
string_value=cdkproxy_task_definition.task_definition_arn,
string_value=cdkproxy_task_definition.attr_task_definition_arn,
)

ssm.StringParameter(
self,
f'CDKProxyContainerParam{envname}',
parameter_name=f'/dataall/{envname}/ecs/container/cdkproxy',
string_value=cdkproxy_container.container_name,
string_value=cdkproxy_container_name,
)

scheduled_tasks_sg = self.create_task_sg(
Expand Down Expand Up @@ -258,6 +298,7 @@ def __init__(
envname, resource_prefix, log_group_name='share-manager'
),
),
readonly_root_filesystem=True,
)

ssm.StringParameter(
Expand Down Expand Up @@ -300,13 +341,13 @@ def __init__(
)

self.ecs_cluster = cluster
self.ecs_task_definitions = [
cdkproxy_task_definition,
sync_tables_task.task_definition,
update_bucket_policies_task.task_definition,
catalog_indexer_task.task_definition,
share_management_task_definition,
subscriptions_task.task_definition,
self.ecs_task_definitions_families = [
cdkproxy_task_definition.family,
sync_tables_task.task_definition.family,
update_bucket_policies_task.task_definition.family,
catalog_indexer_task.task_definition.family,
share_management_task_definition.family,
subscriptions_task.task_definition.family,
]

def create_cicd_stacks_updater_role(self, envname, resource_prefix, tooling_account_id):
Expand Down Expand Up @@ -544,6 +585,7 @@ def set_scheduled_task(
environment=environment,
command=command,
logging=ecs.LogDriver.aws_logs(stream_prefix='task', log_group=log_group),
readonly_root_filesystem=True,
)
scheduled_task = ecs_patterns.ScheduledFargateTask(
self,
Expand Down
17 changes: 8 additions & 9 deletions deploy/stacks/monitoring.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def __init__(
lambdas: [_lambda.Function] = None,
database='dataalldevdb',
ecs_cluster: ecs.Cluster = None,
ecs_task_definitions: [ecs.FargateTaskDefinition] = None,
ecs_task_definitions_families = None,
backend_api=None,
queue_name: str = None,
**kwargs,
Expand All @@ -51,7 +51,7 @@ def __init__(
backend_api,
database,
ecs_cluster,
ecs_task_definitions,
ecs_task_definitions_families,
envname,
lambdas,
resource_prefix,
Expand Down Expand Up @@ -136,7 +136,7 @@ def create_cw_dashboard(
backend_api,
database,
ecs_cluster,
ecs_task_definitions,
ecs_task_definitions_families,
envname,
lambdas,
resource_prefix,
Expand Down Expand Up @@ -173,19 +173,18 @@ def create_cw_dashboard(
cf_ecs.build_ecs_cluster_task_count_widget(cluster_name),
)

if ecs_task_definitions:
if ecs_task_definitions_families:
dashboard.add_widgets(cw.TextWidget(width=24, markdown='# ECS Tasks'))
task: ecs.FargateTaskDefinition
for task in ecs_task_definitions:
for task_family in ecs_task_definitions_families:
dashboard.add_widgets(
cf_ecs.build_ecs_task_container_insight_cpu_widget(
cluster_name, task.family
cluster_name, task_family
),
cf_ecs.build_ecs_task_container_insight_memory_widget(
cluster_name, task.family
cluster_name, task_family
),
cf_ecs.build_ecs_task_container_insight_storage_widget(
cluster_name, task.family
cluster_name, task_family
),
)
if database:
Expand Down

0 comments on commit a3a6bde

Please sign in to comment.