# ECS Task Execution Role (pull images, push logs)
resource "aws_iam_role" "ecs_execution" {
name = "${var.project_name}-ecs-execution"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = {
Service = "ecs-tasks.amazonaws.com"
}
Action = "sts:AssumeRole"
}
]
})
}
resource "aws_iam_role_policy_attachment" "ecs_execution" {
role = aws_iam_role.ecs_execution.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}
# Allow pulling secrets from Secrets Manager
resource "aws_iam_role_policy" "ecs_secrets" {
name = "secrets-access"
role = aws_iam_role.ecs_execution.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"secretsmanager:GetSecretValue"
]
Resource = [
"arn:aws:secretsmanager:${var.aws_region}:${data.aws_caller_identity.current.account_id}:secret:${var.project_name}/*"
]
}
]
})
}
# Application Task Role (what the app can do)
resource "aws_iam_role" "app_task" {
name = "${var.project_name}-app-task"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = {
Service = "ecs-tasks.amazonaws.com"
}
Action = "sts:AssumeRole"
Condition = {
StringEquals = {
"aws:SourceAccount" = data.aws_caller_identity.current.account_id
}
}
}
]
})
}
# S3 access for the app
resource "aws_iam_role_policy" "app_s3" {
name = "s3-access"
role = aws_iam_role.app_task.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
"s3:ListBucket"
]
Resource = [
aws_s3_bucket.uploads.arn,
"${aws_s3_bucket.uploads.arn}/*"
]
}
]
})
}
# SQS access
resource "aws_iam_role_policy" "app_sqs" {
name = "sqs-access"
role = aws_iam_role.app_task.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"sqs:SendMessage",
"sqs:ReceiveMessage",
"sqs:DeleteMessage",
"sqs:GetQueueAttributes"
]
Resource = [
aws_sqs_queue.tasks.arn
]
}
]
})
}
# CI/CD Deployer Role (GitHub Actions)
resource "aws_iam_role" "deployer" {
name = "${var.project_name}-deployer"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = {
Federated = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:oidc-provider/token.actions.githubusercontent.com"
}
Action = "sts:AssumeRoleWithWebIdentity"
Condition = {
StringLike = {
"token.actions.githubusercontent.com:sub" = "repo:${var.github_org}/${var.github_repo}:*"
}
}
}
]
})
}
resource "aws_iam_role_policy" "deployer" {
name = "deploy-permissions"
role = aws_iam_role.deployer.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:PutImage",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload"
]
Resource = "*"
},
{
Effect = "Allow"
Action = [
"ecs:UpdateService",
"ecs:DescribeServices",
"ecs:DescribeTaskDefinition",
"ecs:RegisterTaskDefinition"
]
Resource = "*"
Condition = {
StringEquals = {
"aws:ResourceTag/Project" = var.project_name
}
}
},
{
Effect = "Allow"
Action = [
"iam:PassRole"
]
Resource = [
aws_iam_role.ecs_execution.arn,
aws_iam_role.app_task.arn
]
}
]
})
}
data "aws_caller_identity" "current" {}
AWS IAM (Identity and Access Management) controls access to cloud resources. Policies are JSON documents with Effect, Action, and Resource fields. The principle of least privilege grants only required permissions. Allow permits actions, Deny always overrides. Condition blocks restrict access by IP, time, MFA status, or tags. IAM roles provide temporary credentials—preferred over long-lived access keys. Service-linked roles grant permissions to AWS services. AssumeRolePolicyDocument defines who can assume a role. Policy variables like $${aws:username} enable dynamic policies. sts:AssumeRole enables cross-account access. Always require MFA for sensitive operations. Use AWS Organizations SCPs for account-level guardrails. Regular access reviews with IAM Access Analyzer identify overly permissive policies.
Related snips
# AWS Lambda Function with API Gateway trigger
# === Lambda function ===
resource "aws_lambda_function" "api_handler" {
function_name = "${var.project}-api-handler"
description = "API request handler for ${var.project}"
AWS Lambda serverless functions with Terraform
payload = {
sub: user.id,
iss: 'https://auth.example.com',
aud: 'codesnips-api',
exp: 15.minutes.from_now.to_i,
iat: Time.now.to_i,
JWT issuance and verification without common footguns
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:GetObject"],
Least privilege IAM policy for an application on AWS
#!/usr/bin/env bash
set -euo pipefail
export VAULT_ADDR="https://vault.internal:8200"
export VAULT_TOKEN="${VAULT_TOKEN:?missing VAULT_TOKEN}"
Secrets management with environment isolation and Vault
import { randomBytes, createHash } from "crypto";
import jwt from "jsonwebtoken";
import { RefreshTokenStore } from "./store";
const ACCESS_SECRET = process.env.ACCESS_SECRET!;
const ACCESS_TTL = "15m";
JWT access + refresh token rotation (conceptual)
package files
import (
"context"
"time"
Presigned S3 upload URLs (AWS SDK v2)
Share this code
Here's the card — post it anywhere.