防止拖库、窃听和重放攻击|mongodb安全之 springboot+Kerberos+ecryptfs+ssl (ubuntu)

3,026 阅读19分钟

前言

Kerberos 是一个网络用户验证协议,在希腊神话中 Kerberos 是一只三头保卫神犬,之所以取这样得标题是因为在集成 Kerberos 上花了我足足两个星期的下班时间设置,在外网看到很多文章都是直接关掉服务器的防火墙设置的,但我出于安全考虑仅仅开放了端口,我根据官方文档以及几篇文章的设置,只开了 tcp 的 88 端口,于是往后继续做的时候就默认前面的设置是正确的,在我使用 springboot 连接 mongodb 的时候死活连接不上,因为使用 mongo 客户端在远程可以登录,于是我就认准问题出现在 java 代码上,断点调试过了几遍代码没找到问题,把好多个异常信息谷歌百度了好多次并且跟着设置也解决不了,重装过不同版本的数据库,有多少次想放弃了,但想着数据库的安全很重要,便硬着头皮继续一一排除问题。把问题排除得差不多了,我就开始怀疑是端口开放得问题,想着想试一下开发 udp 88 端口,没想到啊!竟然成功了。

前置知识

  • SSL/TLS

    SSL/TLS 是一种通信协议。SSL/TLS 的一般目标是保护客户端和服务器之间的通信(完整性和机密性)。客户端应该始终检查 SSL/TLS 服务器的身份,它也为服务器提供了检查客户端身份的机制。它可以做什么还取决于它的配置方式。SSL/TLS 最常与 X.509 证书一起使用:这是浏览器检查 HTTPS 服务器身份的方式。服务器还可以配置为请求客户端使用证书来标识自己(客户端证书身份验证)

  • SASL

    SASL 本质上是一个间接层,在现有应用程序协议中允许可插拔的身份验证系统和数据安全性(比如 LDAP, SMTP, Subversion 等等),前提是这些协议需要了解这些扩展。如果您想将 Kerberos 与 SASL 一起使用,您将需要另一个间接级别:GSS-API(最常与 Kerberos 一起使用,但也可以允许其他机制)

  • kerberos

    • kerberos 是什么

      • kerberos 是计算机网络身份验证协议,它旨在通过使用密钥加密技术为客户端/服务器应用程序提供强身份验证。它基于 tickets 运行,允许节点通过非安全的网络通信,以安全的方式相互证明他们的身份。Kerberos 协议能保护消息免受被窃听和重放攻击
    • kerberos 的工作原理 Kerberos 不要求客户端为他们访问的每项服务输入用户名/密码,而是允许使用 ticket进行单点登录。这些用于身份证明的 ticket 由受信任的第 3 方(密钥分发中心 Key Distribution Center)颁发,通常使用密码/对称密钥进行加密。

      • 密钥分发中心 ( Key Distribution Center ) 为了通过 Kerberos 管理对资源的访问,所有组件、服务和用户都必须在 Kerberos Realm(领域) 中。Kerberos 分发中心 (KDC) 是 Kerberos 领域的核心,具有以下关键服务器组件

        • 数据库服务 ( Database Server ) 存储已注册的用户和服务的密匙,实际存储的密匙是经过加盐后使用哈希算法处理过的,数据库本身也是有一个密码保护的
        • 用户验证服务 ( Authentication Server ) 提供访问 ticket 分配服务ticket
        • ticket 分配服务 ( Ticket-Granting Server) 提供访问最终要到达的服务的 ticket
      • 用户验证流程

        image-20211029141001805.png

        • 1️⃣ --> 用户使用 kinit 登录 用户通过运行命令 kinit <username>AS ( 用户验证服务 Authentication Server ) 介绍自己

          • 图中青色的钥匙:你 kinit <username>或者 kinit -kt name.keytab获得的用户 principle 密匙
        • 2️⃣ --> 向 AS 发送获取 TGT (Ticket-Granting Ticket) 的请求

          • AS_REQ1 包含:

            • 用户名:用户名
            • 请求的服务:TGS (Ticket-Granting Service)
            • 网络地址:用户的 ip 地址
            • 过期时间:TGT 的存活时长
            • 时间戳:生成此次请求的时间
        • 3️⃣ --> AS 返回客户密匙和 TGT 用户接收到请求后使用图中青色的钥匙解密 AS_RES1 得到里面的信息

          • AS_RES1 被用户的密匙加密(图中青色的锁),包含:

            • 产生此条消息的服务:TGS
            • 时间戳:生成此次请求的时间
            • 有效时间:TGT 的存活时长
            • TGS 会话密匙
          • AS_RES2/TGTKDC 加密(途中黄色的锁),包含:

            • 用户名:用户名
            • 产生此条消息的服务:TGS
            • 时间戳:生成此次请求的时间
            • 网络地址:ip 地址
            • 有效时间:TGT 的存活时长
            • TGS 会话密匙
          • 图中黄色的钥匙:TGS 密匙

          • 图中品红色的钥匙:TGS 会话密匙

        • 4️⃣ --> 使用 TGT 请求 TGS 来获得访问 Mongodb 的密匙 TGS 收到请求后检查时间戳是否在5分钟内并且检查 MongoDB 服务是否有在 KDC 中注册

          • TGS_REQ1 包含:

            • 有效时间:TGT 的存活时长
            • 请求的服务:MongoDB 服务
            • TGT ( 只有 KDC 能够解密 )
          • TGS_REQ2TGS 密匙加密(图中品红色的锁),包含:

            • 用户名:用户名
            • 时间戳:生成此次请求的时间
        • 5️⃣ --> TGS 生成访问服务的 ticket 和 MongoDB 的会话密匙(图中深蓝色的锁)

          • TGS_RES1TGS 会话密匙加密(图中品红色的锁),包含:

            • 请求的服务:MongoDB 服务
            • 时间戳:生成此次请求的时间
            • 有效时间:TGT 的存活时长
            • 服务会话密匙 (深蓝色的钥匙)
          • TGS_RES2/STKT 被 MongoDB 的会话密匙加密(图中绿色的锁),包含:

            • 用户名:用户名
            • 请求的服务:MongoDB 服务
            • 时间戳:生成此次请求的时间
            • 网络地址:ip 地址
            • 有效时间:STKT 的存活时长
            • MongoDB 的会话密匙
          • 图中绿色的锁:MongoDB 服务在 KDC 中的密匙

        • 6️⃣ --> 用户发送登录请求到 MongoDB 服务 MongoDB 服务器接收到请求,检查时间戳是否在5分钟以内,检查用户是否在 MongoDB 服务器的 MongoDB 数据库中已经注册,最终分配角色和权限给用户

          • AP_REQ1 被 MongoDB 会话密匙(图中蓝色的锁),包含:

            • 用户名:用户名
            • 时间戳:生成此次请求的时间
          • AP_REQ2TGS_RES2/STKT 一样

        • 7️⃣ --> MongoDB 服务器返回信息

          • AP_RES1 被 MongoDB 会话密匙加密(图中深蓝色的锁),包含:

            • 用户名:用户名
            • 服务:MongoDB 服务
        • 8️⃣ --> 用户可以直接和 MongoDB 通信 用户接收到 MongoDB 服务的回应,并且使用 MongoDB 会话密匙解密,解密完成并且验证成功后,用户可以直接和 MongoDB 通信

