用于具有安全项目主机和包仓库配置的Python包模板。
项目描述
安全Python包模板
用于具有安全项目主机和包仓库配置的Python包模板。
本项目的目标是
- 展示如何在GitHub上托管Python包并使用
- 操作安全最佳实践
- 自动发布到PyPI
- 代码质量和漏洞扫描
- 构建可重复性
- 带有来源证明的发布
- 从OpenSSF Scorecard 获得完美评分
- 使用GitHub OIDC的SLSA Level 3
配置git进行提交和标签签名
信息 提交和标签签名是一种推荐的做法,以避免提交作者欺骗,但对于安全项目配置不是强制性的。如果您想跳过此步骤,可以跳转到 创建GitHub存储库。
Git需要配置为能够签名提交和标签。Git使用GPG进行签名,因此如果您还没有,您需要 创建一个GPG密钥。请确保您使用与您的GitHub账户关联的 电子邮件地址 作为密钥的电子邮件地址。如果您希望保持您的电子邮件地址私密,应使用GitHub提供的 noreply
电子邮件地址。
gpg --full-generate-key
在您生成GPG密钥后,您需要 将GPG密钥添加到您的GitHub账户。然后,您可以在本地 配置git以使用您的签名密钥
git config --global --unset gpg.format
列出GPG秘密密钥,在此示例中,密钥ID为'3AA5C34371567BD2'
$ gpg --list-secret-keys --keyid-format=long
/Users/hubot/.gnupg/secring.gpg
------------------------------------
sec 4096R/3AA5C34371567BD2 2016-03-10 [expires: 2017-03-10]
uid Hubot <hubot@example.com>
ssb 4096R/4BB6D45482678BE3 2016-03-10
告诉git关于您的签名密钥
git config --global user.signingkey 3AA5C34371567BD2
然后告诉git自动签名提交和标签
git config --global commit.gpgsign true
git config --global tag.gpgSign true
现在,从这个git实例创建的所有提交和标签都将被签名,并在GitHub上显示为“已验证”。
创建GitHub仓库
在本地上克隆此仓库
git clone ssh://git@github.com/sethmlarson/secure-python-package-template
将文件夹重命名为包名,并删除现有的git仓库
mv secure-python-package-template package-name
cd package-name
rm -rf .git
创建一个新的git仓库,并确保分支名为main
$ git init
Initialized empty Git repository in .../package-name/.git/
$ git status
On branch main
No commits yet
...
如果分支没有命名为main
,您可以重命名分支
git branch -m master main
在GitHub上创建一个空
仓库。为确保仓库为空,您不应添加README文件、.gitignore文件或许可证。以下示例中,GitHub仓库的名称为sethmlarson/package-name
,但您应将其替换为您选择的GitHub仓库名称。
我们需要告诉我们的git仓库关于我们新的GitHub仓库的信息
git remote add origin ssh://git@github.com/sethmlarson/package-name
更改所有名称和URL以匹配您自己的包。需要更新的地方包括
README.md
pyproject.toml
(project.name
和project.urls.Home
)src/<{{secure_package_template}}>
tests/test_<{{secure_package_template}}>.py
您还应该将许可证更改为您想要用于包的许可证。更新以下值
LICENSE
README.md
现在我们可以创建我们的初始提交
git add .
git commit -m "Initial commit"
验证此提交是否已签名。如果没有,您应该配置git以自动签名提交
$ git verify-commit HEAD
gpg: Signature made Fri 15 Jul 2022 10:55:10 AM CDT
gpg: using RSA key 9B2E1343B0B201B8883C79E3A99A0A21AD478212
gpg: Good signature from "Seth Michael Larson <sethmichaellarson@gmail.com>" [ultimate]
现在我们将推送我们的提交和分支
$ git push origin main
Enumerating objects: 25, done.
Counting objects: 100% (25/25), done.
Delta compression using up to 12 threads
Compressing objects: 100% (21/21), done.
Writing objects: 100% (25/25), 17.92 KiB | 1.28 MiB/s, done.
Total 25 (delta 0), reused 0 (delta 0), pack-reused 0
To ssh://github.com/sethmlarson/package-name
* [new branch] main -> main
成功!现在您应该可以在GitHub仓库中看到此提交和所有文件。
配置GitHub仓库
Dependabot
Dependabot是由GitHub提供的一项服务,它通过为您创建更新依赖项的pull request来自动保持您的依赖项更新。不幸的是,当使用Dependabot处理任何非平凡数量的依赖项时,pull request的数量会迅速变得难以处理,尤其是当您想到单个维护者需要管理多个项目的依赖项更新时。
在此仓库中,Dependabot采用的方法是在保持开发和使用包的安全和可维护的依赖项集的同时,将Dependabot的pull request数量保持在最低。政策如下所述
- 如果已固定的版本有公开的安全漏洞,则始终创建升级依赖项的pull request。这是Dependabot的默认行为,无法禁用。
- 当新的主要版本的开发依赖项可用时创建pull request。这很重要,因为通常主要版本包含向后不兼容的更改,因此可能实际上需要我们进行更改。
- 当依赖项的新版本携带像
certifi
这样的安全敏感数据时创建pull request。始终确保此包是最新的,以避免中间人(MITM)攻击。 - 所有其他依赖项的升级都需要手动完成。这些情况类似于影响项目的错误修复或新功能。在此处的开发者体验与Dependabot未自动升级依赖项时的体验相同。
您可以通过阅读dependabot.yml
配置文件来了解如何编码上述政策,或者阅读Dependabot文档以了解配置格式。
启用Dependabot
- 设置 > 代码安全性和分析
- 依赖关系图应启用。这是公共仓库的默认设置。
- 启用Dependabot安全更新
手动升级依赖项
任何开发依赖项的升级,无论是为了修复错误还是使用新功能,都需要手动升级,而不是依赖Dependabot自动更新。可以通过运行以下命令来仅升级一个包
# We want to only upgrade the 'keyring' package
# so we use the --upgrade-package option.
pip-compile \
requirements/publish.in \
-o requirements/publish.txt \
--no-header \
--no-annotate \
--generate-hashes \
--upgrade-package=keyring
CodeQL和有漏洞代码扫描
- CodeQL已经在
.github/workflows/codeql-analysis.yml
中配置 - 阅读完CodeQL 文档后,根据需要进行配置。
受保护分支
- 设置 > 分支
- 选择“添加规则”按钮
- 分支名称模式应该是您的默认分支,通常是
main
- 启用“合并前要求拉取请求”
- 启用“要求审批”。为了从 OpenSSF 评分卡指标“分支保护”获得满分,您必须将所需审阅者数量设置为 2 或更多。
- 启用“当推送新提交时取消过期拉取请求的审批”
- 启用“要求代码所有者审查”
- 启用“合并前要求状态检查通过”
- 添加所有应要求的状态检查。对于此模板,它们将是:
分析(python)
测试(3.8)
测试(3.9)
测试(3.10)
- 确保所有状态检查的“来源”都是有意义的,并且不是设置为“任何来源”。默认情况下,这应该为所有上述状态检查正确配置为“GitHub Actions”。
- 启用“合并前要求分支是最新的”。警告:这将增加接收新贡献者贡献的难度。
- 添加所有应要求的状态检查。对于此模板,它们将是:
- 启用“要求签署提交”。警告:这将增加接收新贡献者贡献的难度。
- 启用“要求线性历史”
- 启用“包括管理员”。此设置更像是一个提醒,并不能阻止管理员在紧急情况下暂时禁用此设置以合并挂起的 PR。
- 确保“允许强制推送”被禁用。
- 确保“允许删除”被禁用。
- 选择“创建”按钮。
受保护标签
- 设置 > 标签 > 新规则
- 使用
*
模式保护所有标签 - 选择“添加规则”
发布 GitHub 环境
- 设置 > 环境 > 新环境
- 命名环境:
publish
- 添加所需的审阅者,应该是维护者
- 选择“保存保护规则”按钮
- 在部署分支下拉菜单中选择“受保护分支”
- 在环境密钥部分中选择“添加密钥”
- 在
PYPI_TOKEN
下添加 PyPI API 令牌值
私人漏洞报告
- 设置 > 代码安全性和分析
- 为“私人漏洞报告”选择“启用”。这将允许用户私下提交漏洞报告直接到存储库。
- 将
SECURITY.md
文件中的 URL 更新为您自己的存储库的 URL。
配置 PyPI
PyPI 正在提高账户安全性和凭证管理的最低要求,以使在 PyPI 上使用软件包更安全。这包括最终要求所有用户使用双因素认证(2FA)和发布软件包时要求 API 令牌。我们不必等待这些最佳实践成为必需,现在就可以选择启用它们。
选择启用 2FA
如果您已经在 PyPI 上启用了 2FA,请参阅PyPI 帮助页面中有关如何为您的账户启用 2FA 的部分。要为新的项目强制 2FA
- 在 PyPI 上打开“您的项目”
- 选择项目的“管理”
- 设置 > 为项目启用 2FA 要求
配置受信任的发布者
如果您的项目托管在 GitHub 上,您可以利用 PyPI 的新功能“受信任的发布者”。建议使用受信任的发布者而不是 API 密钥或密码,因为它通过要求软件包来自预配置的 GitHub 存储库、工作流和环境,提供额外的安全层。
这里有关于如何将受信任的发布者添加到项目的简要指南:如何添加受信任的发布者。下面是如何将GitHub工作流定义映射到PyPI受信任发布者的示例。
注意 应注意,发布工作流只能由您打算使用的GitHub账户触发。请记住,git标签(没有启用保护标签)只需要对仓库的写入访问权限。这就是为什么推荐使用具有一组所需审查者的GitHub环境,以明确列出允许完全执行发布任务的名单。
配置受信任的发布者需要4个值
- GitHub仓库所有者
- GitHub仓库名称
- GitHub工作流文件名
- GitHub环境名称(可选,但强烈推荐!)
以这个仓库(https://github.com/sethmlarson/secure-python-package-template)为例,设置受信任发布者的值如下
- GitHub仓库所有者:
sethmlarson
- GitHub仓库名称:
secure-python-package-template
- GitHub工作流文件名:
publish.yml
- GitHub环境名称:
publish
以下是GitHub工作流所需的最低配置
# Filename: '.github/workflows/publish.yml'
# Note that the 'publish.yml' filename doesn't need the '.github/workflows' prefix.
jobs:
publish:
# ...
permissions:
# This permission allows for the gh-action-pypi-publish
# step to access GitHub OpenID Connect tokens.
id-token: write
# This job requires the 'publish' GitHub Environment to run.
# This value is also set in the Trusted Publisher.
environment:
name: "publish"
steps:
# - ...
# The 'pypa/gh-action-pypi-publish' action reads OpenID Connect
# Note that there's zero config below, it's all magically handled!
- uses: "pypa/gh-action-pypi-publish@0bf742be3ebe032c25dd15117957dc15d0cfc38d"
验证配置
验证可重复构建
找到通过发布GitHub环境完成的最新发布,本例中使用v0.1.0。
打开PyPI上的相应发布页面。选择“下载文件”标签。对于每个.whl
文件选择“查看散列”并复制SHA256值并保存该值(de58d65d34fe9548b14b82976b033b50e55840324053b5501073cb98155fc8af
)
在本地克隆GitHub仓库。不要使用现有克隆以避免污染工作区
git clone ssh://git@github.com/sethmlarson/secure-python-package-template
检出相应的git标签。
git checkout v0.1.0
运行以下命令并将存储的值导出到SOURCE_DATE_EPOCH
$ git log -1 --pretty=%ct
1656789393
$ export SOURCE_DATE_EPOCH=1656789393
安装发布依赖项并构建包
python -m pip install -r requirements/publish.txt
python -m build
将SHA256散列与PyPI上的值进行比较,它们应该与我们在PyPI上之前看到的SHA256值匹配。
$ sha256sum dist/*.whl
de58d65d34fe9548b14b82976b033b50e55840324053b5501073cb98155fc8af
许可证
CC0-1.0
项目详情
下载文件
下载适合您平台的文件。如果您不确定选择哪个,请了解有关安装包的更多信息。