テクノロジー

よりセキュアなHadoopの作り方 〜 ApacheCon Asia 2021登壇報告

Yahoo! JAPAN Advent Calendar 2021の2日目の記事です。

こんにちは。ヤフーでHadoopに携わっているエンジニアの桑折です。
ヤフーを含むZホールディングスグループではNIST(米国標準技術研究所)の定めるセキュリティ基準 SP800-171への準拠に向けた取り組みを始めています。
その要件の一つである「システムと通信の保護」への対応はHadoopをはじめとしたデータプラットフォームの領域も例外ではありません。
本記事ではヤフーのHadoopクラスタにおけるセキュリティ向上に向けた通信の暗号化、およびHDFS上のデータの透過暗号化の取り組みを紹介します。

はじめに

この記事ではHadoopクラスタにおける通信とデータ蓄積の2点に着目して、本番環境への適用を見据えたハードニング手法を紹介します。

前半はHadoop、Spark、Hive、ZooKeeperの通信に着目し、プロトコルごとの通信の暗号化の手法やダウンタイムを伴わない適用の手順、判明した課題とその対策を紹介します。
後半はHDFSデータの蓄積に着目し、ユーザーアプリケーションの変更なしに暗号化を実現する透過暗号化の手法、これを実現するHadoop KMSコンポーネントおよびKeyProvider APIの紹介、本番運用に向けた取り組みを紹介します。

本記事の内容は2021年8月にオンライン開催された ApacheCon Asia 2021 での登壇 Technical tips for secure Apache Hadoop cluster をもとに加筆・編集したものになります。
また、本記事はDistributed computing Advent Calendar 2021の12/2のエントリです。

Hadoopエコシステムの通信の暗号化

背景

Hadoopは初期設定値のみで構築すると容易にスーパーユーザーへのなりすましが可能となってしまうため、Kerberos認証を有効化することがデファクトスタンダードになっています。
これに加えて内部からの盗聴などの脅威への対策として各コンポーネントに追加で通信の暗号化を実施します。

Hadoopにおける通信の暗号化にはWeb UIおよびREST APIのHTTPリクエストのみならず、Hadoopコンポーネント間のRPCリクエスト、HDFSのブロックデータ転送、YARNアプリケーション間のシャッフルデータ転送といった通信も対象に含まれます。

HadoopのHTTP暗号化

HadoopにおけるHTTPリクエストのTLS暗号化は、 http.policy サフィックス付きパラメータを設定することで適用できます。
hdfs-siteの dfs.http.policy 、およびyarn-siteの yarn.http.policy 、mapred-siteの mapreduce.jobhistory.http.policy などが該当します。
また、TLS証明書・秘密鍵・パスフレーズの組をHadoop Credential Providerを使い hadoop.security.credential.provider.path に指定することで、configファイルとのパーミッションの分離、またパラメータ値としてログなどに意図せず暴露してしまうことを防げます。
http.policyの値を HTTPS_ONLY とすることでTLSエンドポイントに切り替わりますが、MapReduceのJobHistoryおよびYARNのTimelineServerはNodeManagerからのリクエストを受け付けるため、ローリングアップデートを交えた無停止での切り替えを行うには下記の手順となります。

  • yarn.timeline-service.webapp.https.address およびmapred-siteの mapreduce.jobhistory.webapp.https.address をそれぞれTimelineServerおよびJobHistoryのHTTPSアドレスに設定
  • TLS証明書・秘密鍵・パスフレーズ設定を追記し、JobHistoryおよびTimelineServerのhttp.policyを HTTP_AND_HTTPS 設定で再起動
  • その他のコンポーネントを HTTPS_ONLY 設定でローリングアップデート
  • JobHistoryおよびTimelineServerを HTTPS_ONLY で再起動

また、Hadoop 3.3.1以降では証明書ローテートなどTLS設定の変更を再起動せずJetty経由で動的にリロードする機能が取り込まれました(HADOOP-16524)。
特に再起動に大きなオーバーヘッドが掛かるNameNodeへの反映時のリスクを大幅に低減できる見通しです。