说明

  • 部署位置:MongoDB 部署在服务器 A,KDC 部署在服务器 B,SpringBoot 部署在服务器 C

  • 领域名:MYEXAMPLE.COM

  • Host:<服务器 A 的 ip> mongodb01.example.com,<服务器 B 的 ip> kdc.example.com,<服务器 C 的 ip> client01.example.com

  • MongoDB 内创建的用户:client01.example.com@MYEXAMPLE.COM

  • MongoDB 内创建的数据库:mytest

  • KDC 内创建的主体:mongodb/mongodb01.example.com@MYEXAMPLE.COM,client01.example.com@MYEXAMPLE.COM

  • 默认文件位置

    • mongodb
      • mongod.conf:/etc/mongod.conf
      • 数据库表:/var/lib/mongodb/
      • 日志文件:/var/log/mongodb/
    • kerberos
      • krb5.conf:/etc/krb5.conf
      • (KDC 才有的文件) kdc.conf:/etc/krb5kdc/kdc.conf
      • (KDC 才有的文件) kadm5.acl:/etc/krb5kdc/kadm5.acl

搭建 MongoDB Enterprise(在服务器 A 部署)

比较简单,只写简单步骤,如出现问题查看以下参考文档

  • 注意

    • 支持以下系统(只针对 Ubuntu)

      • 20.04 LTS ("Focal")
      • 18.04 LTS ("Bionic")
      • 16.04 LTS ("Xenial")
    • 不能使用32位系统

    • 不支持 Windows Subsystem for Linux (WSL)

  • 步骤

    • 在官网选择要下载的版本

      image-20211027144100498.png

    • 导入包管理系统使用的公钥

      wget -qO - https://www.mongodb.org/static/pgp/server-4.4.asc | sudo apt-key add -
      

      返回 OK 说明正常

    • 进入以下的官方网页,选择你使用的 ubuntu 系统,接着复制提供的代码并运行 image-20211021174957531.png

    • 重新加载本地包数据库

      sudo apt-get update
      
    • 安装 MongoDB Enterprise 包,默认安装最新版本,想下特定版本和组件的话去官网看

      sudo apt-get install -y mongodb-enterprise
      
    • 安装成功后先别急着开启服务,等搭建完 ecryptfs 后再开启,因为 ecryptfs 只能在文件还是空的时候加密

    • 更改文件所有权

      sudo chown -R mongodb:mongodb /var/lib/mongodb
      sudo chown -R mongodb:mongodb /var/log/mongodb
      ​
      # 默认数据库文件会放到 /var/lib/mongodb
      # 日志文件会放到 /var/log/mongodb
      # 配置文件放在 /etc/mongod.conf
      
  • 参考

