johntay379-hub/flask-devsecops-aws-pipeline
GitHub: johntay379-hub/flask-devsecops-aws-pipeline
一个生产级 Flask 应用的安全 DevSecOps 流水线,演示如何在 AWS 上通过 OIDC 无凭证认证、Trivy 漏洞扫描和 SSM 无 SSH 部署实现安全的容器化交付。
Stars: 0 | Forks: 0
# 🔐 Flask DevSecOps AWS Pipeline







## 这是什么?
这是一个生产级别的容器化 Flask 应用,通过全自动的 DevSecOps pipeline 部署在 AWS 上。每次将代码推送到 GitHub 时,pipeline 都会构建 Docker 镜像,使用 Trivy 扫描其漏洞,将其推送到私有 ECR 注册表,并部署到 EC2 —— 整个过程无需使用任何硬编码的 AWS 凭证或 SSH 密钥。
在这里,安全性绝不是事后才考虑的问题。它已融入到每一层:通过 OIDC 实现无凭证的 AWS 身份验证、部署前的容器漏洞扫描、最小权限的 IAM role、基于 SSM 且无需 SSH 的远程访问,以及流量完全且独占地通过负载均衡器传输。
## 在线演示
该基础设施经过部署、测试和截图后,已通过 terraform destroy 销毁,以避免持续产生 AWS 费用。/screenshots 目录中的截图展示了在线部署情况。整个技术栈可在几分钟内重新部署。
在线时的 ALB endpoint:
```
http://secure-flask-alb-1046257647.us-east-1.elb.amazonaws.com
```
API 响应:
```
GET /
{"hostname": "f5ed7d14b8a7", "message": "Secure Containerised Flask App", "status": "running"}
GET /health
{"status": "healthy"}
```
hostname 字段返回的是 Docker 容器 ID —— 这证明了应用运行在容器内,而不是直接运行在主机上。
## Flask 应用
这是一个轻量级的 Python Flask API,包含两个 endpoint:
```
GET / returns app info including container hostname
GET /health returns health check status used by ALB
```
该应用运行在 Gunicorn 上 —— 这是一个生产级的 WSGI 服务器,而不是 Flask 内置的开发服务器。Flask 开发服务器是单线程的,不适用于生产环境的流量。Gunicorn 能够正确处理多个并发请求。
```
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "2", "app:app"]
```
## Pipeline 工作原理
### 每次推送到 main 分支都会触发完整的 pipeline
**第 1 步 — OIDC 身份验证**
GitHub Actions 永远不会接触到 AWS 访问密钥。相反,GitHub 会生成一个已签名的 JWT token,以此证明“我是为 johntay379-hub/flask-devsecops-aws-pipeline 仓库运行的 GitHub Actions”。AWS 会针对 OIDC provider 验证此 token,并颁发一个临时会话凭证。任何地方都不存在长期凭证。
**第 2 步 — Docker 构建**
Pipeline 从 ./app/dockerfile 构建 Flask Docker 镜像。每次构建都会生成一个全新的镜像,并使用 latest 和 Git commit SHA 进行标记 —— 这样每次部署都可以精确追溯到触发它的具体 commit。
**第 3 步 — Trivy 安全扫描**
在镜像进入生产环境之前,Trivy 会扫描其已知 CVE。它会检查基础 OS 包、Python 依赖项以及应用层,并与其漏洞数据库进行比对。CRITICAL 和 HIGH 级别的发现会被记录在 pipeline 日志中。这就是介于“我们构建了它”和“我们部署了它”之间的安全闸门。
**第 4 步 — 推送到 ECR**
扫描后的镜像会被推送到私有 ECR 仓库。ECR 还启用了 scan_on_push —— 因此 AWS 在镜像到达时会独立于 Trivy 运行自己的漏洞扫描。两次扫描,两次机会,在镜像运行于 EC2 之前捕捉到潜在问题。
**第 5 步 — 通过 SSM 部署**
Pipeline 使用 AWS Systems Manager 向 EC2 实例发送 shell 命令 —— 无需 SSH,无需密钥对,也不开放 22 端口。SSM 通过附加到实例的 IAM role 进行工作。这些命令会从 ECR 拉取新镜像并重启容器。
## 关键安全决策
**使用 OIDC 代替访问密钥** —— GitHub Secrets 中的长期 AWS 凭证是云账户被盗用的常见根源。OIDC 彻底消除了这一点。GitHub Actions role 会获取一个在 pipeline 运行后即过期的临时 token。
**最小权限 IAM role** —— GitHub Actions role 只能推送到 ECR 并向该特定实例发送 SSM 命令。EC2 role 只能从 ECR 拉取并注册到 SSM。
**无 SSH 访问** —— 安全组中未开放 22 端口。实例未附加任何密钥对。SSM 是唯一的访问途径 —— 并且每个 SSM session 都会被 AWS 记录。
**EC2 仅可通过 ALB 访问** —— EC2 安全组仅允许来自 ALB 安全组的 5000 端口入站流量。直接访问实例 IP 被阻止。
**强制执行 IMDSv2** —— EC2 实例要求数据元数据服务请求提供 session token,从而阻止 SSRF 攻击。
**双层漏洞扫描** —— Trivy 在推送前的 pipeline 中扫描镜像。ECR 在镜像到达时再次进行扫描。
## Trivy 扫描结果
Trivy 在部署期间对 Flask 容器镜像进行了扫描。基础镜像 (Amazon Linux 2023) 和 Python 依赖项 (Flask 3.0.3, Gunicorn 22.0.0) 返回了干净的结果 —— 在部署时未检测到 CRITICAL 或 HIGH 级别的漏洞。可在 pipeline 日志的“Scan image with Trivy”步骤中查看。
## 基础设施
| 资源 | 用途 |
|---|---|
| aws_ecr_repository | 私有 Docker 镜像注册表 |
| aws_ecr_lifecycle_policy | 保留最后 5 个镜像,删除旧镜像 |
| aws_iam_openid_connect_provider | 将 GitHub Actions 作为身份提供商信任 |
| aws_iam_role (github_actions) | GitHub Actions 通过 OIDC 扮演的 role |
| aws_iam_role_policy | 仅允许 ECR 推送 + 发送 SSM 命令 |
| aws_iam_role (ec2) | 附加到 EC2 实例的 role |
| aws_iam_role_policy_attachment (ssm) | 无需 SSH 的 SSM 访问权限 |
| aws_iam_role_policy_attachment (ecr) | 从 ECR 拉取镜像 |
| aws_iam_instance_profile | 封装 EC2 role 供实例使用 |
| aws_security_group (alb) | 允许互联网到 ALB 的 80 端口流量 |
| aws_security_group (ec2) | 仅允许来自 ALB 的 5000 端口流量 |
| aws_lb | Application Load Balancer |
| aws_lb_target_group | 路由到 EC2 的 5000 端口,对 /health 进行健康检查 |
| aws_lb_listener | 80 端口,转发到 target group |
| aws_lb_target_group_attachment | 将 EC2 注册到 target group |
| aws_instance | EC2 t2.micro,Docker,SSM,IMDSv2 |
## 解决的挑战
| 挑战 | 解决方案 |
|---|---|
| GitHub pipeline 中的 AWS 凭证 | OIDC —— 使用临时 token,无存储的密钥 |
| 用于部署的 SSH 密钥管理 | SSM —— 无需密钥,不开放 22 端口 |
| 部署未经测试的镜像 | 推送前由 Trivy 扫描,到达时由 ECR 扫描 |
| 从 ECR 拉取容器 | 使用具有 ECR 只读权限的 EC2 IAM role |
| EC2 直接暴露在互联网中 | 以 ALB 作为单一入口点,安全组阻止直接访问 |
| Terraform provider 二进制文件过大 | .gitignore 排除了 .terraform/ 文件夹 |
## 项目结构
```
flask-devsecops-aws-pipeline/
├── app/
│ ├── app.py # Flask app — 2 endpoints
│ ├── dockerfile # Gunicorn production server
│ └── requirements.txt # flask==3.0.3, gunicorn==22.0.0
├── terraform/
│ ├── main.tf # ECR, OIDC, IAM, EC2, ALB
│ ├── variables.tf # Region, project, GitHub repo
│ ├── outputs.tf # ALB DNS, ECR URL, EC2 ID, Role ARN
│ └── providers.tf # AWS provider
├── .github/
│ └── workflows/
│ └── deploy.yml # Full CI/CD pipeline
├── screenshots/ # Live deployment proof
├── .gitignore
└── README.md
```
## 自己动手部署
```
git clone https://github.com/johntay379-hub/flask-devsecops-aws-pipeline.git
cd flask-devsecops-aws-pipeline/terraform
terraform init
terraform apply
```
将 terraform 输出的内容添加到这些 GitHub Secrets 中:
- AWS_ROLE_ARN
- AWS_REGION
- ECR_REPOSITORY
- EC2_INSTANCE_ID
然后推送到 main —— pipeline 将自动进行部署。
## 相关项目
| 项目 | 涵盖范围 |
|---|---|
| [AWS CLI 安全框架](https://github.com/johntay379-hub/aws-end-to-end-security-framework) | IAM, S3, CloudTrail, VPC, EC2, CloudWatch, SNS |
| [Terraform 安全框架](https://github.com/johntay379-hub/terraform-aws-security-framework) | 以 Infrastructure as Code 实现的相同安全模型 |
| [Zero Trust 安全平台](https://github.com/johntay379-hub/aws-zero-trust-security-platform) | ALB, Auto Scaling, AWS Config |
| [安全 CI/CD Pipeline](https://github.com/johntay379-hub/secure-cicd-pipeline) | 带有 tfsec 扫描的 Terraform pipeline |
| **Flask DevSecOps AWS Pipeline** | **Docker, ECR, OIDC, SSM, Trivy** |
## 作者
**John Kamau** — AWS Cloud Engineer (专注于安全)
[github.com/johntay379-hub](https://github.com/johntay379-hub) · [linkedin.com/in/john-kamau-60ba53342](https://linkedin.com/in/john-kamau-60ba53342)
标签:DevSecOps, NIDS, 上游代理, 容器化, 持续集成/持续交付(CI/CD), 请求拦截, 逆向工具