API安全必修课:如何用自动化脚本精准清理未使用的API令牌
📖 目录导读
- 为什么未使用的API令牌是定时炸弹?(风险分析)
- 清理前的审计清单:你需要收集哪些信息?
- 高效自动化脚本实战:Python+GitHub API 示例
- 常见问题问答(FAQ)
- 持续治理:防止令牌堆积的4条策略
为什么未使用的API令牌是定时炸弹?
在企业DevOps实践中,开发者往往因为快速迭代而创建大量API令牌(Token)。据Cloudflare 2023年安全报告显示,超过62%的企业存在有效期超180天的僵尸令牌,这些被遗忘的凭证是内部威胁与外部攻击的首要切入点——攻击者一旦通过旧代码仓库、日志泄漏或员工设备获取令牌,即可绕过所有认证机制直达核心数据。

用户痛点场景:某SaaS公司因未清理测试环境的旧令牌,导致攻击者利用泄漏的GitHub Personal Access Token克隆了整个私有仓库,损失估值达200万美元,自动化脚本清理不仅是为了合规(如SOC2、PCI-DSS),更是防御性安全的底层守门人。
清理前的审计清单
在写脚本前,必须回答三个核心问题:
-
令牌的元数据来源是什么?
- 是否存储于代码仓库的环境变量(如
.env)? - 是否通过CI/CD系统(如Jenkins、GitHub Actions)自动注入?
- 是否存储于代码仓库的环境变量(如
-
判断“未使用”的合理阈值是多少?
- 建议统一采用90天无API调用作为基准(可参考AWS IAM的未使用凭证策略)。
-
谁拥有删除权限?
- 必须确保脚本不会误删正在生效的生产令牌,建议建立双人复核机制:脚本仅生成候选清单,人工确认后执行。
高效自动化脚本实战:Python+GitHub API 示例
以下脚本将从GitHub组织仓库中扫描所有Personal Access Tokens(classic),识别并标记超过90天未更新的令牌,生成CSV报告供管理员确认删除。
import requests
from datetime import datetime, timedelta
import csv
# 核心配置
ORG_NAME = "your-org"
GITHUB_TOKEN = "ghp_your_admin_token" # 需具有org:admin权限
BASE_URL = "https://api.github.com"
THRESHOLD_DAYS = 90
def get_all_tokens():
"""获取组织内所有活跃令牌"""
headers = {"Authorization": f"token {GITHUB_TOKEN}", "Accept": "application/vnd.github.v3+json"}
url = f"{BASE_URL}/orgs/{ORG_NAME}/tokens"
tokens = []
while url:
resp = requests.get(url, headers=headers)
if resp.status_code != 200:
print(f"API调用失败: {resp.status_code}")
break
tokens.extend(resp.json())
url = resp.links.get('next', {}).get('url') # 处理分页
return tokens
def filter_stale_tokens(tokens):
"""筛选出超过阈值的未使用令牌"""
cutoff = datetime.utcnow() - timedelta(days=THRESHOLD_DAYS)
stale = []
for token in tokens:
# 假设API返回字段包含 last_used_at
last_used = token.get("last_used_at")
if not last_used:
continue
last_used_date = datetime.strptime(last_used, "%Y-%m-%dT%H:%M:%SZ")
if last_used_date < cutoff:
stale.append({
"id": token["id"],
"name": token.get("name", "unnamed"),
"last_used": last_used,
"expires_at": token.get("expires_at"),
"permissions": token.get("scopes", []),
})
return stale
def generate_report(stale_tokens):
"""输出CSV报告"""
with open("stale_tokens_report.csv", "w", newline="") as f:
writer = csv.DictWriter(f, fieldnames=["id", "name", "last_used", "expires_at", "permissions"])
writer.writeheader()
writer.writerows(stale_tokens)
print(f"已生成报告:共发现 {len(stale_tokens)} 个僵尸令牌。")
if __name__ == "__main__":
tokens = get_all_tokens()
stale = filter_stale_tokens(tokens)
generate_report(stale)
脚本关键说明:
- 分页处理:根据GitHub API文档,令牌列表可能超过单页响应大小,必须处理
next链接。 - 安全设计:脚本仅生成报告,不执行自动删除,管理员需审核CSV后,调用
DELETE /orgs/{org}/tokens/{token_id}手动操作。 - 适应扩展:若使用AWS Secret Manager或HashiCorp Vault,只需替换数据源层逻辑,核心过滤算法可复用。
常见问题问答(FAQ)
Q1:如何确保不会误删正在使用的令牌?
A:采用“两步走”策略:
1)脚本基于API调用日志(如CloudTrail、GitHub Audit Log)判断“最后一次使用时间”,而非令牌是否被代码引用。
2)先导出候选清单,由团队主管在灰度环境(非生产)执行删除测试,观察24小时无故障后,再批量处理。
Q2:对于通过IAM角色生成的临时凭证(如AWS STS),需要清理吗?
A:临时凭证由AWS自动轮转,但根用户访问密钥(AWS Access Keys)依然需要监控,建议统一将“最后使用时间大于60天”的IAM密钥标记为旋转(Rotate),可参考:
aws iam list-access-keys --user-name <user> | jq '.AccessKeyMetadata[] | select(.Status=="Active")'
Q3:如果令牌存储在多个应用的环境变量中,如何追踪?
A:引入凭据管理系统:
- 开源方案:使用Vault的
vault kv get -format=json扫描所有挂载路径。 - SaaS方案:GitHub Secrets默认支持创建时自动生成指纹,可配合
gh secret list命令导出。
关键:所有令牌必须带有元数据标签(如purpose=production),脚本以此为依据设置排除规则。
Q4:脚本执行频率如何设定?
A:建议每月运行一次审计脚本,同时结合以下事件驱动:
- 每次员工离职时,自动触发令牌清单扫描。
- 每次安全警报(如已泄漏凭证检测)后,立即全量扫描。
持续治理:防止令牌堆积的4条策略
-
生命周期强制策略
所有新创建的令牌必须在API中设置expires_at(如GitHub Enterprise支持),脚本拒绝接受无过期时间的令牌。 -
分层扫描触发器
- 自动扫描:CI/CD流程中集成
actionlint,当代码仓库出现token、password等关键字时立即标记。 - 定时扫描:使用cron的
weekly-cleanup任务运行上述Python脚本。
- 自动扫描:CI/CD流程中集成
-
黑名单+白名单
在脚本配置文件中定义EXEMPTED_TOKENS(如用于CI的令牌),确保审计跳过已知有效凭证。EXEMPTED_TOKENS = ["token-id-ci-system", "token-id-monitoring-agent"]
-
失败安全(Fail Safe)
脚本应输出JSON格式的审计日志到S3或日志管理服务,一旦发现异常(如单次扫描发现超过20%令牌未使用),自动触发Slack/钉钉通知。
自动化清理不是一个一次性的“扫除”动作,而是嵌入到组织安全策略中的持续合规循环,从上述脚本出发,你可以逐步扩展至多云环境(AWS/Azure/GCP)、CI/CD工具链,甚至Kubernetes的Secrets过期管理,安全始于对每一行“凭据代码”的问责。