Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
248 changes: 248 additions & 0 deletions terraform/org/boundary.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
################################################################################
# Permission Boundary: javabin-developer-boundary
#
# This is the cornerstone of the IAM security model. Any role carrying this
# boundary can only create roles that also carry it (self-replicating).
#
# The boundary defines the maximum possible permissions for any non-platform
# role. Hard denies here cannot be overridden by any policy attached to
# roles that carry this boundary.
#
# IMPORTANT: This resource lives in terraform/org/ (human-applied) because
# the boundary's self-protection denies iam:CreatePolicyVersion on itself.
# The CI pipeline (which carries the boundary) cannot modify it.
################################################################################

resource "aws_iam_policy" "developer_boundary" {
name = "${var.project}-developer-boundary"
description = "Permission boundary for all non-platform roles. Self-replicating: roles with this boundary can only create roles that also carry it."

policy = jsonencode({
Version = "2012-10-17"
Statement = [
########################################################################
# Allow everything not explicitly denied below
########################################################################
{
Sid = "AllowAll"
Effect = "Allow"
Action = "*"
Resource = "*"
},

########################################################################
# Self-replicating: deny creating/modifying roles without this boundary
########################################################################
{
Sid = "DenyRolesWithoutBoundary"
Effect = "Deny"
Action = [
"iam:CreateRole",
"iam:PutRolePermissionsBoundary"
]
Resource = "*"
Condition = {
StringNotEquals = {
"iam:PermissionsBoundary" = "arn:aws:iam::${var.aws_account_id}:policy/${var.project}-developer-boundary"
}
}
},

########################################################################
# Deny creating IAM users and access keys (console/programmatic)
########################################################################
{
Sid = "DenyIAMUserCreation"
Effect = "Deny"
Action = [
"iam:CreateUser",
"iam:CreateLoginProfile",
"iam:UpdateLoginProfile",
"iam:CreateAccessKey",
"iam:DeleteAccountPasswordPolicy",
"iam:CreateVirtualMFADevice",
"iam:DeactivateMFADevice"
]
Resource = "*"
},

########################################################################
# Deny modifying or deleting this boundary policy itself
########################################################################
{
Sid = "DenyBoundaryTampering"
Effect = "Deny"
Action = [
"iam:DeletePolicy",
"iam:DeletePolicyVersion",
"iam:CreatePolicyVersion",
"iam:SetDefaultPolicyVersion"
]
Resource = "arn:aws:iam::${var.aws_account_id}:policy/${var.project}-developer-boundary"
},
{
Sid = "DenyDeleteRoleBoundary"
Effect = "Deny"
Action = [
"iam:DeleteRolePermissionsBoundary"
]
Resource = "*"
},

########################################################################
# Deny IAM Identity Center, Organizations, SCPs
########################################################################
{
Sid = "DenyIdentityCenterAndOrgs"
Effect = "Deny"
Action = [
"organizations:*",
"sso:*",
"sso-directory:*",
"identitystore:*",
"account:*"
]
Resource = "*"
},

########################################################################
# Deny disabling/deleting protective services
########################################################################
{
Sid = "DenyProtectiveServicesTampering"
Effect = "Deny"
Action = [
"guardduty:DeleteDetector",
"guardduty:DeleteMembers",
"guardduty:DisassociateFromMasterAccount",
"guardduty:UpdateDetector",
"config:DeleteConfigurationRecorder",
"config:StopConfigurationRecorder",
"config:DeleteDeliveryChannel",
"config:DeleteRetentionConfiguration",
"securityhub:DisableSecurityHub",
"securityhub:DeleteMembers",
"securityhub:DisassociateFromMasterAccount",
"cloudtrail:DeleteTrail",
"cloudtrail:StopLogging",
"cloudtrail:UpdateTrail",
"cloudtrail:PutEventSelectors"
]
Resource = "*"
},

########################################################################
# Deny platform networking: VPC, subnets, IGW, NAT gateway
########################################################################
{
Sid = "DenyPlatformNetworking"
Effect = "Deny"
Action = [
"ec2:CreateVpc",
"ec2:DeleteVpc",
"ec2:ModifyVpcAttribute",
"ec2:CreateSubnet",
"ec2:DeleteSubnet",
"ec2:CreateInternetGateway",
"ec2:DeleteInternetGateway",
"ec2:AttachInternetGateway",
"ec2:DetachInternetGateway",
"ec2:CreateNatGateway",
"ec2:DeleteNatGateway",
"ec2:CreateRouteTable",
"ec2:DeleteRouteTable"
]
Resource = "*"
},

########################################################################
# Protect platform security groups
#
# Teams CAN create security groups (needed for RDS, custom services).
# Teams CANNOT modify or delete platform-owned security groups.
# Platform SGs are named javabin-* (e.g., javabin-alb-sg, javabin-ecs-tasks-sg).
# App SGs use app-name prefix (e.g., moresleep-rds-sg).
########################################################################
{
Sid = "DenyPlatformSecurityGroups"
Effect = "Deny"
Action = [
"ec2:DeleteSecurityGroup",
"ec2:AuthorizeSecurityGroupIngress",
"ec2:RevokeSecurityGroupIngress",
"ec2:AuthorizeSecurityGroupEgress",
"ec2:RevokeSecurityGroupEgress",
"ec2:ModifySecurityGroupRules",
]
Resource = "arn:aws:ec2:${var.region}:${var.aws_account_id}:security-group/*"
Condition = {
StringLike = {
"ec2:ResourceTag/Name" = "${var.project}-*"
}
}
},

########################################################################
# Deny platform ECS cluster, ALB, ACM certs
########################################################################
{
Sid = "DenyPlatformECSCluster"
Effect = "Deny"
Action = [
"ecs:DeleteCluster",
"ecs:UpdateCluster"
]
Resource = "arn:aws:ecs:${var.region}:${var.aws_account_id}:cluster/${var.project}-platform"
},
{
Sid = "DenyPlatformALB"
Effect = "Deny"
Action = [
"elasticloadbalancing:DeleteLoadBalancer",
"elasticloadbalancing:ModifyLoadBalancerAttributes"
]
Resource = "arn:aws:elasticloadbalancing:${var.region}:${var.aws_account_id}:loadbalancer/app/${var.project}-*"
},
{
Sid = "DenyPlatformACM"
Effect = "Deny"
Action = [
"acm:DeleteCertificate"
]
Resource = "arn:aws:acm:${var.region}:${var.aws_account_id}:certificate/*"
},

########################################################################
# Deny access to state and CI artifact buckets/tables
########################################################################
{
Sid = "DenyStateBuckets"
Effect = "Deny"
Action = [
"s3:DeleteBucket",
"s3:PutBucketPolicy",
"s3:DeleteBucketPolicy",
"s3:PutBucketVersioning",
"s3:PutEncryptionConfiguration"
]
Resource = [
"arn:aws:s3:::${var.project}-terraform-*",
"arn:aws:s3:::${var.project}-ci-*"
]
},
{
Sid = "DenyStateTables"
Effect = "Deny"
Action = [
"dynamodb:DeleteTable",
"dynamodb:UpdateTable"
]
Resource = "arn:aws:dynamodb:${var.region}:${var.aws_account_id}:table/${var.project}-terraform-*"
}
]
})

tags = {
Name = "${var.project}-developer-boundary"
}
}
21 changes: 5 additions & 16 deletions terraform/platform/iam/boundary.tf
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
################################################################################
# Permission Boundary: javabin-developer-boundary
#
# This is the cornerstone of the IAM security model. Any role carrying this
# boundary can only create roles that also carry it (self-replicating).
# MIGRATION NOTE: This resource is being moved to terraform/org/boundary.tf
# because the boundary's self-protection (DenyBoundaryTampering) prevents
# the CI pipeline from modifying it. See docs/org-runbook.md for migration steps.
#
# The boundary defines the maximum possible permissions for any non-platform
# role. Hard denies here cannot be overridden by any policy attached to
# roles that carry this boundary.
# Once the state migration is complete, this file will be replaced with a
# data source reference. Until then, keep this resource to avoid CI destroying it.
################################################################################

resource "aws_iam_policy" "developer_boundary" {
Expand Down Expand Up @@ -103,12 +103,6 @@ resource "aws_iam_policy" "developer_boundary" {

########################################################################
# Deny disabling/deleting protective services
# All roles (infra + app) can create/read/manage these services,
# but no role can disable or delete them. This lets the infra CI role
# manage GuardDuty/Config/SecurityHub while preventing any role from
# turning them off. App roles are further restricted by tag-based
# isolation on their Allow policy (they can't create these resources
# because they lack the platform tags).
########################################################################
{
Sid = "DenyProtectiveServicesTampering"
Expand Down Expand Up @@ -159,11 +153,6 @@ resource "aws_iam_policy" "developer_boundary" {

########################################################################
# Protect platform security groups
#
# Teams CAN create security groups (needed for RDS, custom services).
# Teams CANNOT modify or delete platform-owned security groups.
# Platform SGs are named javabin-* (e.g., javabin-alb-sg, javabin-ecs-tasks-sg).
# App SGs use app-name prefix (e.g., moresleep-rds-sg).
########################################################################
{
Sid = "DenyPlatformSecurityGroups"
Expand Down