文档章节

KMS密钥管理服务(Hadoop)

问津已非少年
 问津已非少年
发布于 2016/12/18 17:26
字数 2936
阅读 774
收藏 1

##前言 KMS是Hadoop下的一个密钥管理服务,它实际是与Hadoop结合,提供HDFS文件做AES加密用的。所以它是用来存储AES秘钥的,AES提供三种位数的秘钥,分别是128, 192, 256,所以KMS只能存储这三种位数的byte数组。

如果你是为了给HDFS文件加密,那么直接通过配置就可以完成,它与Hadoop能够完美契合,由Hadoop调用自动产生秘钥并管理秘钥。但是HDFS文件加密粒度太粗,我们的数据并非要全部加密,而当前针对Hive表列的加密并没有集成方案,官方提供了AES的encrypt函数,但是秘钥key还需明文传入。这样对集群来说其实很不安全。

如果我们自己实现加密UDF,然后借用KMS来做密钥管理,在KMS上加上Kerberos认证,秘钥的处理就可以都封装在UDF内部,不对外暴露,而且可以实现身份认证。

KMS本身提供了一系列API来创建,获取和维护密钥,官网介绍中主要以RESTFUL的形式提供,但如果集群上了Kerberos,请求的认证在RESTFULL里就不好做(具体没操作过)。在Hadoop源码里,提供了KMSClientProvider用于Hadoop的加密,所以我们可以利用这个接口来获取KMS服务,实现创建管理密钥。

##配置

  1. 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/logexport KMS_TEMP=/path/to/logkms.keystore文件本身和里面的存储密钥都有密码保护,默认配置项为hadoop.security.keystore.java-keystore-provider.password-file,密码存储在文件里,不可换行,由于KMS是通过ClassLoader.getResource来加载该文件,所以该配置必须配在KMS Web服务启动对应的conf目录下。此外也可通过环境变量设置,为HADOOP_KEYSTORE_PASSWORD,可将其配置在kms-env.sh里,环境变量的设置优先级最高!

  2. 然后在hadoop的core-site.xml里配上hadoop.security.key.provider.path,未启用https,其值为kms://http@${hostname}:16000/kms,如果启用了https,则应为kms://https@${hostname}:16000/kms

  3. 以上两步配完后,重启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。

  4. 配置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/.keystoreexport KMS_SSL_KEYSTORE_PASS=xxx.c0m,然后更改core-site.xml里的hadoop.security.key.provider.pathhttps。到这里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
  • 上面一步做完后,本机上任何账户都可以使用KMS服务,至此KMS的SSL就配完了。这一步的过程实际是把/home/kms/.keystore的公钥导入到了jssecacerts文件里,私钥还在原文件里。
  • 要想其他机器也正常访问KMS,我们需要把jssecacerts拷贝到其他机器<jre-home>/lib/security/目录下。
  1. 配置KMS Kerberos KMS需要HTTP的凭据,在KMS服务机器上生成凭据,配置kms-site.xml文件,设置hadoop.kms.authentication.typekerberos,然后添加hadoop.kms.authentication.kerberos.keytabhadoop.kms.authentication.kerberos.principal,设置hadoop.kms.authentication.kerberos.name.rulesDEFAULT
  2. 在CDH里配置KMS:CDH里配置很简单,在Cluster界面,Actions -> Add a Service,然后添加Java KeyStore Service,然后一步步走配置流程即可,SSL的配置与上面的一样。然后在Set Up HDFS Dependency这一步里,点击关闭,不配置HDFS文件加密。对于Kerberos配置,选择Administration -> Security -> Kerberos Credentials,查看是否有当前主机的HTTP凭据,没有就生成一个
  3. 配置完后,我们可以使用hadoop key list来查看当前存储的密钥,如果报错没有配置provider,我们可以这么用:hadoop key list -metadata -provider kms://https@${hostname}:16000/kms,需带上provider
  4. 配置KMS秘钥访问权限,配置文件是kms-acls.xml,KMS可整体控制秘钥的权限,也可单独就某个秘钥配置它的具体权限,并且支持白名单和黑名单,策略是先白名单后黑名单。在开源Hadoop上,这个配置是热加载的,但是在CDH里改了它之后需要重启KMS服务。配置示例如下:
  <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>

9、 至此整个KMS就配置完成了,访问KMS服务就需要以下三个条件:

  • 有服务器的受信任证书(如这里的 jssecacerts)
  • 有kerberos认证并且票据没过期
  • 具备相应Key的访问权限

##访问代码集成 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 对文中描述不全和之前的理解不到位做了修改补充

欢迎转载,但请注明出处:https://my.oschina.net/u/2539801/blog/807974

© 著作权归作者所有

问津已非少年
粉丝 19
博文 21
码字总数 33944
作品 0
海淀
程序员
私信 提问
有关Windows的MAK授权与KMS授权

目前微软针对Windows7和Windows10操作系统的批量使用,提供两种授权方式: MAK(Multiple Activation Key),即多次激活密钥, a) MAK简介:顾名思义,MAK指的是一个Windows激活密钥可以激活...

曲景洋Jim
2018/09/11
0
0
对象存储OSS数据加密整体解决方案介绍

一、简介   数据保护是指数据传输(上传数据至OSS、从OSS下载数据)和处于静止状态(数据存储在OSS数据中心磁盘)期间保护数据。可以使用SSL或者客户端加密保护传输中的数据。也可以采用以...

小盆友开飞机
04/01
0
0
【最佳实践】使用指定的CMK ID加密OSS服务端对象

1. 服务端加密介绍 使用服务器端加密方式保护静态数据,即OSS将用户数据写入数据中心内的磁盘时,会在对象级别加密数据,并且在访问这些数据时自动解密。用户只需要验证请求是否拥有访问权限...

figo168hf
2018/10/24
0
0
OSS服务器端加密之初体验

本文介绍如何基于ossutil工具,体验oss服务器端加密功能。 关于服务器端加密 OSS支持在服务器端对用户上传的数据进行加密编码(Server-Side Encryption):用户上传数据时,OSS对收到的用户数...

姜恒
2018/01/29
0
0
[New Feature]OSS支持设置Bucket 服务端默认加密方式

第一章:阿里云OSS Bucket默认加密方式介绍   阿里云OSS支持通过API、SDK、工具以及控制台等方式设置Bucket的默认加密方式。当Bucket设置服务端加密后,具有相应权限的用户上传文件到该Buc...

figo168hf
02/22
0
0

没有更多内容

加载失败,请刷新页面

加载更多

java通过ServerSocket与Socket实现通信

首先说一下ServerSocket与Socket. 1.ServerSocket ServerSocket是用来监听客户端Socket连接的类,如果没有连接会一直处于等待状态. ServetSocket有三个构造方法: (1) ServerSocket(int port);...

Blueeeeeee
今天
6
0
用 Sphinx 搭建博客时,如何自定义插件?

之前有不少同学看过我的个人博客(http://python-online.cn),也根据我写的教程完成了自己个人站点的搭建。 点此:使用 Python 30分钟 教你快速搭建一个博客 为防有的同学不清楚 Sphinx ,这...

王炳明
昨天
5
0
黑客之道-40本书籍助你快速入门黑客技术免费下载

场景 黑客是一个中文词语,皆源自英文hacker,随着灰鸽子的出现,灰鸽子成为了很多假借黑客名义控制他人电脑的黑客技术,于是出现了“骇客”与"黑客"分家。2012年电影频道节目中心出品的电影...

badaoliumang
昨天
14
0
很遗憾,没有一篇文章能讲清楚线程的生命周期!

(手机横屏看源码更方便) 注:java源码分析部分如无特殊说明均基于 java8 版本。 简介 大家都知道线程是有生命周期,但是彤哥可以认真负责地告诉你网上几乎没有一篇文章讲得是完全正确的。 ...

彤哥读源码
昨天
15
0
jquery--DOM操作基础

本文转载于:专业的前端网站➭jquery--DOM操作基础 元素的访问 元素属性操作 获取:attr(name);$("#my").attr("src"); 设置:attr(name,value);$("#myImg").attr("src","images/1.jpg"); ......

前端老手
昨天
7
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部