【轉】Hadoop安全實踐

前言

在2014年初,咱們將線上使用的 Hadoop 1.0 集羣切換到 Hadoop 2.2.0 穩定版, 與此同時部署了 Hadoop 的安全認證。本文主要介紹在 Hadoop 2.2.0 上部署安全認證的方案調研實施以及相應的解決方法。html

背景

集羣安全措施相對薄弱

最先部署Hadoop集羣時並無考慮安全問題,隨着集羣的不斷擴大, 各部門對集羣的使用需求增長,集羣安全問題就顯得頗爲重要。說到安全問題,通常包括以下方面:java

  • 用戶認證(Authentication)
    便是對用戶身份進行覈對, 確認用戶便是其聲明的身份, 這裏包括用戶和服務的認證。node

  • 用戶受權(Authorization)
    便是權限控制,對特定資源, 特定訪問用戶進行受權或拒絕訪問。用戶受權是創建再用戶認證的基礎上, 沒有可靠的用戶認證談不上用戶受權。git

未開啓安全認證時,Hadoop 是以客戶端提供的用戶名做爲用戶憑證, 通常便是發起任務的Unix 用戶。通常線上機器部署服務會採用統一帳號,當以統一帳號部署集羣時,全部執行 Hadoop 任務的用戶都是集羣的超級管理員,容易發生誤操做。即使是以管理員帳號部署集羣,惡意用戶在客戶端仍然能夠冒充管理員帳號執行。github

集羣總體升級到 hadoop 2.0

2013年10月份 Hadoop 2.2.0 發佈,做爲 Apache Hadoop 2.X 的 GA 版本。咱們考慮將集羣總體升級 Hadoop 2.2.0,進入 yarn 時代。與此同時,咱們計劃在升級過程當中一併把集羣安全工做作到位,主要基於如下考慮:web

  • 與升級工做同樣,安全一樣是基礎工做,把安全搞好會方便咱們後續的工做,不然會成爲下一個阻礙。
  • 所謂基礎工做,就是越日後改動越難的工做,目前不作,未來依賴更多,開展代價更大。

綜上,咱們的需求是在低版本hadoop升級到Yarn的過程當中部署Hadoop安全認證,作好認證以後咱們能夠在此之上開啓適當的權限控制(hdfs, 隊列)。shell

方案調研

在方案調研以前先明確如下安全實踐的原則,以下:apache

  • 作爲一個後端服務平臺,部署安全的主要目的是防止用戶誤操做致使的事故(好比誤刪數據,誤操做等)
  • 作安全是爲了開放,開放的前提是保證基本的安全,數據安全與平臺安全
  • 在保證安全的前提下,儘可能簡化運維

分析咱們遇到的問題,這裏咱們須要調研:後端

  • 帳號拆分與相應管理方案
  • 開啓 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 任務提交過程簡單說明一下:
 Yarn 任務提交步驟

  1. 用戶執行任務前,先經過KDC認證本身,獲取TGT(Ticket Granting Ticket)。KDC是 Kerberos 認證的中心服務,存儲用戶和服務的認證信息。
  2. 用戶經過 TGT 向 KDC 請求訪問服務的Ticket, KDC 生成 session key 後一併發給客戶端。
  3. 客戶端經過 service ticket 向服務認證本身,完成身份認證。
  4. 完成身份認證後客戶端向服務請求若干token供後續任務執行認證使用(好比 HDFS NameNode Delegation Token, YARN ResourceManager Delegation Token)
  5. 客戶端連同獲取到的 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集羣的服務來講,上述假設就不成立了。這時候咱們能夠

  1. 擴大 ticket_lifetime 和 renew_lifetime 時限
    擴大憑證存活時限能夠解決此問題,但因爲Kerberos跟咱們線上用戶登錄認證綁定,會帶來安全隱患,故不方便修改。

  2. 按期從新進行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的認證過程以下:

  1. An application instantiates a LoginContext.
  2. The LoginContext consults a Configuration to load all of the LoginModules configured for that application.
  3. The application invokes the LoginContext's login method.
  4. 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.
  5. The LoginContext returns the authentication status to the application.
  6. 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 配置認證:

  1. 經過配置生成 LoginContext
  2. 調用 LoginContext.login 方法完成登錄, 經過 ticket cache 中憑證完成登錄
  3. 判斷是否須要其餘用戶身份(proxy user)執行
  4. 將 HADOOP_TOKEN_FILE_LOCATION 中的 token 加入 Credentials 集合當中
  5. 另起一個線程作週期性的憑證更新 spawnAutoRenewalThreadForUserCreds

步驟5能夠看出當咱們存在憑證後並不須要主動作週期性地憑證更新。

而 loginUserFromKeytab 方法使用 hadoop-kerberos 配置認證:

  1. 經過配置生成 LoginContext
  2. 調用 LoginContext.login 方法完成登錄, 使用keytab完成登錄

loginUserFromKeytab 沒有對憑證作週期的更新, 那怎麼保證憑證不會過時呢?

  1. 在訪問集羣執行相關操做前, 能夠調用 checkTGTAndReloginFromKeytab 來嘗試更新憑證(其實是從新登錄了)
  2. 在憑證過時時,建立 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: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: 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

美團點評技術團隊

相關文章
相關標籤/搜索