是的你可以。
现在已经走上了这条路:是的,它is可以设置从 heroku 到外部数据库的 SSH 隧道。 [注意:我的特定应用程序是用 Ruby on Rails 编写的,但此处给出的解决方案应该适用于 Heroku 上托管的任何语言。]
问题陈述
我正在 Heroku 上运行一个应用程序。该应用程序需要访问外部 MySQL 数据库(托管在 AWS 上),从中获取数据进行分析。对 MySQL 数据库的访问受 ssh 密钥保护,即您无法使用密码访问它:您需要 ssh 密钥对。由于 Heroku 重新启动每个 dyno,如何使用正确的凭据设置 SSH 隧道?
简答
创建一个脚本文件,例如 ssh_setup.sh。将其放入 ${HOME}/.profile.d/ssh_setup.sh 中。 Heroku 会注意到 ${HOME}/.profile.d 中的任何文件,并在创建 dyno 时执行它。使用脚本文件设置 ~/.ssh/id_rsa 和 ~/.ssh/id_rsa.pub,然后以隧道模式启动 ssh。
完整的食谱
1. 生成访问外部DB的密钥对
创建密钥对并将其保存在 ~/.ssh/heroku_id_rsa 和 ~/.ssh/heroku_id_rsa.pub 中。使用空密码(否则 Heroku dyno 将在启动时尝试提示输入):
$ ssh-keygen -t rsa -C "[email protected] /cdn-cgi/l/email-protection"
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访问外部DB
将您的公钥 (~/.ssh/heroku_id_rsa.pub) 发送给外部数据库的管理员,并请求使用该密钥进行访问。之后,您应该能够在本地计算机上的 shell 窗口中输入以下内容:
$ ssh -v -i ~/.ssh/heroku_id_rsa -N -L 3307:${REMOTE_MYSQL_HOST}:3306 ${TUNNEL_USER}@${TUNNEL_SITE}
where
- ${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=
复制该输出并将其粘贴到“Preload theknown_hosts”注释下的 ssh-setup.sh 文件中,然后进行编辑,使其如下所示:
# 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 中通过隧道访问远程数据库。上面的配方做了很多假设,但通过一些自定义,它应该可以满足您的特定需求。
现在我要去睡觉了...