搭建 ecryptfs(在服务器 A 部署)

  • 安装 ecryptfs

    sudo apt-get ecryptfs-utils
    
  • 挂载

    sudo mount -t /var/lib/mongodb /var/lib/mongodb
    
  • 查看是否挂载成功

    • 输入以下命令

      mount
      
    • 显示以下结果则证明加密成功

      -image-20211021182239776.png

  • 取消挂载(有需要的话)

    • 输入以下命令

      sudo umount -t ecryptfs 文件名
      
  • 参考

初始配置 MongoDB(在服务器 A 部署)

  • 开启 MongoDB

    sudo systemctl start mongod
    
  • 查看 mongod 服务状态

    sudo systemctl status mongod
    
  • 添加管理员账户

    在启用角色访问控制前,用户权限不受限制

    # 进入客户端
    mongo
    ​
    # 刚进来默认是在 test 数据库,进入 admin 数据库
    use admin
    # 添加管理员账户
    db.createUser({user:'管理员名称',pwd:'管理员密码',roles:[{role:'userAdminAnyDatabase',db:'admin'}]});
    # 显示 sucessful 说明成功了
    # 除了 userAdminAnyDatabase 还有很多其他角色,详情看 https://docs.mongodb.com/manual/reference/built-in-roles/# 确保自己在 admin 数据库内,在哪里创建的账户就只能在那里登录验证身份
    # 输入 db 查看自己当前所在的数据库
    db
    ​
    # 登录刚新建的管理员账号
    db.auth('管理员名称','管理员密码');
    ​
    # 拥有 userAdminAnyDatabase 角色的账户能够创建新用户# 进入数据库 mytest,如果有的话则进入,没有的话就创建新的并进入
    use mytest
    # 初始没有 mytest 数据库,这里是创建了新的一个数据库# 在 mytest 数据库中新建一个可读写的用户
    db.createUser({ user: '账户名', pwd: '账户密码', roles: [ { role: "readWrite", db: "mytest" } ] });
    
  • 启用基于角色的访问控制

    若在启用角色访问控制前一个用户都没创建,即在启用角色访问控制后才创建用户的话,会存在 localhost exception(在 mongodb 所在主机登入时,可以允许创建一个用户,以后则需要先登录一个用户才能创建新的用户)

    # 编辑 MongoDB 配置文件
    vi /etc/mongodb.conf
    ​
    # 在文件里添加以下内容
    security:
      authorization: enabled
    
  • 修改连接设置

    # 编辑 MongoDB 文件
    vi /etc/mongodb.conf
    ​
    # 修改文件内容
    # 找到 net 开头的设置,把 127.0.0.1 改成 0.0.0.0
    # 将默认端口 27017 改成自己的,比如 66666
    net:
      port: 66666
      bindIp: 0.0.0.0
    
  • 使 mongodb.conf 配置文件生效

    # 重启 mongod 服务
    sudo systemctl restart mongod
    
  • 连接客户端(测试)

    # 在 MongoDB 所在服务器进入客户端
    mongo 127.0.0.1:66666
    
  • 参考

mongodb 配置 ssl(在服务器 A 部署)

  • 使用自签证书 SSL 加密传输

    • 步骤

      • 生成根证书

        • #-x509: 用于生成自签证书,如果不是自签证书则不需要此项
          #-days: 证书的有效期限,默认是365天
          #生成根证书
          openssl req -out ca.pem -new -x509 -days 3650
          
        • openssl req -out ca.pem -new -x509 -days 3650
          
          # 接着会要求你填写以下内容
          # Country Name : 国家名称
          # State or Province Name : 省份名
          # Locality Name : 城市名
          # Organization Name : 公司组织名
          # Origanization Util Name : 公司组织的部门名
          # Common Name : 主机名
          # Email Address : 邮箱号
          
        • 注意

          • 服务端和客户端的 Common Name 要相同,但是不能和根证书的相同
          • 设置过的密码一定要保存好,方便以后要用
      • 生成服务端证书

        • # 生成服务器端私钥
          openssl genrsa -out server.key 2048
          
        • # 生成服务器端申请文件
          openssl req -key server.key -new -out server.req
          
          # 接着会要求你填写以下内容
          # Country Name : 国家名称
          # State or Province Name : 省份名
          # Locality Name : 城市名
          # Organization Name : 公司组织名
          # Origanization Util Name : 公司组织的部门名
          # Common Name : 主机名
          # Email Address : 邮箱号
          

          注意,服务端和客户端的 Common Name 要相同,但是不能和根证书的相同

        • # 生成服务器端证书
          openssl x509 -req -in server.req -CA ca.pem -CAkey privkey.pem -CAcreateserial -out server.crt -days 3650
          
        • # 合并服务器端私钥和服务器端证书,生成server.pem
          cat server.key server.crt > server.pem
          
        • # 校验服务器端pem文件
          openssl verify -CAfile ca.pem server.pem
          
      • 生成客户端证书

        • # 生成客户端私钥
          openssl genrsa -out client.key 2048
          
        • # 生成客户端申请文件
          openssl req -key client.key -new -out client.req
          
        • # 生成客户端证书
          openssl x509 -req -in client.req -CA ca.pem -CAkey privkey.pem -CAserial ca.srl  -out client.crt -days 3650
          
        • # 合并客户端私钥和客户端证书,生成client.pem
          cat client.key client.crt > client.pem
          
        • # 校验客户端pem文件
          openssl verify -CAfile ca.pem client.pem
          
    • mongodb 本地连接 mongodb ssl

      mongo --tlsAllowInvalidHostnames \
      --tls \
      --tlsCertificateKeyFile server.pem的文件路径 \
      --tlsCAFile ca.pem的文件路径 \
      --host 127.0.0.1:27017
      
    • navicat 连接 ssl 后的 mongodb

      image-20211022143500743.png 显示 ”连接成功“ 即可

