HDFS 實作透明、端對端加密。設定後,從特殊 HDFS 目錄讀取和寫入的資料會透明地加密和解密,而無需變更使用者應用程式程式碼。此加密也為端對端,意即資料只能由客戶端加密和解密。HDFS 絕不會儲存或存取未加密的資料或未加密的資料加密金鑰。這符合加密的兩個典型需求:靜態加密(表示持續性媒體(例如磁碟)上的資料)以及傳輸中加密(例如當資料透過網路傳輸時)。
加密可以在傳統資料管理軟體/硬體堆疊的不同層級執行。選擇在特定層級加密有不同的優點和缺點。
應用程式層級加密。這是最安全且最彈性的方法。應用程式對加密的內容擁有最終控制權,並且可以精確反映使用者的需求。然而,撰寫應用程式來執行此操作很困難。對於不支援加密的現有應用程式客戶來說,這也不是一個選項。
資料庫層級加密。在屬性方面類似於應用程式層級加密。大多數資料庫供應商都提供某種形式的加密。然而,可能會出現效能問題。一個範例是索引無法加密。
檔案系統層級加密。此選項提供高效能、應用程式透明性,而且通常容易部署。然而,它無法建模某些應用程式層級政策。例如,多租戶應用程式可能想要根據最終使用者加密。資料庫可能想要為儲存在單一檔案中的每個欄位設定不同的加密設定。
磁碟層級加密。容易部署且高效能,但也很不靈活。實際上只能防範實體竊取。
HDFS 層級加密在這個堆疊中介於資料庫層級和檔案系統層級加密之間。這有許多正面的影響。HDFS 加密能夠提供良好的效能,而且現有的 Hadoop 應用程式能夠在加密資料上透明地執行。在制定政策決策時,HDFS 也比傳統檔案系統擁有更多背景脈絡。
HDFS 層級加密也能防止檔案系統層級及以下(所謂的「作業系統層級攻擊」)的攻擊。由於資料已經由 HDFS 加密,因此作業系統和磁碟只會與加密的位元組互動。
資料加密是許多不同的政府、財務和法規實體所要求的。例如,醫療保健產業有 HIPAA 法規,信用卡支付產業有 PCI DSS 法規,而美國政府有 FISMA 法規。將透明加密建置到 HDFS 中,讓組織更容易遵守這些法規。
加密也可以在應用程式層級執行,但透過將其整合到 HDFS 中,現有的應用程式可以在不變更的情況下操作加密資料。此整合架構意味著更強的加密檔案語意,以及與其他 HDFS 功能更好的協調。
對於透明加密,我們在 HDFS 中引入一個新的抽象概念:加密區。加密區是一個特殊目錄,其內容會在寫入時透明加密,並在讀取時透明解密。每個加密區會與一個單一的加密區金鑰關聯,此金鑰會在建立區時指定。加密區中的每個檔案都有其自己獨特的資料加密金鑰 (DEK)。DEK 永遠不會由 HDFS 直接處理。相反地,HDFS 永遠只會處理已加密資料加密金鑰 (EDEK)。用戶端會解密 EDEK,然後使用後續的 DEK 來讀取和寫入資料。HDFS 資料節點只會看到一個加密位元組串流。
加密一個非常重要的使用案例是「開啟」加密,並確保整個檔案系統中的所有檔案都已加密。為了支援這個強而有力的保證,同時不失去在檔案系統不同部分使用不同加密區金鑰的彈性,HDFS 允許巢狀加密區。在建立加密區後(例如,在根目錄 /
上),使用者可以在其後代目錄(例如,/home/alice
)上使用不同的金鑰建立更多加密區。檔案的 EDEK 將使用來自最近祖先加密區的加密區金鑰產生。
需要一個新的叢集服務來管理加密金鑰:Hadoop 金鑰管理伺服器 (KMS)。在 HDFS 加密的背景下,KMS 執行三項基本職責
提供對已儲存加密區金鑰的存取
為 NameNode 上的儲存產生新的已加密資料加密金鑰
為 HDFS 用戶端使用解密已加密資料加密金鑰
KMS 將在以下更詳細地說明。
在加密區中建立新檔案時,NameNode 會要求 KMS 使用加密區的金鑰產生新的已加密 EDEK。然後,EDEK 會永久儲存在 NameNode 上的檔案的元資料中。
在加密區域中讀取檔案時,NameNode 會提供 EDEK 和用於加密 EDEK 的加密區域金鑰版本給用戶端。用戶端接著會要求 KMS 解密 EDEK,其中包含檢查用戶端是否有權限存取加密區域金鑰版本。假設成功,用戶端會使用 DEK 解密檔案內容。
讀取和寫入路徑的所有上述步驟會透過 DFSClient、NameNode 和 KMS 之間的互動自動執行。
存取加密檔案資料和元資料受 HDFS 檔案系統權限控制。這表示如果 HDFS 遭到入侵(例如透過未經授權存取 HDFS 超級使用者帳戶),惡意使用者只能存取密文和加密金鑰。然而,由於存取加密區域金鑰受 KMS 和金鑰儲存空間上的一組獨立權限控制,因此這不會構成安全威脅。
KMS 是代表 HDFS 背景程式和用戶端與後端金鑰儲存空間介面的代理程式。後端金鑰儲存空間和 KMS 都實作 Hadoop KeyProvider API。請參閱 KMS 文件 以取得更多資訊。
在 KeyProvider API 中,每個加密金鑰都有唯一的金鑰名稱。由於金鑰可以輪替,因此金鑰可以有多個金鑰版本,其中每個金鑰版本都有自己的金鑰資料(加密和解密期間使用的實際機密位元組)。可以透過金鑰名稱擷取加密金鑰,傳回金鑰的最新版本,或透過特定金鑰版本。
KMS 實作了額外功能,可建立和解密加密加密金鑰 (EEK)。EEK 的建立和解密完全在 KMS 中進行。重要的是,要求建立或解密 EEK 的客戶端絕不會處理 EEK 的加密金鑰。若要建立新的 EEK,KMS 會產生新的隨機金鑰,使用指定的金鑰加密,並將 EEK 傳回給客戶端。若要解密 EEK,KMS 會檢查使用者是否有權存取加密金鑰,並使用它來解密 EEK,然後傳回已解密的加密金鑰。
在 HDFS 加密的情況下,EEK 是加密資料加密金鑰 (EDEK),其中資料加密金鑰 (DEK)用於加密和解密檔案資料。通常,金鑰儲存庫會設定為只允許最終使用者存取用於加密 DEK 的金鑰。這表示 EDEK 可以安全地儲存在 HDFS 中並由 HDFS 處理,因為 HDFS 使用者無法存取未加密的加密金鑰。
必要的先決條件是 KMS 的執行個體,以及 KMS 的備份金鑰儲存庫。請參閱 KMS 文件 以取得更多資訊。
一旦設定好 KMS,且 NameNode 和 HDFS 客戶端已正確設定,管理員可以使用 hadoop key
和 hdfs crypto
命令列工具來建立加密金鑰並設定新的加密區域。現有的資料可以透過使用 distcp 等工具複製到新的加密區域中來加密。
在與用於讀取和寫入加密區域的加密金鑰互動時要使用的 KeyProvider。HDFS 客戶端會使用透過 getServerDefaults 從 Namenode 傳回的提供者路徑。如果 namenode 不支援傳回金鑰提供者 URI,則會使用客戶端的 conf。
給定加密編解碼器的字首,包含給定加密編解碼器的實作類別的逗號分隔清單 (例如 EXAMPLECIPHERSUITE)。如果可用,會使用第一個實作,其他則是備援。
預設值:org.apache.hadoop.crypto.OpensslAesCtrCryptoCodec, org.apache.hadoop.crypto.JceAesCtrCryptoCodec
AES/CTR/NoPadding 的加密編解碼器實作的逗號分隔清單。如果可用,會使用第一個實作,其他則是備援。
預設值:AES/CTR/NoPadding
加密編解碼器的密碼組。
預設值:無
CryptoCodec 中使用的 JCE 提供者名稱。
預設值:8192
CryptoInputStream 和 CryptoOutputStream 使用的緩衝區大小。
預設值:100
列出加密區時,一批次中會傳回的最大區數。分批次遞增式擷取清單可提升 Namenode 效能。
crypto
命令列介面用法:[-createZone -keyName <keyName> -path <path>]
建立新的加密區。
路徑 | 要建立的加密區路徑。它必須是空的目錄。會在這個路徑下提供一個垃圾桶目錄。 |
金鑰名稱 | 要使用的加密區金鑰名稱。不支援大寫金鑰名稱。 |
用法:[-listZones]
列出所有加密區。需要超級使用者權限。
用法:[-provisionTrash -path <path>]
為加密區提供垃圾桶目錄。
路徑 | 加密區根目錄的路徑。 |
用法:[-getFileEncryptionInfo -path <path>]
從檔案取得加密資訊。這可以用來找出檔案是否已加密,以及用來加密檔案的金鑰名稱/金鑰版本。
路徑 | 要取得加密資訊的檔案路徑。 |
用法:[-reencryptZone <action> -path <zone>]
重新加密加密區,方法是逐一瀏覽加密區,並呼叫 KeyProvider 的 reencryptEncryptedKeys 介面,以使用金鑰提供者中的最新版本加密區金鑰批次重新加密所有檔案的 EDEK。需要超級使用者權限。
請注意,由於快照的不可變性質,重新加密不適用於快照。
動作 | 要執行的重新加密動作。必須是 -start 或 -cancel 。 |
路徑 | 加密區根目錄的路徑。 |
重新加密是 HDFS 中僅限 NameNode 的操作,因此可能會對 NameNode 造成大量負載。可以變更下列設定來控制 NameNode 的壓力,具體取決於叢集可接受的吞吐量影響。
dfs.namenode.reencrypt.batch.size | 要傳送至 KMS 進行重新加密的一批次中的 EDEK 數。每個批次在持有名稱系統讀寫鎖定時處理,批次之間會進行節流。請參閱下列設定。 |
dfs.namenode.reencrypt.throttle.limit.handler.ratio | 重新加密期間要保留的讀取鎖定比例。1.0 表示沒有限制。0.5 表示重新加密最多可保留 50% 的總處理時間的讀取鎖定。負值或 0 無效。 |
dfs.namenode.reencrypt.throttle.limit.updater.ratio | 重新加密期間要保留的寫入鎖定比例。1.0 表示沒有限制。0.5 表示重新加密最多可保留 50% 的總處理時間的寫入鎖定。負值或 0 無效。 |
用法:[-listReencryptionStatus]
列出所有加密區域的重新加密資訊。需要超級使用者權限。
這些說明假設您以一般使用者或 HDFS 超級使用者身分執行,視情況而定。視您的環境需要而使用 sudo
。
# As the normal user, create a new encryption key hadoop key create mykey # As the super user, create a new empty directory and make it an encryption zone hadoop fs -mkdir /zone hdfs crypto -createZone -keyName mykey -path /zone # chown it to the normal user hadoop fs -chown myuser:myuser /zone # As the normal user, put a file in, read it out hadoop fs -put helloWorld /zone hadoop fs -cat /zone/helloWorld # As the normal user, get encryption information from the file hdfs crypto -getFileEncryptionInfo -path /zone/helloWorld # console output: {cipherSuite: {name: AES/CTR/NoPadding, algorithmBlockSize: 16}, cryptoProtocolVersion: CryptoProtocolVersion{description='Encryption zones', version=1, unknownValue=null}, edek: 2010d301afbd43b58f10737ce4e93b39, iv: ade2293db2bab1a2e337f91361304cb3, keyName: mykey, ezKeyVersionName: mykey@0}
distcp 的一個常見使用案例是在叢集之間複製資料,以進行備份和災難復原。這通常由叢集管理員執行,而叢集管理員是 HDFS 超級使用者。
為了在使用 HDFS 加密時啟用此相同的工作流程,我們引進了一個新的虛擬路徑前置詞 /.reserved/raw/
,讓超級使用者可以直接存取檔案系統中的基礎區塊資料。這讓超級使用者可以 distcp 資料,而不需要存取加密金鑰,同時也能避免解密和重新加密資料的開銷。這也表示來源和目的地資料將逐位元組完全相同,如果資料使用新的 EDEK 重新加密,則不會如此。
在使用 /.reserved/raw
來 distcp 加密資料時,務必使用 -px 旗標保留延伸屬性。這是因為加密的檔案屬性(例如 EDEK)會透過 /.reserved/raw
中的延伸屬性公開,且必須保留才能解密檔案。這表示如果在加密區域根目錄或其上層目錄啟動 distcp,如果目的地尚未存在加密區域,它將自動在目的地建立加密區域。不過,仍建議管理員先在目的地叢集建立相同的加密區域,以避免任何潛在的意外事件。
預設情況下,distcp 會比較檔案系統提供的檢查碼,以驗證資料是否已成功複製到目的地。從未加密或已加密的位置複製到已加密的位置時,檔案系統檢查碼將不匹配,因為基礎區塊資料不同,因為將使用新的 EDEK 在目的地進行加密。在此情況下,請指定 -skipcrccheck 和 -update distcp 旗標以避免驗證檢查碼。
HDFS 限制跨加密區域邊界的檔案和目錄重新命名。這包括將加密檔案/目錄重新命名為未加密目錄(例如,hdfs dfs mv /zone/encryptedFile /home/bob
),將未加密檔案或目錄重新命名為加密區域(例如,hdfs dfs mv /home/bob/unEncryptedFile /zone
),以及在兩個不同的加密區域之間重新命名(例如,hdfs dfs mv /home/alice/zone1/foo /home/alice/zone2
)。在這些範例中,/zone
、/home/alice/zone1
和 /home/alice/zone2
是加密區域,而 /home/bob
則不是。僅當來源和目的地路徑位於同一個加密區域中,或兩個路徑都未加密(不在任何加密區域中)時,才允許重新命名。
此限制顯著增強了安全性並簡化了系統管理。加密區域下的所有檔案 EDEK 都使用加密區域金鑰加密。因此,如果加密區域金鑰遭到入侵,則識別所有容易受攻擊的檔案並重新加密它們非常重要。如果最初在加密區域中建立的檔案可以重新命名為檔案系統中的任意位置,這在根本上很困難。
為了遵守上述規則,每個加密區域在「區域目錄」下都有自己的 .Trash
目錄。例如,在 hdfs dfs rm /zone/encryptedFile
之後,encryptedFile
將移至 /zone/.Trash
,而不是使用者家目錄下的 .Trash
目錄。當整個加密區域被刪除時,「區域目錄」將移至使用者家目錄下的 .Trash
目錄。
如果加密區域是根目錄(例如,/
目錄),則根目錄的廢紙簍路徑是 /.Trash
,而不是使用者家目錄下的 .Trash
目錄,而根目錄中子目錄或子檔案的重新命名行為將與一般加密區域中的行為保持一致,例如本節開頭提到的 /zone
。
Hadoop 2.8.0 之前的 crypto
命令不會自動配置 .Trash
目錄。如果在 Hadoop 2.8.0 之前建立加密區域,然後將叢集升級到 Hadoop 2.8.0 或以上版本,則可以使用 -provisionTrash
選項配置廢紙簍目錄(例如,hdfs crypto -provisionTrash -path /zone
)。
這些漏洞假設攻擊者已取得叢集機器(即資料節點和名稱節點)硬碟的實體存取權。
存取包含資料加密金鑰的程序的交換檔案。
這本身並不會公開明文,因為它也需要存取加密的區塊檔案。
這可以透過停用交換、使用加密的交換,或使用 mlock 來防止金鑰被交換出去而獲得緩解。
存取加密的區塊檔案。
這些漏洞假設攻擊者已取得叢集機器(例如資料節點和名稱節點)的 root shell 存取權。其中許多漏洞無法在 HDFS 中解決,因為惡意的 root 使用者可以存取持有加密金鑰和明文的處理程序的記憶體中狀態。對於這些漏洞,唯一的緩解技術是仔細限制和監控 root shell 存取權。
存取加密的區塊檔案。
傾印用戶端處理程序的記憶體以取得 DEK、委派權杖、明文。
記錄網路流量以偵測傳輸中的加密金鑰和加密資料。
傾印資料節點處理程序的記憶體以取得加密的區塊資料。
傾印名稱節點處理程序的記憶體以取得加密資料加密金鑰。
這些漏洞假設攻擊者已入侵 HDFS,但沒有 root 或 hdfs
使用者的 shell 存取權。
存取加密的區塊檔案。
透過 -fetchImage 存取加密區域和加密的檔案元資料(包括加密的資料加密金鑰)。
流氓使用者可以收集他們有權存取的檔案的金鑰,並在稍後使用這些金鑰來解密這些檔案的加密資料。由於使用者有權存取這些檔案,因此他們已經可以存取檔案內容。這可以透過定期金鑰輪替政策來獲得緩解。通常在金鑰輪替後需要 reencryptZone 指令,以確保現有檔案上的 EDEK 使用新版本的金鑰。
以下是完整的金鑰輪替和重新加密的手動步驟。這些說明假設您以金鑰管理員或 HDFS 超級使用者的身分執行,視情況而定。
# As the key admin, roll the key to a new version hadoop key roll exposedKey # As the super user, re-encrypt the encryption zone. Possibly list zones first. hdfs crypto -listZones hdfs crypto -reencryptZone -start -path /zone # As the super user, periodically check the status of re-encryption hdfs crypto -listReencryptionStatus # As the super user, get encryption information from the file and double check it's encryption key version hdfs crypto -getFileEncryptionInfo -path /zone/helloWorld # console output: {cipherSuite: {name: AES/CTR/NoPadding, algorithmBlockSize: 16}, cryptoProtocolVersion: CryptoProtocolVersion{description='Encryption zones', version=2, unknownValue=null}, edek: 2010d301afbd43b58f10737ce4e93b39, iv: ade2293db2bab1a2e337f91361304cb3, keyName: exposedKey, ezKeyVersionName: exposedKey@1}