AWS ECS – Consider Avoiding Secrets in Container Environment Variables

AWS ECS – Consider Avoiding Secrets in Container Environment Variables

Storing sensitive information such as database passwords, API keys, and authentication tokens directly in ECS container environment variables creates significant security risks and compliance violations. This FinOps policy helps organizations identify and remediate insecure secret management practices in their ECS infrastructure while reducing potential security breaches.

Why this policy matters

Container environment variables in ECS task definitions are stored in plaintext and lack the security controls necessary for protecting sensitive data. When secrets are exposed through environment variables, they become accessible through multiple attack vectors including shell access, application logs, process listings, and the /proc filesystem. This exposure creates substantial financial and operational risks for organizations.

Implementation Guide

Infrastructure-as-Code Example (Terraform)

Problematic configuration - secrets in environment variables

resource "aws_ecs_task_definition" "bad_example" {
  family                   = "my-app"
  requires_compatibilities = ["FARGATE"]
  network_mode             = "awsvpc"
  cpu                      = 256
  memory                   = 512
  execution_role_arn       = aws_iam_role.execution_role.arn
  task_role_arn           = aws_iam_role.task_role.arn
container_definitions = jsonencode([
    {
      name  = "my-app"
      image = "my-app:latest"
      environment = [
        {
          name  = "DATABASE_PASSWORD"
          value = "super-secret-password"  # Security risk!
        },
        {
          name  = "API_KEY"
          value = "abc123xyz789"  # Security risk!
        }
      ]
      logConfiguration = {
        logDriver = "awslogs"
        options = {
          awslogs-group         = "/ecs/my-app"
          awslogs-region        = "us-east-1"
          awslogs-stream-prefix = "ecs"
        }
      }
    }
  ])
}
resource "aws_ecs_task_definition" "bad_example" {
  family                   = "my-app"
  requires_compatibilities = ["FARGATE"]
  network_mode             = "awsvpc"
  cpu                      = 256
  memory                   = 512
  execution_role_arn       = aws_iam_role.execution_role.arn
  task_role_arn           = aws_iam_role.task_role.arn
container_definitions = jsonencode([
    {
      name  = "my-app"
      image = "my-app:latest"
      environment = [
        {
          name  = "DATABASE_PASSWORD"
          value = "super-secret-password"  # Security risk!
        },
        {
          name  = "API_KEY"
          value = "abc123xyz789"  # Security risk!
        }
      ]
      logConfiguration = {
        logDriver = "awslogs"
        options = {
          awslogs-group         = "/ecs/my-app"
          awslogs-region        = "us-east-1"
          awslogs-stream-prefix = "ecs"
        }
      }
    }
  ])
}
resource "aws_ecs_task_definition" "bad_example" {
  family                   = "my-app"
  requires_compatibilities = ["FARGATE"]
  network_mode             = "awsvpc"
  cpu                      = 256
  memory                   = 512
  execution_role_arn       = aws_iam_role.execution_role.arn
  task_role_arn           = aws_iam_role.task_role.arn
container_definitions = jsonencode([
    {
      name  = "my-app"
      image = "my-app:latest"
      environment = [
        {
          name  = "DATABASE_PASSWORD"
          value = "super-secret-password"  # Security risk!
        },
        {
          name  = "API_KEY"
          value = "abc123xyz789"  # Security risk!
        }
      ]
      logConfiguration = {
        logDriver = "awslogs"
        options = {
          awslogs-group         = "/ecs/my-app"
          awslogs-region        = "us-east-1"
          awslogs-stream-prefix = "ecs"
        }
      }
    }
  ])
}

Secure configuration using AWS Secrets Manager