HadoopのRPC暗号化

HadoopのRPCはcore-siteの hadoop.rpc.protection パラメータにより暗号化設定を行います。RPCの暗号化にはTLSではなく、Kerberos認証を利用したSASL暗号化によって担保されます。
このパラメータが受け取る値はデフォルト値の authentication (認証のみ)、 integrity (認証と完全性→改竄されていないことの保証)、および privacy (認証・完全性に加えて機密性→盗聴されていないことの保証) の3種類です。
このパラメータが異なるHadoopコンポーネント間はRPCの互換性がなくなり通信できなくなりますが、複数の値を指定できるため、デフォルト値の状態からローリングアップデートを交えた無停止での移行を行う場合は下記の手順となります。

  • core-site hadoop.rpc.protection の値を privacy,authentication に設定
  • クライアントを含めた全Hadoopコンポーネントへの設定反映・ローリングリスタート
  • hadoop.rpc.protectionprivacy に設定し、再度全Hadoopコンポーネントに反映・ローリングリスタート

HadoopのHTTP暗号化及びRPC暗号化に関連するパラメータをまとめると下記になります。
中間設定値を経由してローリングアップデートを行うことでダウンタイムなしに設定を反映できます。

config パラメータ名 説明 初期値 中間設定値 設定値
hdfs-site dfs.http.policy HDFS HTTPサーバーの暗号化 HTTP_ONLY HTTP_AND_HTTPS HTTPS_ONLY
yarn-site yarn.http.policy YARN HTTPサーバーの暗号化 HTTP_ONLY HTTP_AND_HTTPS HTTPS_ONLY
mapred-site mapred.jobhistory.http.policy MR JobHistory HTTPサーバーの暗号化 HTTP_ONLY HTTP_AND_HTTPS HTTPS_ONLY
core-site hadoop.rpc.protection Hadoop RPCのSASL暗号化 authentication privacy,authentication privacy

HDFSのブロックデータ転送の暗号化

HDFSの実データに相当するブロックデータの転送に対する暗号化はRPC暗号化とはさらに別のパラメータで管理されています。
hdfs-siteの dfs.encrypt.data.transfertrue に、 dfs.encrypt.data.transfer.cipher.suitesAES/CTR/NoPadding にそれぞれ指定することで有効化します。

この設定を無停止で反映するにあたっては、転送の暗号化設定の反映済/未反映を判別する dfs.trustedchannel.resolver.class によるノードの動的な管理、場合によっては拡張による独自実装が必要となります。
HDFSクライアントおよびNameNode・DataNode間で dfs.encrypt.data.transfer の真偽が一致していない場合はブロックデータ転送が成立しませんが、上記のパラメータに TrustedChannelResolver クラスを定義することで例外的に真偽に関わらず平文でブロックデータを転送するノードを指定できます。
ここではHadoopに標準で同梱されている、テキストファイルに列挙されたノードにのみ平文で転送する org.apache.hadoop.hdfs.protocol.datatransfer.WhiteListBasedTrustedChannelResolver を用いてローリングアップデートを行う例を示します。

  • dfs.trustedchannel.resolver.classorg.apache.hadoop.hdfs.protocol.datatransfer.WhiteListBasedTrustedChannelResolver を設定
  • dfs.datatransfer.server.variablewhitelist.file で指定したパス(デフォルト: /etc/hadoop/whitelist )に全DataNodeのIPアドレスを列挙したテキストファイルをHDFSクライアント・NameNode・DataNodeに配布
  • dfs.encrypt.data.transfer および dfs.datatransfer.server.variablewhitelist.enabletrue を設定し、HDFSクライアント・NameNodeに反映・再起動
  • DataNodeのローリングアップデートと同時に、再起動完了したDataNodeのIPアドレスを各コンポーネントのwhitelistから除去
  • dfs.datatransfer.server.variablewhitelist.cache.secs (デフォルト: 3600 )で指定した秒数待機し、全コンポーネントに反映されるのを待って2周目以降のDataNodeのローリングアップデートを実施

