Pipenv: 允许用户使用 PyPI 镜像 URL 覆盖默认的 PyPI 索引 URL(无需修改 Pipfile)

创建于 2018-04-27  ·  46评论  ·  资料来源: pypa/pipenv

大家好,

情况

目前,没有简单的方法可以覆盖默认的 PyPI 索引 URL 以使用指向镜像的 URL。 在企业环境中,要求开发人员使用存储库镜像很常见:

  1. 公司防火墙禁止访问外部软件存储库。
  2. 内部存储库镜像进行恶意软件和漏洞分析,这可能是合规性要求。
  3. 内部镜像保留以后可能在上游不可用的模块(由于中断、删除等),这是确保公司环境中使用的模块的可用性和可审计性所必需的。

不幸的是,pipenv 似乎不容易适应这一点。 尽管可以将镜像作为这些包的源显式添加到 Pipfile 中,但这会破坏可移植性。

  1. 如果在外部发布,内部初始化的项目将包含无法访问的索引。 公共版本的用户必须在安装模块的依赖项之前修改 Pipfile。
  2. 如果不修改 Pipfile,外部初始化的项目将无法在内部工作。 这些修改必须在本地维护(但不共享),如果 Pipfile 在上游更改,则重新应用。

应该有一种方法可以通过指定(真实)镜像来覆盖 PyPI 索引的位置。 这仅适用于 PyPI,不适用于其他第三方存储库(这些仍将在 Pipfile 中明确指定)。

一般建议

Docker 通过允许用户在守护进程的配置文件中指定注册表镜像来适应这种情况。 同样,如果 pipenv 用户可以通过环境变量、配置文件或命令行参数为 PyPI 指定(真正的)镜像,那就太好了。 如果设置了这个值,pipenv 应该为所有 PyPI 包使用镜像,即使与 PyPI 的连接可用。 在某些企业环境中,PyPI 保持畅通,但政策规定使用镜像是出于上述其他原因。

实施注意事项

  1. pip 已经允许用户通过 pip 的配置文件覆盖默认的索引 url。 尽管这可能是内部镜像 url 的最明显来源(并且可能会为这些用户设置),但此参数可用于不是真正镜像的存储库。 因此,它可能不适合此目的。
  2. 对于依赖项都在 PyPI 上可用的模块,我的理解是可以从 Pipfile 中删除显式源,并且将使用 pip 的默认值。 不幸的是,这不适用于具有 PyPI 之外的模块的项目。 此外,由于默认情况下 Pipfile 生成过程是显式的,因此许多现有项目必须通过删除默认索引 url 来修改其合格的 Pipfile 以适应这种模式。
  3. 如果在 Pipfile 中将环境变量设置为源,则可以选择将该变量设置为提供镜像。 不幸的是,这需要现有项目修改其 Pipfile 以适应这种模式,这并不理想。
  4. 如果使用环境变量、命令行参数或配置设置用(真)镜像覆盖 PyPI 索引 url,覆盖将如何工作? 它会假设镜像的索引应该在对 pip 的所有调用中指定,否则会使用 PyPI 索引? 是否需要更改现有的 Pipfile? 是否需要重新定义源的指定方式,包括可覆盖的 PyPI 默认值? 还有什么?

相关讨论

1451

1783

这已在 Freenode 上的 #python 和 #pypa 中讨论过。 经过一些建设性的反复讨论,决定在这里打开一个问题进行讨论会有所帮助。 我感谢大家为解决这个问题所做的努力。

Dependency Resolution Future API Change Behavior Change Discussion

最有用的评论

它应该只覆盖 PyPI,而不是其他 URL。 我猜可能只有几个不同的 PyPI URL 在使用中,所以可以列出它们,如果我们错过一个,那么有人会提交一个错误,它会被添加,很快我们就会拥有所有这些。

所有46条评论

/cc @uranusjr @ncoghlan @altendky @njsmith

我相信这是经常发生的事情(企业固件/缓存代理)——我觉得如果我们在 pipfile 中找到它,我们需要一个覆盖设置来指定要使用的镜像而不是 pypi——比如PIPENV_PYPI_MIRRORPIPENV_PYPI_CACHING_PROXY或类似的东西来指定它应该首先尝试,基本上在pypi前面切成sources

这似乎达到了目标吗? 如果是这样,我们可以在实现精灵中添加标签,告诉我们为什么这是好的或坏的 (@ncoghlan)

