Hadoop安全之Kerberos

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

简介

安全无小事我们常常要为了预防安全问题而付出大量的代价。虽然小区楼道里面的灭火器、消防栓常年没人用但是我们还是要准备着。我们之所以愿意为了这些小概率事件而付出巨大的成本是因为安全问题一旦发生很多时候我们将无法承担它带来的后果。

在软件行业安全问题尤其突出因为无法预料的事情实在太多了。软件的复杂性让我们几乎无法完全扫清安全问题模块 A 独立运行可能没问题但是一旦和模块 B 一起工作也许就产生了安全问题。

不可否认为了让软件更安全我们引入了很多复杂的机制。不少人开发者也抱怨为了进行安全处理而做了太多额外的事情。在一个复杂的分布式软件 Hadoop 中我们为此付出的成本将更大。比如我们可能可以比较轻松的搭建一个无安全机制的集群但是一旦需要支持安全机制的时候我们可能会付出额外几倍的时间来进行各种复杂的配置和调试。

Hadoop 在开始的几个版本中其实并没有安全机制的支持后来 Yahoo 在大规模应用 Hadoop 之后安全问题也就日益明显起来。大家都在一个平台上面进行操作是很容易引起安全问题的比如一个人把另一个人的数据删除了一个人把另一个人正在运行的任务给停掉了等等。在当今的企业应用里面一旦我们的数据开始上规模之后安全机制的引入几乎是必然的选择。所以作为大数据领域的开发者理解 Hadoop 的安全机制就显得非常重要。

Hadoop 的安全机制现在已经比较成熟网上关于它的介绍也很多但相对较零散下面我将尝试更系统的并结合实例代码给大家分享一下最近一段时间关于 Hadoop 安全机制的学习所得抛个砖。

预计将包括这样几个方面

  1. Kerberos 协议介绍及实践
  2. Kerberos 协议发展及 Hadoop 相关源码分析
  3. Hadoop 安全集群搭建及测试
  4. 周边工具的安全支持

安全认证协议

Kerberos

做 Web 开发的同学们可能比较熟悉的认证机制是 JWT近两年 JWT 的流行几乎让其成为了实现单点登录的一个标准。JWT 将认证服务器认证后得到的 token 及一定的用户信息经过 base64 编码之后放到 HTTP 头中发送给服务器端得益于 token 的加密机制一般是非对称加密服务器端可以在不连接认证服务器就进行 token 验证第一次验证时会向认证服务器请求公钥从而实现高性能的鉴权。这里的 token 虽然看起来不可读实际上我们经过简单的解码就能得到 token 的内容。所以 JWT 一般是要结合 HTTPS 一起应用才能带来不错的安全性。

JWT 看起来还不错呀安全模型比较简单能不能直接用在 Hadoop 上面呢可能可以。但是由于 Hadoop 的出现早于 JWT 太多所以当时的设计者们是不可能考虑使用 JWT 的。实际上 JWT 主要是针对 web 的场景设计的对于分布式场景中很多问题它是没有给出答案的。一些典型的场景比如服务间的认证该如何实现如何支持其他的协议等等。Hadoop 的安全认证使用的是 Kerberos 机制。相比 JWTKerberos 是一个更为完整的认证协议然而也正是因为其设计可以支持众多的功能也给其理解和使用带来了困难。

这里之所以提到 JWT是因为 JWT 实际上可以看成是 Kerberos 协议的一个极简版本。JWT 实现了一部分 Kerberos 的功能。如果我们能对于 JWT 的认证机制比较熟悉那么对于 Kerberos 机制的理解应当是有较大帮助的。

Kerberos 协议诞生于 MIT 大学早在上世纪 80 年代就被设计出来了然后经过了多次版本演进才到了现在我们用的 V5 版本。作为一个久经考验的安全协议Kerberos 的使用其实是非常广泛的比如 Windows 操作系统的认证就是基于 Kerberos 的而 Mac Red Hat Enterprise Linux 也都对于 Kerberos 有完善的支持。各种编程语言也都有内置的实现。对于这样一个重要的安全协议就算我们不从事大数据相关的开发也值得好好学习一下。

Kerberos 设计的有几个大的原则:

  1. 利用公开的加密算法实现
  2. 密码尽量不在网络上传输
  3. 高安全性和性能
  4. 支持广泛的安全场景如防止窃听、防止重放攻击、保护数据完整性等

那么这个协议是如何工作的呢与 JWT 类似Kerberos 同样定义了一个中心化的认证服务器不过对于这个认证服务器Kerberos 按照功能进一步将其拆分为了三个组件认证服务器Authentication ServerAS、密钥分发中心Key Distribution CenterKDC、票据授权服务器Ticket Granting ServerTGS。在整个工作流程中还有两个参与者客户端 (Client) 和服务提供端 (Service ServerSS)。

Kerberos 大体上的认证过程与 JWT 一致第一步是客户端从认证服务器拿到 token这里的术语是 Ticket下文将不区分这两个词请根据上下文理解第二步是将这个 token 发往服务提供端去请求相应的服务。

下图是整个认证过程中各个组件按顺序相互传递的消息内容在阅读整个流程之前有几点提需要注意:

  1. 各个组件都有自己独立的秘钥Client 的秘钥由用户提供AS、TGS、SS 需要提前生成自己独立的秘钥
  2. AS、TGS 由于属于认证服务器的一部分它们可以查询 KDC 得到用户或其他服务器的秘钥比如 AS 可以认为拥有用户的、TGS 的以及 SS 的秘钥

看了这个复杂的流程大家心里应该有很多疑惑。整个通信过程传递了很多的消息消息被来来回回加密了很多次真的是有必要的吗背后的原因是什么呢事实上我们结合上面提到的几个设计原则来看问题就会相对清晰一些。

虽然整个通信过程涉及到的消息很多但是我们仔细思考就可以发现这几条规律

  1. 整个认证过程中避免了任何地方有明文的密码传输
  2. 与 JWT 一样通信过程生成有效时间比较短的会话秘钥用于通信
  3. 与 JWT 一样认证服务器无需存储会话秘钥各个参与方Client/SS可以独立进行消息验证从而实现高性能。这也是虽然消息 B 和 E 不能被 Client 解密但是还是会发往 Client然后再由 Client 回发的原因
  4. Kerberos 并没有对 Client 和 SS 之间的通信协议进行限制虽然和认证服务器进行通信需要基于 TCP/UDP但 Client 和 SS 通信可以用任意协议进行

理解了上述通信流程之后可以看到相比 JWTKerberos 还进行了下面的额外验证

  1. 认证过程将验证服务提供端的 ID一般会基于 hostname 进行
  2. 认证过程将验证各个组件的时间相互不能相差太多这也是 Kerberos 要求各个组件进行时间同步的原因

除了上面这些安全验证其实 Kerberos 还支持免密码输入的登录我们可以将用户的秘钥并非真正的密码由真正的密码 hash 生成生成到一个 keytab 格式的文件中这样在第一步中就可以由用户提供 ID (principal) 及 keytab 文件来完成了。

虽然 Kerberos 可以支持多种场景的认证但是由于其协议设计比较复杂在使用上会给我们带来不少的困难。比如我们需要提前为各个组件生成独立的秘钥一般要求每个服务器都不一样与不同的主机绑定这就给我们部署服务带来了挑战特别是在当前微服务、云原生应用、容器、k8s 比较流行的时候。

通信过程演示

为了更清晰的看到整个通信的过程我们可以动手实践一下看看

然后安装配置 kdc 并生成相关的秘钥

# 将kdc kdc.hadoop.com加入hosts以便后续进行基于hosts文件的主机名解析
yum install net-tools -y
ip_addr=$(ifconfig ens33 | grep inet | awk '{print $2}')
#这里主要的作用就是写一个本地的host的ip地址映射如上图
echo "$ip_addr kdc-server kdc-server.hadoop.com" >> /etc/hosts

# 安装相关软件并进行配置
yum install krb5-server krb5-libs krb5-workstation -y
# 创建krb5配置文件详细配置解释请参考https://web.mit.edu/kerberos/krb5-1.12/doc/admin/conf_files/krb5_conf.html
cat > /etc/krb5.conf <<EOF
#Configuration snippets may be placed in this directory as well
includedir /etc/krb5.conf.d/

[logging]
  default = FILE:/var/log/krb5.log
  kdc = FILE:/var/log/krb5kdc.log
  admin_server = FILE:/var/log/kadmind.log

[libdefaults]
  forcetcp = true
  default_realm = HADOOP.COM
  dns_lookup_realm = false
  dns_lookup_kdc = false
  ticket_lifetime = 24h
  renew_lifetime = 7d
  forwardable = true
  udp_preference_limit = 1
  default_tkt_enctypes = des-cbc-md5 des-cbc-crc des3-cbc-sha1
  default_tgs_enctypes = des-cbc-md5 des-cbc-crc des3-cbc-sha1
  permitted_enctypes = des-cbc-md5 des-cbc-crc des3-cbc-sha1

[realms]
  HADOOP.COM = {
    kdc = kdc-server.hadoop.com:2802
    admin_server = kdc-server.hadoop.com:2801
    default_domain = hadoop.com
  }

[domain_realm]
  .hadoop.com = HADOOP.COM
  hadoop.com = HADOOP.COM
EOF
# 创建kdc配置文件详细配置解释请参考https://web.mit.edu/kerberos/krb5-1.12/doc/admin/conf_files/kdc_conf.html
cat > /var/kerberos/krb5kdc/kdc.conf <<EOF
default_realm = HADOOP.COM

