前言
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
-
用户验证流程
-
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/TGT 被 KDC 加密(途中黄色的锁),包含:
- 用户名:用户名
- 产生此条消息的服务:TGS
- 时间戳:生成此次请求的时间
- 网络地址:ip 地址
- 有效时间:TGT 的存活时长
- TGS 会话密匙
-
图中黄色的钥匙:TGS 密匙
-
图中品红色的钥匙:TGS 会话密匙
-
-
4️⃣ --> 使用 TGT 请求 TGS 来获得访问 Mongodb 的密匙 TGS 收到请求后检查时间戳是否在5分钟内并且检查 MongoDB 服务是否有在 KDC 中注册
-
TGS_REQ1 包含:
- 有效时间:TGT 的存活时长
- 请求的服务:MongoDB 服务
- TGT ( 只有 KDC 能够解密 )
-
TGS_REQ2 被 TGS 密匙加密(图中品红色的锁),包含:
- 用户名:用户名
- 时间戳:生成此次请求的时间
-
-
5️⃣ --> TGS 生成访问服务的 ticket 和 MongoDB 的会话密匙(图中深蓝色的锁)
-
TGS_RES1 被 TGS 会话密匙加密(图中品红色的锁),包含:
- 请求的服务:MongoDB 服务
- 时间戳:生成此次请求的时间
- 有效时间:TGT 的存活时长
- 服务会话密匙 (深蓝色的钥匙)
-
TGS_RES2/STKT 被 MongoDB 的会话密匙加密(图中绿色的锁),包含:
- 用户名:用户名
- 请求的服务:MongoDB 服务
- 时间戳:生成此次请求的时间
- 网络地址:ip 地址
- 有效时间:STKT 的存活时长
- MongoDB 的会话密匙
-
图中绿色的锁:MongoDB 服务在 KDC 中的密匙
-
-
6️⃣ --> 用户发送登录请求到 MongoDB 服务 MongoDB 服务器接收到请求,检查时间戳是否在5分钟以内,检查用户是否在 MongoDB 服务器的 MongoDB 数据库中已经注册,最终分配角色和权限给用户
-
AP_REQ1 被 MongoDB 会话密匙(图中蓝色的锁),包含:
- 用户名:用户名
- 时间戳:生成此次请求的时间
-
AP_REQ2 和 TGS_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
搭建 MongoDB Enterprise(在服务器 A 部署)
比较简单,只写简单步骤,如出现问题查看以下参考文档
-
注意
-
支持以下系统(只针对 Ubuntu)
- 20.04 LTS ("Focal")
- 18.04 LTS ("Bionic")
- 16.04 LTS ("Xenial")
-
不能使用32位系统
-
不支持 Windows Subsystem for Linux (WSL)
-
-
步骤
-
在官网选择要下载的版本
-
导入包管理系统使用的公钥
wget -qO - https://www.mongodb.org/static/pgp/server-4.4.asc | sudo apt-key add -返回 OK 说明正常
-
进入以下的官方网页,选择你使用的 ubuntu 系统,接着复制提供的代码并运行
-
重新加载本地包数据库
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 -
显示以下结果则证明加密成功
-
-
-
取消挂载(有需要的话)
-
输入以下命令
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
显示 ”连接成功“ 即可
-
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 -
所有服务器设置 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 # 显示如下内容,若前面是 * 则表示连接成功
-
-
设置客户端(即服务器 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
-
接着被要求提供 Kerberos 服务器主机名
-
然后被要求提供管理服务器的主机名
-
单击 ok 完成安装
-
如果设置完发现主机名打错了,有以下两种修改方式
-
如果需要调整密钥分发中心 (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
-
接着被要求提供 Kerberos 服务器主机名
-
然后被要求提供管理服务器的主机名
-
单击 ok 完成安装
-
-
将 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 协议 ,转载请注明出处!