標籤:
前言
在2014年初,我們將線上使用的 Hadoop 1.0 叢集切換到 Hadoop 2.2.0 穩定版, 與此同時部署了 Hadoop 的安全認證。本文主要介紹在 Hadoop 2.2.0 上部署安全認證的方案調研實施以及相應的解決方案。
背景叢集安全措施相對薄弱
最早部署Hadoop叢集時並沒有考慮安全問題,隨著叢集的不斷擴大, 各部門對叢集的使用需求增加,叢集安全問題就顯得頗為重要。說到安全問題,一般包括如下方面:
未開啟安全認證時,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官方 都推薦按服務劃分分帳號來啟動叢集:
User:Group |
Daemons |
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 任務提交過程簡單說明一下:
- 使用者執行任務前,先通過KDC認證自己,擷取TGT(Ticket Granting Ticket)。KDC是 Kerberos 認證的中心服務,儲存使用者和服務的認證資訊。
- 使用者通過 TGT 向 KDC 請求訪問服務的Ticket, KDC 產生 session key 後一併發給用戶端。
- 用戶端通過 service ticket 向服務認證自己,完成身份認證。
- 完成身份認證後用戶端向服務要求若干token供後續任務執行認證使用(比如 HDFS NameNode Delegation Token, YARN ResourceManager Delegation Token)
- 用戶端連同擷取到的 token 一併提交任務,後續任務執行使用 token 進行來自服務的認證
從上可以看出,出於效能的考慮,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的認證過程如下:
- An application instantiates a LoginContext.
- The LoginContext consults a Configuration to load all of the LoginModules configured for that application.
- The application invokes the LoginContext‘s login method.
- The login method invokes all of the loaded LoginModules. Each LoginModule attempts to authenticate the subject. Upon success, LoginModules associate relevant Principals and credentials with a Subject object that represents the subject being authenticated.
- The LoginContext returns the authentication status to the application.
- If authentication succeeded, the application retrieves the Subject from the LoginContext.
需要認證的程式碼片段可以封裝在 doPrivileged 當中, 可以直接使用 Subject.doAs
方法,支援嵌套。
在安全模式下,UGI 支援不同LoginContext 配置, 均是通過 HadoopConfiguration 類動態產生:
- hadoop-user-kerberos
使用kerberos緩衝憑證登陸的配置, useTicketCache
置為 true.
- hadoop-keytab-kerberos
使用keytab登陸的配置, useKeyTab
置為 true.
UGI 當中有多處認證, getLoginUser 方法使用 hadoop-user-kerberos
配置認證:
- 通過配置產生 LoginContext
- 調用 LoginContext.login 方法完成登陸, 通過 ticket cache 中憑證完成登陸
- 判斷是否需要其他使用者身份(proxy user)執行
- 將
HADOOP_TOKEN_FILE_LOCATION
中的 token 加入 Credentials 集合當中
- 另起一個線程做周期性的憑證更新
spawnAutoRenewalThreadForUserCreds
步驟5可以看出當我們存在憑證後並不需要主動做周期性地憑證更新。
而 loginUserFromKeytab 方法使用 hadoop-kerberos
配置認證:
- 通過配置產生 LoginContext
- 調用 LoginContext.login 方法完成登陸, 使用keytab完成登陸
loginUserFromKeytab 沒有對憑證做周期的更新, 那怎麼保證憑證不會到期呢?
- 在訪問叢集執行相關操作前, 可以調用
checkTGTAndReloginFromKeytab
來嘗試更新憑證(實際上是重新登陸了)
- 在憑證到期時,建立 IPC 失敗會觸發調用
reloginFromKeytab
來重新登陸
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>() { @Override public 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-login if (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:2002718265:0:CID-3dce02cb-a1c2-4ab8-8b12-f23bbefd7bcc] triggering authentication2013-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: Specified version 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 記錄了此問題,主要原因是由於憑證無法更新導致, 一般不需要特殊處理。
【轉自】:http://tech.meituan.com/hadoop-security-practice.html
美團點評技術團隊
【轉】Hadoop安全實踐