[kdcdefaults]
 kdc_ports = 0
 v4_mode = nopreauth

[realms]
 HADOOP.COM = {
    kdc_ports = 2800
    kdc_tcp_ports = 2802
    admin_keytab = /etc/kadm5.keytab
    database_name = /var/kerberos/krb5kdc/principal
    acl_file = /var/kerberos/krb5kdc/kadm5.acl
    key_stash_file = /var/kerberos/krb5kdc/stash
    max_life = 10h 0m 0s
    max_renewable_life = 7d 0h 0m 0s
    master_key_type = des3-hmac-sha1
    supported_enctypes = arcfour-hmac:normal des3-hmac-sha1:normal des-cbc-crc:normal des:normal des:v4 des:norealm des:onlyrealm des:afs3
    default_principal_flags = +preauth
}
EOF

echo -e '123456\n123456' | kdb5_util create -r HADOOP.COM -s  # 创建一个名为HADOOP.COM的域
/usr/sbin/krb5kdc && /usr/sbin/kadmind                        # 启动kdc及kadmind服务

echo -e '123456\n123456' | kadmin.local addprinc gml    # 创建gml账号
kadmin.local xst -k gml.keytab gml@HADOOP.COM           # 生成gml账号的keytab文件

kadmin.local addprinc -randkey root/localhost@HADOOP.COM       # 创建名为root并和kdc主机进行绑定的服务账号
kadmin.local xst -k server.keytab root/localhost@HADOOP.COM    # 创建用于服务器的keytab文件

操作完以后查看下端口

还有对应生成的文件

 将生成的 keytab 文件下载到本地然后就可以进行测试了。编写测试的客户端和服务端代码如下

准备对应的文件

sz /etc/krb5.conf
import org.ietf.jgss.GSSContext;
import org.ietf.jgss.GSSCredential;
import org.ietf.jgss.GSSException;
import org.ietf.jgss.GSSManager;
import org.ietf.jgss.Oid;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;


public class Test {

    public static class TestClient {
        private String srvPrincal;
        private String srvIP;
        private int srvPort;
        private Socket socket;
        private DataInputStream inStream;
        private DataOutputStream outStream;

        public TestClient(String srvPrincal, String srvIp, int srvPort) throws Exception {
            this.srvPrincal = srvPrincal;
            this.srvIP = srvIp;
            this.srvPort = srvPort;
            this.initSocket();
            this.initKerberos();
        }

        private void initSocket() throws IOException {
            this.socket = new Socket(srvIP, srvPort);
            this.inStream = new DataInputStream(socket.getInputStream());
            this.outStream = new DataOutputStream(socket.getOutputStream());
            System.out.println("Connected to server: " + this.socket.getInetAddress());
        }

        private void initKerberos() throws Exception {
            System.setProperty("java.security.krb5.conf", "src/main/krb5.conf");
            System.setProperty("java.security.auth.login.config", "src/main/gml.keytab");
            System.setProperty("javax.security.auth.useSubjectCredsOnly", "false");
            System.setProperty("sun.security.krb5.debug", "true");

            System.out.println("init kerberos: set up objects as configured");
            GSSManager manager = GSSManager.getInstance();
            Oid krb5Oid = new Oid("1.2.840.113554.1.2.2");
            GSSContext context = manager.createContext(
                    manager.createName(srvPrincal, null),
                    krb5Oid, null, GSSContext.DEFAULT_LIFETIME);
            context.requestMutualAuth(true);
            context.requestConf(true);
            context.requestInteg(true);

            System.out.println("init kerberos: Do the context establishment loop");

            byte[] token = new byte[0];

            while (!context.isEstablished()) {
                // token is ignored on the first call
                token = context.initSecContext(token, 0, token.length);

                // Send a token to the server if one was generated by initSecContext
                if (token != null) {
                    System.out.println("Will send token of size " + token.length + " from initSecContext.");
                    outStream.writeInt(token.length);
                    outStream.write(token);
                    outStream.flush();
                }

                // If the client is done with context establishment then there will be no more tokens to read in this loop
                if (!context.isEstablished()) {
                    token = new byte[inStream.readInt()];
                    System.out.println(
                            "Will read input token of size " + token.length + " for processing by initSecContext");
                    inStream.readFully(token);
                }
            }

            System.out.println("Context Established! ");
            System.out.println("Client is " + context.getSrcName());
            System.out.println("Server is " + context.getTargName());

        }

        public void sendMessage() throws Exception {
            // Obtain the command-line arguments and parse the port number

            String msg = "Hello Server ";
            byte[] messageBytes = msg.getBytes();
            outStream.writeInt(messageBytes.length);
            outStream.write(messageBytes);
            outStream.flush();

            byte[] token = new byte[inStream.readInt()];
            System.out.println("Will read token of size " + token.length);
            inStream.readFully(token);

            String s = new String(token);
            System.out.println(s);

            System.out.println("Exiting... ");
        }

        public static void main(String[] args) throws Exception {
            TestClient client = new TestClient("root/localhost@HADOOP.COM", "localhost", 9112);
            client.sendMessage();
        }
    }

    public static class TestServer {
        private int localPort;
        private ServerSocket ss;
        private Socket socket = null;

        public TestServer(int port) {
            this.localPort = port;
        }

        public void receive() throws IOException, GSSException {
            this.ss = new ServerSocket(localPort);
            socket = ss.accept();
            DataInputStream in = new DataInputStream(socket.getInputStream());
            DataOutputStream out = new DataOutputStream(socket.getOutputStream());
            this.initKerberos(in, out);

            int length = in.readInt();
            byte[] token = new byte[length];
            System.out.println("Will read token of size " + token.length);
            in.readFully(token);
            String s = new String(token);
            System.out.println("Receive Client token: " + s);

            byte[] token1 = "Receive Client Message".getBytes();
            out.writeInt(token1.length);
            out.write(token1);
            out.flush();
        }

        private void initKerberos(DataInputStream in, DataOutputStream out) throws GSSException, IOException {
            GSSManager manager = GSSManager.getInstance();
            GSSContext context = manager.createContext((GSSCredential) null);
            byte[] token;

            while (!context.isEstablished()) {
                token = new byte[in.readInt()];
                System.out.println("Will read input token of size " + token.length + " for processing by acceptSecContext");
                in.readFully(token);

                token = context.acceptSecContext(token, 0, token.length);

                // Send a token to the peer if one was generated by acceptSecContext
                if (token != null) {
                    System.out.println("Will send token of size " + token.length + " from acceptSecContext.");
                    out.writeInt(token.length);
                    out.write(token);
                    out.flush();
                }
            }

            System.out.println("Context Established! ");
            System.out.println("Client is " + context.getSrcName());
            System.out.println("Server is " + context.getTargName());
        }

        public static void main(String[] args) throws IOException, GSSException {
            System.setProperty("java.security.krb5.conf", "src/main/krb5.conf");
            System.setProperty("java.security.auth.login.config", "src/main/server.conf");
            System.setProperty("javax.security.auth.useSubjectCredsOnly", "false");
            System.setProperty("sun.security.krb5.debug", "true");

            TestServer server = new TestServer(9112);
            server.receive();
        }
    }
}

先运行 Server 程序再运行 Client 程序我们将能从输出内容中看到整个通信的过程。

当前 web 应用成为主流的时候Kerberos 如何在 HTTP/HTTPS 协议场景下使用呢我们又要如何配置才能运行一套支持认证的 Hadoop 集群呢

我们分析了 Kerberos 协议的设计和通信过程。可以了解到Kerberos 主要实现了不在网络传输密码的同时又能在本地进行高性能鉴权。

Kerberos 协议回顾

假设有三个组件 A B CA 想和 C 进行安全通信而 B 作为一个认证中心保存了认证信息。那么以以下的方式进行通信就可以做到安全

  1. A 向 B 请求说要访问 C将此消息用 A 的秘钥加密之后发给 B
  2. B 验证 A 的权限之后用 A 自己的秘钥加密一个会话密码然后传给 A
  3. 同时 B 还向 A 发送一个 A 自己不能解密只能由 C 解密的消息
  4. A 在解密会话密码之后将需要和 C 通信的消息业务消息用这个会话密码加密然后发给 C同时 A 需要将 B 发给 A 而 A 又不能解密的消息发给 C
  5. C 在拿到消息之后可以将第三步中的消息解密得到会话秘钥从而可以解密 A 发过来的业务消息了

整个过程A 无需知道 C 的密码C 也无需知道 A 的密码就可以完成安全通信。

这里的安全性我们可以从以下几个方面来看

  1. 如果消息被截获
    当第一步中的消息被截获这里的消息用 A 的秘钥加密了截获也无法解密
    当第二步中的消息被截获这里的消息用 A 的秘钥加密了截获也无法解密
    当第三步中的消息被截获这里的消息用 C 的秘钥加密了截获也无法解密
    当第四步中的消息被截获这里的消息分别用会话秘钥、C 的秘钥加密了截获也无法解密
    当第五步中的消息被截获这里的消息用会话秘钥截获也无法解密

  2. 如果 A 是一个攻击方某一个有权限的用户想要提权
    他只能拿到自己的秘钥而无法获取 B 或 C 的秘钥他不能随意生成一个加密消息发给 C 请求服务冒充其他用户因为他无法伪造有会话密码而又用 C 的秘钥加密的消息

  3. 如果 C 是一个攻击方欺骗某个有权限的用户
    他无法解密第三步中的消息所以无法解密 A 的消息从而也就无从提供服务

  4. 如果 B 是一个攻击方
    他无法解密 A 的消息从而无法提供服务

  5. 如果传输的消息被破解任何加密都是可以被破解的只是时间的问题
    由于整个通信过程由会话秘钥来加密会话秘钥的有效期通常比较短当消息被破解之后攻击者也不能利用破解得到的秘钥去破解后续的消息

