Kerberos從入門到放棄(一):HDFS使用kerberos【轉】


本文嘗試記錄HDFS各服務配置使用kerberos的過程,配置的東西比較多,一定會有疏漏

我的環境:

  • 三台服務器,分別命名為zelda1、zelda2、zelda3
  • ubuntu 14.04
  • hadoop 2.7.2
  • spark 2.0/1.6.1

原理

默認Hadoop各個組件間無任何認證,因此可以惡意偽裝某一組件(比如NameNode)接入到集群中搞破壞。而通過kerberos,可以將密鑰事先放到可靠的節點上並只允許有限制的訪問,該節點的服務啟動時讀取密鑰,並與kerberos交互以做認證,從而接入到hadoop集群中。注意,我們這里主要是針對服務與服務之間的安全認證,沒有涉及user。

有很多文章講解Kerberos的原理,或是過於簡單或是過於復雜,下面盜用網上翻譯的官方示例,可以很清晰的理解其過程:

用戶要去游樂場,首先要在門口檢查用戶的身份(即 CHECK 用戶的 ID 和 PASS), 如果用戶通過驗證,游樂場的門衛 (AS) 即提供給用戶一張門卡 (TGT)。

這張卡片的用處就是告訴游樂場的各個場所,用戶是通過正門進來,而不是后門偷爬進來的,並且也是獲取進入場所一把鑰匙。

現在用戶有張卡,但是這對用戶來不重要,因為用戶來游樂場不是為了拿這張卡的而是為了游覽游樂項目,這時用戶摩天樓,並想游玩。

這時摩天輪的服務員 (client) 攔下用戶,向用戶要求摩天輪的 (ST) 票據,用戶說用戶只有一個門卡 (TGT), 那用戶只要把 TGT 放在一旁的票據授權機 (TGS) 上刷一下。 票據授權機 (TGS) 就根據用戶現在所在的摩天輪,給用戶一張摩天輪的票據 (ST), 這樣用戶有了摩天輪的票據,現在用戶可以暢通無阻的進入摩天輪里游玩了。

當然如果用戶玩完摩天輪后,想去游樂園的咖啡廳休息下,那用戶一樣只要帶着那張門卡 (TGT). 到相應的咖啡廳的票據授權機 (TGS) 刷一下,得到咖啡廳的票據 (ST) 就可以進入咖啡廳。

當用戶離開游樂場后,想用這張 TGT 去刷打的回家的費用,對不起,用戶的 TGT 已經過期了,在用戶離開游樂場那刻開始,用戶的 TGT 就已經銷毀了。

下面還有張圖來說明這個過程。

原理圖

Kerberos有如下幾個概念:

Term Description
Key Distribution Center, or KDC 可信任的認證來源,密鑰分發中心
Kerberos KDC Server KDS服務提供者
Kerberos Client 集群中准備去做kerberos認證的機器
Principal The unique name of a user or service that authenticates against the KDC
Keytab A file that includes one or more principals and their keys.當不方便使用密碼的時候
Realm 我簡單理解為域
KDC Admin Account 管理員賬戶,用來添加其他principal

Principal可以理解為用戶或服務的名字,全集群唯一,由三部分組成:username(or servicename)/instance@realm,例如:nn/zelda1@ZELDA.COM,zelda1為集群中的一台機器;或admin/admin@ZELDA.COM,管理員賬戶。

  • username or servicename:在本文里為服務,HDFS的2個服務分別取名為nn和dn,即namenode和datanode
  • instance:在本文里為具體的FQDN機器名,用來保證全局唯一(比如多個datanode節點,各節點需要各自獨立認證)
  • realm:域,我這里為ZELDA.COM(全大寫喲)

對於HDFS的各個服務來說,keytab更合適:生成一個keytab文件,其包含一個或多個principal+key對,例如在HDFS配置文件里為nn指定的keytab文件:

<property> <name>dfs.namenode.keytab.file</name> <value>/etc/security/nn.service.keytab</value> </property> <property> 

配置

配置DNS服務

如上所述,Principal中的instance為各主機的FQDN名,所以集群中需要有DNS,這里我們用的是dnsmasq,相對比較簡單。 簡單說明下DNS:DNS是一個遞歸服務,以查詢zh.wikipedia.org為例:

客戶端發送查詢報文”query zh.wikipedia.org”至DNS服務器,DNS服務器首先檢查自身緩存,如果存在記錄則直接返回結果。 如果記錄老化或不存在,則

  1. DNS服務器向根域名服務器發送查詢報文”query zh.wikipedia.org”,根域名服務器返回.org域的權威域名服務器地址,這一級首先會返回的是頂級域名的權威域名服務器。
  2. DNS服務器向.org域的權威域名服務器發送查詢報文”query zh.wikipedia.org”,得到.wikipedia.org域的權威域名服務器地址。
  3. DNS服務器向.wikipedia.org域的權威域名服務器發送查詢報文”query zh.wikipedia.org”,得到主機zh的A記錄,存入自身緩存並返回給客戶端。

我們平時上網配置的杭州電信DNS地址(202.101.172.35),對於PC來說只要有DNS解析的需求,都會丟給這個地址的服務器(即為hzdns1)上,那么全球這么多國家這么多地址,顯然不可能都存在杭州電信的hzdns1上,所以它會繼續向上一直到根服務器請求解析。

所以,在我們這里其實可以自己架設了一個dns緩存服務器,集群內所有的DNS請求都先走這個服務器,如果查詢失敗,再由這台DNS緩存服務器向上查詢;同時,我們也可以將集群內各個主機的主機名加到DNS緩存服務器里,這樣集群內的FQDN解析就可以通過這個DNS緩存服務器來完成了(當然,需要將各個機器的DNS nameserver配置為集群內的DNS緩存服務器)。多說一句,對於企業內已有自建DNS的情況,加個接入級的DNS服務器對已有拓撲無任何影響。

我的操作系統是ubuntu 14.04,安裝比較簡單,sudo apt-get install dnsmasq即可。不過我人品比較好,遇到了網易163的ubuntu源錯亂的情況,怎么也get不了,總是提示xx包被依賴但不會被安裝,換用aliyun的源正常了。如果你在杭州,建議更換,服務器在杭州電信,速度特別好:

deb http://mirrors.aliyun.com/ubuntu/ trusty main restricted universe multiverse deb http://mirrors.aliyun.com/ubuntu/ trusty-security main restricted universe multiverse deb http://mirrors.aliyun.com/ubuntu/ trusty-updates main restricted universe multiverse deb http://mirrors.aliyun.com/ubuntu/ trusty-backports main restricted universe multiverse deb http://mirrors.aliyun.com/ubuntu/ trusty-proposed main restricted universe multiverse 

dnsmasq安裝后需要做配置,可以參考我的:

no-resolv no-poll server=202.101.172.35 domain=zelda.com 

值得注意的是,配置upstream DNS有兩個辦法:

  1. 從resolv.conf(or compatible)文件中獲取。但我在配置resolv-file=的時候遇到了些障礙,如果用這種方式,發現dnsmasq總是會請求一個61.x.x.x的DNS地址(溫州電信的DNS)作為其upstream DNS,但其實這個DNS已經故障不能訪問了,即使不用resolv.conf新增一個文件也不能解決。
  2. 指定no-resolv,直接在dnsmasq的配置文件中指定upstream server,對我來說更方便。

檢查DNS服務:各機器的/etc/resolv.conf修改為dnsmasq服務所在機器的地址(zelda2),ping集群內各機器的FQDN,如ping zelda1、ping zelda1.zelda.com、ping www.googlebaidu.com。

配置NTP

Kerberos集群對時間同步敏感度較高,默認時間相差超過5分鍾就會出現問題,所以最好是在集群中增加NTP服務器。不過我的集群機器比較少,先跳過NTP吧。

創建MIT KDC

Kerberos有不同的實現,如MIT KDC、Microsoft Active Directory等。我這里采用的是MIT KDC。安裝過程可以參考ubuntu官網

這段是根據history寫的回憶錄,可能有錯誤,下次安裝的時候記得修正

(一)krb5 MDC機器上:

1、安裝KDC服務和管理員服務

apt-get install krb5-kdc krb5-admin-server 

