Hearth is the infrastructure home for the letemcook ecosystem. Ported from coherence-mcp/infra: - Terraform modules (VPC, EKS, IAM, NLB, S3, storage) - Kubernetes manifests (Forgejo, ingress, cert-manager, karpenter) - Deployment scripts (phased rollout) Status: Not deployed. EKS cluster needs to be provisioned. Next steps: 1. Bootstrap terraform backend 2. Deploy phase 1 (foundation) 3. Deploy phase 2 (core services including Forgejo) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
426 lines
11 KiB
HCL
426 lines
11 KiB
HCL
# IAM Module - Roles and IRSA
|
|
# RFC 0039: ADR-Compliant Foundation Infrastructure
|
|
#
|
|
# Creates IAM roles for:
|
|
# - Karpenter node provisioning
|
|
# - EBS CSI Driver
|
|
# - EFS CSI Driver
|
|
# - AWS Load Balancer Controller
|
|
# - External DNS
|
|
# - cert-manager
|
|
|
|
locals {
|
|
oidc_issuer = replace(var.cluster_oidc_issuer_url, "https://", "")
|
|
}
|
|
|
|
# Karpenter Controller IAM Role
|
|
resource "aws_iam_role" "karpenter_controller" {
|
|
name = "${var.cluster_name}-karpenter-controller"
|
|
|
|
assume_role_policy = jsonencode({
|
|
Version = "2012-10-17"
|
|
Statement = [
|
|
{
|
|
Effect = "Allow"
|
|
Principal = {
|
|
Federated = var.cluster_oidc_provider_arn
|
|
}
|
|
Action = "sts:AssumeRoleWithWebIdentity"
|
|
Condition = {
|
|
StringEquals = {
|
|
"${local.oidc_issuer}:aud" = "sts.amazonaws.com"
|
|
"${local.oidc_issuer}:sub" = "system:serviceaccount:karpenter:karpenter"
|
|
}
|
|
}
|
|
}
|
|
]
|
|
})
|
|
|
|
tags = var.tags
|
|
}
|
|
|
|
resource "aws_iam_role_policy" "karpenter_controller" {
|
|
name = "${var.cluster_name}-karpenter-controller"
|
|
role = aws_iam_role.karpenter_controller.id
|
|
|
|
policy = jsonencode({
|
|
Version = "2012-10-17"
|
|
Statement = [
|
|
{
|
|
Sid = "Karpenter"
|
|
Effect = "Allow"
|
|
Action = [
|
|
"ec2:CreateLaunchTemplate",
|
|
"ec2:CreateFleet",
|
|
"ec2:CreateTags",
|
|
"ec2:DescribeLaunchTemplates",
|
|
"ec2:DescribeImages",
|
|
"ec2:DescribeInstances",
|
|
"ec2:DescribeSecurityGroups",
|
|
"ec2:DescribeSubnets",
|
|
"ec2:DescribeInstanceTypes",
|
|
"ec2:DescribeInstanceTypeOfferings",
|
|
"ec2:DescribeAvailabilityZones",
|
|
"ec2:DescribeSpotPriceHistory",
|
|
"ec2:DeleteLaunchTemplate",
|
|
"ec2:RunInstances",
|
|
"ec2:TerminateInstances",
|
|
]
|
|
Resource = "*"
|
|
},
|
|
{
|
|
Sid = "PassNodeRole"
|
|
Effect = "Allow"
|
|
Action = "iam:PassRole"
|
|
Resource = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/${var.cluster_name}-node"
|
|
},
|
|
{
|
|
Sid = "EKSClusterEndpointLookup"
|
|
Effect = "Allow"
|
|
Action = "eks:DescribeCluster"
|
|
Resource = "arn:aws:eks:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:cluster/${var.cluster_name}"
|
|
},
|
|
{
|
|
Sid = "KarpenterInterruptionQueue"
|
|
Effect = "Allow"
|
|
Action = [
|
|
"sqs:DeleteMessage",
|
|
"sqs:GetQueueUrl",
|
|
"sqs:GetQueueAttributes",
|
|
"sqs:ReceiveMessage"
|
|
]
|
|
Resource = aws_sqs_queue.karpenter_interruption.arn
|
|
},
|
|
{
|
|
Sid = "PricingAPI"
|
|
Effect = "Allow"
|
|
Action = "pricing:GetProducts"
|
|
Resource = "*"
|
|
},
|
|
{
|
|
Sid = "SSM"
|
|
Effect = "Allow"
|
|
Action = "ssm:GetParameter"
|
|
Resource = "arn:aws:ssm:${data.aws_region.current.name}::parameter/aws/service/*"
|
|
}
|
|
]
|
|
})
|
|
}
|
|
|
|
# Karpenter Interruption Queue (for Spot instance termination notices)
|
|
resource "aws_sqs_queue" "karpenter_interruption" {
|
|
name = "${var.cluster_name}-karpenter-interruption"
|
|
message_retention_seconds = 300
|
|
|
|
tags = var.tags
|
|
}
|
|
|
|
resource "aws_sqs_queue_policy" "karpenter_interruption" {
|
|
queue_url = aws_sqs_queue.karpenter_interruption.id
|
|
|
|
policy = jsonencode({
|
|
Version = "2012-10-17"
|
|
Statement = [
|
|
{
|
|
Sid = "EC2InterruptionPolicy"
|
|
Effect = "Allow"
|
|
Principal = {
|
|
Service = [
|
|
"events.amazonaws.com",
|
|
"sqs.amazonaws.com"
|
|
]
|
|
}
|
|
Action = "sqs:SendMessage"
|
|
Resource = aws_sqs_queue.karpenter_interruption.arn
|
|
}
|
|
]
|
|
})
|
|
}
|
|
|
|
# EventBridge rules for Karpenter interruption handling
|
|
resource "aws_cloudwatch_event_rule" "spot_interruption" {
|
|
name = "${var.cluster_name}-spot-interruption"
|
|
description = "Spot instance interruption handler for Karpenter"
|
|
|
|
event_pattern = jsonencode({
|
|
source = ["aws.ec2"]
|
|
detail-type = ["EC2 Spot Instance Interruption Warning"]
|
|
})
|
|
|
|
tags = var.tags
|
|
}
|
|
|
|
resource "aws_cloudwatch_event_target" "spot_interruption" {
|
|
rule = aws_cloudwatch_event_rule.spot_interruption.name
|
|
target_id = "KarpenterInterruptionQueue"
|
|
arn = aws_sqs_queue.karpenter_interruption.arn
|
|
}
|
|
|
|
resource "aws_cloudwatch_event_rule" "instance_rebalance" {
|
|
name = "${var.cluster_name}-instance-rebalance"
|
|
description = "Instance rebalance recommendation for Karpenter"
|
|
|
|
event_pattern = jsonencode({
|
|
source = ["aws.ec2"]
|
|
detail-type = ["EC2 Instance Rebalance Recommendation"]
|
|
})
|
|
|
|
tags = var.tags
|
|
}
|
|
|
|
resource "aws_cloudwatch_event_target" "instance_rebalance" {
|
|
rule = aws_cloudwatch_event_rule.instance_rebalance.name
|
|
target_id = "KarpenterInterruptionQueue"
|
|
arn = aws_sqs_queue.karpenter_interruption.arn
|
|
}
|
|
|
|
resource "aws_cloudwatch_event_rule" "instance_state_change" {
|
|
name = "${var.cluster_name}-instance-state-change"
|
|
description = "Instance state change handler for Karpenter"
|
|
|
|
event_pattern = jsonencode({
|
|
source = ["aws.ec2"]
|
|
detail-type = ["EC2 Instance State-change Notification"]
|
|
})
|
|
|
|
tags = var.tags
|
|
}
|
|
|
|
resource "aws_cloudwatch_event_target" "instance_state_change" {
|
|
rule = aws_cloudwatch_event_rule.instance_state_change.name
|
|
target_id = "KarpenterInterruptionQueue"
|
|
arn = aws_sqs_queue.karpenter_interruption.arn
|
|
}
|
|
|
|
# EBS CSI Driver IAM Role
|
|
resource "aws_iam_role" "ebs_csi" {
|
|
name = "${var.cluster_name}-ebs-csi"
|
|
|
|
assume_role_policy = jsonencode({
|
|
Version = "2012-10-17"
|
|
Statement = [
|
|
{
|
|
Effect = "Allow"
|
|
Principal = {
|
|
Federated = var.cluster_oidc_provider_arn
|
|
}
|
|
Action = "sts:AssumeRoleWithWebIdentity"
|
|
Condition = {
|
|
StringEquals = {
|
|
"${local.oidc_issuer}:aud" = "sts.amazonaws.com"
|
|
"${local.oidc_issuer}:sub" = "system:serviceaccount:kube-system:ebs-csi-controller-sa"
|
|
}
|
|
}
|
|
}
|
|
]
|
|
})
|
|
|
|
tags = var.tags
|
|
}
|
|
|
|
resource "aws_iam_role_policy_attachment" "ebs_csi" {
|
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy"
|
|
role = aws_iam_role.ebs_csi.name
|
|
}
|
|
|
|
# Additional EBS CSI policy for encryption
|
|
resource "aws_iam_role_policy" "ebs_csi_kms" {
|
|
name = "${var.cluster_name}-ebs-csi-kms"
|
|
role = aws_iam_role.ebs_csi.id
|
|
|
|
policy = jsonencode({
|
|
Version = "2012-10-17"
|
|
Statement = [
|
|
{
|
|
Effect = "Allow"
|
|
Action = [
|
|
"kms:CreateGrant",
|
|
"kms:ListGrants",
|
|
"kms:RevokeGrant",
|
|
"kms:Encrypt",
|
|
"kms:Decrypt",
|
|
"kms:ReEncrypt*",
|
|
"kms:GenerateDataKey*",
|
|
"kms:DescribeKey"
|
|
]
|
|
Resource = "*"
|
|
}
|
|
]
|
|
})
|
|
}
|
|
|
|
# EFS CSI Driver IAM Role
|
|
resource "aws_iam_role" "efs_csi" {
|
|
name = "${var.cluster_name}-efs-csi"
|
|
|
|
assume_role_policy = jsonencode({
|
|
Version = "2012-10-17"
|
|
Statement = [
|
|
{
|
|
Effect = "Allow"
|
|
Principal = {
|
|
Federated = var.cluster_oidc_provider_arn
|
|
}
|
|
Action = "sts:AssumeRoleWithWebIdentity"
|
|
Condition = {
|
|
StringEquals = {
|
|
"${local.oidc_issuer}:aud" = "sts.amazonaws.com"
|
|
"${local.oidc_issuer}:sub" = "system:serviceaccount:kube-system:efs-csi-controller-sa"
|
|
}
|
|
}
|
|
}
|
|
]
|
|
})
|
|
|
|
tags = var.tags
|
|
}
|
|
|
|
resource "aws_iam_role_policy_attachment" "efs_csi" {
|
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEFSCSIDriverPolicy"
|
|
role = aws_iam_role.efs_csi.name
|
|
}
|
|
|
|
# AWS Load Balancer Controller IAM Role
|
|
resource "aws_iam_role" "aws_load_balancer_controller" {
|
|
name = "${var.cluster_name}-aws-load-balancer-controller"
|
|
|
|
assume_role_policy = jsonencode({
|
|
Version = "2012-10-17"
|
|
Statement = [
|
|
{
|
|
Effect = "Allow"
|
|
Principal = {
|
|
Federated = var.cluster_oidc_provider_arn
|
|
}
|
|
Action = "sts:AssumeRoleWithWebIdentity"
|
|
Condition = {
|
|
StringEquals = {
|
|
"${local.oidc_issuer}:aud" = "sts.amazonaws.com"
|
|
"${local.oidc_issuer}:sub" = "system:serviceaccount:kube-system:aws-load-balancer-controller"
|
|
}
|
|
}
|
|
}
|
|
]
|
|
})
|
|
|
|
tags = var.tags
|
|
}
|
|
|
|
resource "aws_iam_role_policy" "aws_load_balancer_controller" {
|
|
name = "${var.cluster_name}-aws-load-balancer-controller"
|
|
role = aws_iam_role.aws_load_balancer_controller.id
|
|
|
|
policy = file("${path.module}/policies/aws-load-balancer-controller.json")
|
|
}
|
|
|
|
# cert-manager IAM Role
|
|
resource "aws_iam_role" "cert_manager" {
|
|
name = "${var.cluster_name}-cert-manager"
|
|
|
|
assume_role_policy = jsonencode({
|
|
Version = "2012-10-17"
|
|
Statement = [
|
|
{
|
|
Effect = "Allow"
|
|
Principal = {
|
|
Federated = var.cluster_oidc_provider_arn
|
|
}
|
|
Action = "sts:AssumeRoleWithWebIdentity"
|
|
Condition = {
|
|
StringEquals = {
|
|
"${local.oidc_issuer}:aud" = "sts.amazonaws.com"
|
|
"${local.oidc_issuer}:sub" = "system:serviceaccount:cert-manager:cert-manager"
|
|
}
|
|
}
|
|
}
|
|
]
|
|
})
|
|
|
|
tags = var.tags
|
|
}
|
|
|
|
resource "aws_iam_role_policy" "cert_manager" {
|
|
name = "${var.cluster_name}-cert-manager"
|
|
role = aws_iam_role.cert_manager.id
|
|
|
|
policy = jsonencode({
|
|
Version = "2012-10-17"
|
|
Statement = [
|
|
{
|
|
Effect = "Allow"
|
|
Action = [
|
|
"route53:GetChange"
|
|
]
|
|
Resource = "arn:aws:route53:::change/*"
|
|
},
|
|
{
|
|
Effect = "Allow"
|
|
Action = [
|
|
"route53:ChangeResourceRecordSets",
|
|
"route53:ListResourceRecordSets"
|
|
]
|
|
Resource = "arn:aws:route53:::hostedzone/*"
|
|
},
|
|
{
|
|
Effect = "Allow"
|
|
Action = "route53:ListHostedZonesByName"
|
|
Resource = "*"
|
|
}
|
|
]
|
|
})
|
|
}
|
|
|
|
# External DNS IAM Role
|
|
resource "aws_iam_role" "external_dns" {
|
|
name = "${var.cluster_name}-external-dns"
|
|
|
|
assume_role_policy = jsonencode({
|
|
Version = "2012-10-17"
|
|
Statement = [
|
|
{
|
|
Effect = "Allow"
|
|
Principal = {
|
|
Federated = var.cluster_oidc_provider_arn
|
|
}
|
|
Action = "sts:AssumeRoleWithWebIdentity"
|
|
Condition = {
|
|
StringEquals = {
|
|
"${local.oidc_issuer}:aud" = "sts.amazonaws.com"
|
|
"${local.oidc_issuer}:sub" = "system:serviceaccount:external-dns:external-dns"
|
|
}
|
|
}
|
|
}
|
|
]
|
|
})
|
|
|
|
tags = var.tags
|
|
}
|
|
|
|
resource "aws_iam_role_policy" "external_dns" {
|
|
name = "${var.cluster_name}-external-dns"
|
|
role = aws_iam_role.external_dns.id
|
|
|
|
policy = jsonencode({
|
|
Version = "2012-10-17"
|
|
Statement = [
|
|
{
|
|
Effect = "Allow"
|
|
Action = [
|
|
"route53:ChangeResourceRecordSets"
|
|
]
|
|
Resource = "arn:aws:route53:::hostedzone/*"
|
|
},
|
|
{
|
|
Effect = "Allow"
|
|
Action = [
|
|
"route53:ListHostedZones",
|
|
"route53:ListResourceRecordSets"
|
|
]
|
|
Resource = "*"
|
|
}
|
|
]
|
|
})
|
|
}
|
|
|
|
data "aws_caller_identity" "current" {}
|
|
data "aws_region" "current" {}
|