从这几个方面来看这个协议都是比较安全的。

以上的安全通信步骤是 kerberos 安全的核心机制A 对应文章中的 ClientB 对应文章中的 TGSC 对应文章中的 SS

但 kerberos 还引入了一个 AS 的组件这主要为了提高性能和扩展性。

有了 AS 之后我们可以将整个通信看成两个上述 ABC 通信模式的重复。第一个通信模式 A 对应文章中的 ClientB 对应文章中的 ASC 对应文章中的 TGS为了实现 Client 和 TGS 的安全通信。第二个通信模式 A 对应文章中的 ClientB 对应文章中的 TGSC 对应文章中的 SS为了实现 Client 和 SS 的安全通信。

为什么有了两次通信模式之后就能提高性能和扩展性呢实际上一般我们可以将 Client/TGS 的会话秘钥有效期配置得更长一些而将 Client/SS 的会话秘钥有效期配置得比较短。由于一旦我们有一个有效的 TGT 及 Client/TGS 会话秘钥在这个秘钥的有效期内我们无需再访问 AS 去生成新的会话秘钥。当 Client/TGS 会话秘钥有效期较长的时候我们就可以较少的访问 AS从而将 AS 这一第一入口服务的负载降低。而 TGS 由于需要经常参与秘钥生成它的负载会相对较高这里我们就可以将 TGS 扩展到多台服务器来支撑大的负载。AS 可以给 Client 提供一个有效的 TGS 地址从而实现 TGS 的分布式扩展。

Kerberos 协议发展

GSS API

Kerberos 协议本身只是提供了一种安全认证和通信的手段要应用这个协议我们需要一套 API 接口。在具体实现的时候每个人都会写出不一样的代码从而产生不同的 API。这可不是好事对于应用方而言不仅仅学习成本高而且系统迁移能力差比如换一个 Kerberos 服务器可能就会出现兼容性问题。就像 windows 上面的换行用 \r\n而 unix 类操作系统用 \n这给每一个开发者都带来了麻烦。

所以在具体的工程应用时一种通用的 API 就变得非常重要。这就是 GSS API其全称是 The Generic Security Services Application Program Interface即通用安全服务应用程序接口。这套 API 在设计的时候其实不仅仅考虑了对于 Kerberos 的支持还考虑了支持其他的协议所以称为通用接口。由于我们总是会发展出其他的安全协议的抽象一套可以长期保持不变的通用的 API 接口就可以避免应用层进行修改。这一套 API 接口就是在上一篇文章中我们用到的接口了。

从 GSS API 接口来看我们的认证过程可以抽象为这样几个简单的步骤

  • 客户端创建一个 Context 上下文用来保存数据 -> 通过 initSecContext 获取一个 token -> 将 token 发送给服务器 -> 等待服务器回发的用于通信的 token
  • 服务器创建一个 Context 上下文用来保存数据 -> 读取客户端发来的 token -> 验证 token并可能生成一个新的用于通信的 token -> 将 token 发给客户端

这里的认证过程简单到甚至没有出现认证服务器基于这样的一套通用 API 去实现其他应用就相对轻松多了。Kerberos 内部的通信细节多次传输的各种密文全部都隐藏在这样的 API 实现中。具体的 GSS API 使用代码示例。

SPNEGO

由于 GSS API 设计可以支持多种安全协议另一个想法会自然的冒出来。我们可以让服务器支持多种认证协议然后具体用哪种由客户端和服务器端协商决定。这就使得我们在开发应用时可以给最终的用户提供选择便于使用他或她所偏好使用的认证方式从而带来更好的用户体验。同时服务器和客户端在各自实现时也可以相互独立的增量式的添加或去掉对于某一具体协议的支持而不用完全同步的进行修改。这对于同一个服务器要支持多个版本的客户端而言会很有用。

这就是 SPNEGO 了其全称是 Simple and Protected GSSAPI Negotiation Mechanism即基于 GSS API 实现的一套简单的协议协商机制。这一协议由微软最早提出并应用在 windows 操作系统中与我们最贴近的应用当属于浏览器的系统集成认证了。大家回忆一下我们使用 IE 浏览器的体验可以发现很多网站可以直接使用系统的域账户登录。这就是用 SPNEGO 协议实现的浏览器系统集成认证。在企业中如果我们为所有员工配置了 windows 域账户而当我们有一些基于 web 的企业应用需要认证时就可以利用这一机制实现无感知的认证。其实不只是 IE 浏览器Firefox Chrome 等主流浏览器基本上都实现了这样的系统集成登录机制。

这个协议的通信过程大致为

  1. Client 向 Server 请求服务
  2. Server 检查 Client 是否有提供有效的认证信息如果没有返回消息包括服务器支持的认证方式给 Client以便 Client 可以完成认证如果有就提供服务
  3. Client 完成认证之后向 Server 请求服务并带上认证信息
  4. 回到第二步中进行认证检查直到通过或认证次数达到阈值为止

Hadoop 认证机制

介绍了这么多其实都是为了我们分析 Hadoop 的认证机制实现。到这里相信大家应该也猜到了在 Hadoop 的认证中各个节点的通信实际上使用的就是 GSS API 去实现的基于 Kerberos 协议的单点认证。而 Hadoop 对外提供的很多基于 web 的应用比如 Web HDFS、统计信息页面、Yarn Application 管理等等其认证都是基于 SPNEGO 协议的。这两个协议的配置其实在我们后续配置 Hadoop 认证时也是最主要的配置了。

相关源代码分析

下面的内容请大家结合源代码一起分析仅仅读文字可能有很多内容会难以理解

GSS API 中的 Kerberos 实现

我们打开 OpenJDK 的源代码库浏览到下面这里的代码

这里的代码量还是挺大的细节很多我们一起看一下主要的设计。GSS API 在 Java 语言中通过 jgss 模块来实现。jgss 首先定义了一些底层认证机制需要实现的接口即 sun.security.jgss.spi 包中的基本接口 GSSContextSpi GSSNameSpi GSSCredentialSpi 和工厂接口 MechanismFactory。底层的协议只需要实现这几个接口就行了关于 Kerberos 的实现在包 sun.security.jgss.krb5 中其实这个包里面的代码只是对接了真正的 Kerberos 通信协议实现和 GSS API 接口。这里的设计按照 DDD 的思想我们可以理解为一套防腐层GSS API 和 Kerberos 可以看成两个独立的领域通过引入防腐层它们就可以相互独立的各自演进。当接口有改变的时候我们只需要修改防腐层的代码就行了。

真正的 Kerberos 协议实现在包 sun.security.krb5 下面这里的实现通过 javax.security.auth.kerberos 包下面的类对应用层暴露接口应用层在使用 GSS API 时有时还是需要关心底层认证机制的相关信息的。作为应用层如果有必要获取底层认证机制相关的信息我们将只使用 javax.security.auth.kerberos 中定义的接口而无需关心 sun 包下面的实现。这里的实现的核心代码在 Credentials 类中我们看到其定义了 acquireTGTFromCache acquireDefaultCreds acquireServiceCreds 等接口用于交换秘钥。更细节的实现代码大家如有兴趣可以结合上一篇文章中的通信流程自行研究。我们这里只简要分析一下主要的设计思想。

Hadoop 中使用 GSS API 进行认证

Hadoop 中和认证相关的模块主要有两个一个是直接使用 GSS API 进行认证用于 tcp 通信的 org.apache.hadoop.security.UserGroupInformation 类另一个是基于 SPNEGO 协议进行认证用于 HTTP 通信的 org.apache.hadoop.security.authentication.server.KerberosAuthenticationHandler

UserGroupInformation 主要用于 Hadoop 各个内部模块间的通信也可以用于某一个客户端和 Hadoop 的某个模块进行通信它同时为服务器和客户端的认证提供了支持。比如 NameNode 的启动之后它将发起一个登陆请求用于验证给自己配置的 Principal 和 keytab 是否有效这里 108 行。同时当有内部服务如某个 datanode的 rpc 请求到来的时候它将使用登陆得到的认证主体 Subject 中的 doAs 方法来验证发送过来的认证信息并进行权限验证。有客户端的 rpc 请求到来时它将获取客户端的用户信息并根据配置的 ACL访问控制列表进行权限验证实现见这里的 1287 行及这里。为了缓存认证信息避免没必要的重新认证程序需要维护当前登录的账号的信息这也就是为什么 UserGroupInformation 在设计上定义了很多静态的属性。同时我们可以注意到很多 synchronized 关键字附加到了某些静态方法上这是为了支持多线程访问这些全局缓存的信息。

KerberosAuthenticationHandler 的实现是为了支持在 HTTP 服务中进行 Kerberos 认证这个类最终会封装为一个 Web 服务器中的 Filter 实现对所有 HTTP 请求的权限验证这里的 AuthFilter 及其基类 AuthenticationFilter。由于基于 Servlet 的 Web 服务器有很成熟的接口设计这个模块的实现也相对独立和简单。可以看到它在 init 的时候使用 GSS API 完成了登录在 authenticate 的时候将判断是否有有效的认证信息如果没有将返回协商认证的 HTTP 头部消息以便客户端去完成认证如果有将进行认证并提供服务。