springboot 集成 Mongodb 的 ssl

  • 生成 ssl 文件

    # 将服务器 A 中 server.crt 和 client.pem 复制过来#根证书信息 trustStore
    openssl pkcs12 -export -in server.crt -inkey server.key -out server.p12 -name server.12
    ​
    #客户端 keyStore
    openssl pkcs12 -export -in client.pem  -out keystore
    
  • 源码

    • # application.yml
      spring:
        data:
          mongodb:
            host: # mongodb 的主机 ip
            port: # mongodb 的主机端口
            username: # 你要登录的 mongodb 的账户名
            password: # 你要登录的 mongodb 的账户的密码
            database: # 你要访问的数据库
            authentication-database: # 你要通过哪个数据库验证你的身份
      
    • @Configuration
      public class MongoSSLConfig {
          @Bean
          public MongoClient createNetworkMongoClient(MongoProperties properties)
                  throws UnrecoverableKeyException, CertificateException, KeyStoreException, IOException, NoSuchAlgorithmException, KeyManagementException {
              // properties 对象为springboot创建,可直接用
              // 一个 MongoClient 实例代表一个到数据库的连接池
              MongoCredential credential = getCredential(properties);
              String host = properties.getHost() == null ? "localhost" : properties.getHost();
              int port = properties.getPort() == null ? 27017 : properties.getPort();
              List<ServerAddress> addresses = Collections.singletonList(new ServerAddress(host, port));
      ​
              // SSL
              // trustStore 为 server.p12
              String trustStorePath = "trustStore 的文件路径";
              // keyStore 为 keyStore
              String keyStorePath = "keyStore 的文件路径";
              String keyStorePassword = "keyStore 的密码";
              String trustStorePassword = "trustStore 的密码";
      ​
              SSLContext sslContext = SSLContext.getInstance("TLS");
              sslContext.init(
                      getKeyManagers(keyStorePath, keyStorePassword),
                      getTrustManagers(trustStorePath, trustStorePassword),
                      null
              );
              MongoClientSettings settings = MongoClientSettings.builder()
                      .credential(credential)
                      .applyToSslSettings(builder ->
                              builder.enabled(true) // 开启 ssl
                                      .context(sslContext) // 给特定用户配置 keyStore 和 trustStore
                                      .invalidHostNameAllowed(true)) // 禁用 TLS 证书中的主机名验证
                      .applyToClusterSettings(builder ->
                              builder.hosts(addresses)
                                      .mode(ClusterConnectionMode.SINGLE) // 单机模式
                                      .requiredClusterType(ClusterType.STANDALONE))
                      .build();
      ​
              return MongoClients.create(settings);
          }
      ​
          // 获取 keymanagers
          private KeyManager[] getKeyManagers(String keystoreFile, String ksPassword)
                  throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException {
              KeyStore keystore = getKeyStore(keystoreFile, ksPassword);
      ​
              KeyManagerFactory keyManagerFactory =
                      KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
              keyManagerFactory.init(keystore, ksPassword.toCharArray());
              return keyManagerFactory.getKeyManagers();
          }
      ​
          // 获取 trustmanagers
          private TrustManager[] getTrustManagers(String truststoreFile, String tsPassword)
                  throws NoSuchAlgorithmException, KeyStoreException, IOException, CertificateException {
              KeyStore truststore = getKeyStore(truststoreFile, tsPassword);
      ​
              TrustManagerFactory trustManagerFactory =
                      TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
              trustManagerFactory.init(truststore);
              return trustManagerFactory.getTrustManagers();
          }
      ​
          // 获取 keystore
          private KeyStore getKeyStore(String keystoreFile, String ksPassword)
                  throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException {
              KeyStore keystore = KeyStore.getInstance("PKCS12");
              InputStream in = new FileInputStream(keystoreFile);
              keystore.load(in, ksPassword.toCharArray());
              return keystore;
          }
      ​
          private MongoCredential getCredential(MongoProperties properties) {
              String username = properties.getUsername();
              String database = properties.getDatabase() == null
                      ? properties.getMongoClientDatabase()
                      : properties.getDatabase();
              //此处可将加密的密码解密,替换配置文件中的值
              properties.setPassword(new String(properties.getPassword()).toCharArray());
              char[] password = properties.getPassword();
              return MongoCredential.createCredential(username, database, password);
          }
      }
      
    • 这个 MongoSSLConfig 配置类也可以这样写 👇

      @Configuration
      public class MongoSSLConfig {
          @Bean
          public MongoClient createNetworkMongoClient(MongoProperties properties)
                  throws UnrecoverableKeyException, CertificateException, KeyStoreException, IOException, NoSuchAlgorithmException, KeyManagementException {
              // properties 对象为springboot创建,可直接用
              // 一个 MongoClient 实例代表一个到数据库的连接池
              MongoCredential credential = getCredential(properties);
              String host = properties.getHost() == null ? "localhost" : properties.getHost();
              int port = properties.getPort() == null ? 27017 : properties.getPort();
              List<ServerAddress> addresses = Collections.singletonList(new ServerAddress(host, port));
      ​
              // SSL
              String trustStorePath = "trustStore 的文件路径";
              String keyStorePath = "keyStore 的文件路径";
              String keyStorePassword = "keyStore 的密码";
              String trustStorePassword = "trustStore 的密码";
      ​
              // the path to a trust store containing the certificate of the signing authority
              System.setProperty("javax.net.ssl.trustStore", trustStorePath);
              // the password to access this trust store
              System.setProperty("javax.net.ssl.trustStorePassword", trustStorePassword);
              // the path to a key store containing the client’s SSL certificates
              System.setProperty("javax.net.ssl.keyStore", keyStorePath);
              // the password to access this key store
              System.setProperty("javax.net.ssl.keyStorePassword", keyStorePassword);
      
              MongoClientSettings settings = MongoClientSettings.builder()
                      .credential(credential)
                      .applyToSslSettings(builder ->
                              builder.enabled(true) // 开启 ssl
                                      .context(sslContext) // 给特定用户配置 keyStore 和 trustStore
                                      .invalidHostNameAllowed(true)) // 禁用 TLS 证书中的主机名验证
                      .applyToClusterSettings(builder ->
                              builder.hosts(addresses)
                                      .mode(ClusterConnectionMode.SINGLE) // 单机模式
                                      .requiredClusterType(ClusterType.STANDALONE))
                      .build();
      ​
              return MongoClients.create(settings);
          }
      ​
          private MongoCredential getCredential(MongoProperties properties) {
              String username = properties.getUsername();
              String database = properties.getDatabase() == null
                      ? properties.getMongoClientDatabase()
                      : properties.getDatabase();
              //此处可将加密的密码解密,替换配置文件中的值
              properties.setPassword(new String(properties.getPassword()).toCharArray());
              char[] password = properties.getPassword();
              return MongoCredential.createCredential(username, database, password);
          }
      }
      
    • 参考

  • 参考

