Pratik-Nawale/terraform-for-aws

GitHub: Pratik-Nawale/terraform-for-aws

一套完整的教学级Terraform + GitHub Actions CI/CD方案,通过OIDC无密钥认证安全地管理AWS基础设施的全生命周期。

Stars: 6 | Forks: 1

# Terraform for AWS — 结合 CI/CD 的基础设施即代码 [![Terraform](https://img.shields.io/badge/Terraform-1.14.9-7B42BC?logo=terraform)](https://www.terraform.io/) [![AWS](https://img.shields.io/badge/AWS-Provider%20~%3E%206.0-FF9900?logo=amazonaws)](https://registry.terraform.io/providers/hashicorp/aws/latest) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) 一个完整且可用于生产环境的配置,用于通过 **Terraform** 和 **GitHub Actions CI/CD** 管理 AWS 基础设施。本仓库记录了从零开始构建完全自动化基础设施管道的全过程——旨在提供教育意义、保证安全性且易于跟进。 **这个项目是做什么的?** 它使用 Terraform 配置和管理 AWS 资源(S3 存储桶、IAM 角色、EC2 实例等),所有变更均通过 GitHub Actions 进行验证和部署。GitHub 与 AWS 之间的身份验证使用 **OIDC 联合身份验证**——无需长期有效的 AWS 访问密钥。每一项基础设施变更都要经过代码审查流程:开启 PR → 查看计划 → 合并 → 手动应用。 ## 架构 ![Terraform AWS CI/CD 架构](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/10473aff3e111432.png) **流程概览:** 1. **开发者** 将代码推送到 **GitHub 仓库** 2. **GitHub Actions** 工作流根据事件(PR、推送到 master、手动触发)运行 3. 工作流在包含 Terraform、AWS CLI 和 TFLint 的自定义 **Docker 镜像**(从 Docker Hub 拉取)内运行 4. **OIDC 令牌交换** — GitHub Actions 向 AWS STS 出示 JWT(无需长期机密!) 5. AWS **IAM 角色**(Plan = 只读,Apply = 范围限定的写入)授予临时凭证 6. Terraform 在 S3 中读取/写入 **状态** 并管理 **基础设施**(EC2、IAM 等) ## 仓库结构 ``` terraform-for-aws/ ├── .github/ │ ├── FUNDING.yml # Sponsor button config (fill in your links) │ ├── ISSUE_TEMPLATE/ │ │ ├── bug-report.yml # Bug report form │ │ ├── feature-request.yml # Feature request form │ │ └── config.yml # Issue template chooser │ ├── PULL_REQUEST_TEMPLATE.md # PR checklist │ ├── dependabot.yml.example # Auto-update config (rename to enable) │ └── workflows/ │ ├── docker-publish.yml # Build & push Docker toolbox image (disabled) │ ├── terraform.yml # Validate + Plan (disabled) │ └── terraform-apply.yml # Manual Apply (disabled) ├── docs/ │ ├── architecture.png # Architecture diagram │ └── social-preview.png # GitHub social preview image ├── terraform-environments/ # Root module — "what" to deploy │ ├── versions.tf # Terraform + provider version pins │ ├── variables.tf # Input variables with validation │ ├── main.tf # Module calls + demo EC2 instance │ ├── outputs.tf # Output values (ARNs, IPs, etc.) │ └── terraform.tfvars.example # Template for your variable values ├── terraform-modules/ # Reusable modules — "how" to deploy │ ├── state-backend/ # S3 bucket + DynamoDB for state │ │ ├── main.tf │ │ ├── variables.tf │ │ └── outputs.tf │ └── iam-github-oidc/ # OIDC provider + IAM roles │ ├── main.tf │ ├── variables.tf │ └── outputs.tf ├── Dockerfile # Multi-stage build: Terraform + AWS CLI + TFLint ├── Makefile # Build/test/push/scan helpers ├── CODE_OF_CONDUCT.md # Community standards ├── CONTRIBUTING.md # How to contribute ├── SECURITY.md # Vulnerability reporting policy ├── .gitignore # Terraform + Docker + IDE ignores ├── .dockerignore # Keep Docker build context small ├── LICENSE # MIT License └── README.md # You are here ``` ## 前置条件 ### 账户 | 服务 | 所需内容 | 费用 | |---------|--------------|------| | **AWS** | 具有管理员访问权限的 AWS 账户(用于初始设置) | 符合免费套餐条件 | | **GitHub** | 拥有仓库的 GitHub 账户 | 免费 | | **Docker Hub** | Docker Hub 账户(用于存储工具箱镜像) | 免费(1个私有仓库) | ### 本地工具 | 工具 | 版本 | 用途 | |------|---------|---------| | **Terraform** | >= 1.14.0 | 基础设施即代码 | | **AWS CLI** | v2 | AWS 身份验证 + 配置 | | **Docker** | >= 20.0 | 构建工具箱容器镜像 | | **Git** | >= 2.0 | 版本控制 | ## 快速开始 ### 第 1 步:克隆仓库 ``` git clone https://github.com/YOUR_GITHUB_USERNAME/terraform-for-aws.git cd terraform-for-aws ``` ### 第 2 步:在本地配置 AWS 凭证 ``` # 配置您的 AWS CLI 使用管理员凭证(仅用于初始引导) aws configure # 输入您的 Access Key ID、Secret Access Key、region (us-east-1) 和 output format (json) ``` ### 第 3 步:设置变量 ``` cd terraform-environments cp terraform.tfvars.example terraform.tfvars ``` 使用你的值编辑 `terraform.tfvars`: ``` aws_region = "us-east-1" state_bucket_name = "tf-state-demo-xxxxx" # Must be globally unique! github_repo = "YOUR_GITHUB_USERNAME/terraform-for-aws" environment = "dev" ``` ### 第 4 步:构建 Docker 工具箱镜像 ``` # 从 repository 根目录 make build DOCKER_REPO=your-dockerhub-username make test DOCKER_REPO=your-dockerhub-username make push DOCKER_REPO=your-dockerhub-username ``` ### 第 5 步:引导基础设施(首次运行) ``` cd terraform-environments # 初始化 Terraform(下载 providers) terraform init # 预览将要创建的内容 terraform plan # 创建所有资源(S3 bucket、DynamoDB table、OIDC provider、IAM roles、demo EC2) terraform apply ``` ### 第 6 步:将状态迁移至 S3(可选但推荐) 创建 S3 存储桶后,将本地状态迁移到远程存储: 1. 打开 `terraform-environments/versions.tf` 2. 取消注释 `backend "s3"` 块 3. 更新存储桶名称以匹配你的 `state_bucket_name` 4. 运行迁移: ``` terraform init -migrate-state ``` Terraform 会询问:*"Do you want to copy existing state to the new backend?"* — 回答 **yes**。 ### 第 7 步:配置 GitHub 仓库 执行 `terraform apply` 后,输出内容会明确告诉你需要配置什么: ``` # 查看 outputs terraform output github_secrets terraform output github_variables ``` **添加密钥**(Settings → Secrets and variables → Actions → Secrets): | 密钥 | 值 | |--------|-------| | `AWS_PLAN_ROLE_ARN` | 来自 `terraform output plan_role_arn` 的 ARN | | `AWS_APPLY_ROLE_ARN` | 来自 `terraform output apply_role_arn` 的 ARN | | `DOCKERHUB_USERNAME` | 你的 Docker Hub 用户名 | | `DOCKERHUB_TOKEN` | 你的 Docker Hub 个人访问令牌 | **添加变量**(Settings → Secrets and variables → Actions → Variables): | 变量 | 值 | |----------|-------| | `DOCKERHUB_USERNAME` | 你的 Docker Hub 用户名 | | `TF_VAR_github_repo` | `你的GitHub用户名/terraform-for-aws` | | `TF_VAR_state_bucket_name` | 你的 S3 存储桶名称 | ### 第 8 步:推送并验证 ``` git add . git commit -m "Initial infrastructure setup" git push origin master ``` GitHub Actions 工作流现在应该会触发。检查 Actions 选项卡以验证一切是否正常。 ## 组件详情 ### Docker 工具箱镜像 Dockerfile 创建了一个一致的工具环境,包含: | 工具 | 版本 | 用途 | |------|---------|---------| | Terraform | 1.14.9 | 基础设施即代码引擎 | | AWS CLI v2 | 2.34.35 | AWS API 交互 | | TFLint | 0.55.1 | Terraform 代码检查与最佳实践 | | Git | (系统) | 模块获取 | | jq | (系统) | JSON 处理 | **安全特性:** - **多阶段构建** — 构建依赖项不会出现在最终镜像中 - **PGP 签名验证** — 对 AWS CLI 二进制文件进行加密验证 - **SHA-256 校验和验证** — 验证 TFLint 二进制文件的完整性 - **非 root 用户 (UID 1001)** — 容器以非特权用户身份运行 - **最小化基础镜像** — `debian:bookworm-slim` 减少了攻击面 **为什么使用自定义镜像而不是官方 Terraform 镜像?** 官方的 `hashicorp/terraform` 镜像只包含 Terraform。我们需要 AWS CLI 进行身份验证,需要 TFLint 进行代码检查。自定义镜像将所有内容捆绑在一起,确保所有 CI/CD 运行的版本一致性。 ### Terraform 模块 #### `state-backend` — 远程状态存储 **它创建了什么:** | 资源 | 用途 | |----------|---------| | S3 存储桶 | 存储带有版本控制、加密和公共访问阻止功能的 `.tfstate` 文件 | | DynamoDB 表 | 传统状态锁定表(为兼容性保留;推荐使用 S3 原生锁定) | **为什么使用远程状态?** 本地状态文件(在你的笔记本电脑上)存在以下问题: - 可能会丢失(笔记本电脑崩溃、意外删除) - 无法共享(团队协作困难) - 无法锁定(并发修改会破坏状态) S3 后端解决了所有这些问题。通过 `use_lockfile = true`,Terraform 会直接在 S3 中创建 `.tflock` 文件用于状态锁定——不需要 DynamoDB。 **既然不用为什么还要保留 DynamoDB?** 向后兼容性。如果你以后需要回退到不支持 `use_lockfile` 的旧版 Terraform,DynamoDB 表已准备就绪。该表使用 PROVISIONED 计费模式,配置为 2 RCU / 2 WCU 以保持在免费套餐内。 #### `iam-github-oidc` — OIDC 身份验证 **它创建了什么:** | 资源 | 用途 | |----------|---------| | OIDC 提供商 | 告诉 AWS 信任 GitHub Actions 身份令牌 | | Plan 角色 | 用于 `terraform plan` 的只读 IAM 角色 | | Apply 角色 | 用于 `terraform apply` 的限定范围写入 IAM 角色 | 有关详细信息,请参阅下面的 [OIDC 身份验证](#oidc-authentication) 和 [IAM 角色权限](#iam-role-permissions) 部分。 ### GitHub Actions 工作流 #### 1. `terraform.yml` — 验证与计划 **触发条件:** 针对 `master` 分支的 PR 和推送(当 terraform 文件发生更改时) **它的工作内容:** 1. **Validate 作业:** 检查格式(`terraform fmt`)、语法(`terraform validate`)和最佳实践(`tflint`) 2. **Plan 作业:** 运行 `terraform plan` 以显示将会发生的更改。在 PR 上,将计划作为评论发布,并隐去 AWS 账户 ID。 **为什么要隐去账户 ID?** AWS 账户 ID 并不完全算是秘密,但在公开的 PR 评论中暴露它们是不必要的。正则表达式 `s/[0-9]\{12\}/***ACCOUNT_ID***/g` 会替换所有 12 位数字。 #### 2. `terraform-apply.yml` — 手动应用 **触发条件:** 仅限手动触发(`workflow_dispatch`) **它的工作内容:** 1. 要求输入 "apply" 作为确认 2. 运行 `terraform plan` 并将计划文件保存为 artifact 3. 下载计划文件并使用该确切计划运行 `terraform apply` **为什么选择手动而不是在合并时自动应用?** 基础设施变更应该是有意的。自动应用意味着一个合并进去的拼写错误可能会破坏资源。手动应用为你提供了最后的机会进行核查。 **为什么不使用带必需审查者的 GitHub Environments?** 带有保护规则(必需审查者)的 GitHub Environments 对私有仓库需要 **GitHub Pro**。由于本项目面向可能使用 GitHub Free 的个人账户,因此我们改用 `workflow_dispatch` 作为关卡。 #### 3. `docker-publish.yml` — 构建并推送 Docker 镜像 **触发条件:** 推送到 `master`(当 Dockerfile 或 Makefile 更改时) **它的工作内容:** 构建 Docker 工具箱镜像,并将其带有 `latest` 和 SHA 标记的版本推送到 Docker Hub。 ### OIDC 身份验证 OIDC (OpenID Connect) 是 GitHub Actions 向 AWS 进行身份验证的现代方式。以下是它逐步工作的原理: ``` sequenceDiagram participant GH as GitHub Actions participant GHOIDC as GitHub OIDC Provider participant STS as AWS STS participant IAM as IAM OIDC Provider participant AWS as AWS Resources GH->>GHOIDC: 1. Request OIDC token (JWT) GHOIDC-->>GH: Token with claims (repo, branch, event) GH->>STS: 2. AssumeRoleWithWebIdentity (token + role ARN) STS->>IAM: 3. Validate token signature IAM-->>STS: Token is valid Note over STS: 4. Check trust policy:
✓ Audience = sts.amazonaws.com
✓ Subject = repo:owner/name:ref:...
✓ Branch/event matches conditions STS-->>GH: 5. Temporary credentials (1 hour TTL) GH->>AWS: 6. API calls with temporary credentials AWS-->>GH: Results Note over GH,AWS: Credentials auto-expire after 1 hour.
No secrets stored. No rotation needed. ``` **为什么使用 OIDC 而不是访问密钥?** | 特性 | 访问密钥 | OIDC | |---------|------------|------| | 凭证生命周期 | 永久有效直到轮换 | 1 小时(自动过期) | | 机密管理 | 必须存储在 GitHub Secrets 中 | 不需要机密 | | 轮换 | 手动 | 自动(每次运行获取新凭证) | | 影响范围 | 在撤销前无限制 | 仅限于会话持续时间 | | 审计跟踪 | 难以归因于特定运行 | 每个令牌都标识确切的工作流、仓库、分支和事件 | ### 安全模型 — 7 层深度防御 | 层级 | 内容 | 方式 | |-------|------|-----| | **1. OIDC 联合身份验证** | 无永久凭证 | GitHub Actions 每次运行都会获取新的 1 小时凭证 | | **2. 仓库范围信任** | 只有你的仓库才能代入角色 | 信任策略检查 OIDC 主题声明中的 `repo:owner/name` | | **3. 分支范围信任** | Apply 仅限于 master 分支 | Apply 角色信任要求 `ref:refs/heads/master` — PR 和特性分支只能进行 plan | | **4. 最小权限角色** | Plan 无法修改,Apply 无法提权 | 具有不同权限集的两个独立 IAM 角色 | | **5. 显式 IAM 拒绝** | 防止权限提升 | Apply 角色显式拒绝 `iam:CreateUser`、`iam:AttachRolePolicy`、`iam:PassRole` 等 | | **6. 状态加密** | 保护状态中的敏感值 | S3 存储桶使用 KMS 加密 + 仅限 TLS 访问 | | **7. 手动 apply 关卡** | 人工介入 | `workflow_dispatch` 需要有人有意触发 apply | ### IAM 角色权限 #### Plan 角色(只读) | 权限 | 原因 | |-----------|-----| | 针对 state 存储桶的 `s3:Get*`、`s3:List*` | 读取当前状态文件 | | 针对 state 存储桶的 `s3:PutObject`、`s3:DeleteObject` | 创建/释放 `.tflock` 锁定文件 | | `dynamodb:GetItem/PutItem/DeleteItem` | 传统状态锁定操作 | | `ec2:Describe*` | 读取 EC2 资源以进行计划 | | `s3:GetBucket*`(通用) | 读取 S3 存储桶配置以进行计划 | | `iam:Get*`、`iam:List*` | 读取 IAM 资源以进行计划 | | `dynamodb:Describe*`、`dynamodb:ListTagsOfResource` | 读取 DynamoDB 配置 | | `kmsDescribeKey`、`kms:GetKeyPolicy` | 读取 KMS 密钥配置 | | `ssm:GetParameter*` | 通过 SSM 参数解析 AMI ID | **此角色无法执行的操作:** 创建、修改或删除任何基础设施。即使 Plan 工作流遭到破坏,攻击者也只能查看(而不能更改)你的资源。 #### Apply 角色(限定范围的写入) 拥有 Plan 角色的所有权限,另外还包括: | 权限 | 原因 | |-----------|-----| | `ec2:*` | 创建/修改/删除 EC2 资源 | | `s3:CreateBucket`、`s3:PutBucket*` 等 | 管理 S3 存储桶(state-backend 模块) | | `dynamodb:UpdateItem/UpdateTable/TagResource` | 管理 DynamoDB 表 | | `kms:CreateGrant`、`kms:Encrypt/Decrypt` | 使用 KMS 密钥进行加密 | **此角色无法执行的操作(显式拒绝):** | 被拒绝的操作 | 原因 | |--------------|-----| | `iam:CreateUser` | 防止创建后门用户 | | `iam:CreateAccessKey` | 防止创建永久凭证 | | `iam:AttachRolePolicy` / `iam:PutRolePolicy` | 防止权限提升 | | `iam:UpdateAssumeRolePolicy` | 防止修改信任策略 | | `iam:CreateRole` / `iam:DeleteRole` | 防止角色操纵 | | `iam:PassRole` | 防止将角色分配给其他服务 | | `organizations:*` / `account:*` | 防止组织级别的更改 | ## 如何添加你自己的基础设施 演示用的 EC2 实例展示了这种模式。以下是如何添加你自己的资源: ### 示例:添加一个 S3 存储桶 **1. 在 `terraform-environments/variables.tf` 中添加一个变量:** ``` variable "my_bucket_name" { description = "Name of my custom S3 bucket." type = string default = "my-app-data-bucket" } ``` **2. 在 `terraform-environments/main.tf` 中添加资源:** ``` resource "aws_s3_bucket" "my_bucket" { bucket = var.my_bucket_name tags = { Name = var.my_bucket_name Environment = var.environment } } ``` **3. 在 `terraform-environments/outputs.tf` 中添加输出:** ``` output "my_bucket_arn" { description = "ARN of my custom S3 bucket." value = aws_s3_bucket.my_bucket.arn } ``` **4. 预览并应用:** ``` cd terraform-environments terraform plan # See what will be created terraform apply # Create it (or push to trigger CI/CD) ``` ## 状态迁移指南(本地 → S3) 如果你一开始使用的是本地状态并想迁移到 S3: ``` cd terraform-environments # 1. 验证当前状态是否正常 terraform plan # Should show "No changes" # 2. 在 versions.tf 中取消注释 backend "s3" 代码块 # 将 bucket 名称更新为您实际的 bucket # 3. 运行迁移 terraform init -migrate-state # 4. Terraform 将会询问: # "您是否要将现有 state 复制到新的 backend?" # 回答:yes # 5. 验证迁移成功 terraform plan # Should still show "No changes" # 6. (可选)删除本地 state 文件 # 仅在确认远程 state 正常工作后执行! rm terraform.tfstate terraform.tfstate.backup ``` ## 故障排除 ### "Error: No valid credential sources found" **原因:** OIDC 身份验证失败 — AWS 无法验证 GitHub 令牌。 **解决方案:** 1. 验证 OIDC 提供商是否存在:`aws iam list-open-id-connect-providers` 2. 检查信任策略是否与你的仓库名称完全匹配 3. 确保在工作流中设置了 `id-token: write` 权限 4. 对于 Apply 工作流,验证你是否正在从 `master` 分支运行 ### "Error: Error acquiring the state lock" **原因:** 之前的 Terraform 运行崩溃且未释放锁定。 **解决方案:** ``` # 如果使用 S3 原生锁定 (use_lockfile = true): # 从 S3 bucket 中删除 .tflock 文件 aws s3 rm s3://your-bucket-name/terraform.tfstate.tflock # 如果使用 DynamoDB 锁定(旧版): terraform force-unlock LOCK_ID ``` ### "Error: S3 bucket already exists" **原因:** S3 存储桶名称是全局唯一的。其他人已经占用了这个名称。 **解决方案:** 将 `state_bucket_name` 更改为唯一的值。包含随机字符,例如 `tf-state-myproject-a1b2c3`。 ### "Error: insufficient permissions" **原因:** IAM 角色没有为你管理的资源提供所需的权限。 **解决方案:** 在 `terraform-modules/iam-github-oidc/main.tf` 中将必要的权限添加到 Plan 或 Apply 角色。请记住: - 读取权限 → Plan 角色 - 写入权限 → Apply 角色 ### CI 中拉取 Docker 镜像失败 **原因:** Docker 工具箱镜像尚未推送到 Docker Hub。 **解决方案:** 1. 首先在本地运行 `make build && make push` 2. 或者手动触发 `docker-publish.yml` 工作流 ### Plan 成功,但 Apply 失败并提示 "role cannot be assumed" **原因:** Apply 角色的信任策略比 Plan 角色更严格。Apply 要求 `ref:refs/heads/master` — 它在 PR 或其他分支上无法工作。 **解决方案:** 确保你是从 `master` 分支运行 Apply 工作流(这是 `workflow_dispatch` 的默认设置)。 ## 参考资料 - [Terraform 文档](https://developer.hashicorp.com/terraform/docs) - [Terraform AWS Provider](https://registry.terraform.io/providers/hashicorp/aws/latest/docs) - [AWS IAM OIDC 身份提供商](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc.html) - [GitHub Actions — 在 AWS 中配置 OIDC](https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services) - [Terraform S3 后端](https://developer.hashicorp.com/terraform/language/backend/s3) - [TFLint](https://github.com/terraform-linters/tflint) - [Docker 多阶段构建](https://docs.docker.com/build/building/multi-stage/) - [GitHub Actions — 工作流语法](https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions) ## 许可证 本项目基于 MIT 许可证授权 — 详见 [LICENSE](LICENSE) 文件。 你可以自由地将其作为你自己 AWS 基础设施的模板。如果你觉得它有帮助,请给仓库点个 star!
标签:AWS, DevSecOps, Docker容器, DPI, EC2, EC2实例, ECS, GitHub Actions, HashiCorp, IaC, IAM角色, OIDC身份验证, S3存储桶, STS临时凭证, Terraform, TFLint代码检查, 上游代理, 云架构, 代码审查, 开源框架, 微服务架构, 持续交付, 持续集成, 攻击面发现, 教育项目, 无长期密钥, 特权提升, 生产就绪, 自动化部署, 自动笔记, 请求拦截, 零信任安全