在Heroku上使用NPM私有Git模块

58

我正在尝试将我的应用程序部署到Heroku,但是我依赖于使用一些私有的Git仓库作为模块。我这样做是为了在项目之间重复使用代码,例如,我有一个自定义的日志记录器,我在多个应用程序中使用它。

"logger":"git+ssh://git@bitbucket.org..............#master"

问题在于Heroku显然没有对这段代码进行ssh访问权限。我在这个问题上找不到任何信息。理想情况下,Heroku会有一把公钥,我只需将其添加到模块中。


1
模块应该安装在node_modules目录中吗?您可以将应用程序打包,然后将其发送到heroku,在发送到heroku之后进行安装。 - Alfred
我不是很理解,但我认为你的意思是我可以将代码存储在node_modules文件夹和主仓库中,这样做是可行的,但有点像是一个hack。 - henry.oswald
当您在本地PC上执行npm install时,这是自npm 1.0以来的标准行为? - Alfred
交叉的线路。我希望在heroku部署之间不添加任何其他进程,这样就失去了意义。 - henry.oswald
3
我也想知道答案。你可以将你的Github/Bitbucket SSH密钥绑定到你的Heroku账户中: heroku keys:add ~/.ssh/id_rsa.pub。理论上这应该是可行的,但是 git push heroku master 仍然会出现“主机密钥验证失败”的错误。你在六月份问过这个问题,那时你找到答案了吗? - lefnire
10个回答

76

基本身份验证

GitHub支持基本身份验证:

"dependencies" : {
    "my-module" : "git+https://my_username:my_password@github.com/my_github_account/my_repo.git"
}

就像BitBucket一样:

"dependencies" : {
    "my-module": "git+https://my_username:my_password@bitbucket.org/my_bitbucket_account/my_repo.git"
}

但在您的 package.json 中使用明文密码可能不是期望的。

个人访问令牌(GitHub)

为了使这个答案更加最新,我现在建议在 GitHub 上使用个人访问令牌代替用户名/密码组合。请参考如何使用个人访问令牌

您现在应该使用:

"dependencies" : {
    "my-module" : "git+https://<username>:<token>@github.com/my_github_account/my_repo.git"
}

对于Github,您可以在这里生成新令牌:

https://github.com/settings/tokens

应用程序密码(Bitbucket)

应用程序密码主要用于提供与不支持双因素身份验证的应用程序兼容性的方式,您也可以将其用于此目的。首先创建一个应用程序密码,然后像这样指定您的依赖项:

"dependencies" : {
    "my-module": "git+https://<username>:<app-password>@bitbucket.org/my_bitbucket_account/my_repo.git"
}

[已弃用] 团队 API 密钥(Bitbucket)

您可以在“管理团队”页面上生成 Bitbucket 的 API 密钥,然后使用此 URL:

"dependencies" : {
    "my-module" : "git+https://<teamname>:<api-key>@bitbucket.org/team_name/repo_name.git"
}

8
通过授权 API,您可以更安全地完成此操作,方法是发出一个 OAuth 令牌并使用该令牌代替您帐户的用户名和密码:https://help.github.com/articles/git-over-https-using-oauth-token。 - Rafael
4
这是一个简短的更新,我尝试使用提到的 Bitbucket API 方法,但是所示语法是不正确的。您需要执行 git+https://<team-name>:<api-key>@bitbucket.org/<team-name>/<repo_name>.git。请注意不要更改原意。 - Grofit
@Grofit,这个URL格式有没有相关文档?因为我找到的唯一一件事情是:https://confluence.atlassian.com/display/BITBUCKET/OAuth+on+Bitbucket#OAuthonBitbucket-Cloningarepositorywithanaccesstoken - Koen.
1
我已更新答案以引用更新的Bitbucket技术,此前的评论是针对旧版本答案的。 - Flimm
2
请问您能否在package.json的依赖项部分添加一行关于如何使用.env变量的说明? - Brandon Keith Biggs
显示剩余9条评论

45

更新时间 2016-03-26

如果您正在使用npm3,则本文描述的方法已不再适用,因为npm3在运行preinstall 脚本之前会获取 package.json 中描述的所有模块,该问题已确认为bug

官方的node.js Heroku构建包现在包括heroku-prebuildheroku-postbuild,分别在npm install之前和之后运行。 您应该在所有情况下都使用这些脚本来替代preinstallpostinstall,以支持npm2和npm3。

换句话说,您的package.json 应类似于:

 "scripts": {
      "heroku-prebuild": "bash preinstall.sh",
      "heroku-postbuild": "bash postinstall.sh"
    }

我想出了一种替代Michael答案的方法,保留了(在我看来)有利的要求,即将您的凭据保持不在源代码控制下,同时不需要自定义构建包。这是因为我对Michael链接的构建包感到失望,因为它已经过时了。

解决方案是在npm的preinstallpostinstall脚本中设置和撤销SSH环境,而不是在构建包中。

按照以下说明操作:

  • 在您的存储库中创建两个脚本,我们称之为preinstall.shpostinstall.sh
  • 使它们可执行(chmod +x *.sh)。
  • 将以下内容添加到preinstall.sh中:
    #!/bin/bash
    # Generates an SSH config file for connections if a config var exists.

    if [ "$GIT_SSH_KEY" != "" ]; then
      echo "Detected SSH key for git. Adding SSH config" >&1
      echo "" >&1

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

      # Load the private key into a file.
      echo $GIT_SSH_KEY | base64 --decode > ~/.ssh/deploy_key

      # Change the permissions on the file to
      # be read-only for this user.
      chmod 400 ~/.ssh/deploy_key

      # Setup the ssh config file.
      echo -e "Host github.com\n"\
              " IdentityFile ~/.ssh/deploy_key\n"\
              " IdentitiesOnly yes\n"\
              " UserKnownHostsFile=/dev/null\n"\
              " StrictHostKeyChecking no"\
              > ~/.ssh/config
    fi
  • 将以下内容添加到postinstall.sh中:
    #!/bin/bash

    if [ "$GIT_SSH_KEY" != "" ]; then
      echo "Cleaning up SSH config" >&1
      echo "" >&1

      # Now that npm has finished running, we shouldn't need the ssh key/config anymore.
      # Remove the files that we created.
      rm -f ~/.ssh/config
      rm -f ~/.ssh/deploy_key

      # Clear that sensitive key data from the environment
      export GIT_SSH_KEY=0
    fi
  • Add the following to your package.json:

    "scripts": {
      "preinstall": "bash preinstall.sh",
      "postinstall": "bash postinstall.sh"
    }
    
  • Generate a private/public key pair using ssh-agent.

  • Add the public key as a deploy key on Github.
  • Create a base64 encoded version of your private key, and set it as the Heroku config var GIT_SSH_KEY.
  • Commit and push your app to Github.
当Heroku构建您的应用程序时,在npm安装依赖项之前,将运行preinstall.sh脚本。这将从GIT_SSH_KEY环境变量的解码内容创建私钥文件,并创建一个SSH配置文件,告诉SSH在连接到github.com时使用此文件。(如果您连接的是Bitbucket,则更新preinstall.sh中的Host条目为bitbucket.org)。然后,npm使用此SSH配置安装模块。安装完成后,将删除私钥并擦除配置。
这使得Heroku可以通过SSH拉取您的私有模块,同时使私钥不会出现在代码库中。如果您的私钥被泄露,因为它只是部署密钥的一半,您可以在GitHub中撤销公钥并重新生成密钥对。
顺便提一下,由于GitHub部署密钥具有读/写权限,如果您将模块托管在GitHub组织中,则可以创建一个只读团队并向其分配“部署”用户。然后可以使用密钥对的公共部分配置部署用户。这为您的模块增加了一层额外的安全性。

干得好!我从来不喜欢为了做这个而维护构建包的分支。 - Michael Lang
你能详细说明一下如何创建私钥的base64编码版本吗?我已经使用了一个在线工具,但是我仍然收到错误消息“Permission denied (publickey)。”这让我觉得我在Heroku设置中输入的值是不正确的。预安装脚本正在运行,因为我可以在日志中看到。 - Peter
找到了。我使用了 cat /path/to/private/key | base64 > output.txt 命令。然后删除任何 \n(换行符),并将其粘贴到我的 Heroku 设置中。 - Peter
1
@roboli 是的,由于它是无人值守的,所以这种技术只适用于没有密码保护的情况。我建议为此操作创建一个单独的SSH密钥,这将使在以后被攻击时吊销变得容易。 - Tom Spencer
1
@Pier 名称并不重要,你可以轻松地使用 GIT_SSH_01GIT_SSH_02 等等 - 只需记得相应地修改脚本以使用正确的环境变量即可。 - Tom Spencer
显示剩余13条评论

17

在您的git repo中拥有明文密码是一个非常糟糕的想法,使用访问令牌会更好,但您仍然需要非常小心。

"my_module": "git+https://ACCESS_TOKEN:x-oauth-basic@github.com/me/my_module.git"

1
这是最佳答案!在此处创建令牌:https://github.com/settings/applications - Christiaan Westerbeek
1
我正在尝试使用Bitbucket,当Heroku尝试克隆私有仓库时,结果出现了“主机密钥验证失败”:/ - Martin Schaer
1
另外,不要忘记限定您的令牌范围,以便它只具有“repo”访问权限。 - bpaul
1
@bpaul 是的,作用域非常重要,否则就像是把明文密码提交一样。 - Atav32

14

我创建了一个自定义的nodeJS构建包,允许您指定在dynos第一次设置时由ssh-agent注册并由npm使用的SSH密钥。它可以无缝地允许您在package.json中指定您的模块作为ssh url,如下所示:

"private_module": "git+ssh://git@github.com:me/my_module.git"

为了设置您的应用程序使用私钥,请按照以下步骤操作:
  • 生成密钥: ssh-keygen -t rsa -C "your_email@example.com" (不输入密码。该 buildpack 不支持带有密码的密钥)
  • 将公钥添加到 GitHub 中:pbcopy < ~/.ssh/id_rsa.pub (在 OS X 中),并将结果粘贴到 GitHub 管理员中
  • 将私钥添加到 heroku 应用程序的配置中:cat id_rsa | base64 | pbcopy,然后 heroku config:set GIT_SSH_KEY=<paste_here> --app your-app-name
  • 按照包含在项目中的 heroku nodeJS buildpack README 中所述设置应用程序以使用 buildpack。简而言之,最简单的方法是使用 heroku config:set 设置特殊的配置值,以便将所需 buildpack 的 github url 与其关联。我建议分叉我的版本并链接到您自己的 Github 分叉版本,因为我不能保证不更改我的 buildpack。

我的定制 buildpack 可在此处找到:https://github.com/thirdiron/heroku-buildpack-nodejs,它适用于我的系统。欢迎评论和拉取请求。


6

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

  1. Add the buildpack to your app:

    $ heroku buildpacks:add --index 1 https://github.com/simon0191/custom-ssh-key-buildpack
    
  2. Generate a new SSH key without passphrase (lets say you named it deploy_key)

  3. Add the public key to your private repository account. For example:

  4. Encode the private key as a base64 string and add it as the CUSTOM_SSH_KEY environment variable of the heroku app.

  5. Make a comma separated list of the hosts for which the ssh key should be used and add it as the CUSTOM_SSH_KEY_HOSTS environment variable of the heroku app.

    # 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. Deploy your app and enjoy :)

1
脚本的输入部分应该是 --input ~/.ssh/deploy_key。你在脚本中解码了私钥的base64,请纠正一下。 - gmuraleekrishna
@gmuraleekrishna 我发现使用 --input 标志取决于您使用的 base64 版本。例如,在 macOS 中需要它,但在 Ubuntu 14.04 中该标志无法识别。 - Simon Soriano
@gmuraleekrishna 感谢您指出这一点,您是正确的。我已经更正了答案和存储库的README。 - Simon Soriano
我只有在私钥没有密码的情况下才能使其工作... 这是预期的吗? - roboli
1
@roboli,是的,ssh密钥中的口令短语通常不用于自动化,因为要解密私钥,您需要手动输入口令短语,这在此情况下行不通,因为Heroku会自动运行构建包脚本,或者您需要向自动化工具(在这种情况下是构建包)提供口令短语,这将使口令短语失去其添加额外安全层的目的。 感谢您的反馈,我更新了答案以澄清必须在生成SSH密钥时不使用口令短语。 - Simon Soriano
显示剩余4条评论

3