搭建 kerberos 的前期工作

  • 请确定服务器 B 使用的是 MongoDB Enterprise

    mongod --version
    

    image-20211020153111985.png

  • 所有服务器设置 host 文件

    # 编辑 hosts 文件
    cd /etc/hosts
    ​
    # 在 hosts 文件最下面添加
    服务器-A-ip mongodb01.example.com
    服务器-B-ip kdc.example.com
    服务器-C-ip client01.example.com
    
  • 所有服务器(服务器 A、B、C)装 chrony

    • 步骤

      • 开启 UDP 端口 323 123

      • 安装 chrony

        apt-get install chrony
        
      • 设置服务端(即 KDC 服务器 B)

        # 查看 chrony 运行状态
        systemctl status  chrony
        
        • 编辑配置文件

          vi /etc/chrony.conf
          # 注释其他server开头的配置,添加阿里云NTP公共时间同步服务器
          # 添加内容
          server ntp.aliyun.com iburst
          allow 连接此服务端的客户端ip/子网掩码
          
        • 使配置生效

          # 重启服务
          systemctl restart chrony
          
        • 查看同步状态

          chronyc sources –v
          # 显示如下内容,若前面是 * 则表示连接成功
          

          image-20211027161657578.png

      • 设置客户端(即服务器 A 和 C)

        # 查看 chrony 运行状态
        systemctl status  chrony
        
        • 编辑配置文件

          vi /etc/chrony.conf
          ​
          # 注释其他server开头的配置
          # 添加内容
          server 服务端的ip iburst
          
        • 使配置生效

          # 重启服务
          systemctl restart chrony
          
        • 查看同步状态

          chronyc sources –v
          
    • 参考

