本文翻译自uber文章:《One Stone, Three Birds: Finer-Grained Encryption @ Apache Parquet™》
数据访问限制、保留和静态加密是基本的安全控制。 本博客介绍了uber如何构建和利用开源 Apache Parquet™ 的细粒度加密功能以统一的方式支持所有 3 个控件。 特别是,我们将重点关注以安全、可靠和高效的方式设计和应用加密的技术挑战。 本文还将分享uber在生产和大规模管理系统的推荐实践方面的经验。
标准的安全控制
本节描述标准安全控制(即访问限制、保留和静态加密)的详细要求,为后续技术细节提供上下文。
-
更细粒度的访问控制:我们可以在不同级别应用数据访问控制:数据库/表、列、行和单元格。 最通用的方法是表级别,指定某人是否有权访问整个表。 但是,实际上,根据您的数据分类规范,表中可能只有少数列需要进行访问限制; 其余的都可以供所有人使用。 即使在需要访问控制的列中,也可能需要不同级别的访问限制。 应用粗粒度访问限制(例如表级)将排除许多合法用例或激发放松规则。 两者要么是非生产性的,要么是有风险的。 列级访问控制 (CLAC) 通过允许更细粒度(列级)的访问控制来解决此问题。 我们努力提供包括更高级别和递归列的列级访问控制。
-
标签驱动的访问策略:列的类别/标签——而不是列的名称应该决定谁可以访问哪些列。 在实践中,数据所有者将预定义的标签分配给将触发一组预定义的访问策略的列。
-
细粒度保留:一般保留策略可能要求在 X 天后删除某些类别的数据。 不一定说 X 天后删除整个表或分区。 在这项工作中,我们通过 X 天后基于标签的特定列删除来解决此问题。 换句话说,只删除策略要求的内容,同时保留其他数据可供使用。
-
静态加密:数据加密是完善的安全控制。 该项目尝试仅加密某些数据字段,而不是加密所有数据元素。
挑战
应用加密来同时实现访问控制、保留和静态加密并不是一种常见的做法。 我们正在努力采用这种新颖且统一的方法来实现这些关键的安全控制。 但是,我们必须先解决以下真正的挑战,然后才能将它们变为现实:
多种访问路径:作为一个开放平台,基于 Apache Hadoop® 的大数据支持不同的数据访问路径、格式和 API。 访问介质包括 SQL(Apache Hive®、Presto®)、程序化(Apache Spark™、Apache Flink®)和直接(CLI、REST)。 尽管我们决定在可能的最低层(即 Parquet 库)强制执行,但不同的项目可能使用不同的 Parquet 版本。 甚至某些系统(即 Presto)也使用了可能与开源版本不完全一致的自定义 Parquet 库。 此外,虽然大多数访问依赖于 Hive Metastore (HMS),但少数访问不通过 HMS,这使得一致地应用某些策略变得更加困难。
性能:虽然由于最近的硬件级加速(即英特尔® AES-NI)指令,核心加密和解密库变得非常快,但关于读写开销的问题仍然具有一定的相关性。 性能损失可能来自密钥访问、加密决策(例如块与单个值、256 位与 128 位等)。在 Uber 规模上,用户查询可能扫描数十亿条记录,少量开销可能会停止 执行。
处理拒绝访问(硬与软):例如,在用户无法访问仅一列的情况下,系统在 Parquet 级别应如何表现?理想的解决方案是从查询中抛出异常或错误。然而,在现实中,用户可能会得到一个掩码值(即 null)作为列值,因为她不关心敏感列。同时,大多数查询使用通配符(“SELECT * ..”)作为投影运行。在这种情况下,显式选择一长列列(仅跳过一个敏感列)既耗时又不方便用户。更重要的是,多年来,在没有活跃开发人员可用的情况下,有很多查询通过管道定期运行。硬故障将立即导致数千条管道发生故障,从而造成运营噩梦,并立即对业务产生影响。同时,默默地返回掩码值可能会导致意外的业务决策。作为该计划的一部分,我们将需要以可接受的方式解决这些相互冲突的情况。
可靠性:由于此过程会修改数百PB的新旧数据,因此数据丢失的可能性很高。 例如,如果我们丢失了密钥,所有相关的加密数据都将无法破译。 此外,Parquet 加密将处于所有数据访问的关键路径; 一个简单的错误可能会导致业务中断。 特别是,通过 KMS(密钥管理服务)进行的密钥管理为维护这一关键和核心服务的可靠性带来了挑战。
历史数据:通常,大量的历史数据存储在生产系统中。 历史数据的安全重写需要使用大量计算资源仔细编排和执行计划。 由于列标签的频繁更新,此问题会成倍增加。 对标记系统的任何修改都需要大量回填 PB 数据。
多种数据格式:推荐的解决方案依赖于 Parquet 数据格式。 但是,在像 Hadoop 这样的开放平台中很难强制执行单一的数据格式,用户可以在其中选择任何数据格式。 因此,我们在 Uber 生产中使用了一些数据格式,这使得授权更难融合到 Parquet。 将其他数据格式转换为 Parquet 是一项相当大的工作,可能会涉及多方的服务中断。
模式标记和调整:CLAC 基于列标记。 我们需要一个适当的元数据标记系统来管理标签并将其传播到 Parquet 级别。 更重要的是,标签不是静态的。 例如,一列最初可能被标记为一种类型的数据。 然后几个月后,它可能会被重新分类并以不同的方式标记。 CLAC 系统将需要回填完整的数据集(有时 PB 是大小),以在合理的时间内符合最新的标记。
Apache Parquet™ 模块化加密
Parquet 列加密,也称为模块化加密,由 Gidon Gershinsky 引入 Apache Parquet™ 并在 Parquet™ 1.12.0 中发布。 Parquet™ 文件可以受到模块化加密机制的保护,该机制对文件数据和元数据进行加密和验证,同时允许常规 Parquet 功能(列投影、谓词下推、编码和压缩)。 该机制还支持使用不同密钥对不同列进行加密,从而实现列访问控制。
目前,Parquet™ 支持 2 种加密算法:AES-GCM 和 AES-CTR。 AES-GCM 是一种经过身份验证的加密算法,可以防止未经身份验证的写入。 除了数据机密性(加密)之外,它还支持 2 个级别的完整性验证/身份验证:数据(默认)和与可选的附加身份验证数据 (AAD) 相结合的数据,这是一个要签名的自由文本。 与数据。 但是,AAD 需要与文件本身分开存储,例如在 KV 存储中,而 AAD 元数据/索引保存在 Parquet™ 文件本身中。 解密应用程序首先从 Parquet™ 文件中读取 AAD 元数据/索引,然后从 KV 存储中读取 AAD,然后才能解密 Parquet™ 加密的数据。 有关 AAD 的更多详细信息,请参阅 RFC Using AES-CCM and AES-GCM Authenticated Encryption。
AES-CTR 不需要完整性验证/身份验证。 因此,它比 AES-GCM 更快。 基准测试结果显示,在 Java 9 的单线程应用程序中,AES-CTR 比 AES-GCM 快 3 倍,在 Java 8 的单线程应用程序中比 AES-GCM 快 4.5 倍。
一个统一的方法
Apache Parquet™ 更细粒度的加密可以加密上面讨论的不同模块中的数据,包括文件中的列,并且每个列都可以独立加密(即使用不同的密钥)。每个密钥授予不同的人或组访问权限。通过控制每个键的权限,可以实现列级更细粒度的访问控制。当 Parquet 读取器解析文件页脚时,格式中定义的加密元数据将指示在读取数据之前首先从哪个 Parquet 库中获取密钥。如果用户没有该密钥的权限,则会收到“拒绝访问”异常,并且用户的查询将失败。在某些情况下,用户可以有一个像“null”这样的屏蔽值。换句话说,用户在没有密钥权限的情况下无法读取数据。所以更细粒度的访问控制是通过控制对key的权限来实现的。
数据保留,例如 X 天后删除某些类别的数据,可以通过对密钥进行保留策略来实现。当一个密钥被删除时,由该密钥加密的数据就变成了垃圾。这种方式可以避免直接对列数据进行操作,这通常是一个繁琐的操作。
系统架构
加密系统包括 3 层:元数据和标记、数据和加密以及密钥和策略。 它们的交互、数据流和加密控制路径如图 1 中的系统架构所示:
实体交互和数据流
在上层——元数据和标记——存在摄取和 ETL(提取、翻译和加载)巨型存储。它们分别由摄取管道作业和 ETL 翻译作业使用。元数据在字段(列)级别定义每个数据集(表)的名称、类型、可空性和描述。元数据标记实体添加字段隐私属性,用于指示该字段是否将被加密,以及如果加密将使用什么密钥。元数据被放在一个元存储中。我们使用具有 Apache Avro™ 架构格式的摄取元存储用于摄取管道,并将 Hive 元存储用于 ETL 作业。
中间层显示数据如何从事务性上游业务存储(例如,RDBMS 数据库、通过 Kafka 消息系统的 Key-Val 数据库)获取数据,并以 Apache Parquet™ 格式存储在文件存储系统中。数据以更细的粒度加密,由上层的标记指示。加密在摄取管道作业和 ETL 作业内执行,以便数据在发送到空中(传输中)和存储(静态)之前被加密。这比仅存储加密更有利。 ETL 作业通过展平表的列或不同模型将摄取的数据转换为表。如果源表已加密,则转换后的表也将被加密。
底层是 KMS 及其关联策略。 密钥存储在 KMS 的密钥库中,其关联策略确定哪些人可以访问列密钥来解密数据。 列的访问控制在键的策略中实现。 隐私保留和删除规则也通过密钥保留和删除来完成。
元数据中的标记流程控制更细粒度的加密如下:
- 数据集在字段级别被标记以指示该字段是否将被加密,以及如果加密将使用哪个密钥。标记信息存储在摄取元存储中。
- 摄取元存储具有所有元数据,包括摄取管道作业中所需的标记信息。当作业从上游摄取数据集时,相关元数据会从摄取元存储中提取到作业中。
- 数据集被写入文件存储系统。如果元数据标记表明需要加密,摄取作业将在将数据发送到文件存储系统之前对其进行加密。
- 摄取数据集的元数据也被转发到 ETL 元存储,ETL 作业和查询使用该元存储。他们在读取该数据集时需要该元数据信息。
- 当 ETL 作业将数据转换为新数据集(表)时,会提取 ETL 元数据。同样,标记信息用于控制如上所述的加密。
- 转换后的数据被写回文件存储。
模式驱动的加密
要激活 Parquet™ 中的加密功能,它需要修改 Parquet™ 应用程序(例如 Spark、Hive、Presto、Hudi 和许多其他应用程序),例如分析加密属性、处理 KMS 交互、构建参数的加密属性、 错误情况处理和其他几个辅助方法,然后使用新添加的参数调用 Parquet™ API:FileEncryptionProperties。 为了避免对 Parquet™ 应用程序进行这些更改,Parquet-1817 定义了一个接口工厂,以便可以实现一个插件来将上述细节包装到插件中。 这个插件可以作为一个库提供,因此只需添加类路径就可以将它包含在不同的应用程序中。 通过这样做,我们可以避免对每个应用程序的代码进行更改。 在下一节中,我们还将此插件称为加密属性和密钥检索器或交错加密检索器。
现在的问题是加密检索器如何知道哪个列将由哪个密钥加密。 该信息存储在标记存储系统中。 最简单的方法是让插件调用标注存储直接获取当前数据集的标注信息。 问题在于,通过这种方式,我们将标记存储添加为 Parquet™ 应用程序(如 Spark、Hive 和 Presto)的依赖项,这些应用程序通常在 Yarn 或 Peloton 等大型计算集群或 Presto 集群上运行。 有时即使是一个查询作业也可能在短时间内发送数百万个 RPC 调用请求。 这将为标记存储系统带来挑战,并为 Parquet™ 应用程序增加可靠性问题。
我们相信通过消除那些大量的 RPC 调用可以做得更好。 这个想法是将标记信息传播到模式存储(例如离线阶段的 HMS)。 Parquet™ 应用程序通常已经依赖于模式存储,因此不需要额外的 RPC 调用。 一旦模式具有标记信息,在应用程序中运行的 Parquet™ 库就可以对其进行解析并构建 Parquet™ 所需的 FileEncryptionProperties,以了解哪些列应该使用哪些密钥和其他几条信息进行加密。
图 2 中描述了模式控制的 Parquet™ 加密。左侧解释了加密是如何在写入路径中发生的,右侧是读取和解密路径。 在示例示例中,只有 2 列 (c1, c2)。 C2 被定义为敏感列,而 c1 不是。 Parquet™ 加密后,c2 在被发送到存储之前被加密,可以是 HDFS 或云存储,如 S3、GCS、Azure Blob 等。在读取路径上,加密元数据存储在每个文件(格式)中,并且 Parquet™ 库使用它来确定要检索什么密钥来解密数据。 KMS 客户端包含相同的插件。 如果用户对密钥具有权限,则数据将被解密为明文。
模式存储包括传播的数据集标记信息。 如前所述,我们使用具有 Avro 模式格式的摄取元存储用于摄取管道,并将 Hive 元存储用于 ETL 作业。 Parquet™ 编写器通常需要实现 WriteSupport 接口。 例如,Spark 实现了 ParquetWriteSupport,它分析模式并将其从 Spark 转换为 Parquet™。 这是添加加密功能之前的现有行为。 使用模式控制的加密,我们可以通过添加标记信息的解析器并将它们附加到 Parquet™ 模式来扩展 WriteSupport。 加密检索器将使用该信息并使用它来确定要用于加密的密钥。 加密检索器中封装的 KMS 客户端将从 KMS 获取密钥。
下面的图 3 显示了模式中的标记信息如何控制 Parquet™ 中的加密。 使用这种方法,一旦数据集被标记或标记被更新,摄取管道将获取最新的标记并相应地更新加密。 此功能称为自动管理。
性能基准
Parquet™ 社区对 Parquet™ 加密进行了开销评估。 有关性能的更多详细信息,请参阅 Gidon Gershinsky 和 Tomer Solomon 发布的博客。 总体而言,在 Java 8 上使用 GCM 模式有大约 30% 的开销。使用 Java 11,开销减少到大约 3%。 CTR 模式比 Java 8 中的 GCM 快 3 倍。
Parquet™ 社区的开销评估涵盖了 Java 8 与 Java 11、GCM 与 CTR 等变体,因此我们不会重复这些变体的迭代工作。 但这种演变主要集中在加密本身的吞吐量上。 我们的评估更侧重于终端场景。 实际上,还有其他几个变量:
- 文件读取或写入时间并不是影响用户查询或 ETL 作业持续时间的唯一因素,因此就每个用户查询或 ETL 作业的开销而言,博客中的数字与真实用户场景相差甚远。
- 在大多数情况下,并非所有列都需要加密。 当列不需要加密时,将减少加密开销。
- 加密密钥操作时间也应计入整个持续时间,尽管该时间可能在毫秒级别,并且可能只会以非常微妙的方式改变最终结果。
我们的性能评估是在最终用户查询上执行的。 我们开发了对表中 60% 的列进行加密的 Spark 作业,这通常超过了需要加密的列的百分比。 在解密方面,Spark 作业读取与计数一起返回的表。 开销被评估为“增加的时间”与 Spark 作业的总持续时间,我们认为这是更接近真实用户场景的评估。
基准测试工作的一个挑战是读取或写入文件的存储延迟不固定。 在比较加密和不加密的作业时,有时我们发现加密的作业比不加密的作业运行得更快。 这主要是由存储读写延迟造成的。 为了克服这个不确定因素,我们决定更改 Parquet™ 代码,以计算每次运行通过加密添加到总持续时间的时间。 如上所述,另一个开销是 KMS 操作时间。 我们还将该持续时间添加到开销中。 我们多次运行作业并计算平均值。 读取和写入的平均开销计算如下:
写入开销:
读取开销
在我们的评估中,我们选择了 Java 8 和 CTR 模式并加密了 60% 的列。 写入开销为 5.7%,读取开销为 3.7%。 需要指出两点:1) 60% 的加密列通常超过实际需要加密的列的百分比,2) 真实用户的查询或 ETL 除了读取或写入文件之外还有很多其他任务 (例如,表连接、数据混洗)更耗时。 在我们的评估中,那些昂贵的任务不包括在工作中。 考虑到这两个因素,可以进一步降低读写的开销。 在真实场景中,我们不会将加密或解密开销视为问题。
考虑点
-
更细粒度的访问控制可以更好地更简洁地保护数据,Parquet™ 列加密可用于实现对密钥的访问控制。实现数据保留的方法之一是通过列加密的密钥操作。
-
可以通过不同的方式控制 Parquet™ 列加密,并且使用模式来控制它将使解决方案简洁,而不会引入额外的 RPC 调用作为开销。这也使得标签驱动的访问策略成为可能。
-
当需要加入大量表时,自动加入很重要。它不仅节省了人工,而且可以很容易地构建为一个监控数据的系统。
-
数据湖通常有大量数据。加密需要大规模转换数据,这需要高吞吐量的加密工具。 Parquet™ 中的创新已合并到开源中,并将在 Parquet™ 1.13.0 中发布。如果您不能等待发布,您还可以移植更改并构建它以供使用。
-
加密开销的基准测试显示影响最小,通常不会担心增加的延迟。
结论
在大数据中,需要细粒度的访问控制,并且可以通过不同的方式实现。我们的方法一直是把它与加密和删除需求结合起来,这样一个统一的解决方案就可以解决数据访问限制、保留和静态加密3个问题。
Parquet™ 列加密在文件格式级别实施,并提供静态加密。我们提出了应用列加密的挑战,并提供了模式控制的列加密的解决方案,它避免了过多的 RPC 调用并减少了系统可靠性问题。
由于数据的规模,加密存储中的现有数据带来了挑战。我们推出了一种高吞吐量工具,可以将数据加密速度提高 20 倍。
将表加入加密系统时,手动执行此操作会导致一些问题。自动入职不仅消除了手动工作,而且在构建加密监控系统方面也有额外的好处。
当用户无权加密数据时,可以使用数据屏蔽。 Null 类型的数据屏蔽易于实现且不易出错。
本文为从大数据到人工智能博主「xiaozhch5」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://lrting.top/backend/4113/