我首先要注意一点:直到 PyPI 实施了类似于 PEP 458 的包签名机制,为pip提供独立于 TLS 的方式,以确保名义上源自 PyPI 的包实际上与 PyPI 发布的包相匹配,那么从安全的角度来看,提供透明地将流量重定向到不同服务器的能力确实令人担忧。

不幸的是,这个特定的攻击向量已经通过pip.conf开放,因此在pipenv级别提供类似的东西不会使任何事情变得比现在更糟。

除此之外,我认为通用存储库 URL 重写机制实际上可能比 PyPI 特定的东西更容易记录和解释,至少在基础能力层是这样。 就像是:

pipenv --override-source-url 'default=https://pypi-proxy.example.com/api' --override-source-url 'https://pypi.python.org/simple=https://pypi-proxy.example.com/api'  --override-source-url 'https://pypi.org/simple=https://pypi-proxy.example.com/api' install

(唯一的 PyPI 特定位将使用“默认”来指代 pip 的默认下载源,具体在pip.conf中)。

但是,每次都拼出整个源 URL 覆盖映射在实践中使用起来很笨拙,因此 CLI 糖的几个选项可能如下所示:

pipenv --override-source-urls <config file> install

pipenv --pypi-mirror https://pypi-proxy.example.com/api install

是否立即公开--override-source-url层是一个不同的问题——首先实现更简单的--pypi-mirror选项可能更有意义,并且只保留--override-source-url--override-source-urls作为未来可能的选择,同时这样做。

一个通用的{given URL: override URL}映射也是我的第一个想法,但进一步考虑,有一些关于特殊情况 PyPI 的论点:

  • PyPI 在拥有众所周知的公共 URL大量镜像方面非常独特

  • PyPI 实际上有多个 URL(例如,我们可能会使用https://pypi.python.org/simplehttps://pypi.org/simplehttps://pypi.python.org/simple/https://pypi.org/simple/来浮动一段时间的 Pipfile

@njsmith请参阅我帖子最后部分中的--pypi-mirror <URL>糖建议 - 如果最初的实现仅关注于此,那么通用 URL 重写功能可以从内部实现细节开始(由以下事实驱动) PyPI" 有多个 URL,它们最终都解析到同一个地方),然后考虑作为其自身的一项功能在稍后公开(在确认它按主要--pypi-mirror使用所需的工作后)案件)。

啊,对,我错过了:-)

是否有将命令行参数映射到某种更持久配置的通用规则? 我想大多数用户都想设置一次然后忘记它。

@ncoghlan写道:

我首先要注意一点:直到 PyPI 实现了类似于 PEP 458 的包签名机制,为 pip 提供独立于 TLS 的方式,以确保名义上源自 PyPI 的包实际上与 PyPI 发布的包相匹配,然后提供能力从安全的角度来看,透明地将流量重定向到不同的服务器确实令人担忧。

如果我正确地阅读了我的Pipfile.lock ,那么软件包与安装它的源之间没有存储关系。 鉴于现有的功能集允许指定多个来源,这不会产生类似的问题吗? sync最终可能会从不同于创建锁定文件时使用的源获取包。

Pipfile.lock存储每个固定依赖项的可接受工件哈希列表,因此一旦您完成锁定,就很难偷偷更换包。 在锁定生成时,明确选择Pipfile中的源是在说“我相信这个源不会惹我,并将使用 TLS 来验证我实际上是在与这个源点交谈”。 (我认为在某处讨论将特定包绑定到特定源存储库的前景存在问题,尽管它可能在pip或其他 PyPA 存储库之一中,而不是在这里)

更改pip.conf中的默认索引 URL(或添加额外的索引 URL),或通过配置文件或基于 shell 配置文件的机制使用此处提出的覆盖功能是不同的:这就是说“我,或者我的某个任意进程在某个时间点对我的主目录(例如 sdist 的 setup.py 文件)进行了写访问,决定将我的设置配置为信任这个包源”。 如果用于验证的公钥本身存储在您的主目录中的某个位置,而不是需要提升权限才能修改的目录中,那么即使是像 PEP 458 这样的签名方案也不能完全防御这些恶作剧。

具有严格安全要求的组织在只能有限访问整个互联网的锁定服务器上执行构建,或者以其他方式在网络级别监控此类问题是有充分理由的:)

另请注意,如果您使用多个索引并且包来自非主索引,它将在锁定文件中指示。

pep 458 问题本质上是我的想法,因为不同的 url 但实际上在 pypi 点的东西与你只是在本地复制 pypi 并声称它是相同的不同。

