昨晚犯了一个蠢事,不小心把一个权限很大的 key 传上 GitHub 了,记录一下问题以及解决方案。

#事发原因

我有一个动态解析域名的代码:https://github.com/onns/ddns,因为最近需要一个将内网 ip 映射到域名的功能所以修改了一部分代码,但是不小心将我的阿里云 accesskey 和 secret 传上去了,它能获取我在阿里云的全部权限= =,所以还是蛮可怕的,为了下次不发生类似的问题,找寻解决方案并以我为戒。

不得不说阿里云这方便做的还是蛮好的,我传上去的一瞬间就收到了告警短信。

【阿里云】尊敬的onns@onns.xyz:阿里云云安全中心检测到您账号的 AccessKey:LTAIMwxO3zIjXWiQ 被公开在 Github 代码库中,建议您立即登录云安全中心,AK&帐密泄露检测查看详情,建议核实后,尽快禁用。

#思考

对于此类问题,其实一个最好的解决方案是将配置与主代码分离,将配置放到配置文件里,或者是通过参数手动写入

但我的应用场景是,很多台机器跑定时任务更新 ip,如果走配置文件的话换起来成本并不会降低,而且到时候还要考虑文件依赖的问题,所以最好的写法是将配置直接硬编码在代码里,单文件到处可用。

所以问题的解决方案就变成了:如何不把配置信息传上 GitHub

那问题的解答就变得清晰了起来:git hookgit hook可以保证帮我在代码提交前删除对应的配置文件代码。

#解决

git hook本身已经集成在git上了,无需再安装任何插件即可使用。

#hook 流程

  1. 找到所有的变更文件
  2. 筛选符合要求的文件
  3. 对敏感信息进行文本替换

#文件筛选

所幸 Google 下很多,不过有一些有问题,大家直接抄作业即可:

1
2
3
4
go_files=($(git diff --cached --name-only | grep ".go$"))
if [[ "${go_files}" = "" ]]; then
exit 0
fi

这里有个问题是网上的版本都是:

1
go_files=$(git diff --cached --name-only | grep ".go$") # 没有最外层的括号,相比于上面的代码

但是这个得到的结果是:

1
2
a.go
b.go

是一个带换行的字符串变量,不是一个数组,这个可能是 zsh 的问题,我用的是 macOS,大家到时候可以先自己在命令行试一下这个go_files是否可迭代。

#文本替换

以前写正则的时候这个匹配 10s 内解决,但是在 shell 下真的花了半个小时。

先说结论:

  1. sed 的正则模式没有\S这种写法。
  2. macOS 下开启正则需要-E参数。
  3. macOS 下在原文件上变更需要-i参数后加一个''空。
  4. 大多数教程上说()这两个括号也要转义,但是我没转义才成功的,不知道为什么,感觉被误导了好久= =。
1
2
3
4
sed -E -i '' 's/var AccessKeyId = "[_A-Za-z0-9]+"/var AccessKeyId = ""/g' main.go
sed -E -i '' 's/var AccessKeyId = "([\S]+)"/var AccessKeyId = ""/g' main.go
sed -E -i '' 's/var AccessKeyId = ".*"/var AccessKeyId = ""/g' main.go
sed -i '' 's/var AccessKeyId = "LTAIMwxO3zIjXWiQ"/var AccessKeyId = ""/g' main.go

真的试了半小时,感兴趣的可以自己去试试,这里直接上结论:

1
2
sed -E -i '' 's/var AccessKeyId = "[_A-Za-z0-9]+"/var AccessKeyId = ""/g' ${file}
sed -E -i '' 's/var AccessKeySecret = "[_A-Za-z0-9]+"/var AccessKeySecret = ""/g' ${file}

上述代码可以替换:

1
2
var AccessKeyId = "LTAIMwxO3zIjXWiQ"
var AccessKeySecret = "sPvzjjMMA66RaOT0OGiDSkUcAmys5W"

#打包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/bin/zsh

go_files=($(git diff --cached --name-only | grep ".go$"))
if [[ "${go_files}" = "" ]]; then
exit 0
fi

for file in ${go_files}; do
# tmpdate=$(date +%Y%m%d-%H%M%S%N) macOS 没有毫秒
tmpdate=$(perl -MTime::HiRes -e 'printf("%.0f",Time::HiRes::time()*1000)')
cp ${file} /tmp/git-temp-${tmpdate}
sed -E -i '' 's/var AccessKeyId = "[_A-Za-z0-9]+"/var AccessKeyId = ""/g' ${file}
sed -E -i '' 's/var AccessKeySecret = "[_A-Za-z0-9]+"/var AccessKeySecret = ""/g' ${file}
git add ${file}
mv /tmp/git-temp-${tmpdate} ${file}
done

printf "clear access key successfully.\n"
exit 0

最后的代码添加了保存配置的功能,这样不会每次都重新把配置加回去。

需要注意的问题是:

  • 需要赋予这个pre-commit文件可执行权限。

图1

  • 修改的时候要加git add ${file},不然你会发现你变更了但是 commit 的记录里没有= =。

#测试

1
2
3
4
5
6
7
8
9
10
package t

/*
@Time : 2021/11/14 11:59
@Author : onns
@File : t/t.go
*/

var AccessKeyId = "LTAIMwxO3zIjXWiQ"
var AccessKeySecret = "sPvzjjMMA66RaOT0OGiDSkUcAmys5W"

在子目录下创建了一个测试文件。

测试图

有效,完工!

#TODO

  • ~~我测试的时候不论我在当前项目的哪个子目录下执行,变更的文件列表都是从根目录下算起的,不知道到时候的文件名会不会有问题,待测试。~~测试通过,没问题。

测试子目录

  • 更优雅的方案,毕竟当前的方案可能会误伤,不过大概率没问题。这里不得不说,golang 的强制格式化 nb!

#参考资料

#总结

  • 好多时间都花在了 sed 的文档查询上,结果最后还是我自己试出来的。
  • 没记错的话 git 好像也是 Linux 的作者写的?

如有不规范之处,欢迎批评指正,望大家以我为戒。

2021.11.14 13:23:22