逆向工程:揭示Google Colab未公开的秘密

语言: CN / TW / HK

 

来源|Open Source Data Science Tools

翻译|程浩源

 

Google Colaboratory,简称 “Colab” ,是一个免费的Jupyter notebook云平台。Colab 不仅可以为用户提供 Python 和 R notebooks 的运行环境,而且还允许用户免费共享部分 GPU 和 TPU 资源。

 

对于负责在 Jupyter Notebook 编程的数据科学家来说,Colab早已成为了默认的运行环境。然而,将 Colab 的算力运用到除 Jupter Notebooks 以外的其他应用,则是一件极其困难的事。

 

对于那些想生产模型,并将其带出Notebook阶段的机器学习工程师而言,这样的问题尤为明显。虽然 Notebooks 非常适合用来探索,但将它与训练过程编入正式流水线的高级MLOps工具一起使用时,效果不佳。

 

在遇到类似问题后,我决定不让 Colab 的局限性改变我的工作流程,而是尝试围绕我的工作流程去改变 Colab!

 

出于这个原因,今天我们将探究 Google Colab 的内部结构,并尝试稍微改变 Colab 的内置规则。需要提前声明的是,我们只是想探究 Colab,不会对 Colab 本身或者它的用户造成任何影响。

 

 

 

1

揭开幕后的秘密

 

Colab 的秘密在于它的后端:谷歌服务器为 Colab 提供基础设施支持,让用户可以轻松运行代码。因此,我们第一步先分析 Colab 的 API,最简单的方法是检查 Colab 在正常运行期间进行的 API 调用。

 

首先打开谷歌开发者工具,找到网络(Network)选项,然后运行一段代码,开发者工具开始记录 Colab 发出的每个请求,然后我们发现了一些有趣的东西。

 

 

看上去这个URL(/tun/m/<id>/socket.io)是远程机器上运行的 Jupyter socket 的代理。

 

如果我们从 Colab 界面的左窗格打开 Files 窗格(默认显示 /content 目录),就会发现另一个有趣的请求:

 

 

这次 JSON 枚举远程主机上的文件做出了响应。这个URL(/tun/m/<id>/api/contents/)似乎指向提供文件元数据的服务。

 

 

双击 Files 窗格里的文件,Colab 就会开始下载文件并且展示文件详细信息。如果单击 /content/sample_data/README.md,则会对 /tun/m/<id>/files/ 发出请求,返回该文件的内容。

 

 

很明显,https://colab.research.google.com/tun/m/<id>/ 是运行 Colab 实例服务器的反向代理,它提供了 /socket.io 、 /files 和 /api/contents 端点。

 

让我们看看是否有任何服务在 Colab 容器实例内运行。Colab 中内置有 lsof 程序,运行 lsof -iTCP -sTCP:LISTEN,列出所有在 TCP 端口上监听网络请求的进程。

 

 

看!colab-fileshim、node 和 jupyter-notebook 看起来都值得一探究竟。由于我们已经使用过Files窗格,所以先看看 colab-fileshim,它有 PID 28,因此检查 /proc 文件系统,查看进程的完整命令行:

 

 

接下来研究 /usr/local/bin/colab-fileshim.py。讽刺的是,我们其实可以直接在 Files 窗格做到这一点。这个程序更像是一个无趣的文件服务器,除了服务器本身可以响应 localhost:3453/files (带有文件内容)和 localhost:3453/api/contents (带有 JSON 元数据)。这意味着 Colab 会将这些请求从信道 URL 转发到实例本身的端口3453。

 

在谷歌开发者工具的网络选项中,我们可以右键单击,复制 cURL 命令来重现它。例如,这是用于查看 README.md 文件的 cURL 命令:

 

 

如果在本地计算机终端上运行此命令,会将该 README 文件的内容打印到我们的终端,通过不断尝试和纠错,我们可以减少大部分标头,只留下如下内容:

 

 

x-colab-tunnel 标头表面上是为了防止 XSS 攻击,实际上是为了防止我们或黑客从常规浏览器选项发出这些请求。

 

cookie 标头用于向 Google 提供身份验证,证明我们可以访问笔记本实例。由于 cookie 比较长且难以处理,在本文的其余部分我们将其存储到 shell 变量 $COLAB_COOKIE 中。

 

 

 

2

 

胜利1:揭示我们的服务器

 

现在,我们已经发现了 Colab 的反向代理,看看是否可以用它为传输我们自己的请求。

 

我们可以简单地用自己的服务器替换进程,而不会影响现有的 colab-fileshim.py 服务器!运行 pkill -f colab-fileshim 来终止现有服务器,这样就可以在同一个端口上启动我们自己的服务器。

 

下面做一个简短的演示,我们将启动 Python 默认的 HTTP 服务器,然后在 localhost:3453/files 中提供我们自己的文件。

 

 

