mirror of
https://github.com/ghndrx/terraform-foundation.git
synced 2026-02-10 06:45:06 +00:00
feat: Terraform Foundation - AWS Landing Zone
Enterprise-grade multi-tenant AWS cloud foundation. Modules: - GitHub OIDC for keyless CI/CD authentication - IAM account settings and security baseline - AWS Config Rules for compliance - ABAC (Attribute-Based Access Control) - SCPs (Service Control Policies) Features: - Multi-account architecture - Cost optimization patterns - Security best practices - Comprehensive documentation Tech: Terraform, AWS Organizations, IAM Identity Center
This commit is contained in:
673
terraform/modules/github-oidc/main.tf
Normal file
673
terraform/modules/github-oidc/main.tf
Normal file
@@ -0,0 +1,673 @@
|
||||
################################################################################
|
||||
# GitHub OIDC Module
|
||||
#
|
||||
# AWS/Terraform/Security Best Practices:
|
||||
# - Least privilege IAM policies
|
||||
# - Input validation
|
||||
# - Explicit denies for dangerous actions
|
||||
# - Session duration limits
|
||||
# - CloudTrail monitoring integration
|
||||
# - Permissions boundary support
|
||||
# - No wildcard repos by default
|
||||
#
|
||||
# Security scanning: tfsec, checkov, tflint-aws
|
||||
################################################################################
|
||||
|
||||
data "aws_caller_identity" "current" {}
|
||||
data "aws_region" "current" {}
|
||||
data "aws_partition" "current" {}
|
||||
|
||||
################################################################################
|
||||
# Local Variables & Validation
|
||||
################################################################################
|
||||
|
||||
locals {
|
||||
account_id = data.aws_caller_identity.current.account_id
|
||||
region = data.aws_region.current.id
|
||||
partition = data.aws_partition.current.partition
|
||||
|
||||
# Validate permissions boundary requirement
|
||||
boundary_check = var.require_permissions_boundary && var.permissions_boundary == null ? tobool("Permissions boundary is required but not set") : true
|
||||
|
||||
# Normalize repo names with org prefix
|
||||
normalize_repo = { for k, v in var.roles : k => merge(v, {
|
||||
repos = [for repo in v.repos :
|
||||
!contains(split("/", repo), "/") && var.github_org != ""
|
||||
? "${var.github_org}/${repo}"
|
||||
: repo
|
||||
]
|
||||
# Cap session duration at limit
|
||||
max_session_hours = min(v.max_session_hours, var.max_session_hours_limit)
|
||||
})}
|
||||
|
||||
# Validate no wildcard repos unless workflow_ref is set
|
||||
wildcard_check = var.deny_wildcard_repos ? alltrue([
|
||||
for k, v in var.roles : !contains(v.repos, "*") || v.workflow_ref != ""
|
||||
]) : true
|
||||
|
||||
_ = local.wildcard_check ? true : tobool("Wildcard repos (*) require workflow_ref restriction or deny_wildcard_repos=false")
|
||||
|
||||
# Common tags
|
||||
common_tags = merge(var.tags, {
|
||||
ManagedBy = "terraform"
|
||||
Module = "github-oidc"
|
||||
})
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# OIDC Provider
|
||||
################################################################################
|
||||
|
||||
data "tls_certificate" "github" {
|
||||
count = var.create_provider ? 1 : 0
|
||||
url = "https://token.actions.githubusercontent.com"
|
||||
}
|
||||
|
||||
resource "aws_iam_openid_connect_provider" "github" {
|
||||
count = var.create_provider ? 1 : 0
|
||||
|
||||
url = "https://token.actions.githubusercontent.com"
|
||||
client_id_list = ["sts.amazonaws.com"]
|
||||
thumbprint_list = [data.tls_certificate.github[0].certificates[0].sha1_fingerprint]
|
||||
|
||||
tags = merge(local.common_tags, {
|
||||
Name = "github-actions-oidc"
|
||||
Description = "GitHub Actions OIDC Identity Provider"
|
||||
})
|
||||
}
|
||||
|
||||
locals {
|
||||
provider_arn = var.create_provider ? aws_iam_openid_connect_provider.github[0].arn : var.provider_arn
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Custom Roles
|
||||
################################################################################
|
||||
|
||||
resource "aws_iam_role" "github" {
|
||||
for_each = local.normalize_repo
|
||||
|
||||
name = "${var.name_prefix}-${each.key}"
|
||||
path = var.path
|
||||
description = "GitHub Actions: ${join(", ", each.value.repos)}"
|
||||
max_session_duration = each.value.max_session_hours * 3600
|
||||
permissions_boundary = var.permissions_boundary
|
||||
|
||||
assume_role_policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [{
|
||||
Sid = "GitHubActionsOIDC"
|
||||
Effect = "Allow"
|
||||
Action = "sts:AssumeRoleWithWebIdentity"
|
||||
Principal = {
|
||||
Federated = local.provider_arn
|
||||
}
|
||||
Condition = merge(
|
||||
{
|
||||
StringEquals = {
|
||||
"token.actions.githubusercontent.com:aud" = "sts.amazonaws.com"
|
||||
}
|
||||
StringLike = {
|
||||
"token.actions.githubusercontent.com:sub" = distinct(compact(concat(
|
||||
# Branch-based subjects
|
||||
flatten([for repo in each.value.repos :
|
||||
length(each.value.branches) > 0
|
||||
? [for branch in each.value.branches : "repo:${repo}:ref:refs/heads/${branch}"]
|
||||
: length(each.value.tags) == 0 && length(each.value.environments) == 0 && !each.value.pull_request
|
||||
? ["repo:${repo}:*"]
|
||||
: []
|
||||
]),
|
||||
# Tag-based subjects
|
||||
flatten([for repo in each.value.repos :
|
||||
[for tag in each.value.tags : "repo:${repo}:ref:refs/tags/${tag}"]
|
||||
]),
|
||||
# Environment-based subjects
|
||||
flatten([for repo in each.value.repos :
|
||||
[for env in each.value.environments : "repo:${repo}:environment:${env}"]
|
||||
]),
|
||||
# Pull request subjects
|
||||
each.value.pull_request
|
||||
? [for repo in each.value.repos : "repo:${repo}:pull_request"]
|
||||
: []
|
||||
)))
|
||||
}
|
||||
},
|
||||
# Workflow ref condition (for reusable workflows)
|
||||
each.value.workflow_ref != "" ? {
|
||||
StringEquals = merge(
|
||||
{ "token.actions.githubusercontent.com:aud" = "sts.amazonaws.com" },
|
||||
{ "token.actions.githubusercontent.com:job_workflow_ref" = each.value.workflow_ref }
|
||||
)
|
||||
} : {},
|
||||
# Extra conditions
|
||||
each.value.extra_conditions
|
||||
)
|
||||
}]
|
||||
})
|
||||
|
||||
tags = merge(local.common_tags, {
|
||||
Name = "${var.name_prefix}-${each.key}"
|
||||
GitHubRepos = join(",", slice(each.value.repos, 0, min(5, length(each.value.repos))))
|
||||
Purpose = "github-actions-oidc"
|
||||
})
|
||||
}
|
||||
|
||||
# Managed policy attachments
|
||||
resource "aws_iam_role_policy_attachment" "github" {
|
||||
for_each = {
|
||||
for pair in flatten([
|
||||
for role_name, role in local.normalize_repo : [
|
||||
for policy_arn in role.policy_arns : {
|
||||
role_name = role_name
|
||||
policy_arn = policy_arn
|
||||
}
|
||||
]
|
||||
]) : "${pair.role_name}-${md5(pair.policy_arn)}" => pair
|
||||
}
|
||||
|
||||
role = aws_iam_role.github[each.value.role_name].name
|
||||
policy_arn = each.value.policy_arn
|
||||
}
|
||||
|
||||
# Inline policies (raw JSON)
|
||||
resource "aws_iam_role_policy" "github_inline" {
|
||||
for_each = { for k, v in local.normalize_repo : k => v if v.inline_policy != "" }
|
||||
|
||||
name = "inline"
|
||||
role = aws_iam_role.github[each.key].id
|
||||
policy = each.value.inline_policy
|
||||
}
|
||||
|
||||
# Generated policies from statements
|
||||
resource "aws_iam_role_policy" "github_generated" {
|
||||
for_each = { for k, v in local.normalize_repo : k => v if length(v.policy_statements) > 0 }
|
||||
|
||||
name = "generated"
|
||||
role = aws_iam_role.github[each.key].id
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [for stmt in each.value.policy_statements : {
|
||||
Sid = stmt.sid != "" ? stmt.sid : null
|
||||
Effect = stmt.effect
|
||||
Action = stmt.actions
|
||||
Resource = stmt.resources
|
||||
Condition = length(stmt.conditions) > 0 ? {
|
||||
for cond in stmt.conditions : cond.test => {
|
||||
"${cond.variable}" = cond.values
|
||||
}
|
||||
} : null
|
||||
}]
|
||||
})
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Terraform Deploy Role (Template)
|
||||
################################################################################
|
||||
|
||||
locals {
|
||||
tf_role_enabled = try(var.terraform_deploy_role.enabled, false)
|
||||
tf_repos = try(var.terraform_deploy_role.repos, [])
|
||||
tf_repos_normalized = [for repo in local.tf_repos :
|
||||
!contains(split("/", repo), "/") && var.github_org != ""
|
||||
? "${var.github_org}/${repo}"
|
||||
: repo
|
||||
]
|
||||
}
|
||||
|
||||
resource "aws_iam_role" "terraform" {
|
||||
count = local.tf_role_enabled ? 1 : 0
|
||||
|
||||
name = "${var.name_prefix}-terraform"
|
||||
path = var.path
|
||||
description = "GitHub Actions - Terraform deployment"
|
||||
max_session_duration = min(2, var.max_session_hours_limit) * 3600
|
||||
permissions_boundary = var.permissions_boundary
|
||||
|
||||
assume_role_policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [{
|
||||
Sid = "GitHubActionsTerraform"
|
||||
Effect = "Allow"
|
||||
Action = "sts:AssumeRoleWithWebIdentity"
|
||||
Principal = { Federated = local.provider_arn }
|
||||
Condition = {
|
||||
StringEquals = { "token.actions.githubusercontent.com:aud" = "sts.amazonaws.com" }
|
||||
StringLike = {
|
||||
"token.actions.githubusercontent.com:sub" = concat(
|
||||
flatten([for repo in local.tf_repos_normalized :
|
||||
length(try(var.terraform_deploy_role.branches, [])) > 0
|
||||
? [for branch in var.terraform_deploy_role.branches : "repo:${repo}:ref:refs/heads/${branch}"]
|
||||
: ["repo:${repo}:*"]
|
||||
]),
|
||||
flatten([for repo in local.tf_repos_normalized :
|
||||
[for env in try(var.terraform_deploy_role.environments, []) : "repo:${repo}:environment:${env}"]
|
||||
])
|
||||
)
|
||||
}
|
||||
}
|
||||
}]
|
||||
})
|
||||
|
||||
tags = merge(local.common_tags, {
|
||||
Name = "${var.name_prefix}-terraform"
|
||||
Purpose = "terraform-deployment"
|
||||
})
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy" "terraform_state" {
|
||||
count = local.tf_role_enabled && try(var.terraform_deploy_role.state_bucket, "") != "" ? 1 : 0
|
||||
|
||||
name = "terraform-state"
|
||||
role = aws_iam_role.terraform[0].id
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [
|
||||
{
|
||||
Sid = "TerraformStateBucket"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"s3:GetObject",
|
||||
"s3:PutObject",
|
||||
"s3:DeleteObject"
|
||||
]
|
||||
Resource = "arn:${local.partition}:s3:::${var.terraform_deploy_role.state_bucket}/${try(var.terraform_deploy_role.state_bucket_key_prefix, "*")}"
|
||||
},
|
||||
{
|
||||
Sid = "TerraformStateBucketList"
|
||||
Effect = "Allow"
|
||||
Action = ["s3:ListBucket"]
|
||||
Resource = "arn:${local.partition}:s3:::${var.terraform_deploy_role.state_bucket}"
|
||||
Condition = {
|
||||
StringLike = {
|
||||
"s3:prefix" = [try(var.terraform_deploy_role.state_bucket_key_prefix, "*")]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy" "terraform_lock" {
|
||||
count = local.tf_role_enabled && try(var.terraform_deploy_role.dynamodb_table, "") != "" ? 1 : 0
|
||||
|
||||
name = "terraform-lock"
|
||||
role = aws_iam_role.terraform[0].id
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [{
|
||||
Sid = "TerraformLockTable"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"dynamodb:GetItem",
|
||||
"dynamodb:PutItem",
|
||||
"dynamodb:DeleteItem"
|
||||
]
|
||||
Resource = "arn:${local.partition}:dynamodb:*:${local.account_id}:table/${var.terraform_deploy_role.dynamodb_table}"
|
||||
}]
|
||||
})
|
||||
}
|
||||
|
||||
# Service-specific permissions (least privilege approach)
|
||||
resource "aws_iam_role_policy" "terraform_services" {
|
||||
count = local.tf_role_enabled && length(try(var.terraform_deploy_role.allowed_services, [])) > 0 ? 1 : 0
|
||||
|
||||
name = "terraform-services"
|
||||
role = aws_iam_role.terraform[0].id
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [{
|
||||
Sid = "AllowedServices"
|
||||
Effect = "Allow"
|
||||
Action = flatten([for svc in var.terraform_deploy_role.allowed_services : "${svc}:*"])
|
||||
Resource = "*"
|
||||
}]
|
||||
})
|
||||
}
|
||||
|
||||
# Explicit denies for dangerous actions
|
||||
resource "aws_iam_role_policy" "terraform_deny" {
|
||||
count = local.tf_role_enabled && length(try(var.terraform_deploy_role.denied_actions, [])) > 0 ? 1 : 0
|
||||
|
||||
name = "terraform-deny"
|
||||
role = aws_iam_role.terraform[0].id
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [{
|
||||
Sid = "ExplicitDeny"
|
||||
Effect = "Deny"
|
||||
Action = var.terraform_deploy_role.denied_actions
|
||||
Resource = "*"
|
||||
}]
|
||||
})
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# ECR Push Role (Template)
|
||||
################################################################################
|
||||
|
||||
locals {
|
||||
ecr_role_enabled = try(var.ecr_push_role.enabled, false)
|
||||
ecr_repos_gh = try(var.ecr_push_role.repos, [])
|
||||
ecr_repos_normalized = [for repo in local.ecr_repos_gh :
|
||||
!contains(split("/", repo), "/") && var.github_org != ""
|
||||
? "${var.github_org}/${repo}"
|
||||
: repo
|
||||
]
|
||||
}
|
||||
|
||||
resource "aws_iam_role" "ecr" {
|
||||
count = local.ecr_role_enabled ? 1 : 0
|
||||
|
||||
name = "${var.name_prefix}-ecr-push"
|
||||
path = var.path
|
||||
description = "GitHub Actions - ECR push"
|
||||
max_session_duration = 3600
|
||||
permissions_boundary = var.permissions_boundary
|
||||
|
||||
assume_role_policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [{
|
||||
Sid = "GitHubActionsECR"
|
||||
Effect = "Allow"
|
||||
Action = "sts:AssumeRoleWithWebIdentity"
|
||||
Principal = { Federated = local.provider_arn }
|
||||
Condition = {
|
||||
StringEquals = { "token.actions.githubusercontent.com:aud" = "sts.amazonaws.com" }
|
||||
StringLike = {
|
||||
"token.actions.githubusercontent.com:sub" = flatten([for repo in local.ecr_repos_normalized :
|
||||
length(try(var.ecr_push_role.branches, [])) > 0
|
||||
? [for branch in var.ecr_push_role.branches : "repo:${repo}:ref:refs/heads/${branch}"]
|
||||
: ["repo:${repo}:*"]
|
||||
])
|
||||
}
|
||||
}
|
||||
}]
|
||||
})
|
||||
|
||||
tags = merge(local.common_tags, {
|
||||
Name = "${var.name_prefix}-ecr-push"
|
||||
Purpose = "ecr-push"
|
||||
})
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy" "ecr" {
|
||||
count = local.ecr_role_enabled ? 1 : 0
|
||||
|
||||
name = "ecr-push"
|
||||
role = aws_iam_role.ecr[0].id
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = concat(
|
||||
[{
|
||||
Sid = "ECRAuth"
|
||||
Effect = "Allow"
|
||||
Action = "ecr:GetAuthorizationToken"
|
||||
Resource = "*" # Required - GetAuthorizationToken doesn't support resource constraints
|
||||
}],
|
||||
[{
|
||||
Sid = "ECRPush"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"ecr:BatchCheckLayerAvailability",
|
||||
"ecr:GetDownloadUrlForLayer",
|
||||
"ecr:BatchGetImage",
|
||||
"ecr:PutImage",
|
||||
"ecr:InitiateLayerUpload",
|
||||
"ecr:UploadLayerPart",
|
||||
"ecr:CompleteLayerUpload",
|
||||
"ecr:DescribeRepositories",
|
||||
"ecr:DescribeImages"
|
||||
]
|
||||
Resource = [for repo in try(var.ecr_push_role.ecr_repos, []) :
|
||||
"arn:${local.partition}:ecr:*:${local.account_id}:repository/${repo}"
|
||||
]
|
||||
}],
|
||||
try(var.ecr_push_role.allow_create, false) ? [{
|
||||
Sid = "ECRCreate"
|
||||
Effect = "Allow"
|
||||
Action = ["ecr:CreateRepository", "ecr:TagResource"]
|
||||
Resource = "arn:${local.partition}:ecr:*:${local.account_id}:repository/*"
|
||||
}] : [],
|
||||
try(var.ecr_push_role.allow_delete, false) ? [{
|
||||
Sid = "ECRDelete"
|
||||
Effect = "Allow"
|
||||
Action = ["ecr:DeleteRepository", "ecr:BatchDeleteImage"]
|
||||
Resource = [for repo in try(var.ecr_push_role.ecr_repos, []) :
|
||||
"arn:${local.partition}:ecr:*:${local.account_id}:repository/${repo}"
|
||||
]
|
||||
}] : []
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# S3 Deploy Role (Template)
|
||||
################################################################################
|
||||
|
||||
locals {
|
||||
s3_role_enabled = try(var.s3_deploy_role.enabled, false)
|
||||
s3_repos = try(var.s3_deploy_role.repos, [])
|
||||
s3_repos_normalized = [for repo in local.s3_repos :
|
||||
!contains(split("/", repo), "/") && var.github_org != ""
|
||||
? "${var.github_org}/${repo}"
|
||||
: repo
|
||||
]
|
||||
}
|
||||
|
||||
resource "aws_iam_role" "s3_deploy" {
|
||||
count = local.s3_role_enabled ? 1 : 0
|
||||
|
||||
name = "${var.name_prefix}-s3-deploy"
|
||||
path = var.path
|
||||
description = "GitHub Actions - S3 deployment"
|
||||
max_session_duration = 3600
|
||||
permissions_boundary = var.permissions_boundary
|
||||
|
||||
assume_role_policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [{
|
||||
Sid = "GitHubActionsS3Deploy"
|
||||
Effect = "Allow"
|
||||
Action = "sts:AssumeRoleWithWebIdentity"
|
||||
Principal = { Federated = local.provider_arn }
|
||||
Condition = {
|
||||
StringEquals = { "token.actions.githubusercontent.com:aud" = "sts.amazonaws.com" }
|
||||
StringLike = {
|
||||
"token.actions.githubusercontent.com:sub" = flatten([for repo in local.s3_repos_normalized :
|
||||
length(try(var.s3_deploy_role.branches, [])) > 0
|
||||
? [for branch in var.s3_deploy_role.branches : "repo:${repo}:ref:refs/heads/${branch}"]
|
||||
: ["repo:${repo}:*"]
|
||||
])
|
||||
}
|
||||
}
|
||||
}]
|
||||
})
|
||||
|
||||
tags = merge(local.common_tags, {
|
||||
Name = "${var.name_prefix}-s3-deploy"
|
||||
Purpose = "s3-static-site"
|
||||
})
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy" "s3_deploy" {
|
||||
count = local.s3_role_enabled ? 1 : 0
|
||||
|
||||
name = "s3-deploy"
|
||||
role = aws_iam_role.s3_deploy[0].id
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = concat(
|
||||
[{
|
||||
Sid = "S3Deploy"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"s3:PutObject",
|
||||
"s3:GetObject",
|
||||
"s3:DeleteObject",
|
||||
"s3:GetObjectAcl",
|
||||
"s3:PutObjectAcl"
|
||||
]
|
||||
Resource = flatten([for bucket in try(var.s3_deploy_role.bucket_arns, []) : [
|
||||
for prefix in try(var.s3_deploy_role.allowed_prefixes, ["*"]) :
|
||||
"${bucket}/${prefix}"
|
||||
]])
|
||||
}],
|
||||
[{
|
||||
Sid = "S3List"
|
||||
Effect = "Allow"
|
||||
Action = ["s3:ListBucket", "s3:GetBucketLocation"]
|
||||
Resource = try(var.s3_deploy_role.bucket_arns, [])
|
||||
}],
|
||||
length(try(var.s3_deploy_role.cloudfront_arns, [])) > 0 ? [{
|
||||
Sid = "CloudFrontInvalidate"
|
||||
Effect = "Allow"
|
||||
Action = "cloudfront:CreateInvalidation"
|
||||
Resource = var.s3_deploy_role.cloudfront_arns
|
||||
}] : []
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Lambda Deploy Role (Template)
|
||||
################################################################################
|
||||
|
||||
locals {
|
||||
lambda_role_enabled = try(var.lambda_deploy_role.enabled, false)
|
||||
lambda_repos = try(var.lambda_deploy_role.repos, [])
|
||||
lambda_repos_normalized = [for repo in local.lambda_repos :
|
||||
!contains(split("/", repo), "/") && var.github_org != ""
|
||||
? "${var.github_org}/${repo}"
|
||||
: repo
|
||||
]
|
||||
}
|
||||
|
||||
resource "aws_iam_role" "lambda_deploy" {
|
||||
count = local.lambda_role_enabled ? 1 : 0
|
||||
|
||||
name = "${var.name_prefix}-lambda-deploy"
|
||||
path = var.path
|
||||
description = "GitHub Actions - Lambda deployment"
|
||||
max_session_duration = 3600
|
||||
permissions_boundary = var.permissions_boundary
|
||||
|
||||
assume_role_policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [{
|
||||
Sid = "GitHubActionsLambda"
|
||||
Effect = "Allow"
|
||||
Action = "sts:AssumeRoleWithWebIdentity"
|
||||
Principal = { Federated = local.provider_arn }
|
||||
Condition = {
|
||||
StringEquals = { "token.actions.githubusercontent.com:aud" = "sts.amazonaws.com" }
|
||||
StringLike = {
|
||||
"token.actions.githubusercontent.com:sub" = flatten([for repo in local.lambda_repos_normalized :
|
||||
length(try(var.lambda_deploy_role.branches, [])) > 0
|
||||
? [for branch in var.lambda_deploy_role.branches : "repo:${repo}:ref:refs/heads/${branch}"]
|
||||
: ["repo:${repo}:*"]
|
||||
])
|
||||
}
|
||||
}
|
||||
}]
|
||||
})
|
||||
|
||||
tags = merge(local.common_tags, {
|
||||
Name = "${var.name_prefix}-lambda-deploy"
|
||||
Purpose = "lambda-deployment"
|
||||
})
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy" "lambda_deploy" {
|
||||
count = local.lambda_role_enabled ? 1 : 0
|
||||
|
||||
name = "lambda-deploy"
|
||||
role = aws_iam_role.lambda_deploy[0].id
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = concat(
|
||||
[{
|
||||
Sid = "LambdaDeploy"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"lambda:UpdateFunctionCode",
|
||||
"lambda:UpdateFunctionConfiguration",
|
||||
"lambda:GetFunction",
|
||||
"lambda:GetFunctionConfiguration",
|
||||
"lambda:PublishVersion",
|
||||
"lambda:ListVersionsByFunction"
|
||||
]
|
||||
Resource = try(var.lambda_deploy_role.function_arns, [])
|
||||
}],
|
||||
try(var.lambda_deploy_role.allow_create, false) ? [{
|
||||
Sid = "LambdaCreate"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"lambda:CreateFunction",
|
||||
"lambda:DeleteFunction",
|
||||
"lambda:TagResource",
|
||||
"lambda:AddPermission",
|
||||
"lambda:RemovePermission"
|
||||
]
|
||||
Resource = "arn:${local.partition}:lambda:*:${local.account_id}:function:*"
|
||||
}] : [],
|
||||
try(var.lambda_deploy_role.allow_create, false) ? [{
|
||||
Sid = "IAMPassRole"
|
||||
Effect = "Allow"
|
||||
Action = "iam:PassRole"
|
||||
Resource = "arn:${local.partition}:iam::${local.account_id}:role/*"
|
||||
Condition = {
|
||||
StringEquals = {
|
||||
"iam:PassedToService" = "lambda.amazonaws.com"
|
||||
}
|
||||
}
|
||||
}] : [],
|
||||
try(var.lambda_deploy_role.allow_logs, true) ? [{
|
||||
Sid = "CloudWatchLogs"
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"logs:DescribeLogGroups",
|
||||
"logs:DescribeLogStreams",
|
||||
"logs:GetLogEvents"
|
||||
]
|
||||
Resource = "arn:${local.partition}:logs:*:${local.account_id}:log-group:/aws/lambda/*"
|
||||
}] : []
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Security Monitoring (Optional)
|
||||
################################################################################
|
||||
|
||||
resource "aws_cloudwatch_log_metric_filter" "oidc_assume_role" {
|
||||
count = var.enable_cloudtrail_logging && var.alarm_sns_topic_arn != "" ? 1 : 0
|
||||
|
||||
name = "github-oidc-assume-role"
|
||||
pattern = "{ ($.eventName = AssumeRoleWithWebIdentity) && ($.requestParameters.roleArn = \"*${var.name_prefix}*\") }"
|
||||
log_group_name = "aws-cloudtrail-logs" # Adjust to your CloudTrail log group
|
||||
|
||||
metric_transformation {
|
||||
name = "GitHubOIDCAssumeRole"
|
||||
namespace = "Security/OIDC"
|
||||
value = "1"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_cloudwatch_metric_alarm" "oidc_high_usage" {
|
||||
count = var.enable_cloudtrail_logging && var.alarm_sns_topic_arn != "" ? 1 : 0
|
||||
|
||||
alarm_name = "github-oidc-high-usage"
|
||||
alarm_description = "High number of GitHub OIDC role assumptions"
|
||||
comparison_operator = "GreaterThanThreshold"
|
||||
evaluation_periods = 1
|
||||
metric_name = "GitHubOIDCAssumeRole"
|
||||
namespace = "Security/OIDC"
|
||||
period = 300
|
||||
statistic = "Sum"
|
||||
threshold = 100
|
||||
treat_missing_data = "notBreaching"
|
||||
|
||||
alarm_actions = [var.alarm_sns_topic_arn]
|
||||
|
||||
tags = local.common_tags
|
||||
}
|
||||
Reference in New Issue
Block a user