Heroku:私有存储库中的Python依赖项,不存储我的密码

时间:2014-01-23 01:45:01

标签: python heroku github

问题

我的问题与How do I install in-house requirements for Python Heroku projects?How to customize pip's requirements.txt in Heroku on deployment?完全相同。也就是说,我有一个私人仓库,我需要在我的Heroku应用程序中安装Python依赖。由Heroku自己的canonical answer给出的Kenneth Reitz是类似

的东西
-e git+https://username:password@github.com/kennethreitz/requests.git@v0.10.0#egg=requests

requirements.txt文件中。

我的安全需求阻止我将我的密码存储在回购邮件中。(我也不想将依赖项放在我的应用程序的回购中;它们是单独的软件,需要分开存放repos。)我唯一可以将密码(或者,最好是GitHub OAuth令牌或部署密钥)提供给Heroku的地方是一个环境变量,如

heroku config:add GITHUB_OAUTH_TOKEN=12312312312313

尝试解决方案

我可以在我的应用仓库中使用自定义.profile,但每次进程(网络,工作人员等)重新启动时,我都会下载并安装我的依赖项。< / p>

这使得自定义buildpack和Heroku Labs addon在buildpack编译之前暴露了我的heroku config环境。我在building one之上尝试了Buildpack Multi。想法是Buildpack Multi是主要的buildpack,并且在我的应用程序的repo中使用.buildpacks文件,它首先下载普通的Heroku Python buildpack,然后下载我的自定义组件。

即使在Buildpack Multi成功运行Python buildpack之后,问题仍然存在,一旦Buildpack Multi运行,我的buildpack就会Python binary and Pip package are not visible。所以自定义buildpack完全失败了。 (在我的测试中,GITHUB_OAUTH_TOKEN环境变量被正确地暴露给buildpacks。)

我能想到的另一件事就是创建我自己的Python buildpack的fork,它在安装requirements.txt的所有内容时安装我的依赖项,甚至直接重写requirements.txt。这些似乎都是我认为非常常见问题的解决方案。

更新:当前解决方法

我的自定义buildpack(上面链接)现在下载并将我的闭源依赖项(“foo”)保存到geos buildpack使用的供应商目录中。我在我的应用程序中提交了foo本身存在于我的应用程序requirements.txt中的依赖项。因此,Pip通过我的应用程序的requirements.txt安装foo的依赖项,buildpack将foo的托管副本添加到我的应用程序的环境PYTHONPATH(所以foo的setup.py install永远不会运行。)

这种方法的最大问题是将我的(无可否认写得很糟糕)的buildpack与我的应用程序相结合。第二个问题是我的应用程序的requirements.txt应该只将foo列为依赖项,并将foo的依赖项留给foo来确定。最后,如果我忘记设置我的GITHUB_OAUTH_TOKEN环境变量(或者,如果产生更少有用的错误反馈,那么在我忘记如何完成所有这些错误消息之后的六个月内就没有好办法给自己令牌到期且环境变量仍然存在但不再有效。

寻求帮助

我错过了什么(可能是显而易见的)?你是如何在你的应用程序中解决这个问题的?有关让我的构建包工作的建议,或者希望是一个更简单的解决方案吗?

4 个答案:

答案 0 :(得分:3)

我使用存储为环境变量的自定义ssh密钥创建了一个buildpack来解决这个问题。由于buildpack与技术无关,因此可以使用任何工具来下载依赖项,例如用于php的composer,用于ruby的bundler,用于javascript的npm等等:https://github.com/simon0191/custom-ssh-key-buildpack

  1. 将buildpack添加到您的应用中:

    $ heroku buildpacks:add --index 1 https://github.com/simon0191/custom-ssh-key-buildpack
    
  2. 生成新的SSH密钥(假设您将其命名为deploy_key)

  3. 将公钥添加到您的私人存储库帐户。例如:

  4. 将私钥编码为base64字符串,并将其添加为heroku应用程序的CUSTOM_SSH_KEY环境变量。

  5. 以逗号分隔的主机列表,为其应使用ssh密钥,并将其添加为heroku应用程序的CUSTOM_SSH_KEY_HOSTS环境变量。

    # MacOS
    $ heroku config:set CUSTOM_SSH_KEY=$(base64 --input ~/.ssh/deploy_key) CUSTOM_SSH_KEY_HOSTS=bitbucket.org,github.com
    # Ubuntu
    $ heroku config:set CUSTOM_SSH_KEY=$(base64 ~/.ssh/deploy_key) CUSTOM_SSH_KEY_HOSTS=bitbucket.org,github.com
    
  6. 部署您的应用并享受:)

