/
ecs.sp
581 lines (533 loc) · 21.2 KB
/
ecs.sp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
locals {
conformance_pack_ecs_common_tags = merge(local.aws_compliance_common_tags, {
service = "AWS/ECS"
})
}
control "ecs_cluster_container_instance_agent_connected" {
title = "ECS cluster container instances should have connected agent"
description = "This control checks if ECS cluster container instances have connected agent. This control fails if the agent is not connected."
query = query.ecs_cluster_container_instance_agent_connected
tags = local.conformance_pack_ecs_common_tags
}
control "ecs_service_not_publicly_accessible" {
title = "AWS ECS services should not have public IP addresses assigned to them automatically"
description = "This control checks whether AWS ECS services are configured to automatically assign public IP addresses. This control fails if AssignPublicIP is enabled."
query = query.ecs_service_not_publicly_accessible
tags = local.conformance_pack_ecs_common_tags
}
control "ecs_cluster_encryption_at_rest_enabled" {
title = "ECS clusters encryption at rest should be enabled"
description = "This control checks whether ECS Clustes have encryption at rest enabled. The check fails if encryption at rest is not enabled as sensitive data should be protected."
query = query.ecs_cluster_encryption_at_rest_enabled
tags = local.conformance_pack_ecs_common_tags
}
control "ecs_cluster_instance_in_vpc" {
title = "ECS cluster instances should be in a VPC"
description = "Deploy AWS ECS cluster instance within an AWS Virtual Private Cloud (AWS VPC) for a secure communication between a instance and other services within the AWS VPC."
query = query.ecs_cluster_instance_in_vpc
tags = local.conformance_pack_ecs_common_tags
}
control "ecs_cluster_no_registered_container_instance" {
title = "At least one instance should be registered with ECS cluster"
description = "This control ensures that at least one container instance is registered with an ECS cluster."
query = query.ecs_cluster_no_registered_container_instance
tags = local.conformance_pack_ecs_common_tags
}
control "ecs_service_load_balancer_attached" {
title = "ECS services should be attached to a load balancer"
description = "ECS service can be configured to use Elastic Load Balancing to distribute traffic evenly across the tasks in your service. It is recommended to use Application Load Balancers for your AWS ECS services so that you can take advantage of these latest features, unless your service requires a feature that is only available with Network Load Balancers or Classic Load Balancers."
query = query.ecs_service_load_balancer_attached
tags = local.conformance_pack_ecs_common_tags
}
control "ecs_task_definition_user_for_host_mode_check" {
title = "ECS task definition container definitions should be checked for host mode"
description = "Check if AWS Elastic Container Service (AWS ECS) task definition with host networking mode has 'privileged' or 'user' container definitions.The rule is non-compliant for task definitions with host network mode and container definitions of privileged=false or empty and user=root or empty."
query = query.ecs_task_definition_user_for_host_mode_check
tags = merge(local.conformance_pack_ecs_common_tags, {
cis_controls_v8_ig1 = "true"
cisa_cyber_essentials = "true"
fedramp_low_rev_4 = "true"
fedramp_moderate_rev_4 = "true"
ffiec = "true"
gxp_21_cfr_part_11 = "true"
hipaa_final_omnibus_security_rule_2013 = "true"
nist_800_171_rev_2 = "true"
nist_800_53_rev_5 = "true"
nist_csf = "true"
})
}
control "ecs_task_definition_logging_enabled" {
title = "ECS task definitions should have logging enabled"
description = "Ensure logging is enabled for task definitions so that you can access your containerized application logs for debugging and auditing purposes. On top of centralized logging, these log drivers often include additional capabilities that are useful for operation."
query = query.ecs_task_definition_logging_enabled
tags = local.conformance_pack_ecs_common_tags
}
control "ecs_cluster_container_insights_enabled" {
title = "ECS clusters should have container insights enabled"
description = "This control checks if ECS clusters use Container Insights. This control fails if Container Insights are not set up for a cluster."
query = query.ecs_cluster_container_insights_enabled
tags = merge(local.conformance_pack_ecs_common_tags, {
nist_csf = "true"
})
}
control "ecs_task_definition_container_non_privileged" {
title = "ECS containers should run as non-privileged"
description = "This control checks if the privileged parameter in the container definition of AWS ECS Task Definitions is set to true. The control fails if this parameter is equal to true."
query = query.ecs_task_definition_container_non_privileged
tags = merge(local.conformance_pack_ecs_common_tags, {
nist_csf = "true"
})
}
control "ecs_task_definition_container_readonly_root_filesystem" {
title = "ECS containers should be limited to read-only access to root filesystems"
description = "This control checks if ECS containers are limited to read-only access to mounted root filesystems. This control fails if the ReadonlyRootFilesystem parameter in the container definition of ECS task definitions is set to false."
query = query.ecs_task_definition_container_readonly_root_filesystem
tags = merge(local.conformance_pack_ecs_common_tags, {
nist_csf = "true"
})
}
control "ecs_task_definition_container_environment_no_secret" {
title = "ECS task definition containers should not have secrets passed as environment variables"
description = "This control checks if the key value of any variables in the environment parameter of container definitions includes AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, or ECS_ENGINE_AUTH_DATA. This control fails if a single environment variable in any container definition equals AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, or ECS_ENGINE_AUTH_DATA. This control does not cover environmental variables passed in from other locations such as AWS S3."
query = query.ecs_task_definition_container_environment_no_secret
tags = merge(local.conformance_pack_ecs_common_tags, {
nist_csf = "true"
})
}
control "ecs_task_definition_no_host_pid_mode" {
title = "ECS task definitions should not share the host's process namespace"
description = "This control checks if AWS ECS task definitions are configured to share a host's process namespace with its containers. The control fails if the task definition shares the host's process namespace with the containers running on it."
query = query.ecs_task_definition_no_host_pid_mode
tags = merge(local.conformance_pack_ecs_common_tags, {
nist_csf = "true"
})
}
control "ecs_service_fargate_using_latest_platform_version" {
title = "ECS fargate services should run on the latest fargate platform version"
description = "This control checks if AWS ECS Fargate services are running the latest Fargate platform version. This control fails if the platform version is not the latest."
query = query.ecs_service_fargate_using_latest_platform_version
tags = merge(local.conformance_pack_ecs_common_tags, {
nist_csf = "true"
})
}
control "ecs_task_definition_no_root_user" {
title = "ECS task definitions should not use root user."
description = "This control checks if ECS task definitions have root user. This control fails if the ECS task definitions have root user."
query = query.ecs_task_definition_no_root_user
tags = local.conformance_pack_ecs_common_tags
}
control "ecs_cluster_no_active_services_count" {
title = "ECS cluster should be configured with active services"
description = "This control checks if ECS cluster have active services. This control fails if ECS cluster does not have any active services."
query = query.ecs_cluster_no_active_services_count
tags = local.conformance_pack_ecs_common_tags
}
query "ecs_cluster_encryption_at_rest_enabled" {
sql = <<-EOQ
with unencrypted_volumes as (
select
distinct cluster_arn
from
aws_ecs_container_instance as i,
aws_ec2_instance as e,
jsonb_array_elements(block_device_mappings) as b,
aws_ebs_volume as v
where
i.ec2_instance_id = e.instance_id
and b -> 'Ebs' ->> 'VolumeId' = v.volume_id
and not v.encrypted
)
select
c.cluster_arn as resource,
case
when c.registered_container_instances_count = 0 then 'skip'
when v.cluster_arn is not null then 'alarm'
else 'ok'
end as status,
case
when c.registered_container_instances_count = 0 then title || ' has no container instance registered.'
when v.cluster_arn is not null then c.title || ' encryption at rest disabled.'
else c.title || ' encryption at rest enabled.'
end as reason
${local.tag_dimensions_sql}
${replace(local.common_dimensions_qualifier_sql, "__QUALIFIER__", "c.")}
from
aws_ecs_cluster as c
left join unencrypted_volumes as v on v.cluster_arn = c.cluster_arn;
EOQ
}
query "ecs_cluster_instance_in_vpc" {
sql = <<-EOQ
select
c.arn as resource,
case
when i.vpc_id is null then 'alarm'
else 'ok'
end as status,
case
when i.vpc_id is null then c.title || ' not in VPC.'
else c.title || ' in VPC.'
end as reason
${replace(local.tag_dimensions_qualifier_sql, "__QUALIFIER__", "c.")}
${replace(local.common_dimensions_qualifier_sql, "__QUALIFIER__", "c.")}
from
aws_ecs_container_instance as c
left join aws_ec2_instance as i on c.ec2_instance_id = i.instance_id;
EOQ
}
query "ecs_cluster_no_registered_container_instance" {
sql = <<-EOQ
select
cluster_arn as resource,
case
when registered_container_instances_count = 0 then 'alarm'
else 'ok'
end as status,
case
when registered_container_instances_count = 0 then title || ' has no container instance registered.'
else title || ' has ' || registered_container_instances_count || ' container instance(s) registered.'
end as reason
${local.tag_dimensions_sql}
${local.common_dimensions_sql}
from
aws_ecs_cluster;
EOQ
}
query "ecs_service_load_balancer_attached" {
sql = <<-EOQ
select
arn as resource,
case
when jsonb_array_length(load_balancers) = 0 then 'alarm'
else 'ok'
end as status,
case
when jsonb_array_length(load_balancers) = 0 then title || ' has no load balancer attached.'
else title || ' has ' || jsonb_array_length(load_balancers) || ' load balancer(s) attached.'
end as reason
${local.tag_dimensions_sql}
${local.common_dimensions_sql}
from
aws_ecs_service;
EOQ
}
query "ecs_task_definition_user_for_host_mode_check" {
sql = <<-EOQ
with host_network_task_definition as (
select
distinct task_definition_arn as arn
from
aws_ecs_task_definition,
jsonb_array_elements(container_definitions) as c
where
network_mode = 'host'
and
(c ->> 'Privileged' is not null
and c ->> 'Privileged' <> 'false'
)
and
( c ->> 'User' is not null
and c ->> 'User' <> 'root'
)
)
select
a.task_definition_arn as resource,
case
when a.network_mode is null or a.network_mode <> 'host' then 'skip'
when b.arn is not null then 'ok'
else 'alarm'
end as status,
case
when a.network_mode is null or a.network_mode <> 'host' then a.title || ' not host network mode.'
when b.arn is not null then a.title || ' have secure host network mode.'
else a.title || ' not have secure host network mode.'
end as reason
${local.tag_dimensions_sql}
${local.common_dimensions_sql}
from
aws_ecs_task_definition as a
left join host_network_task_definition as b on a.task_definition_arn = b.arn;
EOQ
}
query "ecs_task_definition_logging_enabled" {
sql = <<-EOQ
with task_definitions_logging_enabled as (
select
distinct task_definition_arn as arn
from
aws_ecs_task_definition,
jsonb_array_elements(container_definitions) as c
where
c ->> 'LogConfiguration' is not null
)
select
a.task_definition_arn as resource,
case
when b.arn is not null then 'ok'
else 'alarm'
end as status,
case
when b.arn is not null then a.title || ' logging enabled.'
else a.title || ' logging disabled.'
end as reason
${local.tag_dimensions_sql}
${local.common_dimensions_sql}
from
aws_ecs_task_definition as a
left join task_definitions_logging_enabled as b on a.task_definition_arn = b.arn;
EOQ
}
query "ecs_cluster_container_insights_enabled" {
sql = <<-EOQ
select
cluster_arn as resource,
case
when s ->> 'Name' = 'containerInsights' and s ->> 'Value' = 'enabled' then 'ok'
else 'alarm'
end as status,
case
when s ->> 'Name' = 'containerInsights' and s ->> 'Value' = 'enabled' then title || ' Container Insights enabled.'
else title || ' Container Insights disabled.'
end as reason
${local.tag_dimensions_sql}
${local.common_dimensions_sql}
from
aws_ecs_cluster as c,
jsonb_array_elements(settings) as s;
EOQ
}
query "ecs_task_definition_container_non_privileged" {
sql = <<-EOQ
with privileged_container_definition as (
select
distinct task_definition_arn as arn
from
aws_ecs_task_definition,
jsonb_array_elements(container_definitions) as c
where
c ->> 'Privileged' = 'true'
)
select
d.task_definition_arn as resource,
case
when c.arn is null then 'ok'
else 'alarm'
end as status,
case
when c.arn is null then d.title || ' does not have elevated privileges.'
else d.title || ' has elevated privileges.'
end as reason
${local.tag_dimensions_sql}
${local.common_dimensions_sql}
from
aws_ecs_task_definition as d
left join privileged_container_definition as c on d.task_definition_arn = c.arn;
EOQ
}
query "ecs_task_definition_container_readonly_root_filesystem" {
sql = <<-EOQ
with privileged_container_definition as (
select
distinct task_definition_arn as arn
from
aws_ecs_task_definition,
jsonb_array_elements(container_definitions) as c
where
c ->> 'ReadonlyRootFilesystem' = 'true'
)
select
d.task_definition_arn as resource,
case
when c.arn is not null then 'ok'
else 'alarm'
end as status,
case
when c.arn is not null then d.title || ' containers limited to read-only access to root filesystems.'
else d.title || ' containers not limited to read-only access to root filesystems.'
end as reason
${local.tag_dimensions_sql}
${local.common_dimensions_sql}
from
aws_ecs_task_definition as d
left join privileged_container_definition as c on d.task_definition_arn = c.arn;
EOQ
}
query "ecs_task_definition_container_environment_no_secret" {
sql = <<-EOQ
with definitions_with_secret_environment_variable as (
select
distinct task_definition_arn as arn
from
aws_ecs_task_definition,
jsonb_array_elements(container_definitions) as c,
jsonb_array_elements( c -> 'Environment') as e,
jsonb_array_elements(
case jsonb_typeof(c -> 'Secrets')
when 'array' then (c -> 'Secrets')
else null end
) as s
where
e ->> 'Name' like any (array ['AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY','ECS_ENGINE_AUTH_DATA'])
or s ->> 'Name' like any (array ['AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY','ECS_ENGINE_AUTH_DATA'])
)
select
d.task_definition_arn as resource,
case
when e.arn is null then 'ok'
else 'alarm'
end as status,
case
when e.arn is null then d.title || ' container environment variables does not have secrets.'
else d.title || ' container environment variables have secrets.'
end as reason
${local.tag_dimensions_sql}
${local.common_dimensions_sql}
from
aws_ecs_task_definition as d
left join definitions_with_secret_environment_variable as e on d.task_definition_arn = e.arn;
EOQ
}
query "ecs_task_definition_no_host_pid_mode" {
sql = <<-EOQ
select
task_definition_arn as resource,
case
when pid_mode = 'host' then 'alarm'
else 'ok'
end as status,
case
when pid_mode = 'host' then title || ' shares the host process namespace.'
else title || ' does not share the host process namespace.'
end as reason
${local.tag_dimensions_sql}
${local.common_dimensions_sql}
from
aws_ecs_task_definition;
EOQ
}
query "ecs_service_fargate_using_latest_platform_version" {
sql = <<-EOQ
select
arn as resource,
case
when launch_type <> 'FARGATE' then 'skip'
when platform_version = 'LATEST' then 'ok'
else 'alarm'
end as status,
case
when launch_type <> 'FARGATE' then title || ' is ' || launch_type || ' service.'
when platform_version = 'LATEST' then title || ' running on the latest fargate platform version.'
else title || ' not running on the latest fargate platform version.'
end as reason
${local.tag_dimensions_sql}
${local.common_dimensions_sql}
from
aws_ecs_service;
EOQ
}
query "ecs_cluster_container_instance_agent_connected" {
sql = <<-EOQ
with unconnected_agent_instance as (
select
distinct cluster_arn
from
aws_ecs_container_instance
where
agent_connected = false and status = 'ACTIVE'
)
select
c.cluster_arn as resource,
case
when c.registered_container_instances_count = 0 then 'skip'
when i.cluster_arn is null then 'ok'
else 'alarm'
end as status,
case
when c.registered_container_instances_count = 0 then title || ' has no container instance registered.'
when i.cluster_arn is null then title || ' container instance has connected agent.'
else title || ' container instance is either draining or has unconnected agents.'
end as reason
${local.tag_dimensions_sql}
${local.common_dimensions_sql}
from
aws_ecs_cluster as c
left join unconnected_agent_instance as i on c.cluster_arn = i.cluster_arn;
EOQ
}
query "ecs_service_not_publicly_accessible" {
sql = <<-EOQ
with service_awsvpc_mode_task_definition as (
select
a.service_name as service_name,
b.task_definition_arn as task_definition
from
aws_ecs_service as a
left join aws_ecs_task_definition as b on a.task_definition = b.task_definition_arn
where
b.network_mode = 'awsvpc'
)
select
a.arn as resource,
case
when b.service_name is null then 'skip'
when network_configuration -> 'AwsvpcConfiguration' ->> 'AssignPublicIp' = 'DISABLED' then 'ok'
else 'alarm'
end as status,
case
when b.service_name is null then a.title || ' task definition not host network mode.'
when network_configuration -> 'AwsvpcConfiguration' ->> 'AssignPublicIp' = 'DISABLED' then a.title || ' not publicly accessible.'
else a.title || ' publicly accessible.'
end as reason
${local.tag_dimensions_sql}
${local.common_dimensions_sql}
from
aws_ecs_service as a
left join service_awsvpc_mode_task_definition as b on a.service_name = b.service_name;
EOQ
}
query "ecs_task_definition_no_root_user" {
sql = <<-EOQ
with root_user_task_definition as (
select
distinct task_definition_arn as arn
from
aws_ecs_task_definition,
jsonb_array_elements(container_definitions) as c
where
c ->> 'User' = 'root'
)
select
a.task_definition_arn as resource,
case
when b.arn is not null then 'alarm'
else 'ok'
end as status,
case
when b.arn is not null then a.title || ' have root user.'
else a.title || ' does not have root user.'
end as reason
${local.tag_dimensions_sql}
${local.common_dimensions_sql}
from
aws_ecs_task_definition as a
left join root_user_task_definition as b on a.task_definition_arn = b.arn;
EOQ
}
query "ecs_cluster_no_active_services_count" {
sql = <<-EOQ
select
cluster_arn as resource,
case
when active_services_count > 0 then 'ok'
else 'alarm'
end as status,
case
when active_services_count > 0 then title || ' has ' || active_services_count || ' active service(s).'
else title || ' has no active service.'
end as reason
${local.tag_dimensions_sql}
${local.common_dimensions_sql}
from
aws_ecs_cluster;
EOQ
}