resource "aws_secretsmanager_secret" "database_password" {
  name        = "my-app/database-password"
  description = "Database password for my-app"
}
resource "aws_secretsmanager_secret" "database_password" {
  name        = "my-app/database-password"
  description = "Database password for my-app"
}
resource "aws_secretsmanager_secret" "database_password" {
  name        = "my-app/database-password"
  description = "Database password for my-app"
}
resource "aws_secretsmanager_secret_version" "database_password" {
  secret_id     = aws_secretsmanager_secret.database_password.id
  secret_string = "super-secret-password"
}
resource "aws_secretsmanager_secret_version" "database_password" {
  secret_id     = aws_secretsmanager_secret.database_password.id
  secret_string = "super-secret-password"
}
resource "aws_secretsmanager_secret_version" "database_password" {
  secret_id     = aws_secretsmanager_secret.database_password.id
  secret_string = "super-secret-password"
}
resource "aws_ssm_parameter" "api_key" {
  name  = "/my-app/api-key"
  type  = "SecureString"
  value = "abc123xyz789"
}
resource "aws_ssm_parameter" "api_key" {
  name  = "/my-app/api-key"
  type  = "SecureString"
  value = "abc123xyz789"
}
resource "aws_ssm_parameter" "api_key" {
  name  = "/my-app/api-key"
  type  = "SecureString"
  value = "abc123xyz789"
}
resource "aws_ecs_task_definition" "secure_example" {
  family                   = "my-app"
  requires_compatibilities = ["FARGATE"]
  network_mode             = "awsvpc"
  cpu                      = 256
  memory                   = 512
  execution_role_arn       = aws_iam_role.execution_role.arn
  task_role_arn           = aws_iam_role.task_role.arn
container_definitions = jsonencode([
    {
      name  = "my-app"
      image = "my-app:latest"
      secrets = [
        {
          name      = "DATABASE_PASSWORD"
          valueFrom = aws_secretsmanager_secret.database_password.arn
        },
        {
          name      = "API_KEY"
          valueFrom = aws_ssm_parameter.api_key.arn
        }
      ]
      logConfiguration = {
        logDriver = "awslogs"
        options = {
          awslogs-group         = "/ecs/my-app"
          awslogs-region        = "us-east-1"
          awslogs-stream-prefix = "ecs"
        }
      }
    }
  ])
}
resource "aws_ecs_task_definition" "secure_example" {
  family                   = "my-app"
  requires_compatibilities = ["FARGATE"]
  network_mode             = "awsvpc"
  cpu                      = 256
  memory                   = 512
  execution_role_arn       = aws_iam_role.execution_role.arn
  task_role_arn           = aws_iam_role.task_role.arn
container_definitions = jsonencode([
    {
      name  = "my-app"
      image = "my-app:latest"
      secrets = [
        {
          name      = "DATABASE_PASSWORD"
          valueFrom = aws_secretsmanager_secret.database_password.arn
        },
        {
          name      = "API_KEY"
          valueFrom = aws_ssm_parameter.api_key.arn
        }
      ]
      logConfiguration = {
        logDriver = "awslogs"
        options = {
          awslogs-group         = "/ecs/my-app"
          awslogs-region        = "us-east-1"
          awslogs-stream-prefix = "ecs"
        }
      }
    }
  ])
}
resource "aws_ecs_task_definition" "secure_example" {
  family                   = "my-app"
  requires_compatibilities = ["FARGATE"]
  network_mode             = "awsvpc"
  cpu                      = 256
  memory                   = 512
  execution_role_arn       = aws_iam_role.execution_role.arn
  task_role_arn           = aws_iam_role.task_role.arn
container_definitions = jsonencode([
    {
      name  = "my-app"
      image = "my-app:latest"
      secrets = [
        {
          name      = "DATABASE_PASSWORD"
          valueFrom = aws_secretsmanager_secret.database_password.arn
        },
        {
          name      = "API_KEY"
          valueFrom = aws_ssm_parameter.api_key.arn
        }
      ]
      logConfiguration = {
        logDriver = "awslogs"
        options = {
          awslogs-group         = "/ecs/my-app"
          awslogs-region        = "us-east-1"
          awslogs-stream-prefix = "ecs"
        }
      }
    }
  ])
}

Required IAM permissions for secrets access