kdc是服務提供者,admin-server是admin登錄上去做配置的服務提供者。

跟centos、Suse不一樣,UBUNTU的Kr5安裝時會提示要求用戶輸入KDC的realm(如ZELDA.COM)、提供服務的機器名(如zelda2),直接生成krb5的配置文件和realm(/etc/krb5.conf和/var/lib/krb5/principal等),如下:

[libdefaults]
        default_realm = ZELDA.COM
..
[realms]
        ZELDA.COM = {
                kdc = zelda2
                admin_server = zelda2
        }
[domain_realm]
..
        .zelda.com = ZELDA.COM
        zelda.com = ZELDA.COM

注意domain_realm這里,默認生成的文件里沒有這兩行,你可以試試不加是否也沒問題。

2、創建新的realm

!!在ubuntu版本里,不需要做這步,因為上面包安裝的時候已經做了。如果裝包的時候跳過了config階段,才需要再來一把。

krb5_newrealm 

3、創建一個管理員賬戶

建議使用一個跟平時用的賬戶不同的名字。在KDC的機器上:

$ kadmin.local kadmin.local: addprinc steve/admin 

記住自己的密碼。kadmin.local是在KDC所在的機器上使用的,而kadmin是任意機器都可以,方便遠程管理不過我沒用功。

4、修改ACL給新的管理員賬戶權限

修改/etc/krb5kdc/kadm5.acl:

steve/admin@ZELDA.COM * 

當然也可以把steve改為*,這樣任意admin這個instance里的用戶都有管理員權限了。由於后面配置Hadoop各組件的時候我參考了Hortonwork的文檔,所以我還增加了一個admin/admin的用戶:

kadmin.local -q "addprinc $username/admin 

5、重啟服務

/etc/init.d/krb5-kdc restart
/etc/init.d/krb5-admin-server restart

6、使用kinit測試

$ kinit steve/admin passwd: $ klist Ticket cache: FILE:/tmp/krb5cc_0 Default principal: steve/admin@ZELDA.COM Valid starting Expires Service principal 06/06/2016 17:29:07 06/07/2016 03:29:07 krbtgt/ZELDA.COM@ZELDA.COM renew until 06/07/2016 17:29:03 

(二)在各Krb Client機器上

所有機器上安裝krb5的client:

apt-get install krb5-user krb5-config 

為HDFS等組件生成keytab

具體參考hortonworks官網

使用admin賬戶進入kadmin控制台,對於每個服務來說,命令有2條:

addprinc -randkey nn/zelda1@ZELDA.COM xst -k nn.service.keytab nn/zelda1 

第一條是向KDC增加一個principal,之后使用lisprincs可以看到,注意名字按照HDP的要求來; 第二條是導出keytab文件,生成的文件在/var/lib/krb5kdc中。由於拿到了keytab實際也就拿到了Kerberos認證,所以需要將keytab文件放到一個比較隱秘的目錄(例如/etc/security/),並且小心其讀寫權限,只要給最終用戶授權就好了。

HDFS的datanode一般有多個,我的做法是為每台機器單獨生成keytab,各個機器的keytab只包含自己的principal。也有人為所有datanode生成一個keytab文件,然后扔到各個機器上混用,更方便一點,不過我沒用過。

可以使用klist檢查keytab是不是自己想要的:

klist –k -t /etc/security/nn.service.keytab 

配置HDFS

Hadoop的配置分為2步:

  1. 創建Principal和*unix用戶的映射關系。

例如我們上面datanode服務的Principal是dn/zelda1@ZELDA.COM,默認的hadoop.security.auth_to_local規則是將instance和realm去掉,只保留dn,但實際上hdfs的用戶並不是dn(我這里操作hdfs的用戶是dtdream,如果你用包安裝的話可能是叫hdfs,具體可以看hdfs上默認文件的用戶是誰),dn這個用戶在linux機器里也不存在,那么dn連接nn的時候,會提示錯誤找不到這個用戶的組:

2016-06-07 19:06:58,329 INFO SecurityLogger.org.apache.hadoop.ipc.Server: Auth successful for dn/zelda2@ZELDA.COM (auth:KERBEROS)
2016-06-07 19:06:58,393 WARN org.apache.hadoop.security.UserGroupInformation: No groups available for user dn
2016-06-07 19:06:58,393 INFO org.apache.hadoop.ipc.Server: IPC Server handler 2 on 8020, call org.apache.hadoop.hdfs.server.protocol.DatanodeProtocol.versionRequest from 192.168.103.224:55035 Call#0 Retry#0: org.apache.hadoop.security.AccessControlException: Access denied for user dn. Superuser privilege is required 

map配置方法:修改core-site.xml,增加這段:

<property> <name>hadoop.security.auth_to_local</name> <value> RULE:[2:$1@$0]([nd]n@ZELDA.COM)s/.*/dtdream/ DEFAULT </value> </property> 

RULE的寫法可以參考這兒,跟Kerberos的規則一樣。具體到hadoop,這篇文章更直接一點。

上面的RULE表示會將nn和dn都替換為dtdream。

2、配置HDFS

2.1 總的配置

詳細各參數的說明請見hortonworks官網,下面直接貼出來我的配置,略長。

<property> <name>dfs.block.access.token.enable</name> <value>true</value> </property> <!-- NameNode security config --> <property> <name>dfs.namenode.kerberos.principal</name> <value>nn/_HOST@ZELDA.COM</value> </property> <property> <name>dfs.namenode.keytab.file</name> <value>/etc/security/nn.service.keytab</value> <!-- path to the HDFS keytab --> </property> <property> <name>dfs.https.port</name> <value>50470</value> </property> <property> <name>dfs.https.address</name> <value>zelda1.zelda.com:50470</value> </property> <!-- DataNode security config --> <property> <name>dfs.datanode.kerberos.principal</name> <value>dn/_HOST@ZELDA.COM</value> </property> <property> <name>dfs.datanode.keytab.file</name> <value>/etc/security/dn.service.keytab</value> <!-- path to the HDFS keytab --> </property> <property> <name>dfs.datanode.data.dir.perm</name> <value>700</value> </property> <!-- datanode SASL配置 --> <property> <name>dfs.datanode.address</name> <value>0.0.0.0:61004</value> </property> <property> <name>dfs.datanode.http.address</name> <value>0.0.0.0:61006</value> </property> <property> <name>dfs.http.policy</name> <value>HTTPS_ONLY</value> </property> <property> <name>dfs.data.transfer.protection</name> <value>integrity</value> </property> <property> <name>dfs.permissions.supergroup</name> <value>supergroup</value> <description>The name of the group of super-users.</description> </property> <property> <name>dfs.web.authentication.kerberos.principal</name> <value>HTTP/_HOST@ZELDA.COM</value> </property> <property> <name>dfs.web.authentication.kerberos.keytab</name> <value>/etc/security/spnego.service.keytab</value> </property> 

簡要說明:

  • _HOST是變量,表示所在機器的FQDN名,這樣所有節點可以共用一份hdfs-site.xml
  • namenode的webUI改為了https,端口號這里為50470
  • 需要保證各個keytab文件對當前的dtdream可以訪問

2.2 特別說明下datanode的SASL配置。

如果不加中間SASL這部分配置,啟動時datanode會報錯並退出:

2016-06-07 14:02:03,509 FATAL org.apache.hadoop.hdfs.server.datanode.DataNode: Exception in secureMain
java.lang.RuntimeException: Cannot start secure DataNode without configuring either privileged resources or SASL RPC data transfer protection and SSL for HTTP. Using privileged resources in combination with SASL RPC data transfer protection is not supported. at org.apache.hadoop.hdfs.server.datanode.DataNode.checkSecureConfig(DataNode.java:1173) at org.apache.hadoop.hdfs.server.datanode.DataNode.startDataNode(DataNode.java:1073) at org.apache.hadoop.hdfs.server.datanode.DataNode.<init>(DataNode.java:428) at org.apache.hadoop.hdfs.server.datanode.DataNode.makeInstance(DataNode.java:2370) at org.apache.hadoop.hdfs.server.datanode.DataNode.instantiateDataNode(DataNode.java:2257) at org.apache.hadoop.hdfs.server.datanode.DataNode.createDataNode(DataNode.java:2304) at org.apache.hadoop.hdfs.server.datanode.DataNode.secureMain(DataNode.java:2481) at org.apache.hadoop.hdfs.server.datanode.DataNode.main(DataNode.java:2505) 2016-06-07 14:02:03,513 INFO org.apache.hadoop.util.ExitUtil: Exiting with status 1 2016-06-07 14:02:03,516 INFO org.apache.hadoop.hdfs.server.datanode.DataNode: SHUTDOWN_MSG: 