安装 kerberos KDC(在服务器 B 部署)

  • 步骤

    • 开放 tcp 端口 88 和 udp 端口 88

    • 设置 host

      • 设置完全限定域名

        hostnamectl set-hostname kdc.example.com
        
    • apt update

    • 安装 kerberos 服务端

      sudo apt install krb5-kdc krb5-admin-server
      
      • 安装过程中会要求你提供 Kerberos Realm

        image-20211020171638819.png

      • 接着被要求提供 Kerberos 服务器主机名

        image-20211020171704289.png

      • 然后被要求提供管理服务器的主机名

        image-20211020171733520.png

      • 单击 ok 完成安装

        image-20211020171759164.png

      • 如果设置完发现主机名打错了,有以下两种修改方式

        • 如果需要调整密钥分发中心 (KDC) 设置,只需编辑文件并重新启动 krb5-kdc 守护程序

          sudo systemctl restart krb5-admin-server.service
          
        • 如果需要从头开始重新配置 Kerberos,也许是更改领域名称,输入以下内容

          sudo dpkg-reconfigure krb5-kdc
          
      • 初始化一个 kerberos realm

        krb5_newrealm
        
        • 接下来会要求你输入主要的密码,此密码用于解密 kerberos 数据库,一定要记住这个密码

          # This script should be run on the master KDC/admin server...
          # ...
          # Enter KDC database master key:
          # Re-enter KDC database master key to verify:
          # ...
          # Don't forget to set up DNS information...
          
      • 修改 acl (访问控制表) 给用户分配权限

        vi /etc/krb5kdc/kadm5.acl
        
        # 修改 kadm5.acl 文件内容
        */admin@EXAMPLE.COM *
        

        👆 此条目授予 */admin 对领域中所有主体执行任何操作的能力

        现在重新启动 krb5-admin-server 以使新 ACL 生效

        sudo systemctl restart krb5-admin-server.service
        
      • 修改 krb5.conf (被 kerberos 5 库使用)

        includedir /etc/krb5.conf.d/
        [logging]
         default = FILE:/var/log/krb5libs.log
         kdc = FILE:/var/log/krb5kdc.log
         admin_server = FILE:/var/log/kadmind.log
        [libdefaults]
         dns_lookup_realm = false
         ticket_lifetime = 24h
         renew_lifetime = 7d
         forwardable = true
         rdns = false
         default_realm = EXAMPLE.COM
        [realms]
         EXAMPLE.COM = {
          kdc = kdc.example.com
          admin_server = kdc.example.com
         }
        [domain_realm]
         .example.com = EXAMPLE.COM
         example.com = EXAMPLE.COM
        
      • 修改 kdc.conf (kdc 的配置文件)

        [kdcdefaults]
         kdc_ports = 88
         kdc_tcp_ports = 88
        [realms]
         EXAMPLE.COM = {
          #master_key_type = aes256-cts
          dict_file = /usr/share/dict/words
          supported_enctypes = aes256-cts:normal aes128-cts:normal des3-hmac-sha1:normal arcfour-hmac:normal camellia256-cts:normal camellia128-cts:normal des-hmac-sha1:normal des-cbc-md5:normal des-cbc-crc:normal
         }
        
      • 添加主体

        • 进入 kerberos 的控制台

          kadmin.local
          
        • 输入 ?可以查看所有命令

          add_principal, addprinc, ank
                                   Add principal
          delete_principal, delprinc
                                   Delete principal
          modify_principal, modprinc
                                   Modify principal
          ...
          
        • 主体命名的规则

          # 官方推荐 <username>/<instance>@<KERBEROS REALM>
          # 或者 <username>@<KERBEROS REALM># 对于服务提供端命名为
          <applicationName>/<instance>@<KERBEROS REALM>
          # applicationName 为应用名称,如此处是 mongodb 则写 mongodb
          # instance 为服务器的 host name,host name 可以在 /etc/hosts 定义
          # 如:mongodb/mongodb01.example.com@MYEXAMPLE.COM# 服务的消费端命名为
          <username>@<KERBEROS REALM>
          # 此处 username 即为 host name
          # 如:client01.example.com@MYEXAMPLE.COM
          
        • 输入主体的名字

          # 添加 mongodb 服务的主体
          addprinc -randkey mongodb/mongodb01.example.com
          #实际在 kerberos 数据库中存的是 mongodb/mongodb01.example.com@MYEXAMPLE.COM
          
          # 添加 client01 用户的主体
          addprinc -randkey client01.example.com
          #实际在 kerberos 数据库中存的是 client01.example.com@MYEXAMPLE.COM
          
        • 查看所有 principle

          getprincs
          mongodb/mongodb01.example.com@MYEXAMPLE.COM
          client01.example.com@MYEXAMPLE.COM
          # 可以看到实际生成的会在后面自动补齐 realm
          ...
          
    • 卸载 kerberos 服务端 (如果安装失败就卸载重装吧)

      • 先搜索相关的软件

        dpkg --get-selections | grep krb5
        
      • 执行删除

        sudo apt-get remove --purge 软件名
        
  • 参考

