Hadoop安全实践
前言
在2014年初,我們將線上使用的 Hadoop 1.0 集群切換到?Hadoop 2.2.0 穩定版, 與此同時部署了 Hadoop 的安全認證。本文主要介紹在 Hadoop 2.2.0 上部署安全認證的方案調研實施以及相應的解決方法。
背景
集群安全措施相對薄弱
最早部署Hadoop集群時并沒有考慮安全問題,隨著集群的不斷擴大, 各部門對集群的使用需求增加,集群安全問題就顯得頗為重要。說到安全問題,一般包括如下方面:
-
用戶認證(Authentication)
即是對用戶身份進行核對, 確認用戶即是其聲明的身份, 這里包括用戶和服務的認證。 -
用戶授權(Authorization)
即是權限控制,對特定資源, 特定訪問用戶進行授權或拒絕訪問。用戶授權是建立再用戶認證的基礎上, 沒有可靠的用戶認證談不上用戶授權。
未開啟安全認證時,Hadoop 是以客戶端提供的用戶名作為用戶憑證, 一般即是發起任務的Unix 用戶。一般線上機器部署服務會采用統一賬號,當以統一賬號部署集群時,所有執行 Hadoop 任務的用戶都是集群的超級管理員,容易發生誤操作。即便是以管理員賬號部署集群,惡意用戶在客戶端仍然可以冒充管理員賬號執行。
集群整體升級到 hadoop 2.0
2013年10月份 Hadoop 2.2.0 發布,作為 Apache Hadoop 2.X 的 GA 版本。我們考慮將集群整體升級 Hadoop 2.2.0,進入 yarn 時代。與此同時,我們計劃在升級過程中一并把集群安全工作做到位,主要基于以下考慮:
- 與升級工作一樣,安全同樣是基礎工作,把安全搞好會方便我們后續的工作,否則會成為下一個阻礙。
- 所謂基礎工作,就是越往后改動越難的工作,目前不做,將來依賴更多,開展代價更大。
綜上,我們的需求是在低版本hadoop升級到Yarn的過程中部署Hadoop安全認證,做好認證之后我們可以在此之上開啟適當的權限控制(hdfs, 隊列)。
方案調研
在方案調研之前先明確以下安全實踐的原則,如下:
- 做為一個后端服務平臺,部署安全的主要目的是防止用戶誤操作導致的事故(比如誤刪數據,誤操作等)
- 做安全是為了開放,開放的前提是保證基本的安全,數據安全與平臺安全
- 在保證安全的前提下,盡量簡化運維
分析我們遇到的問題,這里我們需要調研:
- 賬號拆分與相應管理方案
- 開啟 Hadoop 安全認證
- 客戶端針對安全認證的相應調整
賬號拆分與相應管理方案
集群賬號管理
原先我們使用單一賬號作為集群管理員,且這一賬號為線上統一登錄賬號, 這存在極大的安全隱患。我們需要使用特殊賬號來管理集群。這里涉及的問題是,我們需要幾個運維賬號呢?
一種簡單的做法是使用一個特殊運維賬號(比如 hadoop),?CDH?和?Apache官方?都推薦按服務劃分分賬號來啟動集群:
| hdfs:hadoop | NameNode, Secondary NameNode, Checkpoint Node, Backup Node, DataNode |
| yarn:hadoop | ResourceManager, NodeManager |
| mapred:hadoop | MapReduce JobHistory Server |
考慮到精細化控制可以有效避免誤操作,這里我們遵循官方的建議使用多賬號。
在從單一運維賬號遷移到多個賬號部署時,需要考慮相關文件權限問題,包括本地以及hdfs兩部分,這可以在安全部署上線時完成相應改動。
用戶賬號管理
美團很多小組都有使用 Hadoop 來進行大數據處理需求, 故需要一定程度的多租戶環境, 這里主要考慮其中的數據和操作的權限問題。hdfs 本身僅提供類 Unix 的權限系統, 默認的組概念也相對雞肋。鑒于此,在多用戶的管理上可以有簡單粗暴的方案:
不同組有各自的根目錄,使用不同的賬號,對組內文件有全部權限。不同組之間相互不能訪問數據(除非手動修改)。
在一個集中的數據倉庫環境下,又要生產各個部門的統計數據的話,上述策略不夠靈活。目前Cloudera 有一個精細化權限控制的解決方案?sentry, 支持 Role based 的權限管理。由于其定制化較高,不方便使用, 故暫未考慮。
開啟 Hadoop 安全認證
Hadoop 的安全認證是基于?Kerberos?實現的。 Kerberos 是一個網絡身份驗證協議,用戶只需輸入身份驗證信息,驗證通過獲取票據即可訪問多個接入 Kerberos 的服務, 機器的單點登錄也可以基于此協議完成的。 Hadoop 本身并不創建用戶賬號,而是使用 Kerberos 協議來進行用戶身份驗證,從Kerberos憑證中的用戶信息獲取用戶賬號, 這樣一來跟實際用戶運行的賬號也無關。
這里我們從 YARN 上的 MR 任務提交過程簡單說明一下:
從上可以看出,出于性能的考慮,Hadoop 安全認證體系中僅在用戶跟服務通信以及各個服務之間通信適用 Kerberos 認證,在用戶認證后任務執行,訪問服務,讀取/寫入數據等均采用特定服務(NameNode, Resource Manager)發起訪問token,讓需求方憑借 token 訪問相應服務和數據。這里 token 的傳遞,認證以及更新不做深入討論。
關于開啟 Hadoop 安全認證,?Cloudera?有詳細的文檔介紹。由于自身環境以及部署運維的考慮,最終的部署方案有些許出入, 一一說明。
Kerberos 部署
Hadoop 安全認證需要一個 Kerberos 集群,?部署 Kerberos?需要部署KDC。 由于我們的環境中使用?freeIPA?進行主機認證相關的權限控制,已經集成 Kerberos 服務, 故不需要另外部署。
Kerberos 相關的運維操作, 比如添加用戶,服務,導出keytab,均可以通過 ipa 相關接口來進行。
Container 的選擇
從上圖可以看出用戶發起的任務是在特定的容器(Container)內執行的, 一開始我們考慮使用DefaultContainer 而不是官方推薦的 LinuxContainer, 缺點是對任務之間的物理隔離以及防范惡意任務方面會有缺陷, 不過方便部署,使用LinuxContainer需要在集群各臺機器上部署用戶賬號。
實際測試發現由于MAPREDUCE-5208的引入,在 hadoop 2.2.0 上開啟安全認證后無法使用 DefaultContainer。
這里不希望對代碼有過多定制化的修改,我們考慮還是使用 LinuxContainer, 需要解決一下問題:
- 用戶賬號創建
我們需要在集群內添加所有可能的任務發起用戶賬號。借助 freeipa 的統一的用戶管理 , 我們只需要在 freeipa 上添加相應用戶即可。 -
container-executor 和 container-executor.cfg 的部署
container-executor?作為Yarn 的 container 執行程序,有一系列的權限要求:Be owned by root
Be owned by a group that contains only the user running the YARN daemons
Be setuid
Be group readable and executable配置 container-executor.cfg 不僅需要是owned by root,且其所在目錄同樣需要?owned by root。這兩者都給自動化部署帶來不便,鑒于此部分比較獨立且基本不會改變,我們可以將其加入集群機器的 puppet 管理當中。
DataNode 啟動方式
CDH 推薦的datanode 的啟動方式需要使用低端口并且使用jsvc發布, 在運維方面也不太方便。這里我們通過配置ignore.secure.ports.for.testing=true來啟動datanode, 規避這些約束。
客戶端針對安全認證的相應調整
集群開啟安全認證之后, 依賴集群的客戶端(腳本, 服務)都需要做相應修改,不過改動基本無異。大部分服務都已包括對 Kerberos 認證的相應處理, 基本不需要修改。
這里首先得說明一下開啟安全認證后的認證方式:
- 使用密碼認證
使用用戶密碼通過kinit認證, 獲取到的TGT存在本地憑證緩存當中, 供后續訪問服務認證使用。一般在交互式訪問中使用。 - 使用?keytab?認證
用戶通過導出的keytab 可以免密碼進行用戶認證, 后續步驟一致。一般在應用程序中配置使用。
Kerberos 憑證(ticket) 有兩個屬性,?ticket_lifetime?和?renew_lifetime。其中?ticket_lifetime?表明憑證生效的時限,一般為24小時。在憑證失效前部分憑證可以延期失效時間(即Renewable),?renew_lifetime?表明憑證最長可以被延期的時限,一般為一個禮拜。當憑證過期之后,對安全認證的服務的后續訪問則會失敗。這里第一個問題就是如何處理憑證過期。
憑證過期處理策略
在最早的?Security features for Hadoop?設計中提出這樣的假設:
A Hadoop job will run no longer than 7 days (configurable) on a MapReduce cluster or accessing HDFS from the job will fail.
對于一般的任務, 24小時甚至延遲到一周的憑證時限是足夠充分的。所以大部分時間我們只需要在執行操作之前使用 kinit 認證一遍,再起后臺任務進行周期性憑證更新即可。
while true ; do kinit -R; sleep $((3600 * 6)) ; done &不過對于需要常駐的訪問Hadoop集群的服務來說,上述假設就不成立了。這時候我們可以
擴大?ticket_lifetime?和?renew_lifetime?時限
擴大憑證存活時限可以解決此問題,但由于Kerberos跟我們線上用戶登陸認證綁定,會帶來安全隱患,故不方便修改。
定期重新進行kinit 認證更新憑證
不僅僅是定期延長認證時間,可以直接定期重新認證以延長憑證有限期限。一般我們需要導出 keytab 來進行定期認證的操作。
Hadoop 將 Kerberos 認證部分進行了一定的封裝,實際上并不需要那么復雜, 這里重點可以看看UserGroupInformation?這個類。
UserGroupInformation
UserGroupInformation?這個類在?JAAS?框架上封裝了 Hadoop 的用戶信息, 更確切地說是對 Subject 做了一層封裝。
UserGroupInformation(Subject subject) {this.subject = subject;this.user = subject.getPrincipals(User.class).iterator().next();this.isKeytab = !subject.getPrivateCredentials(KerberosKey.class).isEmpty();this.isKrbTkt = !subject.getPrivateCredentials(KerberosTicket.class).isEmpty();}JAAS 是 Java 認證和授權服務(Java Authentication and Authorization Service)的縮寫, 主要包含以下幾個實體:
- Subject
Subject 是一個不可繼承的實體類,它標志一個請求的來源, 包含相關的憑證標識(Principal) 和 公開和私有的憑據(Credential)。 - Principal
憑證標識,認證成功后,一個 Subject 可以被關聯多個Principal。 - Credential
憑據,有公有憑據以及私有憑據。
JAAS的認證過程如下:
需要認證的代碼片段可以包裝在?doPrivileged?當中, 可以直接使用?Subject.doAs?方法,支持嵌套。
在安全模式下,UGI 支持不同LoginContext 配置, 均是通過 HadoopConfiguration 類動態產生:
- hadoop-user-kerberos
使用kerberos緩存憑證登陸的配置,?useTicketCache?置為 true. - hadoop-keytab-kerberos
使用keytab登陸的配置,?useKeyTab?置為 true.
UGI 當中有多處認證, getLoginUser 方法使用?hadoop-user-kerberos?配置認證:
步驟5可以看出當我們存在憑證后并不需要主動做周期性地憑證更新。
而 loginUserFromKeytab 方法使用?hadoop-kerberos?配置認證:
loginUserFromKeytab 沒有對憑證做周期的更新, 那怎么保證憑證不會過期呢?
Client.java
private synchronized void handleSaslConnectionFailure(final int currRetries, final int maxRetries, final Exception ex,final Random rand, final UserGroupInformation ugi) throws IOException,InterruptedException {ugi.doAs(new PrivilegedExceptionAction<Object>() {@Overridepublic Object run() throws IOException, InterruptedException {final short MAX_BACKOFF = 5000;closeConnection();disposeSasl();if (shouldAuthenticateOverKrb()) {if (currRetries < maxRetries) {if(LOG.isDebugEnabled()) {LOG.debug("Exception encountered while connecting to "+ "the server : " + ex);}// try re-loginif (UserGroupInformation.isLoginKeytabBased()) {UserGroupInformation.getLoginUser().reloginFromKeytab();} else {UserGroupInformation.getLoginUser().reloginFromTicketCache();}可見如果是使用 keytab 認證的話,認證是長期有效的。
從上述代碼中可以看到,不論是否是keytab認證,創建IPC失敗均會嘗試重新登陸。
基于keytab 的Kerberos認證方式
為了讓用戶免于記憶密碼,我們可以考慮導出并交付keytab給相關用戶(前提是用戶數量可控, 比如是以虛擬用戶為單位)。
這樣,用戶的Hadoop任務認證方式可以有:
- 直接使用 keytab kinit 之后訪問
- 或者調用?loginUserFromKeytab?完成登錄,然后將代碼片段包裹在 UGI 的?doAs?方法當中執行
上線部署
確定了部署方案之后, 我們在升級 hadoop 版本的同時完成了安全認證的部署。在部署和使用中我們遇到若干問題,這里一一說明。
JCE 部署
開啟安全認證時發現 Kerberos 認證不通過:
Client failed to SASL authenticate: javax.security.sasl.SaslException: GSS initiate failed [Caused by GSSException: Failure unspecified at GSS-API level (Mechanism level: Checksum failed)]由于我們部署的Kerberos默認使用 AES-256 加密, 需要在Hadoop環境(集群以及客戶端)上安裝?Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy File, 否則Kerberos認證不通過。可以通過此?gist?驗證改動是否生效。此步驟可以添加到puppet當中。
SNN getimage 返回 NPE
開啟安全認證發現 SNN 持續由于 getimage 報錯NPE 退出, 相關錯誤如下。
2013-12-29 23:56:19,572 DEBUG org.apache.hadoop.security.authentication.server.AuthenticationFilter: Request [http://XXX.com:50070/getimage?getimage=1&txid=8627&storageInfo=-47:200271 8265:0:CID-3dce02cb-a1c2-4ab8-8b12-f23bbefd7bcc] triggering authentication 2013-12-29 23:56:19,580 WARN org.apache.hadoop.security.authentication.server.AuthenticationFilter: Authentication exception: GSSException: Failure unspecified at GSS-API level (Mechanism level: Specifiedversion of key is not available (44)) org.apache.hadoop.security.authentication.client.AuthenticationException: GSSException: Failure unspecified at GSS-API level (Mechanism level: Specified version of key is not available (44))at org.apache.hadoop.security.authentication.server.KerberosAuthenticationHandler.authenticate(KerberosAuthenticationHandler.java:360)at org.apache.hadoop.security.authentication.server.AuthenticationFilter.doFilter(AuthenticationFilter.java:349) 根據報錯信息?Specified version of key is not available?發現是由于同一個 HTTP 憑證被導出多遍導致之前的keytab中的憑證失效了,重新生成部署所需的 keytab 即可。
這里的提醒就是不要重復導出相同的憑證, 以防止已經分發使用的keytab中的憑證失效。
Balancer 執行過長導致認證過期
在部署安全認證之后, 我們對hdfs數據進行 balance 就需要預先認證一下再執行, 這樣就會遇到我們之前說的認證期限的問題。
這里有兩種方式可以解決此問題:
- 添加外部定時任務重新認證, 刷新憑證緩存, 延遲憑證有效期限。
- 可以寫一個小代碼對 balance 的入口?org.apache.hadoop.hdfs.server.balancer.Balancer?進行一點封裝,將其封裝在一個 doAs 當中, 類似?hue 中的 SudoFsShell 一樣的思路
sssd 服務認證異常
sssd?是指我們用于線上登陸認證的一個底層服務,在過去一段時間內經常出現問題退出,導致用戶登錄動作hang住,進而導致相關任務執行失敗。部署Hadoop安全認證之后相關 kerberos 認證也走這個服務,增大了服務異常退出的概率。目前看起來sssd服務問題是由于系統版本過低sssd服務代碼有bug導致,解決方案最方便的是升級系統或切換服務到新的機器。
"KDC can't fulfill requested option while renewing credentials"
應用執行日志偶爾會報如下錯誤:
2014-03-12 21:30:03,593 WARN security.UserGroupInformation (UserGroupInformation.java:run(794)) - Exception encountered while running the renewal command. Aborting renew thread. org.apache.hadoop.util.Shell$ExitCodeException: kinit(v5): KDC can't fulfill requested option while renewing credentials表示 UGI的憑證更新線程失敗退出了。目前HADOOP-10041?記錄了此問題,主要原因是由于憑證無法更新導致, 一般不需要特殊處理。
參考資料
- Hadoop, Hbase, Zookeeper安全實踐
- YARN & HDFS2 安裝和配置Kerberos
- CDH4 Security Guide
- Kerberos , the network authentication protocol
- Kerberos Overview
- Hadoop Kerberos安全機制介紹
- Security Hadoop is here
- Hadoop Security Analysis
- The Role of Delegation Tokens in Apache Hadoop Security
總結
以上是生活随笔為你收集整理的Hadoop安全实践的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java内存访问重排序的研究
- 下一篇: 改进合作 Git 工作流:自动提取、合并