##前言 KMS
是Hadoop下的一個密鑰管理服務,它實際是與Hadoop結合,提供HDFS文件作AES加密用的。因此它是用來存儲AES祕鑰的,AES提供三種位數的祕鑰,分別是128
, 192
, 256
,因此KMS只能存儲這三種位數的byte
數組。php
若是你是爲了給HDFS文件加密,那麼直接經過配置就能夠完成,它與Hadoop可以完美契合,由Hadoop調用自動產生祕鑰並管理祕鑰。可是HDFS文件加密粒度太粗,咱們的數據並不是要所有加密,而當前針對Hive表列的加密並無集成方案,官方提供了AES的encrypt函數,可是祕鑰key還需明文傳入。這樣對集羣來講其實很不安全。html
若是咱們本身實現加密UDF,而後借用KMS來作密鑰管理,在KMS上加上Kerberos認證,祕鑰的處理就能夠都封裝在UDF內部,不對外暴露,並且能夠實現身份認證。java
KMS自己提供了一系列API來建立,獲取和維護密鑰,官網介紹中主要以RESTFUL的形式提供,但若是集羣上了Kerberos,請求的認證在RESTFULL裏就很差作(具體沒操做過)。在Hadoop源碼裏,提供了KMSClientProvider
用於Hadoop的加密,因此咱們能夠利用這個接口來獲取KMS服務,實現建立管理密鑰。web
##配置apache
KMS是一個Web服務,只須要在一臺機器上配置便可,其主要配置文件是kms-site.xml
,主要配置項是hadoop.kms.key.provider.uri
,配置值是KMS的key以文件形式存在哪一個keystore
文件裏,配置格式是jceks://file@/path/to/kms.keystore
,如jceks://file@/home/kms/kms.keystore
,固然,服務最好以kms
用戶來起。這個文件會在KMS起來後生成。以後在kms-env.sh
裏配置export KMS_LOG=/path/to/log
和export KMS_TEMP=/path/to/log
。 kms.keystore
文件自己和裏面的存儲密鑰都有密碼保護,默認配置項爲hadoop.security.keystore.java-keystore-provider.password-file
,密碼存儲在文件裏,不可換行,因爲KMS是經過ClassLoader.getResource
來加載該文件,因此該配置必須配在KMS Web服務啓動對應的conf目錄下。此外也可經過環境變量設置,爲HADOOP_KEYSTORE_PASSWORD
,可將其配置在kms-env.sh
裏,環境變量的設置優先級最高!數組
而後在hadoop的core-site.xml
裏配上hadoop.security.key.provider.path
,未啓用https,其值爲kms://http@${hostname}:16000/kms
,若是啓用了https,則應爲kms://https@${hostname}:16000/kms
。tomcat
以上兩步配完後,重啓HDFS,而後以kms
身份,啓動KMS(/path/to/hadoop/sbin/kms.sh start
),啓動完後,就能夠用/path/to/hadoop/bin/hadoop key list -metadata
來查看KMS裏存儲的Key了,固然,尚未建立key,因此沒有key信息,可是能夠驗證KMS服務是否配置正確。其次,這個命令雖然能夠建立key,可是隻能建立隨機key,不能建立指定key。安全
配置SSL(https),確保傳輸過程加密。SSL須要用到證書,能夠去CA官網下載一個證書做爲網站根證書和信任證書,也能夠用Java生成一個自簽名證書並添加它爲受信任證書。詳細介紹能夠參考CDH官網,咱們這裏採用自簽名證書。服務器
kms
用戶生成tomcat根證書(此根證書只能爲當前機器上的Web服務所用,其餘機器上的web服務若是須要SSL,也須要像這個同樣單獨生成該服務器的根證書。其次,該證書只是作SSL通訊安全加密所用,並不具有可信任性,由於不是權威機構頒發),執行/usr/java/default/bin/keytool -genkey -alias tomcat -keyalg RSA
,過程當中問到"What is your first and last name?"
時,必須填寫運行KMS Service那臺機器的hostname,而後會提示輸入keystore
的密碼,這個密碼假定爲xxx.c0m
,須要記住,後面配置時須要用到它。這一步執行完後,會在kms
用戶的home目錄下生成.keystore
文件(可用/path/to/java/bin/keytool -list -v -keystore .keystore -storepass xxx.c0m
來顯示當前keystore裏可用的證書)。kms-env.sh
,添加證書的位置和密碼,即export KMS_SSL_KEYSTORE_FILE=/home/kms/.keystore
和export KMS_SSL_KEYSTORE_PASS=xxx.c0m
,而後更改core-site.xml
裏的hadoop.security.key.provider.path
爲https
。到這裏KMS的SSL算是配完了,可是重啓HDFS和KMS後,發現 list 祕鑰會報錯: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target),這是由於咱們沒有添加證書爲受信任根證書,訪問並不認同當前根證書。kms
用戶導出根證書爲crt
文件:/usr/java/default/bin/keytool -export -alias tomcat -keystore /home/kms/.keystore -file /home/kms/tomcat.crt -storepass xxx.c0m
,這裏就要用到上面的密碼。這一步是爲了添加受信任證書作準備,當前證書被稱做keystore,受信任證書是truststore,java truststore有幾個不一樣的判斷維度,可參考這裏javax.net.ssl.trustStore
(也能夠採用配置這個文件),也沒有<jre-home>/lib/security/jssecacerts
文件,因此它會使用<jre-home>/lib/security/cacerts
做爲受信任證書文件,而這裏面並無咱們的KMS根證書。以root
用戶操做,執行cp $JAVA_HOME/jre/lib/security/cacerts $JAVA_HOME/jre/lib/security/jssecacerts
,而後將剛導出的根證書添加到受信任證書jssecacerts裏,即/usr/java/default/bin/keytool -import -alias tomcat -keystore /usr/java/default/jre/lib/security/jssecacerts -file /home/kms/tomcat.crt -storepass changeit
,這裏的密碼是jssecacerts
的密碼,默認是changeit
/home/kms/.keystore
的公鑰導入到了jssecacerts
文件裏,私鑰還在原文件裏。jssecacerts
拷貝到其餘機器<jre-home>/lib/security/
目錄下。HTTP
的憑據,在KMS服務機器上生成憑據,配置kms-site.xml
文件,設置hadoop.kms.authentication.type
爲kerberos
,而後添加hadoop.kms.authentication.kerberos.keytab
和hadoop.kms.authentication.kerberos.principal
,設置hadoop.kms.authentication.kerberos.name.rules
爲DEFAULT
hadoop key list
來查看當前存儲的密鑰,若是報錯沒有配置provider,咱們能夠這麼用:hadoop key list -metadata -provider kms://https@${hostname}:16000/kms
,需帶上provider<property> <name>hadoop.kms.acl.CREATE</name> <value>*</value> </property> <property> <name>hadoop.kms.blacklist.CREATE</name> <value>hdfs,hive</value> </property> <property> <name>hadoop.kms.acl.DELETE</name> <value>*</value> </property> <property> <name>hadoop.kms.blacklist.DELETE</name> <value>hdfs,hive</value> </property> <property> <name>hadoop.kms.acl.ROLLOVER</name> <value>*</value> </property> <property> <name>hadoop.kms.blacklist.ROLLOVER</name> <value>*</value> </property> <property> <name>hadoop.kms.acl.GET</name> <value>kavn,hive</value> </property> <property> <name>hadoop.kms.blacklist.GET</name> <value>hdfs</value> </property> <property> <name>hadoop.kms.acl.GET_KEYS</name> <value>*</value> </property> <property> <name>hadoop.kms.blacklist.GET_KEYS</name> <value>hdfs,hive</value> </property> <property> <name>hadoop.kms.acl.GET_METADATA</name> <value></value> </property> <property> <name>hadoop.kms.blacklist.GET_METADATA</name> <value>hdfs,hive</value> </property> <property> <name>hadoop.kms.blacklist.GENERATE_EEK</name> <value>*</value> </property> <property> <name>hadoop.kms.blacklist.DECRYPT_EEK</name> <value>*</value> </property> <!-- 要使用戶具有create key的權限,必須同時有 acl.CREATE 和 acl.SET_KEY_MATERIAL的權限,缺一不可 --> <property> <name>hadoop.kms.acl.SET_KEY_MATERIAL</name> <value>*</value> </property> <property> <name>hadoop.kms.blacklist.SET_KEY_MATERIAL</name> <value>hdfs,hive</value> </property> <!-- 如下是對單個key作權限控制,下面的是默認配置項,當用戶create key時,若是沒有配置下面的默認配置項,用戶是無法成功建立key的。由於建立一個新key的名稱沒法預料,在建立新key時後臺去校驗用戶對該key的權限就會失敗,因此需用這個默認列表 --> <property> <name>default.key.acl.MANAGEMENT</name> <value>*</value> </property> <property> <name>default.key.acl.READ</name> <value>*</value> </property> <property> <name>default.key.acl.ALL</name> <value>*</value> </property> <!-- 如下是單個key的具體配置項,單個key的權限是在上面所有key權限判別以後的 --> <property> <name>key.acl.key_name.MANAGEMENT</name> <value></value> </property> <property> <name>key.acl.key_name.READ</name> <value>kavn</value> </property> <property> <name>key.acl.key_name.ALL</name> <value></value> </property>
九、 至此整個KMS就配置完成了,訪問KMS服務就須要如下三個條件:oracle
##訪問代碼集成 KMS是在集羣環境中訪問,想要作加密就必須有身份認證,而身份認證就是Kerberos. 這裏KeyProviderFactory
內部封裝了Kerberos認證(實際經過UGI來作的),咱們經過調用它拿到KMS的訪問實例,從而實現Kerberos集羣環境下的祕鑰管理。當用戶運行這段代碼時,可使用當前用戶的身份認證,也能夠利用UGI使用其餘用戶的身份認證,達到祕鑰權限控制的目的。
這裏採用單例模式,但在獲取Instance的時候,加了獲取KeyProvider
的邏輯,這是由於同一代碼裏可能會有多個不一樣的帳戶須要訪問祕鑰,每次訪問祕鑰都用新的帳戶去作Kerberos認證,能夠保證權限正確。不會由於第一次請求以後,之後的用戶請求都用成了第一次請求用戶的Kerberos憑據。
package encryption.codec; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.crypto.key.KeyProvider; import org.apache.hadoop.crypto.key.KeyProviderFactory; import org.apache.log4j.Logger; import java.io.IOException; import java.io.Serializable; import java.util.List; /** * KMS祕鑰建立和獲取類<br/> */ public class KeyManagement implements Serializable { private static final Logger logger = Logger.getLogger(KeyManagement.class); private static KeyProvider provider = null; private static final String KEY_PROVIDER_PATH = "hadoop.security.key.provider.path"; private static final Configuration conf = new Configuration(); private static final String KMS = "kms://https@hostname.domain:16000/kms"; private KeyManagement() { } private static class KeyManagementInstance { private final static KeyManagement instance = new KeyManagement(); } public static KeyManagement getInstance() { provider = getKeyProvider(); return KeyManagementInstance.instance; } /** * 獲取KeyProvider * @return */ private static KeyProvider getKeyProvider() { // 此處由於拿不到KEY_PROVIDER_PATH配置,因此作了硬編碼 conf.set(KEY_PROVIDER_PATH, KMS); KeyProvider provider = null; List<KeyProvider> providers; try { providers = KeyProviderFactory.getProviders(conf); for (KeyProvider p : providers) { if (!p.isTransient()) { provider = p; break; } } } catch (IOException ex) { logger.error("Get KeyProvider failed! " + ex.getMessage()); ex.printStackTrace(); } return provider; } /** * 查看當前KMS裏有哪些Key,以及Key的信息 * @return */ public String[] listAllKeys() { try { final List<String> keys = provider.getKeys(); final KeyProvider.Metadata[] meta = provider.getKeysMetadata(keys.toArray(new String[keys.size()])); String[] out = new String[keys.size()]; for (int i = 0; i < meta.length; ++i) { out[i] = keys.get(i) + " : " + meta[i]; } return out; } catch (Exception ex) { ex.printStackTrace(); } return null; } /** * 獲取當前key的祕鑰 * @param name * @return * @throws IOException */ public byte[] getCurrentKey(String name) throws IOException { if (null == provider) { logger.error("KeyProvider is null!"); return null; } return provider.getCurrentKey(name).getMaterial(); } /** * 根據名稱,描述,祕鑰來建立一個Key, 祕鑰位長爲128位 * @param name * @param description * @param material * @throws IOException */ public void createKey(String name, String description, byte[] material) throws IOException { createKey(name, description, BitLength.ONE_TWO_EIGHT, material); } /** * 根據名稱,描述,祕鑰位長和祕鑰來建立一個Key * @param name * @param description * @param bitLengthOfKey * @param material * @throws IOException */ public void createKey(String name, String description, BitLength bitLengthOfKey, byte[] material) throws IOException { final KeyProvider.Options options = KeyProvider.options(conf); options.setDescription(description); int length = 8 * material.length; try { switch (bitLengthOfKey) { case ONE_TWO_EIGHT: if (length == 128) { options.setBitLength(128); break; } else { throw new IllegalArgumentException("Wrong key length. Required 128, but got " + length); } case ONE_NIGHT_TWO: if (length == 192) { options.setBitLength(192); break; } else { throw new IllegalArgumentException("Wrong key length. Required 192, but got " + length); } case TWO_FIVE_SIX: if (length == 256) { options.setBitLength(256); break; } else { throw new IllegalArgumentException("Wrong key length. Required 256, but got " + length); } } provider.createKey(name, material, options); provider.flush(); logger.info(name + " has been successfully created with options " + options.toString() + "."); } catch (Exception ex) { logger.error(name + " has not been created. " + ex.getMessage()); throw ex; } } } enum BitLength { ONE_TWO_EIGHT, ONE_NIGHT_TWO, TWO_FIVE_SIX }
updata: 2017-03-25 對文中描述不全和以前的理解不到位作了修改補充
歡迎轉載,但請註明出處:http://www.javashuo.com/article/p-bchodjwf-gs.html