我,或者我在某个时间点运行的某个任意进程对我的主目录(例如 sdist 的 setup.py 文件)进行了写访问,决定配置我的设置以信任这个包源

如果这是您的威胁模型,那么我看不出 pipenv 可以做的任何事情会对它产生多大影响。 可以修改你的主目录配置的人也可以做一些事情,比如在$PATH上插入一个新目录,并在那里插入一个假的 pipenv 来做他们想做的任何事情。

@njsmith这也是 pip 的威胁模型,因为软件包安装需要执行 sdist setup.py文件中的任意代码。 该代码确实可以覆盖您的主目录中的内容,例如您的设置,或将内容添加到您的路径或任何数量的内容。 这就是为什么明确授予 pypi(一个已知的、受信任的索引)并要求哈希检查是迈向安全性的良好一步的原因。 它允许集中控制和消除已知的安全威胁,并以分布式方式识别您正在下载的包的验证。 你下载的锁文件对你应该得到的哈希有什么看法? 它与您从索引中获得的不匹配? 为了使这种操作模式失败,您需要在本地机器、索引和网络层中的多个层出现故障,因为您正在谈论在您的应用程序堆栈中有多个损坏的包协同工作,根据可信索引验证哈希,并且在许多情况下,哈希本身来自另一个未涉及的来源。 因此,现在您至少需要对 pip 和 pipenv 中的所有哈希检查进行某种篡改,以便生成与您希望的哈希相同的哈希,但还会安装其他恶意内容?

我想我的意思是,如果您的本地机器受到威胁,那么 pip 或 pipenv 将无法拯救您。 但是我们可以确保您正在下载的包是您正在寻找的包,来自您应该搜索的地方,这可以提供安全链中的一个元素。

@ncoghlan @njsmith这一切对抵制sudo pip install...的举措有何影响,一般意义上,我认为我们都有这样的想法,如果你要使用 pip,你可能也不应该使用你的系统包管理器从广义上讲是安装python的东西。 这也许不是一个真正的 pipenv 问题,但它是现在讨论的地方,这可能会指导接下来的步骤......

@techalchemy我根本看不到与此主题的任何联系? 我认为以上所有内容的结论是,让用户覆盖用于 PyPI 的镜像 pipenv 不会引入任何额外的威胁,并且首先执行sudo pipenv甚至没有意义,对吧?

@njsmith不,我认为任何人都不应该使用sudo pipenv ,就像我提到的那样,它并不是真正的主题,但由于我们沿着威胁模型路径走了一点,我认为值得探索。 具体来说:

如果用于验证的公钥本身存储在您的主目录中的某个位置,而不是需要提升权限才能修改的目录中,那么即使是像 PEP 458 这样的签名方案也不能完全防御这些恶作剧。
具有严格安全要求的组织在只能有限访问整个互联网的锁定服务器上执行构建,或者以其他方式在网络级别监控此类问题是有充分理由的:)

如果防御至少在某些方面依赖于存储在特权位置的密钥,但我们建议不要使用特权 python 安装,我认为这可能值得讨论。 也许我错了。 但它似乎肯定与@ncoghlan的评论有关(但不是sudo pipenv ,那不应该是一件事)

是的,这可能看起来像是突然冒出来的,只是一个随机的想法。 希望额外的上下文可以清除一些

我投票决定将这个问题放在帮助需要使用 PyPI 镜像的人的话题上,而不是对我们如何实现 TUF 进行推测性讨论。 (无论如何,我不认为我们可以或应该做很多事情来尝试防御对用户主目录具有任意写入权限的攻击者。)

好的,让我们定义我们期望或喜欢的行为。 我目前的工作理解是:

  • 如果通过--pypi-mirror或设置PIPENV_PYPI_MIRROR ,我们应该更喜欢
  • 我们应该只喜欢它而不是 PyPI 吗? 我们如何评估给定的索引 url 是否是“PyPI”——我们无法查询它,所以我们必须维护一个列表
  • 该列表是否应该包含所有可能的排列,或者我们是否应该满足于使用我们过去用于生成 Pipfile 的两个 url 作为我们应该首先尝试提供的镜像的东西?

它应该只覆盖 PyPI,而不是其他 URL。 我猜可能只有几个不同的 PyPI URL 在使用中,所以可以列出它们,如果我们错过一个,那么有人会提交一个错误,它会被添加,很快我们就会拥有所有这些。

对我来说似乎是正确的方法。

@njsmith所说的也符合我的观点。 我建议在初始 PR 中替换的 3 个 repo URL 是:

尾部斜杠或非斜杠可能更好地作为 URL 规范化步骤处理,而不是单独列出 URL。

请注意,请求 Pipfile 确实有一个尾部斜杠(在撰写本文时) ,因此我们可能确实需要以一种或另一种方式处理这种情况。

对了,我的想法是:

  • 维护一个不带斜杠的 URL 列表
  • 检查传入的 URL 是否有斜杠,如果找到则将其删除( str.rstrip可能足以胜任该任务,即使它会删除任意数量的斜杠,否则我们可以更严格地处理它,并删除最多一个尾部斜杠)

惊人的。 我认为这足以使用并且构建起来足够简单。 谢谢大家!

希望镜像功能能快点加入~

我也遇到了这个问题。 情况是:

  • 有一个带有一些私有包的内部 PyPI 服务器。
  • 拥有多个使用 Pipenv 管理其依赖项的 Python 应用程序。
  • 一些依赖项存在于内部 PyPI 服务器上,而其他依赖项则存在于社区 PyPI 上。 对于未找到的任何包,内部重定向到社区 PyPI。

我的部署策略已经设置了一个指向内部 PyPI 服务器的系统范围的 pip.conf。 令人惊讶的是,我发现 Pipenv 忽略了这个配置。

我注意到,如果我要移动/重命名内部 PyPI,那么必须更新几个带有 Pipfiles 的应用程序并重新生成它们的 Pipfile.lock 文件。 镜像选项将提供所需的功能。 如果 Pipenv 可以读取 Pip 的系统配置,它也可以工作并且感觉不那么多余。

顺便说一句,欢迎 PRs

你好。 我有同样的需求,但我会将此覆盖功能拆分为另一张票。

这是我的预期行为建议:

  • 可以定义一个配置文件来设置 Pipfile 的 [[source]] 部分中定义的每个值。
  • 可能是一个 toml 文件,其中只有 Pipfile 的 [[source]] 部分
  • 该文件的位置很大程度上受到为 pip.conf 定义的规则的启发(例如:/etc/pipenrc.toml、~/.pipenvrc.toml
  • 也可以定义环境变量来覆盖这些值(提醒:我们需要 [[source]] 的所有值)。 被定义为
  • pipenv 的当前行为现在是:

    • 创建 Pipfile 时,它​​从配置文件/环境变量中获取值

    • 如果没有定义配置文件或环境变量,则 pipenv 的当前行为适用

    • pipenv 继续始终生成 Pipfile 的 [[source]] 部分

    • 如果存在 Pipfile 的 [[source]] 部分,则 pipenv 不会尝试使用配置文件中的值覆盖任何内容。

在第二张票中,可以实施 —override 选项。 例如,在 CI 或其他东西中是有意义的。

作为旁注:我们现在在生产中大量使用 pipenv,但我需要经常提醒大家,当他们开始一个新项目以访问我们的 Arrifactory Pypi 存储库时,他们需要手动更改他们的 Pipfile(作为参考,Nexus 也做了一个 Pypi免费缓存,效果很好!)。 我们有一个非常有限的防火墙,在公司内部缓存外部依赖项是一个非常好的做法,因此可以备份它们并检查漏洞。
如果一个简单的功能类似于通用或用户配置文件(就像我们已经为 pip 或 npm 所做的那样),以便我们将它部署在我们所有的工作站上,以便我们的开发人员少犯错误,那对我来说是完美的)

也许我错过了一些东西,但这似乎是一种回归。 我们在 11.6.0 上已经有一段时间了,并且 pipenv 愉快地委托给我们的 pip.conf 中的设置,它指向一个内部 pypi 镜像。

知道这是什么时候坏的吗? 它使 pipenv 在我们的上下文中完全无法使用。 当它显然可以正常工作很长时间时,我很难将其视为“缺失的功能”。

需要说明的是:升级到 2018.05.18 后,即使使用我们的 Pipfile[.lock] 中指定的镜像,pipenv 也会尝试从 pypi.org 安装新包。

也许我看到的是与这个不同的问题......

@brettdh没有看到你的环境很难说,但我认为这不是同一个问题。 我建议您在版本之间进行一些二等分,以确切了解更改的位置,并为它打开一个新问题。

我正在为此做 PR。

我确实认为这与默认设置相比有所退步。 它可能已经陷入了尚未发布的 pip 10 更新浪潮中,但我相信如果@JacobHenner还没有添加它,我们可以毫不费力地拿起它