上記の手順はローリングアップデートの作業単位ごとにHDFSクライアント・NameNode・DataNode全台へのテキストファイルの更新が必要となります。
また、すべてのHDFSクライアントを把握していない場合はHadoop 3.1.0から同梱(HDFS-13060)された BlackListBasedTrustedChannelResolver で反映済のサーバー・クライアントを逐次追加する手法や、ZooKeeperなどで反映完了したノードを中央管理するTrustedChannelResolverクラスの独自実装などといった手段で代替する必要があります。

config パラメータ名 説明 初期値 設定値
hdfs-site dfs.encrypt.data.transfer HDFSブロックデータ転送の暗号化 false true
hdfs-site dfs.encrypt.data.transfer.cipher.suites ブロックデータ転送暗号化アルゴリズム AES/CTR/NoPadding
hdfs-site dfs.trustedchannel.resolver.class 平文で転送するDataNodeを判別するTrustedChannelResolver実装 (o.a.h.h.p.d.WhiteListBasedTrustedChannelResolver)
hdfs-site dfs.datatransfer.server.variablewhitelist.enable (WhiteListBasedTrustedChannelResolver 使用時)動的リスト読み込みを使用 false (true)
hdfs-site dfs.datatransfer.server.variablewhitelist.file (WhiteListBasedTrustedChannelResolver 使用時)動的リストのパス /etc/hadoop/whitelist  
hdfs-site dfs.datatransfer.server.variablewhitelist.cache.secs (WhiteListBasedTrustedChannelResolver 使用時)動的リストの更新間隔 3600  

Sparkにおける通信の暗号化

SparkのHTTP暗号化において、SparkDriverおよびStandaloneのWeb UI暗号化は後述のApplicationMasterのユーザーパーミッションに起因する課題があるため、SparkHistoryのみを設定します。
Hadoopと違いSparkHistoryのHTTPS設定 spark.ssl.sparkHistory.enabled ではHTTP/HTTPS両ポートでのサービングがサポートされていないため、ローリングアップデート中はsubmitログやResourceManagerから正しくSparkHistoryに到達できない時間帯が発生します。
SparkHistoryにおけるステートはHDFSなどのイベントログ格納先 spark.eventLog.dir に蓄積されるため、ローリングアップグレード中に実行されたアプリケーションの情報が欠損することはありません。

RPCにKerberos認証を適用する spark.authenticate パラメータはspark-defaultsに加えて、YARN向けのSpark External Shuffle Serviceへの適用のためyarn-siteにも設定が必要です。

RPCおよびシャッフルに対してはAESによる暗号化を行います。SASL暗号化にも対応していますがSpark 2.2.0未満への後方互換として残されているため非推奨とされています。
spark.network.crypto.enabled で有効化する他に、メモリからあふれたキャッシュデータやRDDをローカルディスクに出力する際に暗号化を行う spark.io.encryption.enabled パラメータがサポートされています。

config パラメータ名 説明 初期値 設定値
yarn-site spark.authenticate Spark External Shuffle ServiceにKerberos認証を適用 true
spark-defaults spark.authenticate RPCにKerberos認証を適用 false true
spark-defaults spark.ssl.sparkHistory.enabled SparkHistory Web UIのTLS暗号化 false true
spark-defaults spark.network.crypto.enabled RPCおよびシャッフルのAES暗号化 false true
spark-defaults spark.io.encryption.enabled ローカルディスクへのキャッシュ・RDDのI/OのAES暗号化 false true
spark-defaults spark.network.crypto.saslFallback AES暗号化を利用できなかった場合にSASL暗号化にフォールバックする true false

Hiveにおける通信の暗号化

Hiveワークロードの暗号化は、プロトコルによってTLSとSASLの両方の暗号化を使い分けます。
Hive2未満のバイナリモードHiveServer2が提供するThriftプロトコルのJDBC接続にはSASL暗号化を使用し、HTTPモードやHive2以降のHiveServer2ではTLS暗号化を使用します。
また、HiveServer2やHive MetastoreとMySQLなどのリモートRDBMS間のJDBC接続も暗号化の余地があります。