我能够通过个人访问令牌,在Heroku构建中设置解析Github私有存储库。

  • Generate Github access token here: https://github.com/settings/tokens
  • Set access token as Heroku config var: heroku config:set GITHUB_TOKEN=<paste_here> --app your-app-name or via Heroku Dashboard
  • Add heroku-prebuild.sh script:

    #!/bin/bash
    if [ "$GITHUB_TOKEN" != "" ]; then
        echo "Detected GITHUB_TOKEN. Setting git config to use the security token" >&1
        git config --global url."https://${GITHUB_TOKEN}@github.com/".insteadOf git@github.com:
    fi
    
  • add the prebuild script to package.json:

    "scripts": {
        "heroku-prebuild": "bash heroku-prebuild.sh"
    }
    

对于本地环境,我们也可以使用git config ...或者将访问令牌添加到~/.netrc文件中:

machine github.com
  login PASTE_GITHUB_USERNAME_HERE
  password PASTE_GITHUB_TOKEN_HERE

你可以安装私有GitHub仓库。

npm install OWNER/REPO --save将出现在package.json中: "REPO": "github:OWNER/REPO"

同时,在Heroku构建中解析私有仓库也是有效的。你可以选择设置一个后置脚本来取消GITHUB_TOKEN


2

这个答案很好(原文链接),但我稍微更改了一下预安装脚本。希望这能对某些人有所帮助。

#!/bin/bash
# Generates an SSH config file for connections if a config var exists.

echo "Preinstall"

if [ "$GIT_SSH_KEY" != "" ]; then
  echo "Detected SSH key for git. Adding SSH config" >&1
  echo "" >&1

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

  # Load the private key into a file.
  echo $GIT_SSH_KEY | base64 --decode > ~/.ssh/deploy_key

  # Change the permissions on the file to
  # be read-only for this user.
  chmod o-w ~/
  chmod 700 ~/.ssh
  chmod 600 ~/.ssh/deploy_key

  # Setup the ssh config file.
  echo -e "Host bitbucket.org\n"\
          " IdentityFile ~/.ssh/deploy_key\n"\
          " HostName bitbucket.org\n" \
          " IdentitiesOnly yes\n"\
          " UserKnownHostsFile=/dev/null\n"\
          " StrictHostKeyChecking no"\
          > ~/.ssh/config

  echo "eval `ssh-agent -s`"
  eval `ssh-agent -s`

  echo "ssh-add -l"
  ssh-add -l

  echo "ssh-add ~/.ssh/deploy_key"
  ssh-add ~/.ssh/deploy_key

  # uncomment to check that everything works just fine
  # ssh -v git@bitbucket.org
fi

-2

您可以在 package.json 中使用以下身份验证示例的私有存储库:

https://usernamegit:passwordgit@github.com/reponame/web/tarball/branchname

1
在你的代码库和 package.json 文件中存储你的凭据是一个糟糕的想法。 - psyrendust

-4
简而言之,这是不可能的。我想到的解决这个问题的最佳方案是使用新的git subtree。在撰写本文时,它们还没有包含在官方的Git源代码中,因此需要手动安装,但它们将在v1.7.11中包含。目前它可以在Homebrew和apt-get上使用。然后只需要执行以下操作即可:
git subtree add -P /node_modules/someprivatemodue git@github.......someprivatemodule {master|tag|commit}

这会增加仓库的大小,但通过使用gitsubtree pull命令进行更新非常容易。


-6

我以前用过来自Github的模块完成了这个。Npm目前接受包的名称或包含该包的tar.gz文件的链接。

例如,如果您想直接从Github使用express.js(通过下载部分获取链接),您可以执行以下操作:

"dependencies" : {
  "express"   :  "https://github.com/visionmedia/express/tarball/2.5.9"
}

所以你需要找到一种通过http(s)访问你的代码库的tar.gz文件的方法。


谢谢,但这并不能解决仓库对世界的私有问题。 - henry.oswald
如先前所述,无论是使用 npm 还是 tar.gz,在将模块导出为存档文件方面都可能有某种方法。如果没有,总可以使用 git 子模块来实现。 - TheHippo
但是因为该模块是私有的,并且需要 SSH 访问,所以一旦 Heroku 尝试获取它,无论代码如何传输,都将被拒绝。 - henry.oswald
使用cronjob、post commit hook或其他方式将tag.gz转储到可访问的位置。(基本的http身份验证可以保护它免受其他人的干扰。)如前所述,据我所知,没有什么神奇的方法可以通过在package.json中编写一些简单的内容来解决你的问题。你需要在这里更有创意一些。 - TheHippo
@TheHippo 兄弟,把这个删掉就行了。 - Iman Mohamadi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接