Kerberos要求datanode以secure mode啟動,在2.6之前的版本,hadoop只能使用jsvc,先以root用戶來啟動datanode,然后再切到普通用戶;由於我們只對普通用戶做了免密碼打通,以root用戶啟動start-dfs.sh腳本會報錯,而且jsvc看上去比較復雜。下面說的很清楚:

As of version 2.6.0, SASL can be used to authenticate the data transfer protocol. In this configuration, it is no longer required for secured clusters to start the DataNode as root using jsvc and bind to privileged ports. To enable SASL on data transfer protocol, set dfs.data.transfer.protection in hdfs-site.xml, set a non-privileged port for dfs.datanode.address, set dfs.http.policy to HTTPS_ONLY and make sure the HADOOP_SECURE_DN_USER environment variable is not defined. Note that it is not possible to use SASL on data transfer protocol if dfs.datanode.address is set to a privileged port. This is required for backwards-compatibility reasons.

配置上有這么幾點:

  • 設置dfs.data.transfer.protection為integrity
  • 設置dfs.datanode.address為非特權端口,即大於1024的端口,我這用的是61004
  • 設置dfs.http.policy為HTTPS_ONLY
  • 確保HADOOP_SECURE_DN_USER變量為空

不過故事到這里還沒結束,按上面的配置同步各節點后啟動hdfs,會發現namenode報錯后退出:

2016-06-07 14:12:37,273 INFO org.apache.hadoop.http.HttpServer2: HttpServer.start() threw a non Bind IOException java.io.FileNotFoundException: /home/dtdream/.keystore (No such file or directory) at java.io.FileInputStream.open(Native Method) at java.io.FileInputStream.<init>(FileInputStream.java:146) at org.mortbay.resource.FileResource.getInputStream(FileResource.java:275) at org.mortbay.jetty.security.SslSocketConnector.createFactory(SslSocketConnector.java:242) at org.mortbay.jetty.security.SslSocketConnector.newServerSocket(SslSocketConnector.java:476) at org.apache.hadoop.security.ssl.SslSocketConnectorSecure.newServerSocket(SslSocketConnectorSecure.java:46) at org.mortbay.jetty.bio.SocketConnector.open(SocketConnector.java:73) at org.apache.hadoop.http.HttpServer2.openListeners(HttpServer2.java:914) at org.apache.hadoop.http.HttpServer2.start(HttpServer2.java:856) at org.apache.hadoop.hdfs.server.namenode.NameNodeHttpServer.start(NameNodeHttpServer.java:142) at org.apache.hadoop.hdfs.server.namenode.NameNode.startHttpServer(NameNode.java:752) at org.apache.hadoop.hdfs.server.namenode.NameNode.initialize(NameNode.java:638) at org.apache.hadoop.hdfs.server.namenode.NameNode.<init>(NameNode.java:811) at org.apache.hadoop.hdfs.server.namenode.NameNode.<init>(NameNode.java:795) at org.apache.hadoop.hdfs.server.namenode.NameNode.createNameNode(NameNode.java:1488) at org.apache.hadoop.hdfs.server.namenode.NameNode.main(NameNode.java:1554) 2016-06-07 14:12:37,275 INFO org.apache.hadoop.metrics2.impl.MetricsSystemImpl: Stopping NameNode metrics system... 

2.3 沒有keystore文件呀

其實到這跟hadoop,Kerberos就沒什么關系了,純粹是https的配置了。簡單說明下原理:https要求集群中有一個CA,它會生成ca_key和ca_cert,想要加入這個集群的節點,需要拿到這2個文件,然后經過一連串的動作生成keystore,並在hadoop的ssl-server.xml和ssl-client.xml中指定這個keystore的路徑和密碼,這樣各個節點間就可以使用https進行通信了(可能大概是這么回事吧)。更詳細的可以參考SASL配置

