erwanminguillon/secure-serverless-incident-portal
GitHub: erwanminguillon/secure-serverless-incident-portal
基于 Azure Serverless 架构的安全事件报告与分类平台,支持公众匿名提交和跟踪安全报告,管理员通过受保护控制台进行事件审查与内部协作。
Stars: 1 | Forks: 0
# 安全 Serverless 事件门户
安全 Serverless 事件门户(Secure Serverless Incident Portal,简称 SSIP)是一个构建在 Azure 上的云原生事件报告和分类平台。
我构建这个项目是为了探索一个现实的、专注于安全的 serverless 应用程序是什么样的,而不仅仅是一个简单的 CRUD 演示。其目标是创建一个系统,让公众用户可以提交和跟踪安全报告,而管理员可以通过受保护的内部控制台审查、分类、分配、评论和更新事件。
本项目特意围绕实际的安全和云工程问题进行设计:哈希跟踪 token、管理员会话 cookie、Azure Functions、Azure SQL、CORS、部署打包、运维脚本,以及将公共工作流与内部 SOC 风格工作流分离的前端。
建议查看 docs/images 文件夹,因为其中包含 Azure 门户和前端本身的图片。
## 目录
- 本项目存在的原因
- 应用程序的功能
- 截图
- 项目的亮点
- 当前架构
- 安全模型
- 主要功能
- 技术栈
- 部署概述
- 管理员密钥轮换
- 当前状态
- 为什么关注此项目
## 本项目存在的原因
安全事件报告是那些从外部看似简单,但一旦考虑实际限制就会变得更加有趣的工作流之一。
一个有用的报告门户需要处理以下问题:
- 如何让人们在不需要账户的情况下提交事件?
- 同一个人如何能在不暴露内部数据的情况下稍后跟踪该报告?
- 管理员应如何安全地进行身份验证?
- 如何将内部评论和审查员分配与公共跟踪分开?
- 系统如何在保持云原生的同时,维持低运行成本?
- 如果 Azure 资源消失,如何重建整个环境?
SSIP 是我以一个可运行的 Azure 应用程序的形式对这些问题的回答。
## 应用程序的功能
SSIP 有两个主要方面:公共报告端和受保护的管理员端。
### 公共端
公众用户可以:
- 提交安全事件、漏洞或可疑活动报告。
- 选择报告类型、类别和严重性。
- 匿名提交或提供联系信息。
- 获取公共事件 ID。
- 获取一次性跟踪 token。
- 稍后跟踪已提交报告的状态。
跟踪 token 不会以明文形式存储。后端仅存储哈希值,因此无法简单地从数据库中还原该 token。
### 管理员端
管理员可以:
- 通过受保护的管理员登录界面登录。
- 在仪表板中查看所有已提交的事件。
- 根据状态、严重性、类别和搜索文本过滤事件。
- 快速切换事件状态,如分类、调查中、已解决、已关闭和已拒绝。
- 打开详细的案例视图。
- 通过显示名称分配审查员。
- 添加内部评论。
- 查看内部评论时间线。
- 注销并撤销浏览器端的管理员会话。
管理员界面旨在提供更接近轻量级 SOC 控制台的体验,而不是默认的 Web 表单。
## 截图
主页:

事件提交:

跟踪事件:

管理员仪表板:

问题视图:

## 项目的亮点
该项目不仅仅是关于构建表单和表格。重要的部分是其背后的安全和基础设施工作。
其中一些更有趣的部分包括:
- 前端不存储管理员密码或管理员密钥。
- 管理员身份验证现在使用后端颁发的 `HttpOnly`、`Secure`、`SameSite=None` 会话 cookie。
- 管理员会话使用哈希会话 token 存储在服务器端的 Azure SQL 中。
- 管理员密钥本身仅作为 `ADMIN_SHARED_KEY_HASH` 存储在 Azure Function App 配置中。
- 公共跟踪 token 仅返回一次,并且只以哈希形式存储。
- 内部评论使用已验证的管理员会话身份。
- 管理员路由通过 `/internal/auth/me` 验证后端会话。
- CORS 配置为支持来自前端的带凭证请求。
- 部署脚本将前端和后端分开打包。
- 项目正准备使用基础设施即代码进行完整的环境重建。
## 当前架构
该应用程序使用:
- **Azure App Service** 用于 React/Vite 前端。
- **Azure Functions** 用于公共和内部 API。
- **Azure SQL Database** 用于事件、评论、跟踪元数据和管理员会话。
- **Azure Storage** 用于运行时/部署存储以及计划中的证据存储。
- **Application Insights** 用于日志、追踪、错误和关联 ID。
运行时流程大致如下:
```
Browser
→ Azure App Service frontend
→ Azure Functions API
→ Azure SQL Database
→ Application Insights
```
管理员身份验证流程:
```
Admin enters key once
→ POST /internal/auth/login
→ Backend validates SHA-256(adminKey) against ADMIN_SHARED_KEY_HASH
→ Backend creates AdminSession in Azure SQL
→ Backend returns HttpOnly session cookie
→ Admin requests use credentials: include
→ Backend validates the session cookie against AdminSession
```
## 安全模型
当前的管理员安全模型特意设计得比基本的共享密钥前端实现更为高级。
### 早期方案
最初,管理员密钥存储在浏览器的 `sessionStorage` 中,并作为 `x-admin-key` 随每个管理员请求一起发送。
这对于一个 MVP(最小可行性产品)来说是可行的,但这并不理想,因为浏览器 JavaScript 可以访问管理员密钥。
### 当前方案
当前的模型是:
1. 管理员输入一次密钥。
2. 前端仅将密钥发送到 `/internal/auth/login`。
3. 后端对提交的密钥进行哈希处理,并将其与 `ADMIN_SHARED_KEY_HASH` 进行比较。
4. 如果密钥有效,后端将在 SQL 中创建管理员会话。
5. 后端返回一个安全的 HttpOnly cookie。
6. 前端在管理员 API 调用中使用 `credentials: include`。
7. 内部 API 在服务器端验证 cookie 会话。
这意味着前端不再存储明文管理员密钥。
### 管理员会话表
`AdminSession` 表存储:
```
SessionId
SessionTokenHash
PrincipalId
PrincipalName
IdentityProvider
CreatedUtc
ExpiresUtc
RevokedUtc
LastSeenUtc
```
仅存储会话 token 的哈希值。
### Token 处理
公共跟踪 token 和管理员会话 token 遵循相同的安全原则:
```
Return the plaintext token once.
Store only the hash.
Validate later by hashing the submitted token and comparing hashes.
```
这使得敏感 token 不会以明文形式存储在数据库中。
## 主要功能
### 事件提交
- 公共事件表单。
- 匿名或实名提交。
- 报告类型、类别和严重性。
- 公共 ID 生成。
- 一次性跟踪 token 生成。
- 跟踪 token 哈希存储。
### 事件跟踪
- 使用公共 ID 和跟踪 token 进行公共跟踪。
- 无需管理员登录。
- 面向公众的受限事件状态视图。
### 管理员仪表板
- 受保护的管理员登录。
- 带过滤器的事件列表。
- KPI 卡片。
- 状态和严重性徽章。
- 快捷状态操作。
- 针对 Azure 冷启动的加载提示信息。
### 管理员案例视图
- 完整的事件详情。
- 状态和严重性更新。
- 审查员分配。
- 报告人信息。
- 内部评论时间线。
- 添加内部评论。
- 感知会话的注销。
### 安全运维
- 通过脚本轮换管理员密钥。
- 可选的 SQL 撤销活动会话功能。
- 为带凭证请求配置 CORS。
- 前端存储中不存储明文管理员密钥。
## 技术栈
### 前端
- React
- TypeScript
- Vite
- React Router
- Azure App Service
### 后端
- Azure Functions
- Node.js
- TypeScript
- Azure SQL
- mssql
- 用于存储机密/配置的应用程序设置
### 基础设施与运维
- Azure CLI
- Bash 部署脚本
- ZIP 部署
- Application Insights
- Azure Storage
- 计划中基于 Bicep 的重建
## 部署概述
前端和后端是分开部署的。
### 后端
后端部署脚本会构建 Azure Functions 应用程序,将编译后的输出和生产环境依赖项打包,并创建用于部署的 ZIP 文件。
```
./infra/scripts/deploy-api.sh
```
生成的 ZIP 将部署到 Azure Function App。
### 前端
前端部署脚本以部署模式构建 Vite 应用程序,并对 `dist` 的内容进行打包。
```
./infra/scripts/deploy-frontend.sh
```
生成的 ZIP 将部署到 Azure Web App。
重要提示:前端 ZIP 必须包含构建后的生产文件,而不是开发源文件。正确的部署应该从以下路径提供资源:
```
/assets/index-xxxxx.js
```
而不是:
```
/src/main.tsx
```
## 管理员密钥轮换
无需更改源代码即可轮换管理员密钥。
该应用程序使用:
```
ADMIN_SHARED_KEY_HASH
```
位于 Azure Function App 设置中。
轮换密钥的步骤:
```
./infra/scripts/rotate-admin-key.sh
```
该脚本将:
1. 提示输入新的管理员密钥。
2. 使用 SHA-256 对其进行哈希处理。
3. 更新 `ADMIN_SHARED_KEY_HASH`。
4. 重启 Function App。
轮换管理员密钥可防止使用旧密钥进行后续登录。除非撤销了 `AdminSession` 中的相应行,否则现有的管理员会话在过期前仍然有效。
要手动撤销活动的管理员会话:
```
UPDATE dbo.AdminSession
SET RevokedUtc = SYSUTCDATETIME()
WHERE RevokedUtc IS NULL;
```
## 本地运行
前端和后端是独立的工作区。
### 前端构建
```
cd src/frontend
npm run build -- --mode deployment
```
### 后端构建
```
cd src/api
npm run build
```
请勿从存储库根目录运行部署模式的前端构建,因为 `--mode deployment` 参数可能会被错误地传递给所有工作区。
## 当前状态
已实现:
- 公共事件提交。
- 公共事件跟踪。
- 使用后端颁发的会话 cookie 进行管理员登录。
- 管理员事件仪表板。
- 管理员事件详情视图。
- 内部评论。
- 审查员分配。
- 管理员密钥轮换。
- Azure 风格的 UI 重新设计。
- 前端和后端部署脚本。
进行中 / 计划中:
- 使用 Bicep 完全重建基础设施。
- SQL schema 初始化脚本。
- 更好的冒烟测试。
- 登录速率限制。
- 管理员审计日志。
- 证据上传加固。
- 改进的文档和图表。
## 为什么关注此项目
该项目展示的不仅仅是一个基本的 serverless 应用程序。
它演示了一个小型云应用程序如何处理实际问题:
- 分离公共和内部工作流,
- 保护管理员访问权限,
- 避免在前端存储机密,
- 使用哈希 token,
- 处理 CORS 和 cookie,
- 应对 Azure 冷启动,
- 独立部署前端和后端,
- 为环境重建准备基础设施,
- 像对待可移交的系统一样对其进行文档化。
对我来说,SSIP 是一个实用的云安全工程项目:它足够小巧以便于理解,但也足够完整,能够展示真实的架构、安全决策和运维思维。
标签:Azure, MITM代理, 安全运营中心, 工单系统, 应用安全, 网络映射, 网络研究, 自动化攻击