Web 服务器认证实现示例

对于一个运行于 Hadoop 集群的 Spark 应用我们通常是通过 spark-submit 命令行工具来向集群提交任务的。这一机制对于 spark 应用的开发者看起来很灵活但如果我们想进行更多的统一管理比如限制资源使用、提升易用性等等这样的机制就略显不足了。这个时候一般的做法是将运行 spark 应用的这一能力封装为一个服务以便进行统一的管理。Livy 就是为实现这样的功能而开发的一个开源工具。

使用 Livy我们可以使用 REST 的接口向集群提交 spark 任务。在这里 Livy 其实相当于是整个 Hadoop 大数据集群的一个扩展服务。Livy 在实现的时候如何进行权限的支持呢当我们去查看 Livy 的源代码的时候我们会发现要为每个请求添加 Kerberos 认证几乎只需要一行代码这里的 237 行即为那行关键的代码。这里 Livy 就是有效的利用了上面的 KerberosAuthenticationHandler 进行实现的。

搭建Hadoop安全认证

简单起见我们这里的集群所有的组件将运行在同一台机器上。对于 keytab 的配置我们也从简只配置一个 kerberos 的 service 账号供所有服务使用。

建立测试用例

TDD 是敏捷最重要的实践之一可以有效的帮助我们确定目标验证目标它可以带领我们走得又快又稳。跟随 TDD 的思想我们先从测试的角度来看这个问题。有了前面的基础知识假设我们已经有了一套安全的 Hadoop 集群那么我们应当可以从集群读写文件运行 MapReduce 任务。我们可以编写读写文件的测试用例如下

public class HdfsTest {
    TestConfig testConfig = new TestConfig();

    
    public void should_read_write_files_from_hdfs() throws IOException {
        testConfig.configKerberos();

        Configuration conf = new Configuration();
        conf.addResource(new Path(testConfig.hdfsSiteFilePath()));
        conf.addResource(new Path(testConfig.coreSiteFilePath()));
        UserGroupInformation.setConfiguration(conf);
        UserGroupInformation.loginUserFromKeytab(testConfig.keytabUser(), testConfig.keytabFilePath());

        FileSystem fileSystem = FileSystem.get(conf);
        Path path = new Path("/user/root/input/core-site.xml");
        if (fileSystem.exists(path)) {
            boolean deleteSuccess = fileSystem.delete(path, false);
            assertTrue(deleteSuccess);
        }

        String fileContent = FileUtils.readFileToString(new File(testConfig.coreSiteFilePath()));
        try (FSDataOutputStream fileOut = fileSystem.create(path)) {
            fileOut.write(fileContent.getBytes("utf-8"));
        }

        assertTrue(fileSystem.exists(path));

        try (FSDataInputStream in = fileSystem.open(path)) {
            String fileContentRead = IOUtils.toString(in);
            assertEquals(fileContent, fileContentRead);
        }

        fileSystem.close();
    }
}

完整代码请参考这里

到这里我们的任务目标就明确了只要上面的测试能通过我们的集群就应该搭建好了。

如果有条件下面的内容请大家结合代码及参考文档一边读文章一边动手实践否则可能会遗漏很多细节。

建立基本集群

我们先跟随官网的教程搭建一个非安全的集群。

这里我选择的 Hadoop 版本为 2.7.7我这里是为了和实际项目中用到的版本保持一致大家可以自行尝试其他版本思路和大部分的脚本都应该是相同的。我们选择伪分布式模式Pseudo-Distributed来进行尝试这种模式下每个组件会运行为一个独立的 java 进程与真实的分布式环境类似。

我们还是使用容器来进行试验启动一个容器并依次运行下面的命令

注意下面如果使用docker跑容器那么要外部访问的话就把所有的端口暴露出来这里我建议直接在虚拟机上面跑

docker run -it --name shd -h shd centos:7 bash

需要的安装包

链接https://pan.baidu.com/s/1hJogxtc4Kz5nk90VdZN0aA 
提取码yyds 
--来自百度网盘超级会员V5的分享

在容器中运行下面的命令

# 建立并切换到我们的工作目录
mkdir /hd && cd /hd
# 下载软件、解压、进入根目录
yum install wget vim less -y
wget https://archive.apache.org/dist/hadoop/common/hadoop-2.7.7/hadoop-2.7.7.tar.gz
tar xf hadoop-2.7.7.tar.gz
ln -sv hadoop-2.7.7/ hadoop
cd hadoop
# 配置hadoop这里的shd修改成自己的域名或者是ip我的是hadoop104,根据自己的情况修改
echo hadoop104 > etc/hadoop/slaves
cat > etc/hadoop/core-site.xml << EOF
<configuration>
    <property>
        <name>fs.defaultFS</name>
        <value>hdfs://0.0.0.0:9000</value>
    </property>
</configuration>
EOF
cat > etc/hadoop/hdfs-site.xml << EOF
<configuration>
    <property>
        <name>dfs.replication</name>
        <value>1</value>
    </property>
    <property>
        <name>dfs.namenode.name.dir</name>
        <value>/hd/data/hdfs/namenode</value>
    </property>
    <property>
        <name>dfs.datanode.data.dir</name>
        <value>/hd/data/hdfs/datanode</value>
    </property>
</configuration>
EOF
# 配置ssh测试是否能通过`ssh localhost`免密登录
yum install openssh-clients openssh-server -y
echo 'root:screencast' | chpasswd
sed -i 's/PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config
sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd
echo "export VISIBLE=now" >> /etc/profile
ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key -P '' && ssh-keygen -t dsa -f /etc/ssh/ssh_host_dsa_key -P ''
/usr/sbin/sshd
ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa
cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
chmod 0600 ~/.ssh/authorized_keys
# 安装jdk,并配置环境变量
yum install -y java-1.8.0-openjdk-devel
echo 'export JAVA_HOME=/usr/lib/jvm/java' >> ~/.bashrc
export JAVA_HOME=/usr/lib/jvm/java
# 启动hdfs
bin/hdfs namenode -format
sbin/start-dfs.sh
# 测试
bin/hdfs dfs -mkdir /user
bin/hdfs dfs -mkdir /user/root
bin/hdfs dfs -put etc/hadoop /input
bin/hadoop jar share/hadoop/mapreduce/hadoop-mapreduce-examples-2.7.7.jar grep /input /output 'dfs[a-z.]+'
bin/hdfs dfs -cat /output/*  # 这里的结果将显示配置文件里面关于dfs的内容

到这里我们的非安全的单机模式集群应该就能运行起来了。但是在这个集群里面我们还没法运行分布式任务因为目前仅仅是一个 HDFS 分布式文件系统。如果用 jps 查看一下有哪些 java 进程将发现我们启动了三个进程 NameNode SecondaryNameNode DataNode

下一步我们还需要配置并启动用于管理分布式集群任务的关键组件 Yarn。运行如下这些命令即可启动 Yarn

# 配置Yarn
cat > etc/hadoop/mapred-site.xml << EOF
<configuration>
    <property>
        <name>mapreduce.framework.name</name>
        <value>yarn</value>
    </property>
    <property>
        <name>mapreduce.jobhistory.address</name>
        <value>0.0.0.0:10020</value>
    </property>
    <property>
        <name>mapreduce.jobhistory.webapp.address</name>
        <value>0.0.0.0:19888</value>
    </property>
</configuration>
EOF
cat > etc/hadoop/yarn-site.xml << EOF
<configuration>
    <property>
        <name>yarn.nodemanager.aux-services</name>
        <value>mapreduce_shuffle</value>
    </property>
    <property>
        <name>yarn.log-aggregation-enable</name>
        <value>true</value>
    </property>
    <!-- fix node unhealthy issue -->
    <!-- `yarn node -list -all` report node unhealthy with message indicate no disk space (disk space check failed) -->
    <property>
        <name>yarn.nodemanager.disk-health-checker.max-disk-utilization-per-disk-percentage</name>
        <value>99.9</value>
    </property>
    <!-- to fix issue: 'Failed while trying to construct...' (http://blog.51yip.com/hadoop/2066.html) -->
    <property>
         <name>yarn.log.server.url</name>
         <value>http://hadoop104:19888/jobhistory/logs</value>
    </property>
</configuration>
EOF
#文件修改完以后记得查看下是否修改成功
# 启动Yarn启动之后我们将能通过`./bin/yarn node -list -all`查看到一个RUNNIN的node
sbin/start-yarn.sh
# 启动History server用于查看应用日志
sbin/mr-jobhistory-daemon.sh start historyserver
# 测试我们将能看到下面的命令从0%到100%按进度完成。
bin/hadoop jar share/hadoop/mapreduce/hadoop-mapreduce-examples-2.7.7.jar  wordcount /input/* /output/wc/
# 验证运行`./bin/hadoop dfs -cat /output/wc/part-r-00000`还将看到计算出来的结果。
# 验证运行`./bin/yarn application -list -appStates FINISHED`可以看到已运行完成的任务及其日志的地址。

执行上面的命令启动 Yarn 及 historyserver 之后我们将发现有三个额外的进程 ResourceManager NodeManager JobHistoryServer 随之启动了。

注意如果是直接虚拟机启动的话那么直接访问对应的端口就行了

如果我们的容器所在主机有一个浏览器可以用那么我们可以通过访问 http://${SHD_DOCKER_IP}:8088/cluster/apps 将能看到上面的 wordcount 程序运行的状态及日志。这里的 SHD_DOCKER_IP 可以通过下面的命令查找出来。

docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' shd

如果容器是在一个远端的主机上面启动的我们可以用 ssh tunnel 的方式建立一个代理通过代理来访问我们的集群。运行命令 ssh -f -N -D 127.0.0.1:3128 ${USER}@${REMOTE_DOCKER_HOST_IP} 即可建立这样的代理。然后我们运行 echo "${SHD_DOCKER_IP} shd" >> /etc/hosts 将容器的主机名加入到我们本地的 hosts。再使用 firefox 浏览器来配置代理如下图这样我们就可以通过本地的 firefox 来访问到远端的集群了。

 

我们将能看到如下的 web 应用通过这个 web 应用我们实际上还可以查询到更多的集群相关的信息。

http://hadoop104:50070/dfshealth.html#tab-overview

http://hadoop104:8088/cluster

可以看到经过多年的优化即便是一个非常复杂的分布式系统我们现在也可以快速的上手了。几乎所有的配置都有相对合理的默认值我们仅仅需要调整很少的配置。

Hadoop 本身内置了很多实用的工具当我们遇到问题的时候这些工具可以有效的辅助诊断问题。如果大家经过上面的步骤还是没法通过测试命令行中的测试。大家可能可以从以下几个方面去查找问题

  1. 检查各个组件进程是否都启动起来了
  2. 检查各个组件的日志比如如果 datanode 启动失败可能我们要查看 logs/hadoop-root-datanode-shd.log 日志做进一步分析
  3. 使用 bin/yarn node -list -all 检查 node 的状态
  4. 检查最终生成的配置 http://172.17.0.12:8042/conf 是否是我们所希望的比如我们可能由于拼写错误导致配置不对

Kerberos 安全配置

在本系列第一篇文章中我们尝试了搭建一个 kerberos 认证服务器这里我们可以用与之前一致的方式先搭建起一个 kerberos 认证服务器。需要的执行脚本如下

# 将kdc kdc.hadoop.com加入hosts以便后续进行基于hosts文件的主机名解析
yum install net-tools -y
ip_addr=$(ifconfig eth0 | grep inet | awk '{print $2}')
echo "$ip_addr kdc-server kdc-server.hadoop.com" >> /etc/hosts

# 安装相关软件并进行配置
yum install krb5-server krb5-libs krb5-workstation -y
# 创建krb5配置文件详细配置解释请参考https://web.mit.edu/kerberos/krb5-1.12/doc/admin/conf_files/krb5_conf.html
cat > /etc/krb5.conf <<EOF
#Configuration snippets may be placed in this directory as well
includedir /etc/krb5.conf.d/

[logging]
  default = FILE:/var/log/krb5.log
  kdc = FILE:/var/log/krb5kdc.log
  admin_server = FILE:/var/log/kadmind.log

[libdefaults]
  forcetcp = true
  default_realm = HADOOP.COM
  dns_lookup_realm = false
  dns_lookup_kdc = false
  ticket_lifetime = 24h
  renew_lifetime = 7d
  forwardable = true
  udp_preference_limit = 1
  default_tkt_enctypes = des-cbc-md5 des-cbc-crc des3-cbc-sha1
  default_tgs_enctypes = des-cbc-md5 des-cbc-crc des3-cbc-sha1
  permitted_enctypes = des-cbc-md5 des-cbc-crc des3-cbc-sha1

[realms]
  HADOOP.COM = {
    kdc = kdc-server.hadoop.com:2802
    admin_server = kdc-server.hadoop.com:2801
    default_domain = hadoop.com
  }

[domain_realm]
  .hadoop.com = HADOOP.COM
  hadoop.com = HADOOP.COM
EOF
# 创建kdc配置文件详细配置解释请参考https://web.mit.edu/kerberos/krb5-1.12/doc/admin/conf_files/kdc_conf.html
cat > /var/kerberos/krb5kdc/kdc.conf <<EOF
default_realm = HADOOP.COM

[kdcdefaults]
 kdc_ports = 0
 v4_mode = nopreauth

[realms]
 HADOOP.COM = {
    kdc_ports = 2800
    kdc_tcp_ports = 2802
    admin_keytab = /etc/kadm5.keytab
    database_name = /var/kerberos/krb5kdc/principal
    acl_file = /var/kerberos/krb5kdc/kadm5.acl
    key_stash_file = /var/kerberos/krb5kdc/stash
    max_life = 10h 0m 0s
    max_renewable_life = 7d 0h 0m 0s
    master_key_type = des3-hmac-sha1
    supported_enctypes = arcfour-hmac:normal des3-hmac-sha1:normal des-cbc-crc:normal des:normal des:v4 des:norealm des:onlyrealm des:afs3
    default_principal_flags = +preauth
}
EOF

echo -e '123456\n123456' | kdb5_util create -r HADOOP.COM -s  # 创建一个名为HADOOP.COM的域
/usr/sbin/krb5kdc && /usr/sbin/kadmind                        # 启动kdc及kadmind服务

配置 Hadoop 安全支持

前面我们分析了 Kerberos 的运行原理及 Hadoop 的相关源代码可以知道为了启动安全支持每一个集群节点的每一个 hadoop 组件都将需要单独的 Kerberos 账号及其 keytab 文件每个组件最好还能用不同的账户启动。这里由于我们使用伪分布式模式来部署集群所有的组件都运行在同一个节点简单起见我们这里将使用 root 账号来启动集群并让所有的组件使用同一个 kerberos 账号。

首先我们生成账号如下

mkdir /hd/conf/
# 生成hadoop集群需要的账号
kadmin.local addprinc -randkey root/hadoop104@HADOOP.COM
kadmin.local addprinc -randkey HTTP/hadoop104@HADOOP.COM
kadmin.local xst -k /hd/conf/hadoop.keytab root/hadoop104@HADOOP.COM HTTP/hadoop104@HADOOP.COM
# 生成测试用的普通账号
kadmin.local addprinc -randkey root@HADOOP.COM
kadmin.local xst -k /hd/conf/root.keytab root@HADOOP.COM

接下来我们来完成 hadoop 的配置由于配置文件内容比较多我统一整理到了 github 的一个 repo 中下面的配置将主要通过 copy 这些文件来生成而辅以说明主要修改的地方。如果大家有兴趣知道确切的修改之处可以备份这些文件然后用 diff 来查看修改或者用 git 对配置文件进行版本管理然后查看修改。

配置集群

可以运行命令也可以直接修改配置文件

配置 core-site.xml

wget https://raw.githubusercontent.com/gmlove/bigdata_conf/master/auth/hadoop/etc/hadoop/core-site.xml -O etc/hadoop/core-site.xml
sed -i 's/hd01-7/hadoop104/g' etc/hadoop/core-site.xml
<configuration>
    <property>
        <name>fs.defaultFS</name>
        <value>hdfs://hadoop104:9000</value>
    </property>

    <property>
        <name>hadoop.proxyuser.root.hosts</name>
        <value>*</value>
    </property>
    <property>
        <name>hadoop.proxyuser.root.groups</name>
        <value>*</value>
    </property>

    <property>
        <name>hadoop.proxyuser.HTTP.hosts</name>
        <value>*</value>
    </property>
    <property>
        <name>hadoop.proxyuser.HTTP.groups</name>
        <value>*</value>
    </property>


    <property>
      <name>hadoop.security.authorization</name>
      <value>true</value>
    </property>
    <property>
      <name>hadoop.security.authentication</name>
      <value>kerberos</value>
    </property>

</configuration>

这里主要加入的配置项及其解释如下

hadoop.proxyuser.root.hosts=*           # 配置root用户组件启动时认证的kerberos账户可以以任意客户端认证过的用户proxy user来执行操作详见https://hadoop.apache.org/docs/stable/hadoop-project-dist/hadoop-common/Superusers.html
hadoop.proxyuser.root.groups=*
hadoop.proxyuser.HTTP.hosts=*
hadoop.proxyuser.HTTP.groups=*
hadoop.security.authorization=true
hadoop.security.authentication=kerberos

配置 hdfs-site.xml

wget https://raw.githubusercontent.com/gmlove/bigdata_conf/master/auth/hadoop/etc/hadoop/hdfs-site.xml -O etc/hadoop/hdfs-site.xml
sed -i 's/hd01-7/hadoop104/g' etc/hadoop/hdfs-site.xml

<configuration>
    <property>
        <name>dfs.replication</name>
        <value>1</value>
    </property>
    <property>
        <name>dfs.namenode.name.dir</name>
        <value>/hd/data/hdfs/namenode</value>
    </property>
    <property>
        <name>dfs.datanode.data.dir</name>
        <value>/hd/data/hdfs/datanode</value>
    </property>

    <property>
        <name>dfs.block.access.token.enable</name>
        <value>true</value>
    </property>
    <property>
        <name>dfs.namenode.keytab.file</name>
        <value>/hd/conf/hadoop.keytab</value>
    </property>
    <property>
        <name>dfs.namenode.kerberos.principal</name>
        <value>root/_HOST@HADOOP.COM</value>
    </property>
    <property>
        <name>dfs.namenode.kerberos.internal.spnego.principal</name>
        <value>HTTP/_HOST@HADOOP.COM</value>
    </property>
    <property>
        <name>dfs.web.authentication.kerberos.principal</name>
        <value>HTTP/_HOST@HADOOP.COM</value>
    </property>
    <property>
        <name>dfs.web.authentication.kerberos.keytab</name>
        <value>/hd/conf/hadoop.keytab</value>
    </property>
    <property>
        <name>dfs.datanode.keytab.file</name>
        <value>/hd/conf/hadoop.keytab</value>
    </property>
    <property>
        <name>dfs.datanode.kerberos.principal</name>
        <value>root/_HOST@HADOOP.COM</value>
    </property>

    <property>
        <name>dfs.datanode.address</name>
        <value>hadoop104:1004</value>
    </property>
    <property>
        <name>dfs.datanode.http.address</name>
        <value>hadoop104:1006</value>
    </property>

    <property>
        <name>dfs.journalnode.keytab.file</name>
        <value>/hd/conf/hadoop.keytab</value>
    </property>
    <property>
        <name>dfs.journalnode.kerberos.principal</name>
        <value>root/_HOST@HADOOP.COM</value>
    </property>
    <property>
        <name>dfs.journalnode.kerberos.internal.spnego.principal</name>
        <value>HTTP/_HOST@HADOOP.COM</value>
    </property>


</configuration>

这里主要加入的配置项如下

dfs.block.access.token.enable=true
dfs.namenode.keytab.file=/hd/conf/hadoop.keytab
dfs.namenode.kerberos.principal=root/_HOST@HADOOP.COM
dfs.namenode.kerberos.internal.spnego.principal=HTTP/_HOST@HADOOP.COM
dfs.web.authentication.kerberos.principal=HTTP/_HOST@HADOOP.COM
dfs.web.authentication.kerberos.keytab=/hd/conf/hadoop.keytab
dfs.datanode.keytab.file=/hd/conf/hadoop.keytab
dfs.datanode.kerberos.principal=root/_HOST@HADOOP.COM
dfs.datanode.address=hadoop104:1004
dfs.datanode.http.address=hadoop104:1006
dfs.journalnode.keytab.file=/hd/conf/hadoop.keytab
dfs.journalnode.kerberos.principal=root/_HOST@HADOOP.COM
dfs.journalnode.kerberos.internal.spnego.principal=HTTP/_HOST@HADOOP.COM

配置 mapred-site.xml

wget https://raw.githubusercontent.com/gmlove/bigdata_conf/master/auth/hadoop/etc/hadoop/mapred-site.xml -O etc/hadoop/mapred-site.xml
sed -i 's/hd01-7/hadoop104/g' etc/hadoop/mapred-site.xml
<configuration>
<property>
        <name>mapreduce.framework.name</name>
        <value>yarn</value>
    </property>



<!-- to fix issue: 'Failed while trying to construct...' (http://blog.51yip.com/hadoop/2066.html) -->
    <property>
        <name>mapreduce.jobhistory.address</name>
        <value>hadoop104:10020</value>
    </property>
    <property>
        <name>mapreduce.jobhistory.webapp.address</name>
        <value>hadoop104:19888</value>
    </property>

<!-- kerberos auth -->
    <property>
      <name>mapreduce.jobhistory.principal</name>
      <value>root/_HOST@HADOOP.COM</value>
    </property>
    <property>
      <name>mapreduce.jobhistory.keytab</name>
      <value>/hd/conf/hadoop.keytab</value>
    </property>


</configuration>

这里主要加入的配置项如下

mapreduce.jobhistory.address=hadoop104:10020
mapreduce.jobhistory.webapp.address=hadoop104:19888
mapreduce.jobhistory.principal=root/_HOST@HADOOP.COM
mapreduce.jobhistory.keytab=/hd/conf/hadoop.keytab

配置 yarn-site.xml

wget https://raw.githubusercontent.com/gmlove/bigdata_conf/master/auth/hadoop/etc/hadoop/yarn-site.xml -O etc/hadoop/yarn-site.xml
sed -i 's/hd01-7/hadoop104/g' etc/hadoop/yarn-site.xml
<configuration>

<!-- Site specific YARN configuration properties -->
    <property>
        <name>yarn.nodemanager.aux-services</name>
        <value>mapreduce_shuffle</value>
    </property>

    <property>
        <name>yarn.resourcemanager.hostname</name>
        <value>hadoop104</value>
    </property>
    <property>
        <name>yarn.log-aggregation-enable</name>
        <value>true</value>
    </property>

<!-- fix node unhealthy issue -->
<!-- `yarn node -list -all` report node unhealthy with message indicate no disk space (disk space check failed) -->
    <property>
        <name>yarn.nodemanager.disk-health-checker.max-disk-utilization-per-disk-percentage</name>
        <value>99.9</value>
    </property>

<!-- fix spark job submit issue -->
    <property>
        <name>yarn.nodemanager.pmem-check-enabled</name>
        <value>false</value>
    </property>

    <property>
        <name>yarn.nodemanager.vmem-check-enabled</name>
        <value>false</value>
    </property>


<!-- to fix issue: 'Failed while trying to construct...' (http://blog.51yip.com/hadoop/2066.html) -->
    <property>
         <name>yarn.log.server.url</name>
         <value>http://hadoop104:19888/jobhistory/logs</value>
    </property>


<!-- kerberose auth -->
    <property>
      <name>yarn.resourcemanager.principal</name>
      <value>root/_HOST@HADOOP.COM</value>
    </property>
    <property>
      <name>yarn.resourcemanager.keytab</name>
      <value>/hd/conf/hadoop.keytab</value>
    </property>
    <property>
      <name>yarn.resourcemanager.webapp.https.address</name>
      <value>${yarn.resourcemanager.hostname}:8090</value>
    </property>

    <property>
      <name>yarn.nodemanager.principal</name>
      <value>root/_HOST@HADOOP.COM</value>
    </property>
    <property>
      <name>yarn.nodemanager.keytab</name>
      <value>/hd/conf/hadoop.keytab</value>
    </property>

    <!-- <property>
               <name>yarn.nodemanager.container-executor.class</name>
      <value>org.apache.hadoop.yarn.server.nodemanager.LinuxContainerExecutor</value>
    </property>
    <property>
      <name>yarn.nodemanager.linux-container-executor.group</name>
      <value>root</value>
    </property>
    <property>
      <name>yarn.nodemanager.linux-container-executor.path</name>
      <value></value>
    </property> -->

    <property>
      <name>yarn.web-proxy.principal</name>
      <value>root/_HOST@HADOOP.COM</value>
    </property>
    <property>
      <name>yarn.web-proxy.keytab</name>
      <value>/hd/conf/hadoop.keytab</value>
    </property>


</configuration>

这里主要加入的配置项如下

yarn.resourcemanager.principal=root/_HOST@HADOOP.COM
yarn.resourcemanager.keytab=/hd/conf/hadoop.keytab
yarn.resourcemanager.webapp.https.address=${yarn.resourcemanager.hostname}:8090
yarn.nodemanager.principal=root/_HOST@HADOOP.COM
yarn.nodemanager.keytab=/hd/conf/hadoop.keytab
yarn.web-proxy.principal=root/_HOST@HADOOP.COM
yarn.web-proxy.keytab=/hd/conf/hadoop.keytab

配置 hadoop-env.sh

wget https://raw.githubusercontent.com/gmlove/bigdata_conf/master/auth/hadoop/etc/hadoop/hadoop-env.sh -O etc/hadoop/hadoop-env.sh
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Set Hadoop-specific environment variables here.

# The only required environment variable is JAVA_HOME.  All others are
# optional.  When running a distributed configuration it is best to
# set JAVA_HOME in this file, so that it is correctly defined on
# remote nodes.

# The java implementation to use.
export JAVA_HOME=${JAVA_HOME}
export JAVA_HOME=/usr/lib/jvm/java

export JSVC_HOME=/usr/bin


# The jsvc implementation to use. Jsvc is required to run secure datanodes
# that bind to privileged ports to provide authentication of data transfer
# protocol.  Jsvc is not required if SASL is configured for authentication of
# data transfer protocol using non-privileged ports.
#export JSVC_HOME=${JSVC_HOME}

export HADOOP_CONF_DIR=${HADOOP_CONF_DIR:-"/etc/hadoop"}

# Extra Java CLASSPATH elements.  Automatically insert capacity-scheduler.
for f in $HADOOP_HOME/contrib/capacity-scheduler/*.jar; do
  if [ "$HADOOP_CLASSPATH" ]; then
    export HADOOP_CLASSPATH=$HADOOP_CLASSPATH:$f
  else
    export HADOOP_CLASSPATH=$f
  fi
done

# The maximum amount of heap to use, in MB. Default is 1000.
#export HADOOP_HEAPSIZE=
#export HADOOP_NAMENODE_INIT_HEAPSIZE=""

export HADOOP_JAAS_DEBUG=true
export HADOOP_OPTS="-Djava.net.preferIPv4Stack=true -Dsun.security.krb5.debug=true -Dsun.security.spnego.debug"
export HADOOP_SECURE_DN_USER=root
export HADOOP_HDFS_USER=root

# Extra Java runtime options.  Empty by default.
export HADOOP_OPTS="$HADOOP_OPTS -Djava.net.preferIPv4Stack=true"

# Command specific options appended to HADOOP_OPTS when specified
export HADOOP_NAMENODE_OPTS="-Dhadoop.security.logger=${HADOOP_SECURITY_LOGGER:-INFO,RFAS} -Dhdfs.audit.logger=${HDFS_AUDIT_LOGGER:-INFO,NullAppender} $HADOOP_NAMENODE_OPTS"
export HADOOP_DATANODE_OPTS="-Dhadoop.security.logger=ERROR,RFAS $HADOOP_DATANODE_OPTS"

export HADOOP_SECONDARYNAMENODE_OPTS="-Dhadoop.security.logger=${HADOOP_SECURITY_LOGGER:-INFO,RFAS} -Dhdfs.audit.logger=${HDFS_AUDIT_LOGGER:-INFO,NullAppender} $HADOOP_SECONDARYNAMENODE_OPTS"

export HADOOP_NFS3_OPTS="$HADOOP_NFS3_OPTS"
export HADOOP_PORTMAP_OPTS="-Xmx512m $HADOOP_PORTMAP_OPTS"

# The following applies to multiple commands (fs, dfs, fsck, distcp etc)
export HADOOP_CLIENT_OPTS="-Xmx512m $HADOOP_CLIENT_OPTS"
#HADOOP_JAVA_PLATFORM_OPTS="-XX:-UsePerfData $HADOOP_JAVA_PLATFORM_OPTS"

# On secure datanodes, user to run the datanode as after dropping privileges.
# This **MUST** be uncommented to enable secure HDFS if using privileged ports
# to provide authentication of data transfer protocol.  This **MUST NOT** be
# defined if SASL is configured for authentication of data transfer protocol
# using non-privileged ports.
export HADOOP_SECURE_DN_USER=${HADOOP_SECURE_DN_USER}

# Where log files are stored.  $HADOOP_HOME/logs by default.
#export HADOOP_LOG_DIR=${HADOOP_LOG_DIR}/$USER

# Where log files are stored in the secure data environment.
export HADOOP_SECURE_DN_LOG_DIR=${HADOOP_LOG_DIR}/${HADOOP_HDFS_USER}

###
# HDFS Mover specific parameters
###
# Specify the JVM options to be used when starting the HDFS Mover.
# These options will be appended to the options specified as HADOOP_OPTS
# and therefore may override any similar flags set in HADOOP_OPTS
#
# export HADOOP_MOVER_OPTS=""

###
# Advanced Users Only!
###

# The directory where pid files are stored. /tmp by default.
# NOTE: this should be set to a directory that can only be written to by 
#       the user that will run the hadoop daemons.  Otherwise there is the
#       potential for a symlink attack.
export HADOOP_PID_DIR=${HADOOP_PID_DIR}
export HADOOP_SECURE_DN_PID_DIR=${HADOOP_PID_DIR}

# A string representing this instance of hadoop. $USER by default.
export HADOOP_IDENT_STRING=$USER

主要加入的配置项如下

export JSVC_HOME=/usr/bin             # 指定jsvc的路径以便运行安全模式的datanode
export HADOOP_JAAS_DEBUG=true         # 开启Kerberos认证的debug日志
export HADOOP_OPTS="-Djava.net.preferIPv4Stack=true -Dsun.security.krb5.debug=true -Dsun.security.spnego.debug"  # 开启Kerberos认证的debug日志
export HADOOP_SECURE_DN_USER=root     # 运行安全模式的datanode组件的用户
export HADOOP_HDFS_USER=root          # 运行hdfs组件的用户

修复启动脚本

由于我们开启了 Kerberos 的调试日志原来的脚本需要稍加修改才能使用。执行脚本如下

wget https://raw.githubusercontent.com/gmlove/bigdata_conf/master/auth/hadoop/sbin/stop-dfs.sh -O sbin/stop-dfs.sh
wget https://raw.githubusercontent.com/gmlove/bigdata_conf/master/auth/hadoop/sbin/start-dfs.sh -O sbin/start-dfs.sh

stop-dfs.sh

#!/usr/bin/env bash

# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License.  You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

bin=`dirname "${BASH_SOURCE-$0}"`
bin=`cd "$bin"; pwd`

DEFAULT_LIBEXEC_DIR="$bin"/../libexec
HADOOP_LIBEXEC_DIR=${HADOOP_LIBEXEC_DIR:-$DEFAULT_LIBEXEC_DIR}
. $HADOOP_LIBEXEC_DIR/hdfs-config.sh

#---------------------------------------------------------
# namenodes

NAMENODES=$($HADOOP_PREFIX/bin/hdfs getconf -namenodes | tail -n 1)

echo "Stopping namenodes on [$NAMENODES]"

"$HADOOP_PREFIX/sbin/hadoop-daemons.sh" \
  --config "$HADOOP_CONF_DIR" \
  --hostnames "$NAMENODES" \
  --script "$bin/hdfs" stop namenode

#---------------------------------------------------------
# datanodes (using default slaves file)

if [ -n "$HADOOP_SECURE_DN_USER" ]; then
  echo \
    "Attempting to stop secure cluster, skipping datanodes. " \
    "Run stop-secure-dns.sh as root to complete shutdown."
else
  "$HADOOP_PREFIX/sbin/hadoop-daemons.sh" \
    --config "$HADOOP_CONF_DIR" \
    --script "$bin/hdfs" stop datanode
fi

#---------------------------------------------------------
# secondary namenodes (if any)

SECONDARY_NAMENODES=$($HADOOP_PREFIX/bin/hdfs getconf -secondarynamenodes 2>/dev/null | tail -n 1)

if [ -n "$SECONDARY_NAMENODES" ]; then
  echo "Stopping secondary namenodes [$SECONDARY_NAMENODES]"

  "$HADOOP_PREFIX/sbin/hadoop-daemons.sh" \
      --config "$HADOOP_CONF_DIR" \
      --hostnames "$SECONDARY_NAMENODES" \
      --script "$bin/hdfs" stop secondarynamenode
fi

#---------------------------------------------------------
# quorumjournal nodes (if any)

SHARED_EDITS_DIR=$($HADOOP_PREFIX/bin/hdfs getconf -confKey dfs.namenode.shared.edits.dir 2>&- | tail -n 1)

case "$SHARED_EDITS_DIR" in
qjournal://*)
  JOURNAL_NODES=$(echo "$SHARED_EDITS_DIR" | sed 's,qjournal://\([^/]*\)/.*,\1,g; s/;/ /g; s/:[0-9]*//g')
  echo "Stopping journal nodes [$JOURNAL_NODES]"
  "$HADOOP_PREFIX/sbin/hadoop-daemons.sh" \
      --config "$HADOOP_CONF_DIR" \
      --hostnames "$JOURNAL_NODES" \
      --script "$bin/hdfs" stop journalnode ;;
esac

#---------------------------------------------------------
# ZK Failover controllers, if auto-HA is enabled
AUTOHA_ENABLED=$($HADOOP_PREFIX/bin/hdfs getconf -confKey dfs.ha.automatic-failover.enabled | tail -n 1)
if [ "$(echo "$AUTOHA_ENABLED" | tr A-Z a-z)" = "true" ]; then
  echo "Stopping ZK Failover Controllers on NN hosts [$NAMENODES]"
  "$HADOOP_PREFIX/sbin/hadoop-daemons.sh" \
    --config "$HADOOP_CONF_DIR" \
    --hostnames "$NAMENODES" \
    --script "$bin/hdfs" stop zkfc
fi
# eof

start-dfs.sh

#!/usr/bin/env bash

# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License.  You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


# Start hadoop dfs daemons.
# Optinally upgrade or rollback dfs state.
# Run this on master node.

usage="Usage: start-dfs.sh [-upgrade|-rollback] [other options such as -clusterId]"

bin=`dirname "${BASH_SOURCE-$0}"`
bin=`cd "$bin"; pwd`

DEFAULT_LIBEXEC_DIR="$bin"/../libexec
HADOOP_LIBEXEC_DIR=${HADOOP_LIBEXEC_DIR:-$DEFAULT_LIBEXEC_DIR}
. $HADOOP_LIBEXEC_DIR/hdfs-config.sh

# get arguments
if [[ $# -ge 1 ]]; then
  startOpt="$1"
  shift
  case "$startOpt" in
    -upgrade)
      nameStartOpt="$startOpt"
    ;;
    -rollback)
      dataStartOpt="$startOpt"
    ;;
    *)
      echo $usage
      exit 1
    ;;
  esac
fi

#Add other possible options
nameStartOpt="$nameStartOpt $@"

#---------------------------------------------------------
# namenodes

NAMENODES=$($HADOOP_PREFIX/bin/hdfs getconf -namenodes | tail -n 1)

echo "Starting namenodes on [$NAMENODES]"

"$HADOOP_PREFIX/sbin/hadoop-daemons.sh" \
  --config "$HADOOP_CONF_DIR" \
  --hostnames "$NAMENODES" \
  --script "$bin/hdfs" start namenode $nameStartOpt

#---------------------------------------------------------
# datanodes (using default slaves file)

if [ -n "$HADOOP_SECURE_DN_USER" ]; then
  echo \
    "Attempting to start secure cluster, skipping datanodes. " \
    "Run start-secure-dns.sh as root to complete startup."
else
  "$HADOOP_PREFIX/sbin/hadoop-daemons.sh" \
    --config "$HADOOP_CONF_DIR" \
    --script "$bin/hdfs" start datanode $dataStartOpt
fi

#---------------------------------------------------------
# secondary namenodes (if any)

SECONDARY_NAMENODES=$($HADOOP_PREFIX/bin/hdfs getconf -secondarynamenodes 2>/dev/null  | tail -n 1)

if [ -n "$SECONDARY_NAMENODES" ]; then
  echo "Starting secondary namenodes [$SECONDARY_NAMENODES]"

  "$HADOOP_PREFIX/sbin/hadoop-daemons.sh" \
      --config "$HADOOP_CONF_DIR" \
      --hostnames "$SECONDARY_NAMENODES" \
      --script "$bin/hdfs" start secondarynamenode
fi

#---------------------------------------------------------
# quorumjournal nodes (if any)

SHARED_EDITS_DIR=$($HADOOP_PREFIX/bin/hdfs getconf -confKey dfs.namenode.shared.edits.dir 2>&-  | tail -n 1)

case "$SHARED_EDITS_DIR" in
qjournal://*)
  JOURNAL_NODES=$(echo "$SHARED_EDITS_DIR" | sed 's,qjournal://\([^/]*\)/.*,\1,g; s/;/ /g; s/:[0-9]*//g')
  echo "Starting journal nodes [$JOURNAL_NODES]"
  "$HADOOP_PREFIX/sbin/hadoop-daemons.sh" \
      --config "$HADOOP_CONF_DIR" \
      --hostnames "$JOURNAL_NODES" \
      --script "$bin/hdfs" start journalnode ;;
esac

#---------------------------------------------------------
# ZK Failover controllers, if auto-HA is enabled
AUTOHA_ENABLED=$($HADOOP_PREFIX/bin/hdfs getconf -confKey dfs.ha.automatic-failover.enabled | tail -n 1)
if [ "$(echo "$AUTOHA_ENABLED" | tr A-Z a-z)" = "true" ]; then
  echo "Starting ZK Failover Controllers on NN hosts [$NAMENODES]"
  "$HADOOP_PREFIX/sbin/hadoop-daemons.sh" \
    --config "$HADOOP_CONF_DIR" \
    --hostnames "$NAMENODES" \
    --script "$bin/hdfs" start zkfc
fi

# eof

主要修改为将通过 hdfs getconf SOME_CONFIG 命令拿到的配置修改为通过 hdfs getconf SOME_CONFIG >/dev/null | tail -n 1 去获取配置。这里的 tail -n 1 可以去掉命令运行中的 Kerberos 调试日志。

启动集群

启动集群并运行测试如下

yum install -y apache-commons-daemon-jsvc.x86_64     # 安装jsvc以便可以用安全模式启动datanode详见https://hadoop.apache.org/docs/stable/hadoop-project-dist/hadoop-common/SecureMode.html#Secure_DataNode
sbin/start-dfs.sh && ./sbin/start-secure-dns.sh && sbin/start-yarn.sh && sbin/mr-jobhistory-daemon.sh start historyserver     # 依次启动集群的其他组件
# 测试我们将能看到下面的命令从0%到100%按进度完成。
bin/hadoop jar share/hadoop/mapreduce/hadoop-mapreduce-examples-2.7.7.jar  wordcount /input/* /output/wcc/
# 验证运行`./bin/hadoop dfs -cat output/wc/part-r-00000`还将看到计算出来的结果。
# 验证运行`./bin/yarn application -list -appStates FINISHED`可以看到已运行完成的任务及其日志的地址。

如果我们无需再测试了可以用以下命令停止集群

sbin/stop-dfs.sh && ./sbin/stop-secure-dns.sh && sbin/stop-yarn.sh && sbin/mr-jobhistory-daemon.sh stop historyserver

运行最初定义的测试

执行命令如下

# 加入相关的hosts
SHD_DOCKER_IP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' shd)
echo "${SHD_DOCKER_IP} shd kdc-server kdc-server.hadoop.com" >> /etc/hosts
# 下载源代码
git clone https://github.com/gmlove/bigdata_conf.git
# 更新配置文件
cd bigdata_conf
cd test/src/test && mv resources resources.1
docker cp shd:/hd/hadoop/etc/hadoop/hdfs-site.xml ./resources/
docker cp shd:/hd/hadoop/etc/hadoop/core-site.xml ./resources/
docker cp shd:/hd/hadoop/etc/hadoop/yarn-site.xml ./resources/
docker cp shd:/etc/krb5.conf ./resources/
docker cp shd:/hd/conf/root.keytab ./resources/
cp ./resources.1/log4j.properties ./resources/
# 运行测试
mvn -Dtest=test.HdfsTest test

运行上面的命令我们将能看到测试成功执行。

如果容器在一个远端的主机上启动

如果容器是在一个远端的主机上面启动的我们还是可以通过 ssh tunnel 的方式将远端的端口映射到本地来执行此测试。不过我们需要对前面步骤中的内容作出一些修改。主要的修改是将涉及到的 hostname 配置从 shd 改为 localhost。这是由于在做端口映射之后所有的服务均会通过 localhost 来访问如果我们还是用 shd则集群在进行 Kerberos 认证时主机名验证会出错。

这个任务还是挺有意思的可以有效的检验我们对于网络、Hadoop 集群、Kerberos 认证机制等的理解。有兴趣的小伙伴可以尝试实验一下本文就不赘述了。

总结

搭建一套安全的 hadoop 集群确实不容易即使我们只是一个伪分布式环境还做了各种配置简化也需要花费一番功夫更别提真正在生产环境中搭建一套集群了。如果是生产可用我们可能还需要关心机架、集群网络情况、稳定性、性能、跨地域高可用、不停机升级等等一系列的问题。在实际企业应用中这些大数据基础设施运维实际上是一个比较复杂的工作这些工作更可能是由一个单独的运维团队去完成的。这里我们所完成的例子的主要价值不在于生产可用而在于它可以帮助我们理解 hadoop 集群的安全机制以便指导我们日常的开发工作。另一个价值是这里的例子实际上完全可以作为我们平时测试用的一套小集群简单而又功能完整我们完全可以将这里完成的工作制作为一个 docker 镜像后续文章将尝试制作此镜像随时启动这样一套集群这对于我们测试一些集群集成问题时将带来很大的便利。

大家如果有自己实践相信在这个过程中可能还会碰到其他的问题欢迎留言交流一起学习。

在这篇文章里我们搭建了一个安全的 hadoop 集群那么大数据相关的其他组件应该要如何安全的和 hadoop 集群进行整合呢下一篇文章我们将选取几个典型的组件来分析并进行实践欢迎持续关注。

参考

Hadoop安全认证机制 三 | Bright LGM's Blog

附录

Kerberos相关概念

 

Kerberos工作原理

      也就是说kdc里面存储了客户端还有服务的秘匙客户端先请求kdc认证得到服务端的私钥然后通过服务端的私钥加密信息发送给服务端服务端揭秘用客户端的私钥加密发送给客户端然后双方通过session key进行共同信任的加密揭秘秘钥串进行通信。还有就是他们与时间相关默认session key的有效时间是8-10小时所以服务器间的时间要尽量的同步最少不超过5分钟。

Kerberos优点

Kerberos认证流程

实际操作一波 

前提条件

 

KDC相关操作

#域名前期规划
域名: mydomain.com
领域名: MYREALM.COM 

# 安装密匙分发中心KDC
yum install krb5-server krb5-libs krb5-workstation -y

#配置下/etc/hosts
vi /etc/hosts
192.168.10.102 kdc-server.hadoop.com

#配置KDC,krb5.conf是最高层的配置配置KDC的位置管理服务器主机名与Kerberos领域名的映射
vi /etc/krb5.conf

#配置如下
# Configuration snippets may be placed in this directory as well
includedir /etc/krb5.conf.d/
 
[logging]
 default = FILE:/var/log/krb5.log
 kdc = FILE:/var/log/krb5kdc.log
 admin_server = FILE:/var/log/kadmind.log
  
[kdc]
 profile = /var/kerberos/krb5kdc/kdc.conf
 
[libdefaults]
 forwardable = true
 default_realm = MYREALM.COM
 dns_lookup_realm = false
 dns_lookup_kdc = false
 ticket_lifetime = 24h
 renew_lifetime = 7d
  
[realms]
  MYREALM.COM = {
    kdc = hadoop102
    admin_server = hadoop102
  }
 
[domain_realm]
 .mydomain.com = MYREALM.COM
 mydomain.com = MYREALM.COM


#kdc.conf文件包括kdc配置中Kerberos票据领域相关配置kdc数据库和登录详细信息
#修改配置
vi /var/kerberos/krb5kdc/kdc.conf

[kdcdefaults]
 kdc_ports = 88
 
[realms]
 MYREALM.COM  = {
    profile = /etc/krb5.conf
	supported_enctypes = arcfour-hmac:normal des3-hmac-sha1:normal des-cbc-crc:normal des:normal des:v4 des:norealm des:onlyrealm des:afs3
	allow-null-ticket-addresses = true
    database_name = /var/kerberos/krb5kdc/principal
	acl_file = /var/kerberos/krb5kdc/kadm4.acl
	admin_database_lockfile = /var/kerberos/krb5kdc/kadm5_adb.lock
	admin_keytab = FILE:/var/kerberos/krb5kdc/kadm5.keytab
	key_stash_file = /var/kerberos/krb5kdc/.k5stash
    kdc_ports = 88
	kadmind_port = 748
    max_life = 10h 0m 0s
    max_renewable_life = 7d 0h 0m 0s
}

 

KDC数据库 

#建立KDC数据库用于存储用户密码信息这个数据库可以是一个文件或者是ldap存储
kdb5_util create -r MYREALM.COM -s

命令数据以后会初始下密码

cd /var/kerberos/krb5kdc/

我生成的文件没有下面描述的最后一个文件没有影响 

 启动Kerberos守护进程

/usr/sbin/krb5kdc && /usr/sbin/kadmind

创建管理员标识和密码 

#创建管理员标识和密码下面表示账号为admin,密码为admin,admin\nadmin这个表示第一次输入密码和第二次密码
echo -e 'admin\nadmin' | kadmin.local addprinc admin
#验证管理员认证确保KDC支持认证
kinit admin@MYREALM.COM

输入的密码就是刚才设置的admin 

 

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: Hadoop