diff --git a/modules/ecs_fargate/README.md b/modules/ecs_fargate/README.md index f361015..4b9f0b6 100644 --- a/modules/ecs_fargate/README.md +++ b/modules/ecs_fargate/README.md @@ -255,6 +255,7 @@ No modules. | [dd\_is\_datadog\_dependency\_enabled](#input\_dd\_is\_datadog\_dependency\_enabled) | Whether the Datadog Agent container is a dependency for other containers | `bool` | `false` | no | | [dd\_log\_collection](#input\_dd\_log\_collection) | Configuration for Datadog Log Collection |
object({
enabled = optional(bool, false)
fluentbit_config = optional(object({
registry = optional(string, "public.ecr.aws/aws-observability/aws-for-fluent-bit")
image_version = optional(string, "stable")
cpu = optional(number)
memory_limit_mib = optional(number)
is_log_router_essential = optional(bool, false)
is_log_router_dependency_enabled = optional(bool, false)
environment = optional(list(object({
name = string
value = string
})), [])
log_router_health_check = optional(object({
command = optional(list(string))
interval = optional(number)
retries = optional(number)
start_period = optional(number)
timeout = optional(number)
}),
{
command = ["CMD-SHELL", "exit 0"]
interval = 5
retries = 3
start_period = 15
timeout = 5
}
)
firelens_options = optional(object({
config_file_type = optional(string)
config_file_value = optional(string)
}))
log_driver_configuration = optional(object({
host_endpoint = optional(string, "http-intake.logs.datadoghq.com")
tls = optional(bool)
compress = optional(string)
service_name = optional(string)
source_name = optional(string)
message_key = optional(string)
}),
{
host_endpoint = "http-intake.logs.datadoghq.com"
}
)
mountPoints = optional(list(object({
sourceVolume : string,
containerPath : string,
readOnly : bool
})), [])
dependsOn = optional(list(object({
containerName : string,
condition : string
})), [])
}),
{
fluentbit_config = {
registry = "public.ecr.aws/aws-observability/aws-for-fluent-bit"
image_version = "stable"
log_driver_configuration = {
host_endpoint = "http-intake.logs.datadoghq.com"
}
}
}
)
}) | {
"enabled": false,
"fluentbit_config": {
"is_log_router_essential": false,
"log_driver_configuration": {
"host_endpoint": "http-intake.logs.datadoghq.com"
}
}
} | no |
| [dd\_memory\_limit\_mib](#input\_dd\_memory\_limit\_mib) | Datadog Agent container memory limit in MiB | `number` | `null` | no |
+| [dd\_readonly\_root\_filesystem](#input\_dd\_readonly\_root\_filesystem) | Datadog Agent container runs with read-only root filesystem enabled | `bool` | `false` | no |
| [dd\_registry](#input\_dd\_registry) | Datadog Agent image registry | `string` | `"public.ecr.aws/datadog/agent"` | no |
| [dd\_service](#input\_dd\_service) | The task service name. Used for tagging (UST) | `string` | `null` | no |
| [dd\_site](#input\_dd\_site) | Datadog Site | `string` | `"datadoghq.com"` | no |
diff --git a/modules/ecs_fargate/datadog.tf b/modules/ecs_fargate/datadog.tf
index d22fda2..e2ce992 100644
--- a/modules/ecs_fargate/datadog.tf
+++ b/modules/ecs_fargate/datadog.tf
@@ -77,6 +77,27 @@ locals {
}
] : []
+ dd_agent_mount = concat(
+ local.apm_dsd_mount,
+ var.dd_readonly_root_filesystem ? [
+ {
+ containerPath = "/etc/datadog-agent"
+ sourceVolume = "agent-config"
+ readOnly = false
+ },
+ {
+ containerPath = "/tmp"
+ sourceVolume = "agent-tmp"
+ readOnly = false
+ },
+ {
+ containerPath = "/opt/datadog-agent/run"
+ sourceVolume = "agent-run"
+ readOnly = false
+ }
+ ] : []
+ )
+
apm_socket_var = local.is_apm_socket_mount ? [
{
name = "DD_TRACE_AGENT_URL"
@@ -223,6 +244,18 @@ locals {
)
]
+ rofs_volumes = var.dd_readonly_root_filesystem ? [
+ {
+ name = "agent-config"
+ },
+ {
+ name = "agent-tmp"
+ },
+ {
+ name = "agent-run"
+ }
+ ] : []
+
# Volume configuration for task
apm_dsd_volume = local.is_apm_dsd_volume ? [
{
@@ -238,6 +271,7 @@ locals {
modified_volumes = concat(
[for k, v in coalesce(var.volumes, []) : v],
+ local.rofs_volumes,
local.apm_dsd_volume,
local.cws_volume,
)
@@ -264,6 +298,10 @@ locals {
name = "DD_INSTALL_INFO_INSTALLER_VERSION"
value = local.install_info_installer_version
},
+ {
+ name = "DD_LOG_FILE"
+ value = "/opt/datadog-agent/run/logs"
+ }
]
dynamic_env = [
@@ -308,52 +346,85 @@ locals {
local.dd_environment,
)
+ dd_agent_dependency = concat(
+ var.dd_readonly_root_filesystem ? [
+ {
+ condition = "SUCCESS"
+ containerName = "init-volume"
+ }
+ ] : [],
+ try(var.dd_log_collection.fluentbit_config.is_log_router_dependency_enabled, false) && local.dd_firelens_log_configuration != null ? local.log_router_dependency : [],
+ )
+
# Datadog Agent container definition
- dd_agent_container = [
- merge(
+ dd_agent_container = concat(
+ var.dd_readonly_root_filesystem ? [
{
- name = "datadog-agent"
- image = "${var.dd_registry}:${var.dd_image_version}"
- essential = var.dd_essential
- environment = local.dd_agent_env
- dockerLabels = var.dd_docker_labels
- cpu = var.dd_cpu
- memory = var.dd_memory_limit_mib
- secrets = var.dd_api_key_secret != null ? [
+ cpu = 0
+ memory = 128
+ name = "init-volume"
+ image = "${var.dd_registry}:${var.dd_image_version}"
+ essential = false
+ readOnlyRootFilesystem = true
+ command = ["/bin/sh", "-c", "cp -vnR /etc/datadog-agent/* /agent-config/ && exit 0"]
+ mountPoints = [
{
- name = "DD_API_KEY"
- valueFrom = var.dd_api_key_secret.arn
+ sourceVolume = "agent-config"
+ containerPath = "/agent-config"
+ readOnly = false
}
- ] : []
- portMappings = [
- {
- containerPort = 8125
- hostPort = 8125
- protocol = "udp"
- },
- {
- containerPort = 8126
- hostPort = 8126
- protocol = "tcp"
+ ]
+ }
+ ] : [],
+ [
+ merge(
+ {
+ name = "datadog-agent"
+ image = "${var.dd_registry}:${var.dd_image_version}"
+ essential = var.dd_essential
+ environment = local.dd_agent_env
+ dockerLabels = var.dd_docker_labels
+ cpu = var.dd_cpu
+ memory = var.dd_memory_limit_mib
+
+ readonlyRootFilesystem = var.dd_readonly_root_filesystem
+ secrets = var.dd_api_key_secret != null ? [
+ {
+ name = "DD_API_KEY"
+ valueFrom = var.dd_api_key_secret.arn
+ }
+ ] : []
+ portMappings = [
+ {
+ containerPort = 8125
+ hostPort = 8125
+ protocol = "udp"
+ },
+ {
+ containerPort = 8126
+ hostPort = 8126
+ protocol = "tcp"
+ }
+ ],
+
+ mountPoints = local.dd_agent_mount,
+ logConfiguration = local.dd_firelens_log_configuration,
+ dependsOn = local.dd_agent_dependency
+ systemControls = []
+ volumesFrom = []
+ },
+ try(var.dd_health_check.command == null, true) ? {} : {
+ healthCheck = {
+ command = var.dd_health_check.command
+ interval = var.dd_health_check.interval
+ timeout = var.dd_health_check.timeout
+ retries = var.dd_health_check.retries
+ startPeriod = var.dd_health_check.start_period
}
- ],
- mountPoints = local.apm_dsd_mount,
- logConfiguration = local.dd_firelens_log_configuration,
- dependsOn = try(var.dd_log_collection.fluentbit_config.is_log_router_dependency_enabled, false) && local.dd_firelens_log_configuration != null ? local.log_router_dependency : [],
- systemControls = []
- volumesFrom = []
- },
- try(var.dd_health_check.command == null, true) ? {} : {
- healthCheck = {
- command = var.dd_health_check.command
- interval = var.dd_health_check.interval
- timeout = var.dd_health_check.timeout
- retries = var.dd_health_check.retries
- startPeriod = var.dd_health_check.start_period
}
- }
- )
- ]
+ )
+ ]
+ )
dd_log_environment = var.dd_log_collection.fluentbit_config.environment != null ? var.dd_log_collection.fluentbit_config.environment : []
diff --git a/modules/ecs_fargate/main.tf b/modules/ecs_fargate/main.tf
index 28213a6..16d189a 100644
--- a/modules/ecs_fargate/main.tf
+++ b/modules/ecs_fargate/main.tf
@@ -177,6 +177,10 @@ resource "aws_ecs_task_definition" "this" {
condition = var.dd_log_collection.enabled == false || (var.dd_log_collection.enabled == true && local.is_linux == true)
error_message = "Log collection is not supported on Windows. Please set `dd_log_collection.enabled` to `false`."
}
+ precondition {
+ condition = var.dd_readonly_root_filesystem == false || (var.dd_readonly_root_filesystem == true && local.is_linux == true)
+ error_message = "Readonly root filesystem is only supported on Linux. Please set `dd_readonly_root_filesystem` to `false`."
+ }
# Must provide only one of the two Datadog API key options
precondition {
condition = (var.dd_api_key == null && var.dd_api_key_secret != null) || (var.dd_api_key != null && var.dd_api_key_secret == null)
diff --git a/modules/ecs_fargate/variables.tf b/modules/ecs_fargate/variables.tf
index d5cc82e..9f91715 100644
--- a/modules/ecs_fargate/variables.tf
+++ b/modules/ecs_fargate/variables.tf
@@ -65,6 +65,13 @@ variable "dd_is_datadog_dependency_enabled" {
nullable = false
}
+variable "dd_readonly_root_filesystem" {
+ description = "Datadog Agent container runs with read-only root filesystem enabled"
+ type = bool
+ default = false
+ nullable = false
+}
+
variable "dd_health_check" {
description = "Datadog Agent health check configuration"
type = object({
diff --git a/smoke_tests/ecs_fargate/all-dd-disabled.tf b/smoke_tests/ecs_fargate/all-dd-disabled.tf
index 46aab76..ef913ea 100644
--- a/smoke_tests/ecs_fargate/all-dd-disabled.tf
+++ b/smoke_tests/ecs_fargate/all-dd-disabled.tf
@@ -17,6 +17,7 @@ module "dd_task_all_dd_disabled" {
dd_tags = "team:cont-p, owner:container-monitoring"
dd_essential = true
dd_is_datadog_dependency_enabled = false
+ dd_readonly_root_filesystem = false
dd_environment = []
diff --git a/smoke_tests/ecs_fargate/all-dd-inputs.tf b/smoke_tests/ecs_fargate/all-dd-inputs.tf
index 54f1951..2da83cd 100644
--- a/smoke_tests/ecs_fargate/all-dd-inputs.tf
+++ b/smoke_tests/ecs_fargate/all-dd-inputs.tf
@@ -17,6 +17,7 @@ module "dd_task_all_dd_inputs" {
dd_tags = "team:cont-p, owner:container-monitoring"
dd_essential = true
dd_is_datadog_dependency_enabled = true
+ dd_readonly_root_filesystem = true
dd_environment = [
{
diff --git a/smoke_tests/ecs_fargate/all-windows.tf b/smoke_tests/ecs_fargate/all-windows.tf
index a0946bb..0ac9526 100644
--- a/smoke_tests/ecs_fargate/all-windows.tf
+++ b/smoke_tests/ecs_fargate/all-windows.tf
@@ -16,6 +16,8 @@ module "dd_task_all_windows" {
dd_site = var.dd_site
dd_service = var.dd_service
+ dd_readonly_root_filesystem = false
+
dd_apm = {
enabled = true
}
diff --git a/smoke_tests/ecs_fargate/apm-dsd-tcp-udp.tf b/smoke_tests/ecs_fargate/apm-dsd-tcp-udp.tf
index e5f66d6..1bda78d 100644
--- a/smoke_tests/ecs_fargate/apm-dsd-tcp-udp.tf
+++ b/smoke_tests/ecs_fargate/apm-dsd-tcp-udp.tf
@@ -18,6 +18,8 @@ module "dd_task_apm_dsd_tcp_udp" {
dd_tags = "team:cont-p, owner:container-monitoring"
dd_essential = true
+ dd_readonly_root_filesystem = true
+
dd_dogstatsd = {
enabled = true,
socket_enabled = false,
diff --git a/smoke_tests/ecs_fargate/logging-only.tf b/smoke_tests/ecs_fargate/logging-only.tf
index 1caa355..12015e5 100644
--- a/smoke_tests/ecs_fargate/logging-only.tf
+++ b/smoke_tests/ecs_fargate/logging-only.tf
@@ -15,6 +15,8 @@ module "dd_task_logging_only" {
dd_service = var.dd_service
dd_essential = true
+ dd_readonly_root_filesystem = false
+
dd_dogstatsd = {
enabled = false,
}
diff --git a/smoke_tests/ecs_fargate/ust-docker-labels.tf b/smoke_tests/ecs_fargate/ust-docker-labels.tf
index d614c58..592db4b 100644
--- a/smoke_tests/ecs_fargate/ust-docker-labels.tf
+++ b/smoke_tests/ecs_fargate/ust-docker-labels.tf
@@ -20,6 +20,7 @@ module "dd_task_ust_docker_labels" {
dd_essential = true
dd_is_datadog_dependency_enabled = true
+ dd_readonly_root_filesystem = true
dd_log_collection = {
enabled = true,
diff --git a/tests/all_dd_disabled_test.go b/tests/all_dd_disabled_test.go
index d44efa3..2b8963e 100644
--- a/tests/all_dd_disabled_test.go
+++ b/tests/all_dd_disabled_test.go
@@ -59,8 +59,7 @@ func (s *ECSFargateSuite) TestAllDDDisabled() {
s.Equal(int32(3), *agentContainer.HealthCheck.Retries, "Agent health check retries should be 3")
s.Equal(int32(60), *agentContainer.HealthCheck.StartPeriod, "Agent health check start period should be 60")
- // Verify no mount points (apm/dsd volumes should not be present)
- s.Equal(0, len(agentContainer.MountPoints), "Expected no mount points for datadog-agent when features are disabled")
+ s.Equal(0, len(agentContainer.MountPoints), "Expected no mount points when features are disabled")
// Test dummy container
dummyContainer, found := GetContainer(containers, "dummy-container")
diff --git a/tests/all_dd_inputs_test.go b/tests/all_dd_inputs_test.go
index a03114b..b198ad3 100644
--- a/tests/all_dd_inputs_test.go
+++ b/tests/all_dd_inputs_test.go
@@ -25,7 +25,11 @@ func (s *ECSFargateSuite) TestAllDDInputs() {
err := json.Unmarshal([]byte(task["container_definitions"]), &containers)
s.NoError(err, "Failed to parse container definitions")
- s.Equal(6, len(containers), "Expected 6 containers in the task definition")
+ s.Equal(7, len(containers), "Expected 6 containers in the task definition")
+
+ initContainer, found := GetContainer(containers, "init-volume")
+ s.True(found, "Container init-volume not found in definitions")
+ AssertMountPoint(s.T(), initContainer, MountInitVolume)
// Test Agent Container
agentContainer, found := GetContainer(containers, "datadog-agent")
@@ -37,6 +41,9 @@ func (s *ECSFargateSuite) TestAllDDInputs() {
AssertPortMapping(s.T(), agentContainer, PortUDP)
AssertPortMapping(s.T(), agentContainer, PortTCP)
AssertMountPoint(s.T(), agentContainer, MountDdSocket)
+ AssertMountPoint(s.T(), agentContainer, MountAgentConfig)
+ AssertMountPoint(s.T(), agentContainer, MountAgentTmp)
+ AssertMountPoint(s.T(), agentContainer, MountAgentRun)
AssertContainerDependency(s.T(), agentContainer, DependencyLogRouter)
expectedAgentEnvvars := map[string]string{
diff --git a/tests/apm_dsd_tcp_udp_test.go b/tests/apm_dsd_tcp_udp_test.go
index 1a361bb..3f722e7 100644
--- a/tests/apm_dsd_tcp_udp_test.go
+++ b/tests/apm_dsd_tcp_udp_test.go
@@ -26,7 +26,7 @@ func (s *ECSFargateSuite) TestApmDsdTcpUdp() {
err := json.Unmarshal([]byte(task["container_definitions"]), &containers)
s.NoError(err, "Failed to parse container definitions")
- s.Equal(3, len(containers), "Expected 3 containers in the task definition")
+ s.Equal(4, len(containers), "Expected 3 containers in the task definition")
// Test Agent Container
agentContainer, found := GetContainer(containers, "datadog-agent")
@@ -60,8 +60,11 @@ func (s *ECSFargateSuite) TestApmDsdTcpUdp() {
}
AssertNotEnvVars(s.T(), agentContainer, disabledSocketEnvVars)
- // Verify no mount points (as sockets are not used)
- s.Equal(0, len(agentContainer.MountPoints), "Expected no mount points for datadog-agent when socket is disabled")
+ s.Equal(3, len(agentContainer.MountPoints), "Expected 3 mount points when socket is disabled")
+ // Verify none are apm/dsd volume mount points
+ for _, mountPoint := range agentContainer.MountPoints {
+ s.NotEqual("dd-socket", *mountPoint.SourceVolume, "Mount point should not be dd-socket")
+ }
// Test DogStatsD App Container
dogstatsdContainer, found := GetContainer(containers, "datadog-dogstatsd-app")
diff --git a/tests/logging_only_test.go b/tests/logging_only_test.go
index 92eba35..f9218e4 100644
--- a/tests/logging_only_test.go
+++ b/tests/logging_only_test.go
@@ -73,8 +73,7 @@ func (s *ECSFargateSuite) TestLoggingOnly() {
s.Equal("datadog-log-router", *agentContainer.DependsOn[0].ContainerName, "Agent should depend on datadog-log-router")
s.Equal(types.ContainerConditionHealthy, agentContainer.DependsOn[0].Condition, "Agent should depend on log router being healthy")
- // Verify no mount points
- s.Equal(0, len(agentContainer.MountPoints), "Expected no mount points for datadog-agent")
+ s.Equal(0, len(agentContainer.MountPoints), "Expected 2 mount points for datadog-agent")
// Test Log Router Container
logRouterContainer, found := GetContainer(containers, "datadog-log-router")
diff --git a/tests/ust_docker_labels_test.go b/tests/ust_docker_labels_test.go
index 6bde250..1cbd049 100644
--- a/tests/ust_docker_labels_test.go
+++ b/tests/ust_docker_labels_test.go
@@ -25,7 +25,7 @@ func (s *ECSFargateSuite) TestUSTDockerLabels() {
err := json.Unmarshal([]byte(task["container_definitions"]), &containers)
s.NoError(err, "Failed to parse container definitions")
- s.Equal(4, len(containers), "Expected 4 containers in the task definition (1 app container + 3 agent sidecar)")
+ s.Equal(5, len(containers), "Expected 4 containers in the task definition (1 app container + 3 agent sidecar)")
// Expected UST docker labels that should be present on all application containers
expectedUSTLabels := map[string]string{
diff --git a/tests/utils.go b/tests/utils.go
index d372f3b..2b679d7 100644
--- a/tests/utils.go
+++ b/tests/utils.go
@@ -16,6 +16,10 @@ import (
var (
MountDdSocket = types.MountPoint{SourceVolume: aws.String("dd-sockets"), ContainerPath: aws.String("/var/run/datadog"), ReadOnly: aws.Bool(false)}
MountCWS = types.MountPoint{SourceVolume: aws.String("cws-instrumentation-volume"), ContainerPath: aws.String("/cws-instrumentation-volume"), ReadOnly: aws.Bool(false)}
+ MountInitVolume = types.MountPoint{SourceVolume: aws.String("agent-config"), ContainerPath: aws.String("/agent-config"), ReadOnly: aws.Bool(false)}
+ MountAgentConfig = types.MountPoint{SourceVolume: aws.String("agent-config"), ContainerPath: aws.String("/etc/datadog-agent"), ReadOnly: aws.Bool(false)}
+ MountAgentTmp = types.MountPoint{SourceVolume: aws.String("agent-tmp"), ContainerPath: aws.String("/tmp"), ReadOnly: aws.Bool(false)}
+ MountAgentRun = types.MountPoint{SourceVolume: aws.String("agent-run"), ContainerPath: aws.String("/opt/datadog-agent/run"), ReadOnly: aws.Bool(false)}
PortTCP = types.PortMapping{ContainerPort: aws.Int32(8126), HostPort: aws.Int32(8126), Protocol: types.TransportProtocolTcp}
PortUDP = types.PortMapping{ContainerPort: aws.Int32(8125), HostPort: aws.Int32(8125), Protocol: types.TransportProtocolUdp}
DependencyAgent = types.ContainerDependency{ContainerName: aws.String("datadog-agent"), Condition: types.ContainerConditionHealthy}