问题

几年前的 GitHub 论文仓库,设置了一个 GitHub Actions,使用 texlive 编译成最终的论文 pdf 文件,通过企业微信机器人 webhook 发送到我的手机上。这个 GitHub Action,使用到的企业微信机器人 webhook 相关的配置,通过 repository secrets 传入:

image.png

现在想配置到另外的地方,但是找不到原始的值了,而 GitHub 并没有提供查看原始值的方式。

尝试

一个很自然的想法是,把这些秘密值在日志中打印出来,但是失败了,因为直接 echo,加密值会被 GitHub Action 打码:

image.png
其实,如果 GitHub 真的在日志中明文输出秘密值,那么一旦仓库是公开的,就会泄密。

思考 🤔

既然直接 echo 不工作,就想到将它们 base64 之后输出: echo xxx | base64,但是这样,又不安全,因为一旦仓库公开,就会泄密。毕竟任何人看到了 base64 的输出,都能解码查看明文。有没有办法既能绕过 GitHub Action 的打码机制,又能保证安全呢?

base64 只是编码,要保证内容安全,还需要加密才行。使用一个密钥,将秘密值输出为密文,而只有我有密钥,在本地解密查看秘密值即可。

如何加密?

怎样使用命令行加密呢?可以使用 openssl 这个命令。在输出秘密前,用它加密;在本地,用它解密。注意,openssl 在不同的机器上运行表现不一样,如果在 GitHub Actions 上是使用 Ubuntu 进行 openssl 加密的话,在本地解密时也需要用 Ubuntu 来运行 openssl,没有 Ubuntu 机器,可以使用 docker 下载一个。

加密命令

以上面的 TARGET_URL 为例: shell echo ${TARGET_URL} | openssl enc -e -aes-256-cbc -a -pbkdf2 -iter ${MY_OPENSSL_ITER} -k ${MY_OPENSSL_PASSWORD}

注意,这里在加密时使用了额外的两个参数: MY_OPENSSL_ITER 和 MY_OPENSSL_PASSWORD。后者是密钥,可以存放在 GitHub Actions 的 SECRETS 里,在执行时将它注入环境变量;前者是一个数字,越大则让暴力碰解的难度越大,注意这个变量建议“不要”存放在 SECRETS 里,比如这个值如果为 5,那么会导致以上命令的输出中,任何的 5 这个文本都会被替换成 ***,三个星号,带来不必要的麻烦。

比如使用了 upload.sh 来执行对企业微信 API 的调用,那么这一步的 GitHub Actions yml 内容如下:

yaml ... - name: send to wechat if: ${{ always() }} env: MY_OPENSSL_ITER: 5 MY_OPENSSL_PASSWORD: ${{ secrets.MY_OPENSSL_PASSWORD }} TARGET_URL: ${{ secrets.TARGET_URL }} FILE_UPLOAD_URL: ${{ secrets.FILE_UPLOAD_URL }} SECRET_KEY: ${{ secrets.SECRET_KEY }} INPUT_ARGS: *.pdf run: | sh ./tools/upload.sh

这样改造后,执行后的 GitHub Actions 日志输出如下:

image.png

如何解密?

解密命令

shell echo ENCRYPTED_TEXT | openssl base64 -d | openssl enc -d -pbkdf2 -iter $MY_OPENSSL_ITER -aes-256-cbc -k $MY_OPENSSL_PASSWORD

正如之前所说的,如果 GitHub Actions 运行时的镜像是 Ubuntu,则以上解密命令也得在 Ubuntu 上运行。如果本地电脑是 Mac,直接运行会报错。可以运行一个 docker 镜像,进行镜像分别执行以上命令,便依次解密了 GitHub Actions 中的 SECRETS,如下截图所示:

image.png

现在,就可以愉快地拷贝这些值,配置到其他需要重用它们的地方了。

openssl 的其他用途

在用 openssl 解决了这个问题后,想起来另一个用法,就是用来生成哈希值。之前有人在 StackOverflow 问如何用 python 计算哈希值,我由于兴趣原因,不仅给他提供了 python 脚本,还提供了对应的 nodejs 和 shell 脚本,它们都是一行脚本,非常实用。其中的 shell 脚本也是用了 openssl:
https://stackoverflow.com/questions/9594125/salt-and-hash-a-password-in-python/71422369#71422369

比如想将 password 生成一个哈希后的值,并给这串哈希值加上盐值 salt,对应的 shell 脚本非常简单(使用了 openssl,并且可以直接在 Mac 上运行。):

shell echo -n password | openssl sha256 -hmac salt

而如果安装了 nodejs,那么也可以这样执行: shell echo console.log(require(crypto).createHmac(sha256, salt).update(password).digest(hex)) | node

如果安装了 python,也可以使用 python 生成:

shell python3 -c import hashlib;import base64;import hmac;print(hmac.new(bsalt, password.encode(), hashlib.sha256).hexdigest())