答案 1 :(得分:1)

我遇到了同样的问题。和你一样,我很惊讶找到关于如何安装私有依赖(无论使用何种语言和服务)的好文档是多么困难。

因为这不是服务提供商的主要关注点,所以我现在尝试一种尽可能少依赖于特殊功能的系统方法。我尝试为每个步骤找到更简单的解决方案:

  • 使用安全通道将凭据传递给构建环境。对于python,使用包含SSH密钥的环境变量作为base64字符串。对于js,与npm标记相同。
  • 配置构建过程以使用这些凭据。在最好的情况下,它涉及配置ssh以使用部署密钥。否则它可以像克隆依赖项一样基本以供以后使用。对于python和heroku的特定情况,您可以使用hook&#39; pre_compile&#39;。

我详细介绍了我未来的过程:https://gist.github.com/michelbl/a6163522d95540cf0c8b6667bd35d5f5

我需要访问私有依赖项。它可能发生在持续集成或部署中。

这里我们使用python和github,使用CircleCI和Heroku服务。但是,这些原则适用于所有地方。

什么是部署密钥?

请参阅https://developer.github.com/v3/guides/managing-deploy-keys/

有4种方法可以授予对私有依赖项的访问权限,但是对于不需要太多依赖项的项目(在这种情况下,更喜欢机器用户)而言,部署密钥在安全性和易用性方面是一个很好的折衷方案。在任何情况下,请勿使用开发者帐户或oauth令牌的用户名/密码,因为它们不提供权限限制。

创建部署密钥:

ssh-keygen -t rsa -b 4096 -C "myself@my_company.com"

将公众分享给gihub。

将私有部分提供给需要访问的服务。见下文。

一般策略

无论我使用何种服务或技术,目标都是使用部署密钥使用ssh访问git repo。

显然,我不想将部署密钥放在repo中。但是大多数服务(CI,部署)提供​​了一种设置可在构建时使用的受保护环境变量的方法。密钥可以使用base64编码:

cat deploy-key | base64
cat deploy-key.pub | base64

大多数服务还提供了一种定制构建过程的方法。这是配置ssh以使用部署密钥所必需的。

CircleCI

使用env变量设置部署密钥,使用base64编码。

config.yml中,添加一个步骤:

echo $DEPLOY_KEY_PRIVATE | base64 --decode > ~/.ssh/deploy-key
chmod 400 ~/.ssh/deploy-key
echo $DEPLOY_KEY_PUBLIC | base64 --decode > ~/.ssh/deploy-key.pub
ssh-add ~/.ssh/deploy-key

# Run this to check which private key is used. If the checkout key is used,
# github replies "Hi my_org/my_package". If the deploy key is used as wished,
# github replies "Hi my_org/my_dependency".
#ssh -i ~/.ssh/deploy-key -T git@github.com || true

# Now pip connects to git+ssh using the deploy key
export GIT_SSH_COMMAND="ssh -i ~/.ssh/deploy-key"

pip install -r requirements.txt

requirements.txt可以是:

# The purpose of this file is to install the private dependency *before*
# setup.py is run.

# Be sure ssh is configured to use a ssh key with read permission to the repo.
git+ssh://git@github.com/my_org/my_dependency@1.0.10

# Run setup.py. The private dependency is already installed with the good
# version so pip doesn't try to fetch it from PyPI.
--editable .

setup.py并不关心私有的依赖:

from distutils.core import setup

setup(
    name='my_package',
    version='1.0',
    packages=[
        'my_package',
    ],
    install_requires=[
        # Beware, the following package is a private dependency.
        # Python provides several way to install private dependencies, none
        # are really satisfactory.
        # 1. Use dependency_links / --process-dependency-links. Good luck with
        #    that!
        # 2. Maintain a private package repository. Good luck with that!
        # 3. Install the private dependency separately before setup.py is run.
        #    This is now the prefered way. Be sure that ssh is properly
        #    configured to use a ssh key with read permission to the github repo
        #    of the private dependency, then run:
        #    `pip install -r requirements.txt`
        'my_dependency==1.0.10',
        ... # my normal dependencies
        'unidecode==1.0.22',
        'uwsgi==2.0.15',
        'nose==1.3.7', # tests
        'flake8==3.5.0', # style
    ],
)

的Heroku

对于python,不需要编写自定义buildpack。首先,使用env变量设置deploy键,使用base64编码。

然后添加钩子bin/pre_compile

