##前言 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