Kerberos 介紹
Kerberos 是一個網絡認證的框架協議,其設計的初衷便是通過密鑰系統為 Client 和 Server 應用程序之間提供強大的認證服務。在使用 Kerberos 認證的集群中,Client 不會直接和 Server 進行認證,而是通過 KDC(Key Distribution Center)來完成互相的認證。首先,我們需要簡單的介紹下跟 Kerberos 相關的術語,如表 1 所示。
表 1.Kerberos 相關的術語
Kerberos 的認證過程如下圖所示。我們可以簡單的理解為,一個 User 或者一個 Service 會用 Principal 到 AS 去認證,AS 會返回一個用 Principal Key 加密的 TGT,這時候只有 AS 和這個 Principal 的使用者可以識別該 TGT。在拿到加密的 TGT 之后,User 或者 Service 會使用 Principal 的 Key 來解密 TGT,並使用解密后的 TGT 去 TGS 獲取 Service Ticket。在 Kerberos 認證的集群中,只有拿着這個 Service Ticket 才可以訪問真正的 Server 從而實現自己的業務邏輯。一般我們將 TGT 的存放文件,稱為 Kerberos Confidential 文件,默認的存放目錄為/tmp,文件名則由 krb5cc 和用戶的 id 組成,例如“/tmp/krb5cc_0”為 root 的 confidential 文件。
圖 1. Kerberos 的認證流程
表 2. 常用的 Kerberos 命令
有興趣的讀者可以在 Kerberos 的環境中試試以上的命令。目前業界常見的 Kerberos 的實現有 MIT Kerberos 和 Microsoft Kerberos,從名稱便可以知道分別是由麻省理工和微軟實現的。本文將要介紹的 Ambari 的 Automated Kerberization 機制,使用 MIT Kerberos。
Ambari 與 Kerberos
在 Ambari 環境中,Kerberos 本身是作為 Ambari 的一個 Service 而存在。當用戶通過 Ambari 的 Automated Kerberization 機制啟用 Kerberos 認證的時候,Ambari 會為各個 Service 創建對應的 Principal 以及 Keytab 文件。在 Linux Redhat6(CentOS 6)上,Ambari 默認的會使用 Kerberos1.10.3 的版本,而在 Redhat7(CentOS 7)上,則默認使用 Kerberos1.13.2。因此,需要啟用 Kerberos 認證的集群,需要注意 Kerberos 版本的兼容性問題。Ambari、Stack、Service 以及 KDC 的關系大致如下圖所示:
圖 2. Ambari 與 KDC 的關系圖
當一個模塊或者一個用戶要通過 KDC 認證自己的時候,會需要一個 Principal 以及該 Principal 的 Key。當 Keytab 文件存在的時候,模塊(或用戶)則可以直接使用 Principal 以及包含該 Principal 的 Keytab 文件向 KDC 認證。因而,對 Ambari 而言,其功能就是要為對應的 Service 或者 Component 創建 Principal 以及該 Principal 的 Keytab 文件。這里要理解,Principal 是在 KDC 中創建,並保存在 KDC Server 的數據庫。Keytab 是一個存放 Principal 以及加密過的 Principal Key 的文件,該文件需要存放在 Kerberos Client 對應的機器中,並且要對相應的用戶設置只讀權限。在 Ambari 的環境中,Ambari 已經為用戶完成了以上的操作。有興趣的讀者可以想想,為什么這里需要定向的設置只讀權限。
圖 3. 啟動 Kerberos 的 Ambari 機器上面的 Keytab 文件權限
Ambari Server 端的接口
下來我們簡單看下 Ambari 如何創建 Principal 以及 Keytab 文件。首先我們需要假設 Ambari Server 已經獲取到將要創建的 Principal 的名字以及存放 Keytab 文件的路徑(這些參數都定義在 Kerberos Descriptor 中,后面會詳細介紹)。這里主要涉及兩個 Java 代碼文件,分別是 CreatePrincipalServerAction.java 和 CreateKeytabFileServerAction.java。具體接口如下:
清單 1. 創建 Principal 的代碼(Ambari Server)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
/**CreatePrincipalServerAction.java
* Creates a principal in the relevant KDC
*
* @param principal the principal name to create
* @param isServicePrincipal true if the principal is a service principal; false if the
* principal is a user principal
* @param kerberosConfiguration the kerberos-env configuration properties
* @param kerberosOperationHandler the KerberosOperationHandler for the relevant KDC
* @param actionLog the logger (may be null if no logging is desired)
* @return a CreatePrincipalResult containing the generated password and key number value
*/
public CreatePrincipalResult createPrincipal(String principal, boolean isServicePrincipal,
Map<
String
,String> kerberosConfiguration, KerberosOperationHandler kerberosOperationHandler,
ActionLog actionLog) {
….
//根據用戶設定的規則生成一個密碼字符串
String password = securePasswordHelper.createSecurePassword(length, minLowercaseLetters,
minUppercaseLetters, minDigits, minPunctuation, minWhitespace);
…
…
//調用 kerberosOperationHandler 向 KDC 創建 Principal
Integer keyNumber = kerberosOperationHandler.createPrincipal(principal, password, isServicePrincipal);
…
}
|
從上面的簡要代碼中,我們可以看到是由 Ambari Server 調用 KDC 的接口創建的 Principal。有興趣的讀者可以看看完整的開源代碼,這里 Principal 的 Key,也就是 password 變量。Ambari Server 會根據用戶在 Kerberos 中配置的密碼限制條件生成一個臨時密碼串來作為 Principal 的 Key,並且不會將該密碼保存到持久化的存儲中(但是會保存到內存中一個 Map 的數據結構)或者返回。也就是說 Ambari Server 並不會給自己賦予管理 Principal 密碼的責任。這是由 Ambari 的設計決定的。熟悉 Ambari 的讀者,應該清楚 Ambari Server 會將所有的相關配置信息持久化的存儲在 Postgres 數據庫中。默認情況下,庫的名字為 ambari,並且數據庫對應的訪問密碼為 bigdata。 Ambari 目前的實現中,並不會將數據庫的密碼等信息加密存儲,而是明文的放在”/etc/ambari-server/conf/password.dat”。所以,一個別有用心的人可以很容易的獲取 Ambari 數據庫的信息,甚至可以更改其中的內容。
圖 4. Ambari 的數據庫密碼
由此,我們可以認為 Ambari Server 數據庫中的數據本身就不是很安全,所以如果將固定的 Principal 密碼存放在 Postgres 數據庫中,就顯得更不合理了。並且對於 Kerberos 的認證機制來說,是完全可以拋棄密碼而使用 Keytab 來代替的。如果有的讀者需要將第三方服務托管給 Ambari,並且期望使用 Ambari 的 Automated Kerberization 機制,就需要注意,在 Service 的配置項中不要提供 Principal 的密碼(其實也不必要)。
到這里,我們已經大致了解了 Ambari 如何創建 Principal。下來讓我們再看下 Ambari 如何創建對應的 Keytab 文件。具體的接口如下:
清單 2. 創建 Keytab 的入口代碼(Ambari Server)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
/**CreteKeytabFilesServerActon.java
* For each identity, create a keytab and append to a new or existing keytab file.
* <
*
*@param identityRecord a Map containing the data for the current identity record
*@param evaluatedPrincipal a String indicating the relevant principal
*@param operationHandler a KerberosOperationHandler used to perform Kerberos-related
* tasks for specific Kerberos implementations
* (MIT, Active Directory, etc...)
* @param kerberosConfiguration a Map of configuration properties from kerberos-env
* @param requestSharedDataContext a Map to be used a shared data among all ServerActions related
* to a given request @return a CommandReport, indicating an error
* condition; or null, indicating a success condition
* @throws AmbariException if an error occurs while processing the identity record
*/
@Override
protected CommandReport processIdentity(Map<
String
, String> identityRecord, String evaluatedPrincipal,
KerberosOperationHandler operationHandler,
Map<
String
, String> kerberosConfiguration,
Map<
String
, Object> requestSharedDataContext)
throws AmbariException {
…
//創建 Keytab 文件的內容,也就是 Keytab data
Keytab keytab = createKeytab(evaluatedPrincipal, password, keyNumber,
operationHandler, visitedPrincipalKeys != null, canCache, actionLog);
…
…
//將創建的 Keytab data,寫入 Keytab 文件中,如果文件不存在就創建,如果存在,就將內容 merge 到一起
operationHandler.createKeytabFile(keytab, destinationKeytabFile)) {
ensureAmbariOnlyAccess(destinationKeytabFile);
…
}
|
在上面這個接口中,我只簡要的列出了兩行比較關鍵的代碼,前一行是生成 Keytab 的內容,后一行則將內容寫入 Keytab 文件中。我們從代碼中看到的 password 參數,這個參數也就是來源於創建 Principal 的函數中。在上一段描述中,已經提到 Ambari Server 雖然不會將 Principal 的密碼存入數據庫,但會放到一個共享的 Map 中,這里便會從該 Map 中取出對應的 Principal 密碼,並生成該 Principal 的 Keytab Data。有興趣的讀者可以仔細閱讀完整的 Ambari Server 代碼,這里我們還需要注意一個變量,便是 destinationKeytabFile,也就是 Keytab 文件的存放路徑,這個路徑可能是遠程的(不在 Ambari Server)。如果需要完整的理解 Ambari Server 創建 Keytab 和 Principal 的流程,還需要查看很多代碼如 Keytab 類以及 KerberosOperationHandler(接口類,提供具體操作 Principal 和 Keytab 的方法)。
Ambari Agent 端接口
為了更好的理解 Ambari Server 在 Automated Kerberization 中的職責,我們介紹了 Ambari Server 中比較關鍵的兩個函數。接下來讓我們再看下 Ambari Agent 中提供的 Kerberos 相關的方法。首先我們知道 Agent 主要是由 Python 代碼以及一些 Shell 腳本實現。因而,下面的內容主要也是以 Python 和 Shell 為基礎。在之前的文章中我們已經了解,在 Agent 端,Service 必須實現 install\start\stop\status 等控制函數,這些都是最基礎的邏輯。對於 Automated Kerberization 來說,Service 首先需要獲取當前集群 Kerberos 認證啟用的狀態(enabled/disabled),而這個狀態被保存在 cluster-env 中。以下我們就先介紹下如何獲取該狀態的方法。
在之前的文章中已經介紹過 Script 類以及其提供的 get_config 接口,調用該接口可以在 Agent 端獲得 Ambari 集群配置信息的全集,也就自然會包含 Kerberos 狀態的信息。簡要的 Python 示例代碼如下:
清單 3. Python 示例代碼
1
2
3
4
5
6
|
from resource_management.libraries.script.script import Script
config = Script.get_config()
security_enabled = config['configurations']['cluster-env']['security_enabled']
if security_enabled:
#do something
pass
|
在 Ambari Server 的安裝目錄中提供了一個名為 configs.sh 的 Shell 腳本,其通過 curl 命令以及 sed 編輯器,實現了獲取 Ambari 集群配置的功能,並且會格式化輸出結果。因此,我們可以將該腳本同步到 Agent 機器,在 Agent 端直接調用這個腳本獲取 Kerberos 的狀態信息。這里需要注意,該腳本不僅可以獲取集群的相關配置,也可以更新或刪除某一個配置項。本質上講,該腳本就是通過 curl 命令調用 Rest API 來操作 Ambari 的 Desired Configuration(Ambari 的一種資源概念)信息,有興趣的讀者可以嘗試更多該腳本的功能。獲取 Kerberos 狀態的示例命令如下 (configs.sh 腳本路徑為/var/lib/ambari-server/resources/scripts/):
configs.sh get ambari-server cluster-name cluster-env
圖 5. 執行 configs.sh 腳本的結果
通過以上的方法,我們便可以從 Agent 端獲取到集群的 Kerberos 狀態,這只是第一步。Ambari 只會幫助 Service 創建對應的 Principal 和 Keytab 文件,如何使用則是 Service 該考慮的問題。當 Ambari 中的 Service 成功拿到 Principal 和 Keytab 的名稱之后,便需要通過 KDC 去認證,進而生成對應的 Kerberos Confidential 文件。該文件一般為 8 小時有效,並默認存放在/tmp 目錄中。有了該文件便可以直接向其他模塊通信。所以在 Service 的控制腳本中,一般需要生成 Kerberos Confidential 文件。這里 Ambari 提供了一個公用的方法“get_kinit_path”獲取 Kinit 命令的路徑,這樣腳本便可以調用 Kinit 來生成 Kerberos 的 Confidential 文件。如果要使用該方法,需要在 Python 腳本中導入該模塊,具體如下:
from resource_management.libraries.functions import get_kinit_path
Ambari Kerberos Descriptor 介紹
從上面的介紹中,我們了解了 Ambari Server 如何創建 Principal 和 Keytab,以及如何在 Agent 端獲取對應的 Kerberos 信息。這一小節,便介紹下如何在 Ambari 中對一個 Service 定義相關的 Kerberos 參數,而這個定義文件,便稱之為 Kerberos Descriptor。
Kerberos Descriptor 是一個 Json 格式的文件,存放於 Service 或者 Stack 的根目錄,並命名為 kerberos.json,其包含有 Principal 的名字、Keytab 的路徑等 Kerberos 相關的配置。這里需要注意,當一個 Service 通過 Kerberos.json 聲明相關的 Kerberos 配置時,其所歸屬的 Stack 也必須存在 Kerberos.json 文件。換句話說,先要有 Stack 級別的 Kerberos.json,才能有 Service 級別的 Kerberos.json,並且在 Service 級別的配置文件中,又會有 Compoent 級別的區分。下面我們用 Spark 的 kerberos.json 為例,來介紹下該文件。
清單 4.Spark 的 kerbers.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
{
"services": [
{
"name": "SPARK",
"identities": [
{
"name": "/smokeuser"
},
{
"name": "sparkuser",
"principal": {
"value": "${spark-env/spark_user}-${cluster_name}@${realm}",
"type" : "user",
"configuration": "spark-defaults/spark.history.kerberos.principal",
"local_username" : "${spark-env/spark_user}"
},
"keytab": {
"file": "${keytab_dir}/spark.headless.keytab",
"owner": {
"name": "${spark-env/spark_user}",
"access": "r"
},
"group": {
"name": "${cluster-env/user_group}",
"access": ""
},
"configuration": "spark-defaults/spark.history.kerberos.keytab"
}
}
],
"configurations": [
{
"spark-defaults": {
"spark.history.kerberos.enabled": "true"
}
}
],
"components": [
{
"name": "SPARK_JOBHISTORYSERVER",
"identities": [
{
"name": "/HDFS/NAMENODE/hdfs"
}
]
},
{
"name": "SPARK_CLIENT"
}
]
}
]
}
|
從以上的配置代碼中可以看到,Spark 只在 Service 級別定義了一個 sparkuser 段,並配置了 Principal 和 Keytab 等定義。因此,這里我們就先仔細看下 sparkuser 段的配置。該段中配置了 Principal 的名稱,由 spark-env 中的 spark_user(默認為 spark)加上 Ambari 的 cluster name 和 Kerberos 的 REALM 名組成。Type 為 user(也可以是 service)。Principal 段和 Keytab 段中的 configuration 標簽用於關聯該 Service 的配置項參數,從而呈現在 Ambari WEB 中。當在 Ambari 啟用 Automated Kerberization 機制的時候,如果已經部署了 Spark,Ambari 會讀取 kerberos.json 中的 Principal 和 Keytab 的值,並將其中 value 段的配置關聯到 spark-default 中的 spark.history.kerberos.principal 和 spark.history.kerberos.keytab。在 Keytab 字段中,還通過 file 標簽配置了 keytab 的存放路徑({keytab_dir} 默認為/etc/security/keytabs,可以在啟用 Automated Kerberization 的時候更改)。Owner 和 group 配置了文件的用戶組權限,這里注意 keytab 一般只能配置為只讀權限,否則會有安全問題。另外,在獨立的 configuration 段的配置會被增加到該 Service 的 Desired Configuration 信息中。用戶也可以在 Compoent 段為 Service 的模塊配置不同的 Principal 和 Keytab。在 Service 的 Kerberos.json 文件中,也可以關聯其他 Service 的配置,或者關聯 Stack 級別的配置。當需要關聯的時候,只需要使用字符“/”。例如“/smokeuser”關聯的便是 Stack 級別的配置,“/HDFS/NAMENODE/hdfs”則是關聯 HDFS 中 namenode 的配置。Kerberos.json 在 Stack 之間以及 Service 版本之間,存在繼承關系。不過目前的實現中還不支持部分更新。也就是說 Service 或者 Stack 的新版本,如需要定義一個新的 Kerberos.json 文件,就必須完整重寫上一個版本的所有配置。在 Ambari 中啟用 Kerberos 之后,我們可以使用 configs.sh 的腳本查看 spark-defaults 的更新,以及 cluster-env 的變化。
圖 6. 使用 configs.sh 獲得 spark-defaults 的配置項(啟用 Kerberos 之后)
為第三方的 Service 定義 Kerberos Descriptor
這里我簡要的實現了一個 Sample Service,其目錄結果如下:
圖 7. 示例 Sample 目錄結構
我們主要看下其中的 sample-env 和 Kerberos.json 文件。
清單 5. Kerberos.json 配置代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
|
{
"services": [
{
"name": "SAMPLE",
"identities": [
{
"name": "sample1",
"principal": {
"value": "sample1/_HOST@${realm}",
"type" : "user",
"configuration": "sample-env/sample1.principals",
"local_username" : "root"
},
"keytab": {
"file": "${keytab_dir}/sample1.keytab",
"owner": {
"name": "root",
"access": "r"
},
"group": {
"name": "${cluster-env/user_group}",
"access": ""
},
"configuration": "sample-env/sample1.keytab"
}
},
{
"name": "sample2",
"principal": {
"value": "sample2/_HOST@${realm}",
"type" : "service",
"configuration": "sample-env/sample2.principals",
"local_username" : "root"
},
"keytab": {
"file": "${keytab_dir}/sample2.keytab",
"owner": {
"name": "root",
"access": "r"
},
"group": {
"name": "${cluster-env/user_group}",
"access": ""
},
"configuration": "sample-env/sample2.keytab"
}
}
],
"configurations": [
{
"sample-env": {
"mysample.kerberos.enabled": "true"
}
}
],
"components": [
{
"name": "MY_MASTER",
"identities": [
{
"name": "my_master",
"principal": {
"value": "sample/master@${realm}",
"type" : "service",
"configuration": "sample-env/mymaster.principals",
"local_username" : "root"
},
"keytab": {
"file": "${keytab_dir}/mymaster.keytab",
"owner": {
"name": "root",
"access": "r"
},
"group": {
"name": "${cluster-env/user_group}",
"access": ""
},
"configuration": "sample-env/mymaster.keytab"
}
}
]
}
]
}
]
}
|
之上的配置中,我在 Service 級別定義了兩個 identity,Component 級別只定義了一個,並且沒有引用 Stack 和其他 Service 的配置。其中有一個 user 類型,兩個 service 類型。對於 user 類型,Ambari 會將其配置顯示在 Kerberos 配置的首頁。在配置中,引用了“_HOST”這個變量,Ambari 會用對應得主機名替換掉該變量。使用 HOST 作為 Principal 的一部分,可以使 Principal 在集群范圍內保持唯一性(如果多個機器在同一個集群中使用一個 Principal,並且同時去 KDC 認證,KDC 會以重復為由拒絕掉其他的鏈接)。接着我們看下 sample-env.xml 的默認配置,如下。
清單 6. Sample-env.xml 的配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
<
configuration
supports_final
=
"true"
xmlns:xi
=
"http://www.w3.org/2001/XInclude"
>
<
property
>
<
name
>mymaster.principals</
name
>
<
display-name
>master principals</
display-name
>
<
value
>none</
value
>
</
property
>
<
property
>
<
name
>mymaster.keytab</
name
>
<
value
>none</
value
>
</
property
>
<
property
>
<
name
>sample1.principals</
name
>
<
value
>none</
value
>
</
property
>
<
property
>
<
name
>sample1.keytab</
name
>
<
value
>none</
value
>
</
property
>
<
property
>
<
name
>sample2.principals</
name
>
<
value
>none</
value
>
</
property
>
<
property
>
<
name
>sample2.keytab</
name
>
<
value
>none</
value
>
</
property
>
</
configuration
>
|
Sample 的 sample-env.xml 文件,只是為了讓 Ambari Web 在 Sample 的 Config 頁面顯示這 6 個屬性值而存在。雖然這 6 個參數,是以 Service 的配置項存在,卻不需要用戶在安裝的時候修改。Ambari 目前的實現並不支持將 Kerberos.json 中的定義顯示在 Config 頁面,所以才間接使用 configuration 目錄的配置文件。也就是說當啟用 Automated Kerberization 的時候,Ambari 會將該配置中的 none 更新成 Kerberos.json 中關聯的屬性值,進而顯示在 Config 頁面。
當在 Ambari Server 中增添 Sample 的代碼之后,需要重啟 Ambari Server 來讀取所有的配置。如果在 Ambari 中已經啟用了 Kerberos 認證,這時候只需要在 Web 中增加該 Service,Ambari Server 便會對該 Service 調用 Automated Kerberization 機制。當成功部署 Sample 之后,Ambari 會在 Kerberos 的頁面以及 Service 的 Config 頁面顯示對應值(由於更新的頁面較多,這里只例舉下 Service 的 Config 頁面)。
圖 8. Sample 的 Config 頁面
我們可以在機器的目錄“/etc/security/keytab”中看到 Ambari 為 Sample Service 生成的三個 keytab 文件,sample1.keytab、sample2.keytab、mymaster.keytab。有興趣的讀者可以使用 klist 命令查看 keytab 文件中的 Principal 列表(需要指定參數-kt)。由於我並未在控制腳本中使用 kinit 文件,所以/tmp 目錄中不存在該 Service 相關的 Confidential 文件。這時候,我們也可以使用 configs.sh 腳本查看 Sample Service 的 Kerberos 配置信息,這里就不再舉例了。
結束語
隨着雲計算和終端的發展,安全問題逐漸成為一個大眾關心的話題。大數據相關的框架往往處於關鍵的基礎構建層,所以也不得不想方設法提升其安全性。Hadoop 等框架都默認的支持了 Kerberos 的認證,因此作為其部署和監管的工具,Ambari 也不得不提升其安全性進而支持 Kerberos。不過目前 Ambari 對 Kerberos 的支持還比較有限,希望未來能得到更多的關注和發展。