Hiveの実体であるTezおよびMapReduceでのシャッフルデータ転送はTLSによる暗号化を行います。
このため、導入にはすべてのNodeManagerでサーバー証明書の配置とHTTP暗号化が必要になります。
シャッフルのTLS暗号化はアプリケーションパフォーマンスへの影響が特に大きく、コネクション数やkeep-alive設定などのチューニングが推奨されています。

config パラメータ名 説明 初期値 設定値
hive-site hive.server2.thrift.sasl.qop バイナリモードHiveServer2のSASL暗号化 auth auth-conf
hive-site hive.server2.use.SSL HTTPモードHiveServer2・Hive2以降のバイナリモードHiveServer2のTLS暗号化 false true
tez-site tez.runtime.shuffle.ssl.enable TezのシャッフルのTLS暗号化 false true
tez-site tez.runtime.shuffle.keep-alive.enabled Tezシャッフルのkeep-alive接続を有効化 false true
mapred-site mapreduce.shuffle.ssl.enabled MapReduceシャッフルのTLS暗号化 false true

ApplicationMasterにおけるHTTP暗号化の課題

MapReduceおよびSparkアプリケーションのsubmitごとにNodeManagerノードで生成されるApplicationMasterやSparkDriverプロセスは、Hadoop 3.3.0(MAPREDUCE-4669)、およびSpark 3.0.0(SPARK-24621)以前ではHTTP暗号化の設定が考慮されず平文でWeb UIが起動します。
ApplicationMasterが起動するWeb UIは、通常ResourceManagerのWebApplicationProxyによるリバースプロキシ経由で参照されるため、ユーザーからはResourceManagerのHTTPS終端により見かけ上HTTPSに見えますが、ResourceManager・ApplicationMaster間の通信が平文で流れていることになります。

これに加えて、HTTP暗号化にはApplicationMasterの起動ユーザーに起因するキーストアのパーミッションの課題があります。
Kerberos認証が有効な場合、ApplicationMasterやSparkDriverプロセスはNodeManagerノードにアプリケーションをsubmitしたPOSIXユーザーの権限で起動します。
マルチテナント環境で他のHadoopコンポーネント同様にノード単位で共通の証明書・秘密鍵を使おうとするとsubmit可能な不特定多数のユーザーから参照可能にする必要があり、秘密鍵の危殆化につながってしまいます。

これに対する根本的な対策として、Hadoop 3.3.0からResourceManager(WebApplicationProxy)がApplicationMasterとの間でのみ有効な独自認証局(ProxyCA)を持ち、証明書をアプリケーションIDごとに動的に発行し、ApplicationMasterがキーストアを受け取ってHTTPSでWeb UIを起動する機構が実装されました(YARN-6586Securing YARN Application Web UIs and REST APIs)。
ResourceManagerにおいてyarn-siteの yarn.resourcemanager.application-https.policy パラメータを LENIENT または STRICT に指定することで、アプリケーション内の KEYSTORE_FILE_LOCATION / KEYSTORE_PASSWORD 、および TRUSTSTORE_FILE_LOCATION / TRUSTSTORE_PASSWORD 環境変数にWebApplicationProxyから受け取ったキーストア・トラストストアとそれぞれのパスワードが配備されます。

ApplicationMasterでyarn-siteの yarn.app.mapreduce.am.webapp.https.enabled パラメータが true になっていれば、自動的に上記の環境変数を参照してHTTPSでWeb UIを起動します。
ただし、SparkDriverにおいては上記のキーストア・トラストストアが自動的に認識されないため、 spark-env.sh 内でSparkDriverの実行時にspark.ssl.ui.keyStore および spark.ssl.ui.trustStore パラメータ引数に追加するとなどの対応が別途必要になります。
また、Tezアプリケーションは自身でWeb UIを起動せず、TezViewなどNodeManagerノードから独立したWeb UIでトラッキングするため、この影響を受けません。

config パラメータ名 説明 初期値 設定値
yarn-site yarn.resourcemanager.application-https.policy WebAppliaciotnProxyからオリジンにHTTPSで接続する(Hadoop 3.3.0以上) NONE STRICT
yarn-site yarn.app.mapreduce.am.webapp.https.enabled ApplicationMasterのHTTP暗号化 false true