mongodb 集成 kerberos(在服务器 A 部署)

  • 步骤

    • 开放 tcp 端口 88 和 udp 端口 88

    • 设置 host

      • 设置完全限定域名

        hostnamectl set-hostname mongodb01.example.com
        
      • 编辑计算机上的 /etc/hosts 文件并设置主机名解析,以便两个系统可以通过主机名进行通信

        sudo vi /etc/hosts
        服务器-A-ip mongodb01.example.com
        服务器-B-ip kdc.example.com
        服务器-C-ip client01.example.com
        
    • 安装 kerberos 客户端

      sudo apt install krb5-user
      
      • 安装过程中会要求你提供 Kerberos Realm

        image-20211020171638819.png

      • 接着被要求提供 Kerberos 服务器主机名

        image-20211020171704289.png

      • 然后被要求提供管理服务器的主机名

        image-20211020171733520.png

      • 单击 ok 完成安装

        image-20211020171759164.png

    • 将 KDC 服务器里的 /etc/krb5.conf 文件复制到 MongoDB 服务器 B 中

      # 可通过 shell 将文件提取出来再一次性复制到各个 kerberos 客户端服务器中
      cat /etc/krb5.conf
      
    • 给 mongod.conf 添加 Kerberos 的设置

      systemLog:
        destination: file
        logAppend: true
        path: /var/log/mongodb/mongod.log
      storage:
        dbPath: /var/lib/mongo
        journal:
          enabled: true
      processManagement:
        fork: true  # fork and run in background
        timeZoneInfo: /usr/share/zoneinfo
      net:
        port: 27017
        bindIp: 0.0.0.0 # <-- TODO: Exposes MongoDB to Public IP. Please use internal IPs instead
      security:
        authorization: enabled
      setParameter:
        authenticationMechanisms: GSSAPI
      
    • 新建一个文件夹存放 Kerberos keytab file

      sudo mkdir -p /var/lib/mongo/private
      
    • 在 KDC 处生成相应的 keytab

      # 生成 mongodb/mongodb01.example.com 的 keytab
      ktadd -k mongodb.keytab mongodb/mongodb01.example.com
      
    • 将 keytab 文件的绝对路径添加到环境变量中

      export KRB5_KTNAME="/var/lib/mongo/private/mongodb.keytab"
      
  • 设置 private 文件夹的文件权限仅限系统用户 mongodb 可读

    sudo chown -R mongodb:mongodb /var/lib/mongo/private
    sudo systemctl start mongod
    
  • 将 kerberos 数据库中的 priciple 添加到 mongodb 数据库中

    db.createUser({user: 'mongodb/mongodb01.example.com@MYEXAMPLE.COM', roles: [{ role: 'readWrite', db: 'mytest'}]});
    
  • 测试连接 mongokerberos

    • 服务端模式(服务端就是 mongodb 所在的服务器端,比如服务器 A)

      • 输入命令

        mongokerberos --server
        
      • 如果在服务器上正确配置了 Kerberos,并且成功创建了服务主体,则输出可能类似于以下内容

        Resolving kerberos environment...
        [OK] Kerberos environment resolved without errors.
        Verifying DNS resolution works with Kerberos service at <hostname>...
        [OK] DNS test successful.
        Getting MIT Kerberos KRB5 environment variables...
          * KRB5CCNAME: not set.
          * KRB5_CLIENT_KTNAME: not set.
          * KRB5_CONFIG: not set.
          * KRB5_KTNAME: not set.
          * KRB5_TRACE: not set.
        [OK]
        Verifying existence of KRB5 keytab FILE:/etc/krb5.keytab...
        [OK] KRB5 keytab exists and is populated.
        Checking principal(s) in KRB5 keytab...
        Found the following principals for MongoDB service mongodb:
          * mongodb/server.example.com@SERVER.EXAMPLE.COM
        Found the following kvnos in keytab entries for service mongodb:
          * 3
        [OK] KRB5 keytab is valid.
        Fetching KRB5 Config...
        KRB5 config profile resolved as:
           <Your Kerberos profile file will be output here>
        [OK] KRB5 config profile resolved without errors.
        Attempting to initiate security context with service credentials...
        [OK] Security context initiated successfully.
        
    • 客户端模式(客户端就是将要连接 mondodb 的服务器,比如服务器 C)

      • 输入命令

        mongokerberos --client --username mongodb/mongodb01.example.com
        
      • 如果提供的凭据有效,并且配置文件中的 Kerberos 选项有效,则输出可能类似于以下内容

        Resolving kerberos environment...
         [OK] Kerberos environment resolved without errors.
         Verifying DNS resolution works with Kerberos service at <hostname>...
         [OK] DNS test successful.
         Getting MIT Kerberos KRB5 environment variables...
           * KRB5CCNAME: not set.
           * KRB5_CLIENT_KTNAME: not set.
           * KRB5_CONFIG: not set.
           * KRB5_KTNAME: not set.
           * KRB5_TRACE: not set.
         [OK]
         Verifying existence of KRB5 client keytab FILE:/path/to/client.keytab...
         [OK] KRB5 client keytab exists and is populated.
         Checking principal(s) in KRB5 keytab...
         [OK] KRB5 keytab is valid.
         Fetching KRB5 Config...
         KRB5 config profile resolved as:
            <Your Kerberos profile file will be output here>
         [OK] KRB5 config profile resolved without errors.
         Attempting client half of GSSAPI conversation...
         [OK] Client half of GSSAPI conversation completed successfully.
        
  • 查看日志(一定一定一定要学会查看日志)

    • tail /var/log/mongodb/mongod.log -n 100
      
  • 登录数据库

    kinit -kt mongodb.keytab mongodb/mongodb01.example.com
    
    # 进入客户端
    mongo \
    --tlsAllowInvalidHostnames \
    --tls \
    --tlsCertificateKeyFile #server.pem 的路径 \
    --tlsCAFile #ca.pem 的路径 \
    --host mongodb01.example.com \
    --port 23332
    
    # 进入数据库
    use $external
    
    # 登入用户
    db.auth( {mechanism: "GSSAPI", user: "client01.example.com@MYEXAMPLE.COM"} )
    # 返回 1 说明成功了
    
  • 参考

