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 的基础设施即代码
[](https://www.terraform.io/)
[](https://registry.terraform.io/providers/hashicorp/aws/latest)
[](LICENSE)
一个完整且可用于生产环境的配置,用于通过 **Terraform** 和 **GitHub Actions CI/CD** 管理 AWS 基础设施。本仓库记录了从零开始构建完全自动化基础设施管道的全过程——旨在提供教育意义、保证安全性且易于跟进。
**这个项目是做什么的?** 它使用 Terraform 配置和管理 AWS 资源(S3 存储桶、IAM 角色、EC2 实例等),所有变更均通过 GitHub Actions 进行验证和部署。GitHub 与 AWS 之间的身份验证使用 **OIDC 联合身份验证**——无需长期有效的 AWS 访问密钥。每一项基础设施变更都要经过代码审查流程:开启 PR → 查看计划 → 合并 → 手动应用。
## 架构

**流程概览:**
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!
✓ 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代码检查, 上游代理, 云架构, 代码审查, 开源框架, 微服务架构, 持续交付, 持续集成, 攻击面发现, 教育项目, 无长期密钥, 特权提升, 生产就绪, 自动化部署, 自动笔记, 请求拦截, 零信任安全