保持敏感数据的安全和私密是软件开发的首要任务。应用程序日志是常见的泄漏媒介之一,受到仔细保护,以防止出现秘密。同样的担忧和风险也适用于测试日志,它可能会泄露密码或访问令牌。运行 ci 工作流程的工具通常提供一种机制,可以轻松屏蔽日志中的敏感数据。虽然这非常方便、高效且易于使用,但在某些情况下,这可能还不够。
为什么仅 ci 工作流屏蔽可能还不够
例如,Github actions 在处理机密方面做得很好。工作流程中定义的任何秘密都会自动从捕获的输出中屏蔽掉,这就像一个魅力。然而,与任何 ci 系统一样,它也有其局限性。如果输出报告采用不同的路径(例如保存到文件、生成 junit 或发送到远程日志存储),github actions 无法检查内容并掩盖机密。
此外,测试并不总是在 ci 工作流程中运行,即使如此,秘密可能仍然需要隐藏。想象一下您正在本地运行测试并共享日志来讨论问题。在没有意识到的情况下,您在访问令牌中包含了一个 url。
因此,拥有一种处理测试日志中敏感数据的机制在各个层面都是至关重要的。最好的方法是直接在测试级别或测试框架本身内实现这一点。这可以确保秘密不会从主要来源泄漏,从而防止它们通过系统向上传递。
在适当的级别增加保护
直接在测试中维护秘密屏蔽可能成本相对较高且容易出错,而且常常感觉像是一场失败的战斗。例如,假设您需要设计一个以令牌作为参数的 url。与日志中的存在相比,此 url 必须以不同的方式呈现才能在请求中使用。
相比之下,在测试框架内拦截报告生成提供了一个理想的机会来挂钩流程并修改记录以消除敏感数据。这种方法对测试是透明的,不需要修改测试代码,并且其功能就像 ci 工作流程中的秘密屏蔽功能一样 — 只需运行它,而无需管理秘密。它使该过程自动化,确保敏感数据受到保护,而不会给测试设置增加额外的复杂性。
这正是 pytest-mask-secrets 所做的,显然当 pytest 用于测试执行时。在其众多功能中,pytest 提供了丰富且灵活的插件系统。为此,它允许您在生成任何日志之前、在所有数据都已收集完毕时挂接到该进程。这使得在输出记录之前可以轻松搜索并删除记录中的敏感值。
进行测试:实用演示
为了说明这是如何工作的,一个简单的例子将是最有效的。下面是一个简单的测试,可能不代表真实世界的测试场景,但可以很好地演示 pytest-mask-secrets。
1
2
3
4
5
6
7
8
|
import logging
import os
def test_password_length():
password = os.environ[ "password" ]
logging.info( "tested password: %s" , password)
assert len(password) > 18
|
在此示例中,有一个断言可能会失败(而且确实会失败),以及一条包含秘密的日志消息。是的,在日志中包含秘密可能看起来很愚蠢,但请考虑这样一个场景:您有一个带有令牌作为参数的 url,并且启用了详细的调试日志记录。在这种情况下,像 requests 这样的库可能会无意中以这种方式记录秘密。
现在进行测试。首先,设置测试所需的秘密:
1
|
(.env) $ export password= "top-secret"
|
接下来,运行测试:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
(venv) $ pytest --log-level=info test.py
============================= test session starts ==============================
platfORM Linux -- Python 3.12.4, pytest-8.3.2, pluggy-1.5.0
rootdir: /tmp/tmp.avztz7nhzs
collected 1 item
test.py f [100%]
=================================== failures ===================================
_____________________________ test_password_length _____________________________
def test_password_length():
password = os.environ[ "password" ]
logging.info( "tested password: %s" , password)
> assert len(password) > 18
e assertionerror: assert 10 > 18
e + Where 10 = len( 'top-secret' )
test.py:8: assertionerror
------------------------------ captured log call -------------------------------
info root:test.py:7 tested password: top-secret
=========================== short test summary info ============================
failed test.py::test_password_length - assertionerror: assert 10 > 18
============================== 1 failed in 0.03s ===============================
|
默认情况下,秘密值在输出中出现两次:一次在捕获的日志消息中,另一次在失败的断言中。
但是如果安装了 pytest-mask-secrets 会怎么样?
1
|
(venv) $ pip install pytest-mask-secrets
|
并进行相应配置。它需要知道保存秘密的环境变量列表。这是通过设置 mask_secrets 变量来完成的:
1
|
(venv) $ export mask_secrets=password
|
现在,重新运行测试:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
(venv) $ pytest --log-level=info test.py
============================= test session starts ==============================
platform linux -- Python 3.12.4, pytest-8.3.2, pluggy-1.5.0
rootdir: /tmp/tmp.AvZtz7nHZS
plugins: mask-secrets-1.2.0
collected 1 item
test.py F [100%]
=================================== FAILURES ===================================
_____________________________ test_password_length _____________________________
def test_password_length():
password = os.environ[ "PASSWORD" ]
logging.info( "Tested password: %s" , password)
> assert len(password) > 18
E AssertionError: assert 10 > 18
E + where 10 = len( '*****' )
test.py:8: AssertionError
------------------------------ Captured log call -------------------------------
INFO root:test.py:7 Tested password: *****
=========================== short test summary info ============================
FAILED test.py::test_password_length - AssertionError: assert 10 > 18
============================== 1 failed in 0.02s ===============================
|
现在,在打印秘密的地方会出现星号,而不是秘密值。工作已完成,测试报告现已不含敏感数据。
结束语
从这个例子来看,pytest-mask-secrets 似乎并没有比 github actions 默认情况下所做的更多,这使得这项工作显得多余。然而,如前所述,ci 工作流执行工具仅掩盖捕获的输出中的秘密,而使 junit 文件和其他报告保持不变。如果没有 pytest-mask-secrets,敏感数据仍然可能会在这些文件中暴露——这适用于 pytest 生成的任何报告。另一方面,当使用 log_cli 选项时,pytest-mask-secrets 不会屏蔽直接输出,因此 ci 工作流程的屏蔽功能仍然有用。通常最好结合使用这两种工具来确保敏感数据的保护。
就是这样。感谢您花时间阅读这篇文章。我希望它为使用 pytest-mask-secrets 来增强测试过程的安全性提供了宝贵的见解。
测试快乐!