feat(landingzone): add minimal AWS Landing Zone baseline module

- Introduce minimal landingzone module with account-level IAM baseline:
  - deny-root.json
  - deny-no-mfa.json (MFA enforced)
  - deny-console-write.json (Console readonly mode)
  - deny-ri-sp.json (deny Reserved Instances / Savings Plans purchases)
This commit is contained in:
Haitao Pan 2025-11-17 17:33:41 +08:00
parent f2c9b114cf
commit f2996804ac
15 changed files with 311 additions and 0 deletions

View File

@ -0,0 +1,9 @@
region: "ap-northeast-1"
account_id: "730335654753"
landingzone:
console_mode: "readonly" # 可选deny / readonly
enable_risp_controls: true # 限制 RI/SP 购买
enable_root_limited: true # 限制 root API
enable_mfa_enforce: true # 强制 MFA

View File

@ -0,0 +1,25 @@
# Local terraform files
.terraform/
.terraform.lock.hcl
terraform.tfstate
terraform.tfstate.backup
# Auto tfvars generated by CI/CD or sensitive data
*.tfvars
*.auto.tfvars
*.tfvars.json
# IDE / editor files
.idea/
.vscode/
*.swp
# AWS credentials — never commit
.aws/
credentials
config
# OS-specific
.DS_Store
Thumbs.db

View File

@ -0,0 +1,9 @@
init:
terraform init --upgrade
plan:
terraform plan
apply:
terraform apply -auto-approve
destroy:
terraform destroy -auto-approve

View File

@ -0,0 +1,8 @@
terraform {
backend "s3" {
bucket = "svc-plus-iac-state"
key = "bootstrap/dev-landingzone/terraform.tfstate"
region = "ap-northeast-1"
dynamodb_table = "svc-plus-iac-state-dynamodb-lock"
}
}

View File

@ -0,0 +1,16 @@
locals {
account = yamldecode(
file("${path.root}/../../config/accounts/dev-landingzone.yaml")
)
}
module "landingzone" {
source = "../../modules/landingzone"
region = local.account.region
account_id = local.account.account_id
console_mode = local.account.landingzone.console_mode
enable_risp_controls = local.account.landingzone.enable_risp_controls
enable_root_limited = local.account.landingzone.enable_root_limited
enable_mfa_enforce = local.account.landingzone.enable_mfa_enforce
}

View File

@ -0,0 +1,20 @@
terraform {
required_version = ">= 1.2"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.92.0"
}
}
}
provider "aws" {
region = local.account.region
assume_role {
role_arn = "arn:aws:iam::730335654753:role/TerraformDeployRole-Dev"
session_name = "TerraformDevSession"
}
}

View File

@ -0,0 +1,25 @@
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.
provider "registry.terraform.io/hashicorp/aws" {
version = "5.92.0"
constraints = "~> 5.92.0"
hashes = [
"h1:KS0bRFXK4N1Do9Y6olKtu4cMhcHvgGYYRHpN+VNfsnM=",
"zh:1d3a0b40831360e8e988aee74a9ff3d69d95cb541c2eae5cb843c64303a091ba",
"zh:3d29cbced6c708be2041a708d25c7c0fc22d09e4d0b174360ed113bfae786137",
"zh:4341a203cf5820a0ca18bb514ae10a6c113bc6a728fb432acbf817d232e8eff4",
"zh:4a49e2d91e4d92b6b93ccbcbdcfa2d67935ce62e33b939656766bb81b3fd9a2c",
"zh:54c7189358b37fd895dedbabf84e509c1980a8c404a1ee5b29b06e40497b8655",
"zh:5d8bb1ff089c37cb65c83b4647f1981fded993e87d8132915d92d79f29e2fcd8",
"zh:618f2eb87cd65b245aefba03991ad714a51ff3b841016ef68e2da2b85d0b2325",
"zh:7bce07bc542d0588ca42bac5098dd4f8af715417cd30166b4fb97cedd44ab109",
"zh:81419eab2d8810beb114b1ff5cbb592d21edc21b809dc12bb066e4b88fdd184a",
"zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425",
"zh:9dea39d4748eeeebe2e76ca59bca4ccd161c2687050878c47289a98407a23372",
"zh:d692fc33b67ac89e916c8f9233d39eacab8c438fe10172990ee9d94fba5ca372",
"zh:d9075c7da48947c029ba47d5985e1e8e3bf92367bfee8ca1ff0e747765e779a1",
"zh:e81c62db317f3b640b2e04eba0ada8aa606bcbae0152c09f6242e86b86ef5889",
"zh:f68562e073722c378d2f3529eb80ad463f12c44aa5523d558ae3b69f4de5ca1f",
]
}

