是的,你可以。
现在已经走上了这条路:是的,是可以设置从 heroku 到外部数据库的 SSH 隧道。 [注意:我的特定应用程序是用 Ruby on Rails 编写的,但这里给出的解决方案应该适用于 Heroku 上托管的任何语言。]
问题陈述
我正在 Heroku 上运行一个应用程序。该应用程序需要访问外部 MySQL 数据库(托管在 AWS 上),从中获取数据进行分析。对 MySQL 数据库的访问受 ssh 密钥保护,即您无法使用密码访问它:您需要一个 ssh 密钥对。由于 Heroku 会重新启动每个测功机,如何使用正确的凭据设置 SSH 隧道?
简答
创建一个脚本文件,比如 ssh_setup.sh。把它放在 ${HOME}/.profile.d/ssh_setup.sh 中。 Heroku 会注意到 ${HOME}/.profile.d 中的任何文件,并在创建您的测功机时执行它。使用脚本文件设置 ~/.ssh/id_rsa 和 ~/.ssh/id_rsa.pub,然后以隧道模式启动 ssh。
完整配方
1。生成用于访问外部数据库的密钥对
创建一个密钥对并将其保存在 ~/.ssh/heroku_id_rsa 和 ~/.ssh/heroku_id_rsa.pub 中。使用空密码(否则 Heroku dyno 会在启动时尝试提示输入):
$ ssh-keygen -t rsa -C "me@example.com"
Generating public/private rsa key pair.
Enter file in which to save the key (/home/.ssh/id_rsa): /home/.ssh/heroku_id_rsa
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/.ssh/heroku_id_rsa.
Your public key has been saved in /home/.ssh/heroku_id_rsa.pub.
2。测试 ssh 访问外部数据库
将您的 PUBLIC 密钥 (~/.ssh/heroku_id_rsa.pub) 发送给外部数据库的管理员,并请求使用该密钥进行访问。之后,您应该可以在本地计算机上的 shell 窗口中键入以下内容:
$ ssh -v -i ~/.ssh/heroku_id_rsa -N -L 3307:${REMOTE_MYSQL_HOST}:3306 ${TUNNEL_USER}@${TUNNEL_SITE}
在哪里
- ${REMOTE_MYSQL_HOST} 是远程数据库的地址。在我们的例子中,它类似于 long_complicated_string.us-west-2.rds.amazonaws.com
- ${TUNNEL_USER} 是访问数据库的站点上的用户帐户
- ${TUNNEL_SITE}是访问数据库的机器地址
您应该得到一长串调试输出,包括以下内容:
debug1: Authentication succeeded (publickey).
...
debug1: forking to background
debug1: Entering interactive session.
恭喜。您已经在自己的机器上设置了到外部数据库的隧道。现在说服 Heroku 也这样做......
3。设置配置变量
目标是在 Heroku dyno 启动时将 ~/.ssh/heroku_id_rsa 和 ~/.ssh/heroku_id_rsa.pub 的内容复制到 Heroku dyno 上的相应目录,但你真的不想暴露你的私人键入脚本文件。
相反,我们将使用 Heroku 的配置变量,它在启动 dyno 时简单(且安全)设置 shell 环境变量。
$ heroku config:set HEROKU_PRIVATE_KEY=`cat ~/.ssh/heroku_rsa_id`
$ heroku config:set HEROKU_PUBLIC_KEY=`cat ~/.ssh/heroku_rsa_id.pub`
在此过程中,我们还将设置一些其他可能敏感的变量:
$ heroku config:set REMOTE_MYSQL_HOST=<your value of REMOTE_MYSQL_HOST from above>
$ heroku config:set TUNNEL_USER=<your value of TUNNEL_USER from above>
$ heroku config:set TUNNEL_SITE=<your value of TUNNEL_SITE from above>
4。创建脚本文件的 1.0 版
在您的项目主目录中,创建一个目录 .profile.d。在该目录中,创建以下内容:
# file: .profile.d/ssh-setup.sh
#!/bin/bash
echo $0: creating public and private key files
# Create the .ssh directory
mkdir -p ${HOME}/.ssh
chmod 700 ${HOME}/.ssh
# Create the public and private key files from the environment variables.
echo "${HEROKU_PUBLIC_KEY}" > ${HOME}/.ssh/heroku_id_rsa.pub
chmod 644 ${HOME}/.ssh/heroku_id_rsa.pub
# Note use of double quotes, required to preserve newlines
echo "${HEROKU_PRIVATE_KEY}" > ${HOME}/.ssh/heroku_id_rsa
chmod 600 ${HOME}/.ssh/heroku_id_rsa
# Preload the known_hosts file (see "version 2" below)
# Start the SSH tunnel if not already running
SSH_CMD="ssh -f -i ${HOME}/.ssh/heroku_id_rsa -N -L 3307:${REMOTE_MYSQL_HOST}:3306 ${REMOTE_USER}@${REMOTE_SITE}"
PID=`pgrep -f "${SSH_CMD}"`
if [ $PID ] ; then
echo $0: tunnel already running on ${PID}
else
echo $0 launching tunnel
$SSH_CMD
fi
5。推送配置并在 Heroku 上测试
你知道演习...
$ git add .
$ git commit -m 'launching ssh when Heroku dyno starts up'
$ git push heroku master
试一试……
$ heroku run sh
您可能会看到如下内容:
Running `sh` attached to terminal... up, run.1926
bash: creating public and private key files
bash: launching tunnel
The authenticity of host 'example.com (11.22.33.44)' can't be established.
ECDSA key fingerprint is 1f:aa:bb:cc:dd:ee:ff:11:22:33:44:55:66:77:88:99.
Are you sure you want to continue connecting (yes/no)?
这是一个问题,因为这意味着测功机需要用户输入才能继续。但我们即将解决这个问题。接下来是一个有点丑陋的黑客,但它有效。 (如果有人有更好的解决方案,请发表评论!)
6。创建脚本文件的 2.0 版本
(从上面继续)回答yes 提示并让脚本运行完成。我们现在要捕获 known_hosts 文件的输出:
heroku $ cat ~/.ssh/known_hosts
|1|longstringofstuff= ecdsa-sha2-nistp256 more stuff=
|1|morestuff= ecdsa-sha2-nistp256 yetmorestuff=
复制该输出并将其粘贴到您的 ssh-setup.sh 文件中的“Preload the known_hosts”注释下,然后进行编辑,使其看起来像这样:
# Preload the known_hosts file (see "version 2" below)
echo '|1|longstringofstuff= ecdsa-sha2-nistp256 more stuff=
|1|morestuff= ecdsa-sha2-nistp256 yetmorestuff=' > ${HOME}/.ssh/known_hosts
# Start the SSH tunnel if not already running
... etc ...
7。推送和测试 v2
你知道演习...
$ git add .
$ git commit -m 'preload known_hosts file to avoid prompt'
$ git push heroku master
试一试。运气好的话,您应该会看到如下内容:
$ heroku run sh
Running `sh` attached to terminal... up, run.1926
bash: creating public and private key files
bash: launching tunnel
8。调试
如果隧道未正确设置,请尝试在脚本文件中为 SSH 命令预先添加 -v(详细)参数:
SSH_CMD="ssh -v -f -i ${HOME}/.ssh/heroku_id_rsa -N -L ${LOCAL_PORT}:${REMOTE_MYSQL_HOST}:${MYSQL_PORT} ${REMOTE_USER}@${REMOTE_SITE}"
重复git add ... git commit ... git push 序列并调用heroku run sh。它将打印大量调试输出。比我有更多头脑的系统管理员朋友应该能够解码该输出以告诉您问题出在哪里。
9。 (仅限 Rails):配置数据库
如果您正在运行 Rails,您将需要一种在 Rails 应用程序中访问数据库的方法,对吗?将以下内容添加到您的 config/database.yml 文件中(更改相应的名称):
mysql_legacy:
adapter: mysql2
database: mysql_legacy
username: <%= ENV['LEGACY_DB_USERNAME'] || 'root' %>
password: <%= ENV['LEGACY_DB_PASSWORD'] || '' %>
host: 127.0.0.1
port: 3307
需要注意的重要一点是主机是本地主机(127.0.0.1)并且端口(3307)必须匹配脚本中给ssh的-L参数:
-L 3307:${REMOTE_MYSQL_HOST}:3306
总结
尽管在其他地方已经说过,您可以通过 Heroku 隧道访问远程数据库。上面的配方做了很多假设,但是通过一些自定义,它应该可以满足您的特定需求。
现在我要去睡觉了……