ZooKeeperの暗号化

ヤフーのHadoopエコシステムにおけるZooKeeperはKerberos認証情報をもとにアクセス制御を適用しており、これを維持するため認証およびアクセス制御にSASL、暗号化にTLSを併用する構成を採用します。

ZooKeeper Server

ZooKeeper 3.5.5からNettyによるTLS暗号化に対応したサーバー起動がサポートされています。
また、ZooKeeper Server/Client間に加えて、ZooKeeper Server同士でのQuorumの構成においてもKerberosを介したSASLレイヤの認証(SASL認証)およびTLS暗号化を設定します。

TLS暗号化を有効化する際デフォルトでTLS認証も有効化されますが、SASL認証を優先するため明示的に ssl.clientAuth および ssl.quorum.clientAuthNONE に設定してバイパスします。
また、より厳密な管理に向けてSASL認証のない匿名リクエストを遮断するオプションがZooKeeper 3.6.0以上でサポートされています(なお、3.7.0でパラメータが変更されています)。

平文ポートを閉鎖するには暗号化ポートのみでの起動を許可するZOOKEEPER-4276のバックポートが必要となります。
また、ZooKeeper 3.5.0以降ZooKeeper Commandの後継としてREST APIリクエストを受け付けるAdmin Serverには認証やTLS暗号化を設定できないため、アドレスをローカルホストに限定します。

config パラメータ名 説明 初期値 設定値
zoo.cfg serverCnxnFactory サーバー実装の切り替え o.a.z.s.NIOServerCnxnFactory o.a.z.s.NettyServerCnxnFactory
zoo.cfg admin.serverAddress Admin Serverの起動アドレス 0.0.0.0 127.0.0.1
zoo.cfg ssl.clientAuth Server/Client間でTLS認証を利用する NEED NONE
zoo.cfg ssl.quorum.clientAuth Quorum間でTLS認証を利用する NEED NONE
zoo.cfg quorum.auth.enableSasl Quorum間でSASL認証を利用する false true
zoo.cfg sessionRequireClientSASLAuth SASL認証のない匿名リクエストを遮断する(ZooKeeper 3.6.0以上) false (true)
zoo.cfg enforce.auth.enabled 認証のない匿名リクエストを遮断する(ZooKeeper 3.7.0以上) false (true)
zoo.cfg enforce.auth.schemes 認証方式の指定(ZooKeeper 3.7.0以上) (sasl)

ローリングアップデート手順はSASL認証について Server-Server mutual authentication 、TLS暗号化について Upgrading existing non-TLS cluster with no downtime の各ドキュメントに従います。
無停止での移行手順の中で複数回変更・反映を行う必要があるパラメータをまとめると下記の表になります。
中間設定値でローリングアップデートすることでZooKeeper Serverが平文・暗号化通信を同時に受け付ける状態となり、後述のZooKeeper Clientの反映を含めて全台に更新が完了した後に設定値に移行することで平文ポートを遮断します。

config パラメータ名 説明 初期値 中間設定値 設定値
zoo.cfg quorum.auth.learnerRequireSasl Quorumフォロワー・オブザーバーの通信にSASL認証を強制する false true true
zoo.cfg quorum.auth.serverRequireSasl Quorumリーダーの通信にSASL認証を強制する false (false) true
zoo.cfg sslQuorum Quorum通信のTLS暗号化 false true true
zoo.cfg portUnification clientPortでQuorumの平文および暗号化通信を同時に受け付ける false true false
zoo.cfg client.portUnification clientPortでClientからの平文および暗号化通信を同時に受け付ける false true false
zoo.cfg clientPort 平文ポート番号 (2181) (パラメータ消去)
zoo.cfg secureClientPort 暗号化ポート番号 2181

ZooKeeper Client