我想你说的是使用 devpi 作为官方 PyPi 的缓存代理。 对于 pip 本身,您需要为 pip 修改/etc/pip.conf/usr/lib64/python3.6/disutils/distutils.cfg以使用本地 devpi 服务器处理所有请求。

但是,看起来 pipenv忽略了这些系统范围的设置,因此您被迫修改 Pipfile 中的[[source]]配置设置以引用您的 devpi 服务器。 但是,如果您在外部发布 Pipfile,外部贡献者必须删除您的[[source]]设置才能真正构建自己的环境。

我认为 pipenv 应该只尊重/etc/pip.conf/usr/lib.../distutils.cfg的全局设置

@polski-g

我想你说的是使用 devpi 作为官方 PyPi 的缓存代理

Nexus Repository,但是,是的,同样的想法。

但是,看起来 pipenv忽略了这些系统范围的设置

正如@techalchemy提到的,我相信 pipenv (11.6.0)曾经尊重pip.conf (homedir),但最新版本没有 - 具体来说,某处有一个硬编码的 pypi.org URL(依赖分辨率,IIRC),不能被覆盖。

我认为 pipenv 应该只尊重 /etc/pip.conf 和 /usr/lib.../distutils.cfg 的全局设置

同意 - 虽然我个人不必在我的用例中修改distutils.cfg

IIRC 有一个不尊重 pip.conf 的决议,但您需要深入挖掘问题跟踪器才能找到它。 无论如何,这艘船已经航行了,并且 PyPI 镜像几乎完成,这在不久的将来不太可能改变。

我相当有信心此功能将在下一个版本中发布(运气好的话将在一两天内发布)

我也不确定这一点,但我们可能只需要在此处创建配置解析器后调用.load()即可获取配置默认值

https://github.com/pypa/pipenv/blob/master/pipenv/project.py#L573 -#L577

@uranusjr只要镜像配置有效(即不使用我提到的硬编码 pypi.org URL),我认为 pipenv 有自己的配置并忽略 pip 没有任何问题。

@brettdh你能检查我的分支并确认它符合你的要求吗
您的环境中的用例?

>

@JacobHenner是的,谢谢。 我使用--pypi-mirror选项( pipenv installpipenv lock )的初始测试看起来效果很好。 我在 PR 上留下了一个小建议。

不过,我有点担心 pypi.org 的硬编码 URL 仍然分散在 pipenv 源中。 我无法确定[[source]]条目中正确覆盖了哪些条目,而且我不记得究竟是哪个工作流程导致了我上面的问题。 所以很难说它是否已修复。 😬

是的,在这个版本之后,我计划进行一次主要的代码清理。 Cli 东西移动到 cli,在那里冒泡异常并处理那里的所有出口,重复代码删除等。这将是很多工作,如果有人愿意提供帮助,我们将不胜感激:p

刚刚提取了最新版本,它仍在对源代码中的 pypi.org 进行硬编码。 目标是采用环境变量还是 pypi-mirror 并将其作为 [[source]] 的默认值?

编辑:

只是挖掘代码..看起来你有

if PIPENV_TEST_INDEX:
    DEFAULT_SOURCE = {
        u"url": PIPENV_TEST_INDEX,
        u"verify_ssl": True,
        u"name": u"custom",
    }
else:
    DEFAULT_SOURCE = {
        u"url": u"https://pypi.org/simple",
        u"verify_ssl": True,
        u"name": u"pypi",
    }

我认为如果您将 PIPENV_TEST_INDEX 更改为环境变量 PIPENV_PYPI_MIRROR 这将是一个好的开始

这里讨论的解决方案早已实施。 您引用的片段是默认的,即在创建 Pipfile 时不提供源代码时使用。

不,Pipfile 中的源不应更改。 本次变更的目标
是允许用户使用镜像覆盖 PyPI URL,_without_ 更改
点文件。

@JacobHenner镜像处理代码对源列表进行后处理,并将pypi.org URL替换为对指定镜像的引用。

这就是允许镜像覆盖工作的原因,即使$ Pipfile中有明确的pypi.org条目。 然后pipenv也依赖相同的逻辑来覆盖它自己的默认源。

如果当前存在未正确应用后处理的情况,则这是针对已实现功能的新错误报告,而不是功能请求。

我认为最后一条评论是针对@kylecribbs 的?

@JacobHenner啊,抱歉-我误解了您的评论,说此更改没有实现其最初的目标,而不是对 Kyle 的回应,旨在澄清该结果的实际含义。

此页面是否有帮助?
0 / 5 - 0 等级