下面直接貼命令。

CA機器上:

openssl req -new -x509 -keyout test_ca_key -out test_ca_cert -days 9999 -subj '/C=CN/ST=zhejiang/L=hangzhou/O=dtdream/OU=security/CN=zelda.com' 

將上面生成的test_ca_key和test_ca_cert丟到所有機器上,在各個機器上繼續:

keytool -keystore keystore -alias localhost -validity 9999 -genkey -keyalg RSA -keysize 2048 -dname "CN=zelda.com, OU=test, O=test, L=hangzhou, ST=zhejiang, C=cn" keytool -keystore truststore -alias CARoot -import -file test_ca_cert keytool -certreq -alias localhost -keystore keystore -file cert openssl x509 -req -CA test_ca_cert -CAkey test_ca_key -in cert -out cert_signed -days 9999 -CAcreateserial -passin pass:changeit keytool -keystore keystore -alias CARoot -import -file test_ca_cert keytool -keystore keystore -alias localhost -import -file cert_signed 

最終在當前目錄下會生成keystore、trustkeystore文件。

2.4 配置ssl-server.xml和ssl-client.xml

從{target}.xml.example文件拷貝一份出來,並制定keystore、trustkeystore兩個文件的路徑、password,然后同步到所有節點。

2.5 啟動HDFS

從namenode的日志可以看到服務已經正常啟動,DN都連接上來了,使用https://{namenode_ip}:50470上可以看到各個datanode。但是使用hadoop命令行 fs -ls時會報錯:

./hadoop fs -ls / 16/06/08 13:24:35 WARN ipc.Client: Exception encountered while connecting to the server : javax.security.sasl.SaslException: GSS initiate failed [Caused by GSSException: No valid credentials provided (Mechanism level: Failed to find any Kerberos tgt)] ls: Failed on local exception: java.io.IOException: javax.security.sasl.SaslException: GSS initiate failed [Caused by GSSException: No valid credentials provided (Mechanism level: Failed to find any Kerberos tgt)]; Host Details : local host is: "zelda1/192.168.103.237"; destination host is: "zelda1":8020; 

提示沒有credentials,也就是說沒有憑據。一方面說明Kerberos的確是保護了hdfs,另一方面也說明我們還需要給本地加一個Principal來訪問hdfs。

2.6 增加用戶Principal

KDC上:

kadmin.local: addprinc dtdream@ZELDA.COM WARNING: no policy specified for dtdream@ZELDA.COM; defaulting to no policy Enter password for principal "dtdream@ZELDA.COM": Re-enter password for principal "dtdream@ZELDA.COM": Principal "dtdream@ZELDA.COM" created. 

再在hdfs客戶端的機器上使用kinit初始化credentials,再去訪問hdfs就正常了。

$ kinit dtdream@ZELDA.COM Password for dtdream@ZELDA.COM: $ klist Ticket cache: FILE:/tmp/krb5cc_1000 Default principal: dtdream@ZELDA.COM Valid starting Expires Service principal 2016-06-08T13:37:21 2016-06-08T23:37:21 krbtgt/ZELDA.COM@ZELDA.COM renew until 2016-06-09T13:37:19 $ ./hadoop fs -ls / Found 3 items drwxr-xr-x - dtdream supergroup 0 2016-05-27 09:45 /home drwxrwxr-x - dtdream supergroup 0 2016-05-25 16:01 /tmp drwxr-xr-x - dtdream supergroup 0 2016-05-25 16:11 /user 

2.7 配置chrome以訪問被Kerberos保護的hdfs webUI

配置FIREFOX、chrome、IE支持Kerberos HTTP SPNEGO,參考這里

以firefox為例,需要將zelda.com作為可信uri,設置到network.negotiate-auth.trusted-uris里去。

but,還是有問題,我不知道怎么把認證傳過去,先放一下。

http://docs.hortonworks.com/HDPDocuments/HDP2/HDP-2.4.0/bk_Security_Guide/content/_optional_install_a_new_mit_kdc.html

Kerberos安裝參考

主要參考hortonworks官網


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM