来自 Heroku 的 SSH 隧道

Posted

技术标签:

【中文标题】来自 Heroku 的 SSH 隧道【英文标题】:SSH tunneling from Heroku 【发布时间】:2014-03-01 18:51:16 【问题描述】:

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

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

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

谢谢你, 阿德里安

【问题讨论】:

为什么需要 ssh 隧道?如果他们担心“以明文方式”传输数据,只需让您的数据库通过 ssl 连接即可。所有数据库(嗯,主要数据库)都支持。 感谢@NitzanShaked,1. 客户对不会泄露任何数据感到更加自信,2. 它允许绕过一些防火墙,3. 使用 Oracle,据我所知,很难确定我们是总是使用加密的,4。没有隧道,互联网上的任何其他人都可以尝试暴力连接,而有了隧道,它必须来自我。 你提到的所有理由都是正确的,毫无疑问。我的观点是,使用 ssl 连接字符串(对于 Oracle,对于 mysql,等等)同样可以解决问题。我不确定甲骨文有什么“困难”,但我相信它们是可以克服的。如果您绝对设置使用隧道,那么当然可以这样做 - 只需运行 ssh -L ::,其中 是您客户端的数据库服务器的名称 从您正在连接的机器上看到,并让您的代码连接到 localhost:. 您可以将其转化为真正的答案。所以 ssh -L 将在 Heroku 机器上打开一个新端口(仅限 127.0.0.1 访问),是否允许?似乎不是,如果您在该页面上搜索“回送”一词:heroku.com/policy/security - 我是否理解该页面? 【参考方案1】:

是的,你可以。

现在已经走上了这条路:是的,可以设置从 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 隧道访问远程数据库。上面的配方做了很多假设,但是通过一些自定义,它应该可以满足您的特定需求。

现在我要去睡觉了……

【讨论】:

谢谢,你的食谱对我有用!不过,您在脚本中有一个小错字:s/REMOTE_USER/TUNNEL_USER/s/REMOTE_SITE/TUNNEL_SITE/。您还可以提及 autossh 以获得更可靠的 ssh 隧道。 这太好了,谢谢!两个可能的改进: 1) 对 mysql 使用 ssh 隧道的一个问题是它们经常掉线,使用 autossh 会自动重新连接。已经有一个heroku build pack 了。 2) 您可以使用ssh -o StrictHostKeyChecking=no 解决已知主机问题 很棒的答案,谢谢!它启发我写了一份完整而详细的指南——专门针对在 Heroku 上运行 Metabase(一个很棒的免费 BI 报告应用程序),但任何人都可以轻松地按照自己的应用程序需求进行操作:github.com/rmcsharry/metabase-deploy/blob/master/… @PatrickBrowne 我正在尝试使用它连接到 AWS EC2 实例,但在连接到 localhost: 时遇到问题,我认为这与 Heroku 的政策有关环回网络接口(即,没有连接到本地主机,如果我理解正确的话)。你是怎么解决这个问题的? 不错的答案,您可以通过使用 ssh-keyscan 并将结果放入 known_hosts 文件中来避免用户交互,如ssh-keyscan -t rsa &lt;REMOTE_MYSQL_HOST&gt; &gt; ~/.ssh/known_hosts

以上是关于来自 Heroku 的 SSH 隧道的主要内容,如果未能解决你的问题,请参考以下文章

apt-get 通过隧道代理安装,但 ssh 仅来自客户端

用于 sftp 或 GUI 的 ssh 隧道

如何为 ipython 集群(ipcluster)设置 ssh 隧道

通过 SSH 隧道访问 Ezproxy

使用带有公钥和密码的 SSH 隧道通过 SQLAlchemy 连接到远程 PostgreSQL 数据库,所有这些都来自 Windows 机器

通过vscode的SSH隧道打开一个Matplotlib图