Skip to main content

Overview

The IAM Module creates AWS IAM roles and policies for Artos application workloads running in EKS. Using IAM Roles for Service Accounts (IRSA), Kubernetes pods can securely access AWS services without hardcoded credentials. The module provisions separate roles for the backend application, Celery workers, and the AWS Load Balancer Controller, each with specific permissions tailored to their operational needs.

Key Features

  • OIDC-Based Authentication: Kubernetes service accounts assume IAM roles via OpenID Connect
  • No Hardcoded Credentials: Temporary credentials automatically provided and rotated
  • Least Privilege Access: Each component has only the permissions it needs
  • Secure Credential Management: Access to Secrets Manager with KMS encryption
  • CloudWatch Integration: Logging and metrics publication for observability

IAM Roles for Service Accounts (IRSA)

What is IRSA?

IAM Roles for Service Accounts (IRSA) is an AWS feature that allows Kubernetes service accounts to assume AWS IAM roles. This eliminates the need to store AWS credentials in your application code or Kubernetes secrets. How It Works:
  1. EKS cluster has an OIDC identity provider
  2. Kubernetes service account is annotated with an IAM role ARN
  3. When a pod uses this service account, AWS STS provides temporary credentials
  4. Pod can make AWS API calls using these credentials
  5. Credentials automatically expire and rotate
Benefits:
  • Security: No long-lived credentials stored in cluster
  • Auditability: All AWS API calls logged in CloudTrail
  • Isolation: Each workload has separate IAM permissions
  • Automatic Rotation: Credentials refresh automatically

Core IAM Roles

1. Application Service Account Role

Purpose: Main backend API server that handles user requests, business logic, and data operations. Kubernetes Service Account: system:serviceaccount:default:app-service-account AWS Permissions:

S3 Access

{
  "Action": [
    "s3:GetObject",
    "s3:PutObject",
    "s3:DeleteObject",
    "s3:ListBucket"
  ]
}
Use Cases:
  • Store and retrieve uploaded documents
  • Save processed files and artifacts
  • Manage user-generated content
  • Access application assets and templates

Secrets Manager Access

{
  "Action": [
    "secretsmanager:GetSecretValue",
    "secretsmanager:DescribeSecret"
  ]
}
Use Cases:
  • Retrieve database credentials
  • Access API keys for third-party services
  • Load application configuration secrets
  • Get encryption keys for sensitive data

KMS Decryption

{
  "Action": [
    "kms:Decrypt",
    "kms:DescribeKey"
  ],
  "Condition": {
    "StringEquals": {
      "kms:ViaService": [
        "secretsmanager.{region}.amazonaws.com",
        "s3.{region}.amazonaws.com"
      ]
    }
  }
}
Use Cases:
  • Decrypt Secrets Manager secrets encrypted with KMS
  • Access S3 objects encrypted with customer-managed keys
  • Read encrypted configuration data
KMS Condition: The ViaService condition ensures the KMS key can only be used by Secrets Manager and S3, not directly by the application. This adds an extra security layer.

RDS IAM Authentication

{
  "Action": [
    "rds-db:connect"
  ]
}
Use Cases:
  • Connect to RDS PostgreSQL using IAM authentication
  • Eliminate database password management
  • Automatic credential rotation via IAM

Amazon Bedrock Access

{
  "Action": [
    "bedrock:InvokeModel",
    "bedrock:InvokeModelWithResponseStream",
    "bedrock:ListFoundationModels",
    "bedrock:GetFoundationModel"
  ]
}
Use Cases:
  • Generate AI-powered responses
  • Process natural language queries
  • Summarize documents
  • Classify and analyze content

CloudWatch Logs

{
  "Action": [
    "logs:CreateLogStream",
    "logs:CreateLogGroup",
    "logs:DescribeLogStreams",
    "logs:PutLogEvents"
  ]
}
Use Cases:
  • Send application logs to CloudWatch
  • Create structured log streams
  • Enable centralized log aggregation

CloudWatch Metrics

{
  "Action": [
    "cloudwatch:PutMetricData",
    "cloudwatch:GetMetricStatistics",
    "cloudwatch:ListMetrics"
  ]
}
Use Cases:
  • Publish custom application metrics
  • Monitor business KPIs
  • Track performance indicators
  • Enable automated alerting