サーバーのTLSに対応するクライアント側のTLS設定もZooKeeper 3.5.5で追加されました。
ZooKeeperと疎通を行うすべてのコンポーネント(ZKFC, HttpFS, ResourceManager, HiveServer2を含むHiveクライアント, Oozie, Livyなど)で、ZooKeeper 3.5.5以上、および依存するApache Curator 4.2.0、Jetty 4.1以上に更新・換装した上で
HADOOP_OPTS環境変数などを経由して各プロセスに下記のJVM引数を追加することでZooKeeper ServerとのTLS接続を確立します。

-Dzookeeper.client.secure=true -Dzookeeper.clientCnxnSocket=org.apache.zookeeperClientCnxnSocketNetty

例外として、Oozieを使用している場合はCoordinator MapReduceジョブに対して環境変数からJVM引数を追加できないため、mapred-siteの mapreduce.admin.map.child.java.opsmapreduce.admin.reduce.child.java.opts に上記のJVM引数を追加します。

また、ZooKeeper Serverで匿名リクエストの遮断を有効化した場合には、多段でZooKeeperへのリクエストを行うワークロードは使用できなくなり、代替手段が必要になります。
HadoopにおけるDelegation Tokenのようにユーザーの認証情報をZooKeeperクライアントに委譲する機構が存在せず、匿名でのリクエストとなるためです。
Oozieジョブの内部でHiveクライアントがZooKeeperにアクセスするOozie Hiveジョブがこれに該当し、HiveクライアントでなくHiveServer2を経由するOozie Hive2ジョブで代替することで解決可能です。

HDFSデータの透過暗号化

背景

HDFSデータはデフォルトで128MBごとのブロックに分割され、DataNodeのローカルディスクに暗号化せず直接書き込まれます。このため、それぞれのブロックを結合することで容易に元のデータを復元可能な状態になっています。
HDFSをストレージとしてPCI-DSSやNIST SP800-171などデータ暗号化を要求するセキュリティ基準に準拠させるには、データを格納するまでの下記の層のうちいずれか一層以上で暗号化処理を施す必要があります。

階層 特徴
アプリケーション 最もセキュアだが、最も実装コストが高い
ミドルウェア 暗号化の実装次第でパフォーマンスに影響
ファイルシステム 高速かつ透過的だが柔軟性に欠ける
ディスク 物理的な盗難以外には効果なし

一般に暗号化処理はユーザーアプリケーションに近いほどよりセキュアになりますが、ユーザーが負担するコストも比例して増大します。
HDFSの透過暗号化はミドルウェア層とファイルシステム層にまたがって、性能・柔軟性・透過的なアクセスのバランスを兼ね備えた機能として実装されています。

透過暗号化のフロー

HDFSデータの透過暗号化を実現するにあたり、HDFSに Hadoop KMS そして KeyProvider を導入します。
KMSはKey Management Serverの略称であり、透過暗号化においてはHDFSクライアント・NameNodeとのデータ暗号化キーの受け渡し・生成・暗号および復号を担います。
KeyProviderはHadoop KMSが参照するAPIであり、外部のストアとHadoop KMSでキー暗号化キーを受け渡す責任を負います。

下記はHDFSにファイルを作成し暗号化する際のシーケンス図です。

Hadoop KMS・KeyProviderを介したHDFS透過暗号化のシーケンス図

クライアントがHDFSにファイルを作成する場合、

  1. クライアントがNameNodeにファイルの作成を依頼し、
  2. NameNodeがHadoop KMSにファイル用のEDEK(暗号化済データ暗号化キー)をリクエストします。
  3. リクエストを受けたHadoop KMSはDEK(データ暗号化キー)を作成し、
  4. KEK(キー暗号化キー)をKeyProviderから取得します。
  5. そして、Hadoop KMSはKEKを用いてDEKを暗号化し、DEKはEDEKとなります。
  6. NameNodeはEDEKをfsimageに保存し、EDEKをクライアントに返します。
  7. 次に、DFSClientはHadoop KMSにEDEKの復号を依頼し、DEKを取得します。
  8. 最後に、クライアントは入手したDEKを使って暗号化されたデータをDataNodeに書き込みます。

これらの処理はユーザーから見るとHadoopのDFSClientの内部で完結しており、ユーザーアプリケーションに変更を加える必要はありません。これが 透過 暗号化 の所以です。