resource "aws_iam_role_policy" "task_secrets_policy" {
  name = "task-secrets-policy"
  role = aws_iam_role.task_role.id
policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Action = [
          "secretsmanager:GetSecretValue"
        ]
        Resource = aws_secretsmanager_secret.database_password.arn
      },
      {
        Effect = "Allow"
        Action = [
          "ssm:GetParameter"
        ]
        Resource = aws_ssm_parameter.api_key.arn
      }
    ]
  })
}
resource "aws_iam_role_policy" "task_secrets_policy" {
  name = "task-secrets-policy"
  role = aws_iam_role.task_role.id
policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Action = [
          "secretsmanager:GetSecretValue"
        ]
        Resource = aws_secretsmanager_secret.database_password.arn
      },
      {
        Effect = "Allow"
        Action = [
          "ssm:GetParameter"
        ]
        Resource = aws_ssm_parameter.api_key.arn
      }
    ]
  })
}
resource "aws_iam_role_policy" "task_secrets_policy" {
  name = "task-secrets-policy"
  role = aws_iam_role.task_role.id
policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Action = [
          "secretsmanager:GetSecretValue"
        ]
        Resource = aws_secretsmanager_secret.database_password.arn
      },
      {
        Effect = "Allow"
        Action = [
          "ssm:GetParameter"
        ]
        Resource = aws_ssm_parameter.api_key.arn
      }
    ]
  })
}

The secure implementation uses the secrets block instead of the environment block for sensitive data. Infracost automatically detects when ECS task definitions use environment variables that appear to contain secrets and flags them for remediation.

Manual Step-by-Step Instructions

Audit existing task definitions: Review all ECS task definitions to identify environment variables containing sensitive data such as passwords, keys, tokens, or connection strings.

Create secrets in AWS Secrets Manager or SSM Parameter Store:

  • For frequently rotated secrets (database passwords, API tokens): Use AWS Secrets Manager

  • For configuration parameters that change infrequently: Use SSM Parameter Store with SecureString type

  • Update IAM roles: Ensure the ECS task role has appropriate permissions to access the secrets from Secrets Manager or Parameter Store.

  • Modify task definitions: Replace environment blocks containing secrets with secrets blocks that reference the ARNs of stored secrets.

  • Test the deployment: Verify that applications can successfully retrieve secrets and function correctly.

  • Remove old secrets: Clean up any hardcoded secrets from previous configurations and rotate them if they were previously exposed.

Best Practices

  • Use AWS Secrets Manager for secrets that require automatic rotation

  • Implement least-privilege IAM policies for secret access

  • Enable CloudTrail logging for secret access auditing

  • Use consistent naming conventions for secrets across environments

  • Implement secret scanning in CI/CD pipelines to prevent future violations

  • Regular audit secret access patterns and remove unused permissions

Tools and Scripts

Infracost supports this policy in its FinOps policy engine, including in the free trial. The platform automatically scans ECS task definitions and identifies when environment variables contain potential secrets based on variable names and patterns. Organizations can use Infracost to continuously monitor their infrastructure-as-code for secret management violations and track remediation progress over time.

Considerations and Caveats

While this policy generally applies to all sensitive data, there are some considerations:

  • Performance impact: Retrieving secrets from external services adds minimal latency compared to environment variables, but this should be considered for high-frequency operations

  • Cost implications: AWS Secrets Manager charges per secret per month, while SSM Parameter Store has different pricing tiers

  • Legacy applications: Some older applications may require code changes to support retrieving secrets from external sources

  • Development environments: Consider using different secret management strategies for development versus production environments

  • Network dependencies: Applications must have network access to AWS APIs to retrieve secrets

Create Free Account

This policy is supported in Infracost and available in the free trial. Sign up today and scan your code using our entire library of FinOps policies.

Get started
with Infracost

© 2026 Infracost Inc

Manage cookies

Get started
with Infracost

© 2026 Infracost Inc

Manage cookies

Get started
with Infracost

© 2026 Infracost Inc

Manage cookies