来自Heroku的SSH隧道

时间:2014-02-05 11:10:54

标签: heroku ssh ssh-tunnel

我正在提供一个托管在Heroku上的服务,它允许用户使用他们的数据库报告他们自己的数据。我的客户必须将我的Heroku应用程序连接到他们的数据库。其中一些人显然害怕让数据通过互联网明确传输。

Heroku是否可以从我的应用程序(Play Framework / Java)打开SSH隧道到他们的机器?

注意:我知道SSH tunneling to a remote DB from Heroku?但是在这个问题上,使用内置的Heroku数据库是可能的。

谢谢你, 阿德里安

1 个答案:

答案 0 :(得分:59)

是的,你可以。

现在已经走了这条道路:是的, 可以设置从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。生成用于访问外部数据库的密钥对

创建密钥对并将其保存在〜/ .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。设置配置变量

目标是将〜/ .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)?

这是一个问题,因为这意味着dyno需要用户输入才能继续。但我们正要解决这个问题。接下来是一个有点丑陋的黑客,但它的确有效。 (如果有人有更好的解决方案,请发表评论!)

6。创建脚本文件的2.0版

(从上面继续)对提示回答yes并让脚本运行完成。我们现在要捕获known_hosts文件的输出:

heroku $ cat ~/.ssh/known_hosts
|1|longstringofstuff= ecdsa-sha2-nistp256 more stuff=
|1|morestuff= ecdsa-sha2-nistp256 yetmorestuff=

复制该输出并将其粘贴到&#34;预加载known_hosts&#34;下的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隧道访问远程数据库。上面的配方做了很多假设,但是通过一些自定义,它可以满足您的特定需求。

现在我要睡一觉......