2. Celery Service Account Role

Purpose: Asynchronous task workers for background job processing, scheduled tasks, and long-running operations running within the Artos platform. Kubernetes Service Account: system:serviceaccount:default:celery-service-account AWS Permissions: Same as Application Service Account The Celery workers require identical permissions to the main application because they:
  • Process documents stored in S3
  • Access the same secrets for database and API connectivity
  • Use Bedrock for AI processing in background jobs
  • Write logs and metrics to CloudWatch
  • Connect to RDS for data operations

3. AWS Load Balancer Controller Role

Purpose: Kubernetes controller that manages AWS Application Load Balancers (ALBs) and Network Load Balancers (NLBs) for Ingress resources. Kubernetes Service Account: system:serviceaccount:kube-system:aws-load-balancer-controller AWS Permissions: Comprehensive Elastic Load Balancing and EC2 permissions Key Capabilities:

Load Balancer Management

  • Create and delete ALBs and NLBs
  • Modify load balancer attributes
  • Configure listeners and rules
  • Manage SSL/TLS certificates

Target Group Operations

  • Create target groups for Kubernetes services
  • Register and deregister pod IPs as targets
  • Configure health checks
  • Manage target group attributes

Security Group Management

  • Create security groups for load balancers
  • Modify ingress/egress rules
  • Tag resources for cluster association

Network Discovery

  • Describe VPCs, subnets, and availability zones
  • Query ENIs and instance metadata
  • Discover ACM certificates for HTTPS
How It Works:
  1. You create a Kubernetes Ingress resource
  2. Load Balancer Controller detects the Ingress
  3. Controller creates an ALB in your VPC
  4. ALB routes traffic to Kubernetes service endpoints
  5. Controller updates target groups as pods scale
Example Ingress Resource:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: artos-api
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:...
spec:
  rules:
  - host: api.yourcompany.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: artos-api
            port:
              number: 8000

Module Configuration

Basic Configuration

module "iam" {
  source = "./modules/iam"

  cluster_name = "artos-production"
  
  # OIDC provider from EKS module
  oidc_provider_arn = module.eks.oidc_provider_arn
  oidc_provider_url = module.eks.oidc_provider_url
  
  # S3 bucket for application data
  s3_bucket_arn = module.s3.bucket_arn
  
  # Secrets Manager for application secrets
  secrets_manager_arns = [
    "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/db-credentials-abc123",
    "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/api-keys-def456"
  ]
  
  # RDS database cluster
  db_cluster_identifier = module.rds.cluster_identifier
  
  # KMS key for encryption
  kms_key_arn = module.kms.key_arn
  
  # AWS account information
  aws_region     = "us-east-1"
  aws_account_id = "123456789012"
  
  tags = {
    Environment = "production"
  }
}

Configuration with Multiple Secrets

For our deployment strategy only one set of secrets should be necessary.
module "iam_production" {
  source = "./modules/iam"

  cluster_name = "artos-production"
  
  oidc_provider_arn = module.eks.oidc_provider_arn
  oidc_provider_url = module.eks.oidc_provider_url
  
  s3_bucket_arn = module.s3.bucket_arn
  
  # Multiple secrets for different components
  secrets_manager_arns = [
    "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/database/*",
    "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/redis/*",
    "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/smtp/*",
    "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/oauth/*"
  ]
  
  db_cluster_identifier = module.rds.cluster_identifier
  kms_key_arn          = module.kms.key_arn
  
  aws_region     = "us-east-1"
  aws_account_id = data.aws_caller_identity.current.account_id
  
  tags = {
    Environment = "production"
    ManagedBy   = "terraform"
  }
}

Security Best Practices

1. Principle of Least Privilege

Each IAM role grants only the minimum permissions required for its workload. Example Custom Policy:
resource "aws_iam_policy" "custom_app_policy" {
  name = "${var.cluster_name}-custom-app-policy"
  
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Action = [
          "s3:GetObject",
          "s3:PutObject"
        ]
        Resource = ["${var.s3_bucket_arn}/*"]
      }
      # Only include services you actually use
    ]
  })
}