springboot 集成 kerberos(在服务器 C 部署)

  • 步骤

    • 安装 kerberos 客户端(和在服务器 A 的安装一样)

    • 将 KDC 的 /etc/krb5.conf 文件复制并替换本服务器的 krb5.conf

    • 在 KDC 处生成相应的 keytab

      # 生成随机密码的 keytab
      addprinc -randkey client01.example.com
      
      # 生成 client01.example.com 的 keytab
      ktadd -k client.keytab client01.example.com
      
    • 测试 mongodb 连接

      kinit -kt client.keytab client01.example.com
      ​
      # 进入客户端
      mongo \
      --tlsAllowInvalidHostnames \
      --tls \
      --tlsCertificateKeyFile # server.pem 的路径 \
      --tlsCAFile # ca.pem 的路径 \
      --host mongodb01.example.com \
      --port 23332
      ​
      # 进入数据库
      use $external# 登入用户
      db.auth( {mechanism: "GSSAPI", user: "client01.example.com@EXAMPLE.COM"} )
      # 返回 1 说明成功了
      
    • 创建 gss-jass.conf 文件

      com.sun.security.jgss.initiate {
      com.sun.security.auth.module.Krb5LoginModule required
      useKeyTab=true
      keyTab="client.keytab 的绝对路径"
      useTicketCache=false
      principal="client01.example.com@EXAMPLE.COM"
      doNotPrompt=true
      debug=true;
      };
      
    • springboot 通过 kerberos 和 ssl 连接 mongodb

      @Configuration
      public class MongoSSLConfig {
          @Bean
          public MongoClient createNetworkMongoClient(MongoProperties properties) throws IOException {
              // SSL
              String trustStorePath = "server.p12 的路径";
              String keyStorePath = "keystore 的路径";
              String keyStorePassword = "keyStore 密码";
              String trustStorePassword = "trustStore 密码";
      ​
              // the path to a trust store containing the certificate of the signing authority
              System.setProperty("javax.net.ssl.trustStore", trustStorePath);
              // the password to access this trust store
              System.setProperty("javax.net.ssl.trustStorePassword", trustStorePassword);
              // the path to a key store containing the client’s SSL certificates
              System.setProperty("javax.net.ssl.keyStore", keyStorePath);
              // the password to access this key store
              System.setProperty("javax.net.ssl.keyStorePassword", keyStorePassword);
      ​
              // Kerberos
              String gssJaasPath = "gss-jaas.conf 的路径";
      
      // 以下两种方法都可以
      // 方法一:设置 JVM 变量
              System.setProperty("java.security.krb5.realm", "MYEXAMPLE.COM");
              System.setProperty("java.security.krb5.kdc", "kdc.example.com");
      // 方法二:通过读取 krb5.conf 文件获取领域信息
      //        String krb5Path = "krb5.conf 的路径";
      //        System.setProperty("java.security.krb5.conf", krb5Path);
      
              System.setProperty("javax.security.auth.useSubjectCredsOnly", "false");
              System.setProperty("java.security.auth.login.config", gssJaasPath);
      ​
              MongoCredential credential = getCredential(properties);
              String host = properties.getHost() == null ? "localhost" : properties.getHost();
              int port = properties.getPort() == null ? 23332 : properties.getPort();
              List<ServerAddress> addresses = Collections.singletonList(new ServerAddress(host, port));
      ​
              MongoClientSettings settings = MongoClientSettings.builder()
                      .applyToSslSettings(builder ->
                              builder.enabled(true)
                                      .invalidHostNameAllowed(true))
                      .applyToClusterSettings(builder ->
                              builder.hosts(addresses)
                                      .mode(ClusterConnectionMode.SINGLE)
                                      .requiredClusterType(ClusterType.STANDALONE))
                      .credential(credential)
                      .build();
      
      // 使用 uri 也可以
      //        MongoClientSettings settings = MongoClientSettings.builder()
      //                .applyConnectionString(new ConnectionString(
      //                        "mongodb://" +
      //                                "client01.example.com%40MYEXAMPLE.COM@" +
      //                                "mongodb01.example.com:66666/?" +
      //                                "authSource=$external&" +
      //                                "tls=true&" +
      //                                "tlsAllowInvalidHostnames=true&" +
      //                                "authMechanism=GSSAPI&" +
      //                                "authMechanismProperties=CANONICALIZE_HOST_NAME:true"))
      //                .build();
      ​
              return MongoClients.create(settings);
          }
      ​
          private MongoCredential getCredential(MongoProperties properties) {
              String username = properties.getUsername();
              return MongoCredential
                      .createGSSAPICredential(username)
                      .withMechanismProperty(MongoCredential.CANONICALIZE_HOST_NAME_KEY, true);
          }
      }
      
    • application.yml

      spring:
        data:
          mongodb:
            host: mongodb01.example.com
            port: 23332
            username: 'client01.example.com@MYEXAMPLE.COM'
            database: mytest
      
  • 参考

本账号所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!