View File

@ -0,0 +1,20 @@
terraform {
required_version = ">= 1.2"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.92.0"
}
}
}
provider "aws" {
region = local.account.region
assume_role {
role_arn = "arn:aws:iam::730335654753:role/TerraformDeployRole-Dev"
session_name = "TerraformDevSession"
}
}

View File

@ -0,0 +1,40 @@
locals {
root_policy = var.enable_root_limited ? "deny-root.json" : null
mfa_policy = var.enable_mfa_enforce ? "deny-no-mfa.json" : null
console_policy = var.console_mode == "readonly" ? "deny-console-write.json" : null
risp_policy = var.enable_risp_controls ? "deny-ri-sp.json" : null
policies = compact([
local.root_policy,
local.mfa_policy,
local.console_policy,
local.risp_policy
])
}
#
# Baseline IAM group
#
resource "aws_iam_group" "baseline" {
name = "LandingZoneBaseline"
}
#
# Create IAM policies
#
resource "aws_iam_policy" "baseline" {
for_each = toset(local.policies)
name = "landingzone-${replace(each.value, ".json", "")}"
policy = file("${path.module}/policies/${each.value}")
}
#
# Attach policies to baseline group
#
resource "aws_iam_group_policy_attachment" "attach" {
for_each = aws_iam_policy.baseline
group = aws_iam_group.baseline.name
policy_arn = each.value.arn
}

View File

@ -0,0 +1,4 @@
output "policy_arns" {
value = { for k, v in aws_iam_policy.baseline : k => v.arn }
}

View File

@ -0,0 +1,27 @@
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyWriteActionsForIAMUsers",
"Effect": "Deny",
"NotAction": [
"iam:Get*",
"iam:List*",
"sts:GetCallerIdentity",
"ec2:Describe*",
"s3:Get*",
"s3:List*",
"eks:Describe*",
"eks:List*",
"cloudwatch:Get*",
"cloudwatch:List*"
],
"Resource": "*",
"Condition": {
"StringLike": {
"aws:username": "*"
}
}
}
]
}

View File

@ -0,0 +1,23 @@
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyUnlessMFA",
"Effect": "Deny",
"NotAction": [
"iam:CreateVirtualMFADevice",
"iam:EnableMFADevice",
"iam:ListMFADevices",
"iam:ResyncMFADevice",
"sts:GetSessionToken",
"sts:GetCallerIdentity"
],
"Resource": "*",
"Condition": {
"BoolIfExists": {
"aws:MultiFactorAuthPresent": "false"
}
}
}
]
}

View File

@ -0,0 +1,33 @@
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyReservedInstancesPurchaseForNonFinOps",
"Effect": "Deny",
"Action": [
"ec2:PurchaseReservedInstancesOffering",
"ec2:ModifyReservedInstances"
],
"Resource": "*",
"Condition": {
"StringNotEquals": {
"aws:PrincipalArn": "arn:aws:iam::<ACCOUNT_ID>:role/FinOpsRole"
}
}
},
{
"Sid": "DenySavingsPlansPurchaseForNonFinOps",
"Effect": "Deny",
"Action": [
"savingsplans:CreateSavingsPlan",
"savingsplans:DeleteQueuedSavingsPlan"
],
"Resource": "*",
"Condition": {
"StringNotEquals": {
"aws:PrincipalArn": "arn:aws:iam::<ACCOUNT_ID>:role/FinOpsRole"
}
}
}
]
}

View File

@ -0,0 +1,25 @@
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "RootLimitedActions",
"Effect": "Deny",
"NotAction": [
"aws-portal:*",
"iam:CreateVirtualMFADevice",
"iam:EnableMFADevice",
"iam:ListMFADevices",
"iam:ResyncMFADevice",
"iam:Get*",
"iam:List*",
"sts:GetCallerIdentity"
],
"Resource": "*",
"Condition": {
"StringEquals": {
"aws:username": "root"
}
}
}
]
}

View File

@ -0,0 +1,27 @@
variable "region" {
type = string
}
variable "account_id" {
type = string
}
variable "console_mode" {
type = string
default = "readonly"
}
variable "enable_risp_controls" {
type = bool
default = true
}
variable "enable_root_limited" {
type = bool
default = true
}
variable "enable_mfa_enforce" {
type = bool
default = true
}