2. Resource-Level Restrictions

Where possible, restrict permissions to specific resources rather than using wildcards. Good - Specific Resources:
{
  "Resource": [
    "arn:aws:s3:::artos-production-data",
    "arn:aws:s3:::artos-production-data/*"
  ]
}
Avoid - Wildcard Resources (only when necessary):
{
  "Resource": "*"
}

3. KMS Key Conditions

Always use ViaService conditions with KMS permissions to ensure keys can only be used by specific AWS services.
{
  "Condition": {
    "StringEquals": {
      "kms:ViaService": [
        "secretsmanager.us-east-1.amazonaws.com",
        "s3.us-east-1.amazonaws.com"
      ]
    }
  }
}

4. Secrets Manager Naming Conventions

Use consistent naming patterns for secrets to enable prefix-based permissions. Recommended Structure:
{environment}/{component}/{secret-name}

Examples:
- prod/database/master-credentials
- prod/redis/connection-string
- prod/smtp/api-key
ARN Pattern Matching:
secrets_manager_arns = [
  "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/*"
]

Troubleshooting

Pod Cannot Assume IAM Role

Symptoms: Application logs show Unable to locate credentials or AccessDenied errors. Troubleshooting Steps:
  1. Verify Service Account Annotation:
kubectl get serviceaccount app-service-account -o yaml

# Should show:
# metadata:
#   annotations:
#     eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/...
  1. Check Pod Environment Variables:
kubectl exec -it <pod-name> -- env | grep AWS

# Should show:
# AWS_ROLE_ARN=arn:aws:iam::123456789012:role/...
# AWS_WEB_IDENTITY_TOKEN_FILE=/var/run/secrets/eks.amazonaws.com/serviceaccount/token
  1. Verify IAM Trust Relationship:
aws iam get-role --role-name artos-production-app-service-account

# Check that trust policy includes correct OIDC provider and service account
  1. Test IAM Role Assumption:
# Inside pod
aws sts get-caller-identity

# Should return:
# {
#   "UserId": "AROA...:eks-...",
#   "Account": "123456789012",
#   "Arn": "arn:aws:sts::123456789012:assumed-role/artos-production-app-service-account/..."
# }

Access Denied to AWS Service

Symptoms: AccessDenied or UnauthorizedOperation errors when accessing specific AWS services. Solutions:
  1. Verify IAM Policy Permissions:
aws iam get-role-policy \
  --role-name artos-production-app-service-account \
  --policy-name artos-production-app-policy
  1. Check Resource ARNs Match: Ensure S3 bucket ARNs, Secrets Manager ARNs, and KMS key ARNs in your IAM policy match the actual resources.
  2. Test Permissions from Pod:
# Test S3 access
kubectl exec -it <pod-name> -- aws s3 ls s3://artos-production-data

# Test Secrets Manager access
kubectl exec -it <pod-name> -- aws secretsmanager get-secret-value \
  --secret-id prod/db-credentials

KMS Decryption Errors

Symptoms: KMS.DisabledException or AccessDeniedException when accessing encrypted secrets. Solutions:
  1. Verify KMS Key Policy: Ensure the KMS key policy allows the IAM role to decrypt:
{
  "Sid": "AllowApplicationDecryption",
  "Effect": "Allow",
  "Principal": {
    "AWS": "arn:aws:iam::123456789012:role/artos-production-app-service-account"
  },
  "Action": [
    "kms:Decrypt",
    "kms:DescribeKey"
  ],
  "Resource": "*"
}
  1. Check ViaService Condition: Ensure KMS permissions include the correct ViaService condition for Secrets Manager or S3.
  • EKS Module - Provides OIDC provider for IAM role authentication
  • S3 Module - S3 buckets that application roles access
  • RDS Module - Database cluster for IAM authentication
  • Bastion Module - Bastion host with IAM role for EKS management

Module Maintenance: This module is compatible with Terraform 1.0+ and AWS Provider 5.x. All IAM roles use OIDC authentication via the EKS cluster’s identity provider. Ensure the EKS module is deployed first to generate the OIDC provider.