瞧!我们现在可以更改 cURL 命令来下载我们自己的文件!

 

 

注意 Colab 单元中的日志行,可以证明我们的服务器处理了请求:

 

 

遗憾的是,由于需要 x-colab-tunnel: Google 标头,所以我们无法从浏览器直接访问服务器。

 

3

 

进一步研究 

 

继续进行研究,这次看一下之前发现的另一个有趣的东西,node。如果我们检查 /proc/7/cmdline,会看到进程正在运行 /datalab/web/app.js。

 

一旦我们跳转并阅读该代码,会发现 /datalab/web 包含一个相当标准的NodeJS应用程序。除了之前看到的 /socketio/ 路由,它还公开了 /_proxy/{port}/ 路由。这应该让我们可以从 Colab 实例上的任何端口访问任何 URL!

 

启动一个快速服务器并测试一下。

 

 

可惜我们并不能从浏览器选项中查看这个 HTML 页面,Colab 拒绝代理任何请求,除非设置了 x-colab-tunnel: Google 标头,如果我们尝试从浏览器访问这些URL,会看到一个 HTTP 400 客户端错误页面:

 

 

 

 4

 

胜利2:揭示整个网页

 

幸运的是,我们可以使用谷歌浏览器扩展程序将 HTTP 标头即时插入浏览器请求中。我们将其设置为在所有请求上发送 x-colab-tunnel: Google 标头:

 

 

然后我们可以在浏览器中启动信道 URL!

 

 

 

5

 

前往Jupyter Notebook

 

最后,让我们看看第三个,也是最后一个有趣的进程,jupyter-notebook,它监听端口 9000。

 

我们可以通过访问 /tun/m/<id>/_proxy/9000 来使用之前的代理和标头,尝试从浏览器直接访问端口。遗憾的是,出现了 HTTP 500 服务器错误页面,而不是 Jupyter 用户界面。

 

奇怪的是,当我们从 Colab notebook 本身运行 !curl -i localhost:9000 来诊断这个问题时,仍然报错了:

 

 

之前 lsof 的输出为我们提供了一个线索:Jupyter 只监听提供给 Colab 实例的私有 IP,而不是监听 0.0.0.0/:: (所有接口上的所有 IP),这大概是为了避免将 Jupyter 接口暴露给我们。

 

谷歌并没有尽全力隐藏接口,因此有一个快速修复的方法。

 

为了绕过监听地址的限制,我们需要创建一个进程来监听所有接口和 IP,并将它获得的所有流量转发到 Jupyter 正在监听的特定 IP 地址。我们可以安装socket代理工具 socat(Socket Cat) 来做到这一点。使用 socat 将流量在 localhost:9000 和 $HOSTNAME:9000 之间来回转发:

 

 

这是一个开始!如果我们在浏览器中重新加载 URL,我们会看到部分 Jupyter 用户界面,但显然出现了问题。

 

 

这是因为 Jupyter 设定在域的根目录下访问(URL路径 /),但我们的 Colab 信道的路径是 /tun/m/<id>/_proxy/9000,这会弄乱 CSS 和 JS 文件等资源的绝对路径。

 

目前还没有简单的解决方案,我们需要一个完整的(子)域来将流量转发到Jupyter服务器。

 

6

 

胜利3:显示Jupyter用户界面

 

万幸,Colab 有一个隐蔽的端口转发的官方解决方案,它提供了一个完整的子域!它隐藏得非常好,发现它比发现内部反向代理花费了更长的时间!

 

如何使用 Colab 的官方端口转发流量?从左侧边栏中打开 Code Snippets 选项,然后找到 Output Handling 代码段。

 

单击“查看Source Notebook”,将会看到advanced_outputs.ipynb,这是Colab 高级用户片段,展示了该平台鲜为人知的功能。我们需要的特定片段可以在“浏览内核上执行的服务器”标题下找到。

 

我们可以使用此代码段将 Jupyter 用户界面公开为子域。

 

 

现在,我们可以单击该链接(将 /tree 附加到 URL 来稳住 Jupyter),然后就可以查看功能齐全的 Jupyter UI!

 

 

终于,几乎完成了所有工作。谷歌似乎已将官方代理限制为仅GET请求,只允许我们查看但不能运行Notebooks。

 

7

 

结语

 

恭喜你看到了最后,希望这对展示你不了解的Colab相关工作原理以及学习逆向工程工具的半结构化方法会有价值。也希望能激发你更深入地了解自己每天使用的工具和产品的内部结构!

 

(本文经授权后编译发布。原文:

https://dagshub.com/blog/reverse-engineering-google-colab/)

 

头图源自wir_sind_klein, Pixabay

 

其他人都在看

欢迎体验OneFlow v0.8.0:https://github.com/Oneflow-Inc/oneflow/

 


本文分享自微信公众号 - OneFlow(OneFlowTechnology)。
如有侵权,请联系 [email protected] 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。