KeyProvider

KeyProviderはHadoopが提供するAPIであり、Hadoopはリファレンス実装として JavaKeyStoreProvider を提供しています。
この実装では、KEKはjceksファイルとしてローカルファイルシステムやHDFS、S3をはじめとしたクラウドストレージなど、Hadoop互換ファイルシステムに格納されていることを想定しています。
しかし、格納先のファイルシステムでjceksファイル自体が暗号化されていない場合が考えられること、また JavaKeyStoreProvider 自体が実験的な実装でありパスワードの取り扱いに注意を要することから、本番環境での利用は推奨されません。

OSSで提供されている別の実装として、Apache Rangerが提供する RangerKeyStoreProvider があります。
Apache Rangerは、Hadoopエコシステム全体の包括的なデータセキュリティを実現、監視、管理するためのオープンソースのフレームワークであり、RangerKeyStoreProvider を内包したRanger KMSを提供しています。
RangerKeyStoreProvider ではRDBMSにKEKを保存する実装となっており、KEKはRangerマスターキーで暗号化されています。
RangerマスターキーはデフォルトではRDBMSにプレーンテキストで保存されますが、PCI-DSSやFIPS 140-2など暗号モジュールのセキュリティ基準に準拠するためにRangerマスターキーのLuna HSMへの格納をサポートしています。

KeyProviderの拡張

上述のRanger KMSやLuna HSMを利用せずとも、組織内に既に鍵管理システムが整備されている場合はKeyProvider APIを拡張して連携することが可能です。

ヤフーにおいてもKeyProvider APIを拡張し、社内で提供されている鍵管理システムへの連携を実装しました。
実装はテストコードを含めて500行以内に収まっており、実装のハードルは高くないと言えます。
Hadoopのヘビーユーザーとして知られるLinkedIn社においても、社内の鍵管理システムと連携したKeyProvider拡張を利用して透過暗号化を実現しています。

LinkedIn has its own key management service, LiKMS, which is the only service certified and approved for managing cryptographic keys and secrets internally. We used pluggable interfaces such as KeyProvider supported by HDFS to integrate LiKMS with transparent encryption at rest.

出典:The exabyte club: LinkedIn’s journey of scaling the Hadoop Distributed File System | LinkedIn Engineering

KeyProvider APIが提供する8つのメソッドのうち、最低限KEKを取得する getKeyVersion, getCurrentKey, getMetadata の3つのメソッドを実装すればHDFSデータの透過暗号化に利用できます。
残りのメソッドはKEKの作成(createKey)、削除(deleteKey)、リスト(getKeys, getKeysMetadata, getKeyVersions)、ローリング(rollNewVersion)をそれぞれ担いますが、代替の運用手順を用意できる場合実装は必須ではありません。

なお、KeyProvider APIはHadoop 3.3.1現在 @Unstable となっていますが、互換性を懸念する必要はありません。
Hadoopにおける @Unstable は今後非互換の変更が行われうることを意味しますが、KeyProvider APIにおいては初登場のHadoop 2.6.0以来非互換の変更が行われたことはなく、またApache Rangerは2015年からKeyProvider APIを利用しています。
これらの実績からHadoop 3.4.0では @Stable に変更される予定です(HADOOP-17544)。

Hadoop KMS

Hadoop KMSはJettyによるWebサーバーであり、HadoopクライアントやNameNodeからのリクエストを受け付け、アクセス制御で認可されたリクエストに対してキーの受け渡しを行います。

上のシーケンス図ではNameNodeはファイル作成のリクエストがあるたびにHadoop KMSにEDEKを問い合わせているように見えますが、実際はパフォーマンス向上のため事前にまとまった量のEDEKをキャッシュとして事前確保し、HDFSファイル作成のたびにNameNode単体でEDEKを払い出す実装となっています。
デフォルトではNameNode起動時に500個のEDEKを事前確保し、30%にあたる150個を下回らないよう定期的にNameNodeからKMSに問い合わせ・EDEKの補充を行っています。