# This script configures ssh on Heroku to use the deploy key.
# This is needed to install private dependencies.
#
# Note that this does not work with Heroku review apps. Indeed review apps can
# inherits env variables from their parents, but they access their values after
# the build. You would need a way to pass the ssh key to this script another
# way.
#
# See also
# * https://stackoverflow.com/questions/21297755/heroku-python-dependencies-in-private-repos-without-storing-my-password#
# * https://github.com/bjeanes/ssh-private-key-buildpack

# Ensure we have an ssh folder
if [ ! -d ~/.ssh ]; then
  mkdir -p ~/.ssh
  chmod 700 ~/.ssh
fi

# Create the key files
cat $ENV_DIR/DEPLOY_KEY | base64 --decode > ~/.ssh/deploy-key
chmod 400 ~/.ssh/deploy-key
cat $ENV_DIR/DEPLOY_KEY | base64 --decode > ~/.ssh/deploy-key.pub
#ssh-add ~/.ssh/deploy-key

# If you want to disable host verification, you could use that.
#ssh -oStrictHostKeyChecking=no -T git@github.com 2>&1

# Run that if you want to check that ssh uses the correct key.
#ssh -i ~/.ssh/deploy-key -T git@github.com || true

# Configure ssh to use the correct deploy key when connecting to github.
# Disables host verification.
echo -e "Host github.com\n"\
        "  IdentityFile ~/.ssh/deploy-key\n"\
        "  IdentitiesOnly yes\n"\
        "  UserKnownHostsFile=/dev/null\n"\
        "  StrictHostKeyChecking no"\
        >> ~/.ssh/config

# Unfortunately this does not seem to work.
#export GIT_SSH_COMMAND="ssh -i ~/.ssh/deploy-key"

# The vanilla python buildpack can now install all the dependencies in
# requirement.txt

答案 2 :(得分:1)

创建私有PyPI服务器

如果创建自己的PyPI服务器,则只需在requirements.txt文件中列出软件包,然后将服务器的URL(包括用户名和密码)存储在配置变量PIP_EXTRA_INDEX_URL中。

例如:
heroku config:set PIP_EXTRA_INDEX_URL='https://username:password@privateserveraddress.com/simple'

请注意,这与使用pip install命令行选项--extra-index-url相同。 (请参见https://pip.pypa.io/en/stable/user_guide/#environment-variables) 主索引URL仍将是默认值(https://pypi.org/simple)。这意味着pip首先会尝试在默认PyPI服务器上解析需求文件中的软件包名称,然后再尝试使用私有服务器。

如果您的私有服务器中需要与PyPI中的软件包同名的软件包,则需要将主索引url作为服务器,并将--extra-index-url选项作为默认服务器的url。如果要托管自己的现有软件包版本而不更改软件包名称,则需要执行此操作。我还没有尝试过,但是目前看来您需要创建heroku的官方python buildpack的分支,并对bin/steps/pip-install文件进行少量更改。

pip之所以可以访问PIP_EXTRA_INDEX_URL的原因是由于该文件中的该块:

    # Set Pip env vars
    # This reads certain environment variables set on the Heroku app config
    # and makes them accessible to the pip install process.
    #
    # PIP_EXTRA_INDEX_URL allows for an alternate pypi URL to be used.
    if [[ -r "$ENV_DIR/PIP_EXTRA_INDEX_URL" ]]; then
        PIP_EXTRA_INDEX_URL="$(cat "$ENV_DIR/PIP_EXTRA_INDEX_URL")"
        export PIP_EXTRA_INDEX_URL
        mcount "buildvar.PIP_EXTRA_INDEX_URL"
    fi

这样的代码对于读取buildpacks中的配置变量是必需的(请参见https://devcenter.heroku.com/articles/buildpack-api#buildpack-api),但是您应该能够简单地复制此代码块,将PIP_EXTRA_INDEX_URL替换为PIP_INDEX_URL。然后,将PIP_INDEX_URL设置为私有服务器的URL,并将PIP_EXTRA_INDEX_URL设置为默认的PyPI URL。

如果您使用其他来源而不是私有PyPI服务器(例如github),并且仅需要一种避免在requirements.txt文件中对用户名和密码进行硬编码的方法,那么请注意,您可以使用环境变量在requirements.txt中(请参阅https://pip.pypa.io/en/stable/reference/pip_install/#using-environment-variables)。您只需像在bin/steps/pip-install中那样将它们导出到PIP_INDEX_URL中。

答案 3 :(得分:0)

您可以使用预编译步骤described here运行类似M4的操作来对您的requirements.txt执行替换,以便从环境变量中输入密码。