大家下午好。我叫内森·克拉克,使用 JMP 已经 22 年了,我来自 IDEXX 实验室。我将与我的搭档萨奇·达林一起为大家讲解密钥管理。
大家好,我是Sage,我和Nathan都在IDEXX工作。我在这里是一名高级分析工程师,今天我们要和大家聊聊一些秘密。
太好了。我们先来简单聊聊什么是密钥?通常来说,至少从代码的角度来看,密钥是一种凭证,是你的程序运行所必需的,这样你才能访问某些东西,但同时,其他人不应该知道它。它和你登录电脑的密码非常相似。你需要经常使用它,但你不应该把它告诉其他人。
密钥是不同平台开发的一种机制,允许凭证以一种一旦发布就无法被查看的方式存在。因此,在后台任务中使用它们会更加安全。JMP 本身并不具备这种功能。但是,随着我们在 JSL 中越来越多地使用代码和后台功能,我们希望能够拥有这种能力,以便与他人分享我们的工作成果,访问不同的资源,同时避免意外地将关键信息泄露给任何人。
不久前,我和 JMP 支持团队讨论过,他们给了我一小段代码,我相信是 Evan McCorkle 写的。这段代码可以掩盖这些秘密,使其像我们在这里看到的那样存在,但你可以加密脚本并使用它。
它的基本原理是使用一个包含密钥的函数,但需要一个辅助密钥或访问短语(你知道它的存在),加密代码中的所有函数都包含这个辅助密钥或访问短语,但其他人无法知道它的具体内容。即使他们知道使用密钥 A 是他们想要调用的函数,或者类似的信息,他们也无法在不知道这个访问短语的情况下直接访问这些密钥。加密之后,你可以将其作为秘密脚本包含在内,这样你就可以在不知情的情况下获取这些信息。到目前为止,我与 JMP 支持团队沟通时,尽管他们尝试过,但在测试中仍然找不到暴露密钥的方法。我自己也在尝试,但同样没有成功。这似乎是一个非常不错的框架,可以用来构建你想要的密钥安全存储方式,同时又能允许用户利用这些密钥执行各种代码操作。
今天我们有两个例子。我将以 Snowflake 为例,讲解我们如何实现这个功能。我这里有一个 Snowflake 连接模板。这基本上就是我编写的函数,其中所有重要的部分都隐藏起来了。
我在这里稍微放大一下。你看,我有一个 `get_secrets` 函数,它存储着密钥。就我目前的需求而言,我只需要用户名和密码就能登录 Snowflake。这是一个相对简单的密码数组。
但我还有其他辅助功能可以利用它们。第一个功能会创建一个与数据库的实际连接,并返回新的数据连接器。这是一个需要你组合起来的字符串。它包含了数据连接器,并连接到所有特定于你公司的组件。你只需要安装 Snowflake 驱动程序。这是你从 Snowflake 网站安装到计算机上的 Snowflake 驱动程序的名称。这样你就可以建立 ODBC 连接。你的数据库是特定于你公司的。但是,你的公司服务器连接到 Snowflake,等等。然后,它会从我们在这里获取的另一个数组中获取密码。它会将密码放入其中,然后你就可以连接其他组件了。
我提供的最后一个函数更进一步。它接收你要运行的查询字符串,并在此函数中完成所有连接,然后运行查询。最后,它会清除所有与该表相关的表脚本。我会在示例中解释它的用途。但简而言之,它提供了另一层保护,防止凭据意外共享或泄露。
接下来,我举一个非常简单的例子。在第一个例子中,它基本上就是执行一个原始查询,我编写了自定义 SQL,并插入了查询语句的字符串版本。运行这段代码后,我会从 Snowflake 获取一个表。它看起来很像 Big Class,因为它就是。我只是把它的副本上传到了 Snowflake。这样就能得到你想要的表。它包含了一些源信息。这很有用。我注意到,这样做会屏蔽用户名和密码,这也在我的预期之内。不过,我在尝试各种方法时也注意到,有几个例子(不管是不是偶然现象)密码没有泄露,但用户 ID 却泄露了。根据你对这些凭据的监控程度,我编写了一个额外的函数来移除这些信息。
如果我注释掉这部分代码,现在运行它,它会创建一个新表,并使用另一个函数来执行查询,结果是一样的。只不过现在这个表里没有任何表脚本了。这很好。为了更详细地解释它的工作原理,例如,如果我想删除这个表,只选择星形图及其所有列,现在应该可以正常工作,并将它们全部提取出来。如果想添加一些筛选功能,也可以在这里操作。它的工作方式与标准 SQL 语言执行这些查询的方式完全相同。你只需将字符串传递给它,就能得到清理后的数据表。这是一个相对简单的例子。现在我想把这个问题交给 Sage,他们有一个更复杂的版本,可以结合你的函数分层来实现。我现在就交给 Sage 了。
FoxDrive 是一个在线文件夹,你可以在这里保存信息。我们用它来做很多不同的事情,所以我首先想解决的是如何通过 Box 进行身份验证,以便将文件从 Box 拉取到我的本地计算机。这需要通过 Box Developers 构建一个应用程序来进行身份验证。然后,这个应用程序需要提供一个叫做 JWT(JSON Web Token)的东西,这个 JWT 可以用来获取访问令牌,然后我才能使用它来访问 Box。我的情况有点复杂,因为我以前从未接触过 JSON 或 JWT,所以最终我不得不自己实现它。
正如 Nathan 所描述的,右侧是我的 secrets.jsl 文件示例。目前它尚未加密,因此您可以查看其内部结构。我的文件也以一个获取密钥的函数开头。这里存放着我的所有 JWT 信息。JWT 包含客户端 ID、客户端密钥等应用程序特有的信息。我将这些信息放入一个关联数组中,以便提取密钥。然后,为了访问这些信息,我编写了其他函数,这些函数从客户端 ID 开始,依次访问关联数组中的各个部分。
我只展示其中一个例子,因为如果全部展示,你就会看到关联数组的所有组成部分,那可能会很枯燥。这是一个获取客户端 ID 的例子。在这个例子中,我使用密钥来获取另一个密钥,也就是我的 Box 访问密钥,然后通过这个密钥获取客户端 ID。再次强调,为了确保所有信息都经过加密,以便从同一个函数中提取,所有操作都严格保密。
我需要补充一点,这与获取 Box JWT 令牌有关。这需要一些技巧,因为 JWT 需要断言才能获取新的访问令牌,而访问令牌又需要签名组件。由于签名组件本身就是敏感信息,所以它被视为敏感信息。我的 secrets.jsl 文件包含一些函数,用于进一步处理从 Box 获取这些信息。在这个例子中,我的 JWT 函数……我把它封装起来,以便于理解。我的 JWT 函数会向 Box 创建断言,以便获取获取访问令牌所需的数据。为此,我必须获取上面 secrets 文件中包含的显式信息,包括我的 Box 企业 ID、Box 客户端 ID、Box 公钥、私钥以及我的应用程序独有的密钥。
接下来,我找到了一些故障排除方面的资料,比如密钥缺失时需要注意的事项,以及真正重要的部分。为了进行 JWT 断言,我实际上必须使用 Python 来实现。这还包括另一个在 secrets.jsl 中使用其他语言的例子,这也是我需要处理的问题,以确保某些内容可以打印出来。这就是为什么 JWT 信息要放在 secrets.jsl 中,而不是直接放在我用来访问这些信息的脚本中,这样才能确保信息被加密。这也是一个难得的机会,可以看到 Python 如何集成到 JSL 中,这非常酷。
然后我将这些信息发送给 Python 脚本,脚本会获取该 JWT 的特定签名,并据此创建凭据,最后返回 JWT 断言,以便我后续使用。有了 JWT 断言,我就可以获取访问令牌了。接下来,我获取 Box 的 JWT 令牌(如上所示),然后使用 client_ID 和 client_secret,通过 HTTP 请求将 JWT 断言、client_ID 和 client_secret 发送到 Box,从而创建一个新的访问令牌。
这里还有一些空气捕捉功能,供有兴趣深入挖掘的人使用,但它会发送我是否真正获得了访问令牌的信息,然后将访问令牌返回给用户。
它在哪里使用?如何使用?我将展示一个示例,因为关于访问令牌本身,我们可以展开一些有趣的讨论。为了集成它,我创建了一个通用文件夹作为示例,但在这里,我的实际代码只展示一些输入。
这里我加入了 Box 连接(在我的情况下是加密的),因为在处理完密钥文件后,你需要这样做。Box 会给文件夹分配 ID 代码,这样就能唯一地知道你要搜索哪个文件夹里的文件。你需要在这里输入你唯一的文件夹 ID。我这里只提取 JMP 文件。根据你的需求,这可能更复杂,但我的示例只涉及 JMP 文件。
我还在这里添加了一个参数,可以用来查看递归深度。Box 很有意思,因为它采用的是面向对象的方式。你有一个文件夹 ID,然后你可以在该文件夹 ID 下找到子文件夹 ID,所以你必须递归地搜索这些文件夹。我允许我的脚本递归到我认为合适的层数,在这个例子中是五层。然后我指定了下载文件的位置,以及文件的命名方式。在这个例子中,在讲解完它的工作原理以及出于安全考虑的访问令牌的一些细节之后,我将展示我的示例:我将从 Box 中提取一些 JMP 文件。
这里面有一些关于递归搜索 JSL 的函数。我不会详细介绍这些函数或 Python 下载,但我会重点讲解 Box 的主要连接。我们之前讨论过一些安全方面的问题,比如每个公司/每个人应该把这个连接放在哪里比较合适,这个问题可能有点意思。放置位置可能略有不同,安全性也取决于你讨论的是哪个位置,但就我而言,我把它放在这里了。首先,这个主函数需要从 Box 获取信息。为此,我必须获取我的访问令牌。获取访问令牌是之前你看到的加密 secrets.jsl 文件的一部分。在这个例子中,它会返回这个访问令牌。这是一个存在于 JSL 文件中的变量。
在这个 JSL 代码中,因为我在这里获取了访问令牌,如果我在第 583 行添加一个打印语句,你就能看到这个访问令牌。Box 的一个优点(或者说它的一个优点)是,这些访问令牌会过期。实际上,几乎不存在有人以后还能使用它们的风险。虽然风险已经降低,但如果你打印这个访问令牌,或者获取脚本并运行它,然后在该位置打印,仍然存在潜在的安全风险。
理想情况下,你可以这样做:与其像我这样把 Box 访问令牌放在这里,不如把身份验证部分(包括测试和搜索)移到 secret.jsl 文件中,然后进行加密。这样一来,令牌就无法访问,其他人也无法查看。这是一种略有不同的使用场景,你可以根据自己的喜好选择合适的实现方式。最安全的方式是将 Box 访问令牌放在 secrets.jsl 文件中并进行加密,这样任何人都无法查看。不过,这里我先这样处理,这样就好,我们可以就此展开讨论。
然后它会获取 Box 访问令牌,之后我就可以搜索 Box 访问文件了,因为我已经有了身份验证信息,证明我的身份,所以应该允许我访问。我这里有一个演示,演示中我从 Box 驱动器上的一个文件中提取数据。这个文件区域看起来像这样:Secrets_JMP Discovery、File Examples_Secrets,我有一些 JMP 表要从这个位置提取并连接起来。为了实现这一点,我要运行这个脚本。接下来会播放一段令人沮丧的等待音乐,因为运行脚本有时会出现这种情况,需要一些时间。
身份验证,告诉我我是谁,告诉我你是谁。好了,这就是我拿到的文件。你可以看到我的源表,那些是我在那个 Box 位置的 JMP 表的名称,这些是连接在一起的表。只是一些有趣的玩笑。
这就是我处理 Box 访问令牌并将其与 JWT 集成的方法。我打算把它转交给 Nathan。
鉴于 Sage 和我展示的所有内容,我们该如何真正地将其付诸实践,并找到一种方法,让更多人(而不仅仅是我和 Sage)能够参与到这项工作中来呢?我们已经将代码上传到了 GitHub。这里是 GitHub 的链接。我们已经创建了代码仓库。它也与演示文稿相关联,并且包含我们的示例。
现在,根据你想要如何组织,这里有很多选择。但我们主要整理的是密钥模板。这就是我之前展示的包含基本值的模板。然后我们这里有一些不同的示例。在这些示例中,我们基本上有三个主要文件,这是我们展示的包含所有信息的模板。你可以在这里填写你自己的个人或公司信息,以及你进行加密的操作。为了确保安全,我们建议你将此模板保存到你的代码仓库或克隆的仓库中,并将模板替换为“raw”。这样,所有真正重要的信息都会被隐藏起来。我们在 .gitignore 文件中忽略了 raw.jsl 文件。这样,这些信息就保留在本地供你使用。你的凭据也保存在这里。然后对原始文件进行加密,并将加密后的文件版本上传。
现在,GitHub 上应该只保留你可用的加密版本。每个文件夹里都有相应的文档,说明其具体用法。你想在主 README 文件或文档中分享多少信息,完全取决于你想让用户了解如何使用这些信息。如果你有类似 Project Jab 这样的工具(我和 Sage 去年介绍过),它提供了一种构建插件的方法,允许你从各个仓库拉取文件。这样,你就可以拉取所需的密钥文件来完成你的任务。这是一种既能保证信息的可用性,又能兼顾不同级别保密性的好方法。
正如 Sage 所暗示的那样,如果你的代码变得越来越复杂,为了保持这种程度的保密性,你可能会陷入这样的境地:最终你会将越来越多的信息放入这个加密文件中。
就像 Sage 在她的模板里提到的那样,她有很多函数可以完成很多工作,但当她举例说明时,她特别指出了那个特定的访问令牌。我记不清它在函数里的具体位置了,但如果你不想让其他人在主要示例中看到它,你可以把这个函数添加到你的模板或模板化的原始文件中,然后加密它,这样就完全隐藏起来了。你需要处理的层数取决于你的具体用例和你想要实现的目标。在 Snowflake 中,这相对容易,因为我可以直接使用连接或者简单地清理表。所以实际情况可能会有所不同,取决于你需要连接什么,比如 API 之类的。
但我认为这是一种相对容易理解和利用的方式,可以梳理你的秘密,并为那些想要改进其背景编码的人提供多层次的指导,同时保持可靠的网络安全实践,以保持一切干净整洁。
我想今天我们要讨论的内容就到此为止了。非常感谢JMP的各位给我们这次机会,也感谢所有观看的观众。
完全同意。希望你守住了秘密。如果你愿意,今年可以来JMP Discovery找我们,我们会在那里。
是的,我们会的。谢谢。