Hadoop KMSはHDFSのパーミッションとは独立したアクセス制御を行っており、HDFSの認証が漏えいした場合であってもKMSによってアクセスを防ぐといった設計が可能となっています。
一例として hdfs ユーザーはHDFSにおけるスーパーユーザーですが、KMSによって明示的に hdfs ユーザーへの許可が無い限り暗号化済みのHDFSデータの復号・参照は抑止されます。

HDFSの任意の空ディレクトリに対して対応するKEKを hdfs crypto -createZone コマンドで紐付けることで、対象のディレクトリ以下に対する透過暗号化を有効化します。
このディレクトリとKEKの組をEncryption Zoneと呼び、それぞれ読み書き可能なユーザー・グループを管理することでアクセス制御を行います。
KMSのアクセス制御は kms-acls.xml ファイルで管理され、変更はKMSの再起動なしにホットリロードによって認識・更新されます。
キー暗号化キー keyA で暗号化されているHDFS上のパス(Encryption Zone)をユーザー userA が読み書きしたい場合には、下記のような設定になります。

<property>  
  <name>key.acl.keyA.DECRYPT_EEK</name>  
  <value>userA</value>  
</property>  
<property>  
  <name>key.acl.keyA.READ</name>  
  <value>userA</value>  
</property>

注意点として、なりすまし(impersonation)機能によるアクセスをKMSが正常に処理できないバグが報告されています(HDFS-13697)。
解決までは該当のEncryption Zoneに対してimpersonation元のユーザーを明示的に許可するワークアラウンドが必要になります。

Hadoop KMSの冗長化

Hadoop KMSがダウンするとDEKの生成から暗号化・EDEKの復号が共に失敗し、暗号化されたHDFSデータへの読み書き共に不可能となるため、本番運用にあたっては複数台での冗長化が不可欠です。

core-siteの hadoop.security.key.provider.path に複数のKMS URIを記述すると、いずれかのKMSにランダムに接続する挙動となりロードバランサーなしに冗長化できますが、クライアントを含めた複数台にエンドポイント設定が分散するためサービスイン・アウト時のメンテナンスコストが増大します。
ヘルスチェック付きのロードバランサーを利用して確実に正常なKMSにリクエストすることでクライアントのリトライのコストを最小化でき、サービスイン・アウト操作も容易なため、ロードバランサーを利用しての冗長化が推奨されます。

複数台のKMSを動作させる場合には ZKDelegationTokenSecretManager を利用してKMS間でHadoop認証トークン(Delegation Token)を同期し、また hadoop.security.token.service.use_ipfalse に設定してホスト名をTLS証明書に一致させる必要があります(HADOOP-17794, HADOOP-12665)。

Hadoop KMSのパフォーマンスチューニング

現状のHadoop KMSのRPC処理性能はNameNodeを下回るため、パフォーマンスチューニングが必要になります。
チューニングの手法として

  • SSLセッションサイズの縮小
  • HTTPSアイドルタイムアウトを設定
  • ファイルディスクリプタ上限を引き上げ

などが挙げられます。
また、KMS同様にJetty(HttpServer2)で大量のコネクションを処理しているHttpFSに対するチューニングも参考になります。
HADOOP-15743でチューニング手法についての議論がなされています。

おわりに

本記事ではHadoopクラスタにおける通信と蓄積の2点に着目して、本番環境への適用を見据えた機能・設定、適用手法を紹介しました。
ApacheCon Asia 2021での登壇内容から、ローリングアップデート手順やApplicationMasterの課題に対する解決策の加筆などより実践的な内容となりました。

世界で稼働するHadoopクラスタのより一層のセキュアな運用に向けて、少しでも参考になる部分があれば幸いです。
最後までお読み頂きありがとうございました。

(この記事に関連する採用情報「データプラットフォームエンジニア(AIプラットフォーム/データ基盤/DBA)」もぜひご覧ください)


桑折 慧
データプラットフォームエンジニア
2017年中途入社。Hadoopを中心としたデータ基盤、およびKubernetesを中心としたAIプラットフォームの構築・開発に従事。

関連記事

このページの先頭へ