概述
在这篇教程中,我将会描述使用一个本地证书机构(CA)所签署的证书创建基于认证的X.509的详细过程,包括集群成员的配置以及客户端认证的配置。
首先,我们需要了解成员认证和客户端认证之间的区别。MongoDB是一个分布式的数据库,大部分部署由运行在多台机器上的多个mongod或者mongos进程组成。成员认证指的是这些机器需要相互验证以保证尝试复制数据的某节点确实是集群的一个部分。
另一方面,客户端认证指的是这些MongoDB客户端,包括mongos shell,导入/导出工具及MongoDB驱动程序。
对于成员认证,MongoDB支持基于密钥文件和X.509的机制。后者从某种程度而言更加安全,因为每台机器需要一个专门的密钥才能加入集群。因此,从另一台机器中窃取一个已经存在的密钥对恶意进程而言并不会有太大的帮助。
为了简化,在这里,我打算使用一台机器,在不同端口上运行多个mongod进程。
准备
- 一台运行着linux的服务器,我使用RedHat 7,但是其它版本也适用。
- 下载MongoDB3.2企业版:https://www.mongodb.com/download-center#enterprise
- 使用下面文档化的指令安装MongoDB:https://docs.mongodb.org/manual/tutorial/install-mongodb-enterprise-on-linux/
注意:您必须下载企业版以便于启动X.509认证。
主要工作流程
- 为成员认证创建本地环境认证机构签署的证书&签署密钥生成&签署服务器认证
- 为客户端认证生成&签署客户端认证
- 在非认证模式启动MongoDB集群
- 创建复制集并且初始化用户
- 使用服务器证书启动MongoDB集群
- 使用客户端证书连接到MongoDB
下面是案例脚本的主要内容:
0. 一些参数设置
首先,设置一些变量,方面您之后调整,特别是中文名:
cn_prefix="/C=CN/ST=GD/L=Shenzhen/O=MongoDB China"
ou_member="MyServers"
ou_client="MyClients"
mongodb_server_hosts=( "server1" "server2" "server3" )
mongodb_client_hosts=( "client1" "client2" )
mongodb_port=27017
注意:在生产环境中,服务器主机&客户端主机应该是完整有效的域名而不是主机名。
为了有一个干净的开始,我们先停止运行中的mongods进程 并且清空工作目录:
kill (ps -ef | grep mongod | grep set509 | awk '{print2}')
rm -Rf db/*
mkdir -p db
1.创建本地最高凭证管理中心(root CA)
理想说来,应该使用一个第三方凭证管理中心。然而,在隔离网络或者用于测试木器的情况下,我们只需要使用本地凭证管理中心来测试功能即可。
echo "##### STEP 1: Generate root CA "
openssl genrsa -out root-ca.key 2048
!!! In production you will want to password protect the keys
openssl genrsa -aes256 -out
root-ca.key 2048
openssl req -new -x509 -days 3650 -key root-ca.key -out root-ca.crt -subj "$cn_prefix/CN=ROOTCA"
mkdir -p RootCA/ca.db.certs
echo "01" >> RootCA/ca.db.serial
touch RootCA/ca.db.index
echo $RANDOM >> RootCA/ca.db.rand
mv root-ca* RootCA/
2: 创建凭证管理中心配置
在签署证书时,您可以使用这个文件来存储一些默认设置:
echo "##### STEP 2: Create CA config"
cat >> root-ca.cfg < [ RootCA ]
dir = ./RootCA
certs = $dir/ca.db.certs
database = $dir/ca.db.index
new_certs_dir = $dir/ca.db.certs
certificate = $dir/root-ca.crt
serial = $dir/ca.db.serial
private_key = $dir/root-ca.key
RANDFILE = $dir/ca.db.rand
default_md = sha256
default_days = 365
default_crl_days= 30
email_in_dn = no
unique_subject = no
policy = policy_match
[ SigningCA ]
dir = ./SigningCA
certs = $dir/ca.db.certs
database = $dir/ca.db.index
new_certs_dir = $dir/ca.db.certs
certificate = $dir/signing-ca.crt
serial = $dir/ca.db.serial
private_key = $dir/signing-ca.key
RANDFILE = $dir/ca.db.rand
default_md = sha256
default_days = 365
default_crl_days= 30
email_in_dn = no
unique_subject = no
policy = policy_match
[ policy_match ]
countryName = match
stateOrProvinceName = match
localityName = match
organizationName = match
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
[ v3_req ]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
[ v3_ca ]
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid:always,issuer:always
basicConstraints = CA:true
EOF
3. 生成签名密钥
上面生成的根密钥一般不用于真实的签名。我们需要创建一对从密钥用于签名:
echo "##### STEP 3: Generate signing key"
openssl genrsa -out signing-ca.key 2048
again you would want to password protect your signing key:
openssl genrsa -aes256 -out signing-ca.key 2048
openssl req -new -days 1460 -key signing-ca.key -out signing-ca.csr -subj "$cn_prefix/CN=CA-SIGNER"
openssl ca -batch -name RootCA -config root-ca.cfg -extensions v3_ca -out signing-ca.crt -infiles signing-ca.csr
mkdir -p SigningCA/ca.db.certs
echo "01" >> SigningCA/ca.db.serial
touch SigningCA/ca.db.index
Should use a better source of random here..
echo $RANDOM >> SigningCA/ca.db.rand
mv signing-ca* SigningCA/
然后。我们将所有的签名证书连结到一起形成一个凭证管理中心文件,这个文件包含了所有验证过的用户所信任的签名授权证书。
Create root-ca.pem
cat RootCA/root-ca.crt SigningCA/signing-ca.crt > root-ca.pem
4. 创建&签署服务器认证
这些认证是为mongod & mongos进程设计的,主要用于成员内部的认证。
注意变量 $ou_member
,这标志着服务器认证和客户端认证的主要区别。服务器&客户端认证必须在专有名称上有所不同,换句话说,至少在 O,OU或者DC上有所不同:
echo "##### STEP 4: Create server certificates"
# Pay attention to the OU part of the subject in "openssl req" command
for host in "${mongodb_server_hosts[@]}"; do
echo "Generating key for $host"
openssl genrsa -out ${host}.key 2048
openssl req -new -days 365 -key ${host}.key -out ${host}.csr -subj "$cn_prefix/OU=$ou_member/CN=${host}"
openssl ca -batch -name SigningCA -config root-ca.cfg -out ${host}.crt -infiles ${host}.csr
cat ${host}.crt ${host}.key > ${host}.pem
done
上面的脚本在一个for循环中生成多个认证。在每个认证中至少包括3个基本步骤:
* 使用openssl genrsa命令生成一个新的密钥
* 使用openssl req命令生成一个签名请求
* 使用openssl ca 对密钥进行签名并且使用上述认证的签名密钥输出一个证书
5.生成&签署客户端认证
这些证书都是客户端用于连接到MongoDB集群,例如mongo shell,mongodump,Java/python/C# 驱动程序等。
注意OU部分$ou_client的使用,需要和上面的服务器证书不相同。
echo "##### STEP 5: Create client certificates"
# Pay attention to the OU part of the subject in "openssl req" command
for host in "${mongodb_client_hosts[@]}"; do
echo "Generating key for $host"
openssl genrsa -out ${host}.key 2048
openssl req -new -days 365 -key ${host}.key -out ${host}.csr -subj "$cn_prefix/OU=$ou_client/CN=${host}"
openssl ca -batch -name SigningCA -config root-ca.cfg -out ${host}.crt -infiles ${host}.csr
cat ${host}.crt ${host}.key > ${host}.pem
done
6.在非授权模式启动复制集
这个不走对于我们设置初始化用户而言是非常有必要的。否则,一旦我们启动了授权,我们将不能登录到系统中。
echo "##### STEP 6: Start up replicaset in non-auth mode"
mport=$mongodb_port
for host in "${mongodb_server_hosts[@]}"; do
echo "Starting server $host in non-auth mode"
mkdir -p ./db/${host}
mongod --replSet set509 --port $mport --dbpath ./db/$host \
--fork --logpath ./db/${host}.log
let "mport++"
done
sleep 3
现在复制集启动了,我们需要初始化复制集并且增加用户。
7.初始化复制集&增加初始化用户
当使用X.509客户端授权时,每个客户端都必须在MongoDB创建一个用户并且授予合适的权限。用户名必须与客户端的中文名称相同,可以通过运行一个openssl命令获取:
# obtain the subject from the client key:
client_subject=`openssl x509 -in client1.pem -inform PEM -subject -nameopt RFC2253 | grep subject | awk '{sub("subject= ",""); print}'
这个命令将会打印类似的信息:
CN=client1,OU=MyClients,O=MongoDB China,L=Shenzhen,ST=GD,C=CN
一旦我们有了中文名,我们就可以初始化复制集并且增加用户了:
myhostname=`hostname`
cat > setup_auth.js <<EOF
rs.initiate();
mport=$mongodb_port;
mport++;
rs.add("$myhostname:" + mport);
mport++;
rs.add("$myhostname:" + mport);
sleep(5000);
db.getSiblingDB("\$external").runCommand(
{
createUser: "$client_subject",
roles: [
{ role: "readWrite", db: 'test' },
{ role: "userAdminAnyDatabase", db: "admin" },
{ role: "clusterAdmin", db:"admin"}
],
writeConcern: { w: "majority" , wtimeout: 5000 }
}
);
EOF
mongo localhost:$mongodb_port setup_auth.js
Then stop the non-auth replicaset(so that we can restart later):
kill $(ps -ef | grep mongod | grep set509 | awk '{print $2}')
sleep 3
8.在x.509模式重启复制集
启动所有三个节点,每个进程都使用它们自己的密钥文件:
echo "##### STEP 8: Restart replicaset in x.509 mode"
mport=$mongodb_port
for host in "${mongodb_server_hosts[@]}"; do
echo "Starting server $host"
mongod --replSet set509 --port $mport --dbpath ./db/$host \
--sslMode requireSSL --clusterAuthMode x509 --sslCAFile root-ca.pem \
--sslAllowInvalidHostnames --fork --logpath ./db/${host}.log \
--sslPEMKeyFile ${host}.pem --sslClusterFile ${host}.pem
let "mport++"
done
9. 使用证书测试连接到复制集的连接性
为了使用证书连接到mongod,您必须做两件事:
* 使用–sslPEMKeyFile选项指明以提供客户端证书
* 使用–sslCAFile选项提供最高凭证管理中心密钥文件
–sslAllowInvalidHostnames只在单机的情况下需要。在生产环境下,你不需要使用这个选项。
echo "##### STEP 9: Connecting to replicaset using certificate\n"
cat > do_login.js <<EOF
db.getSiblingDB("\$external").auth(
{
mechanism: "MONGODB-X509",
user: "$client_subject"
}
)
EOF
mongo --ssl --sslPEMKeyFile client1.pem --sslCAFile root-ca.pem --sslAllowInvalidHostnames --shell do_login.js
运行脚本
在您运行脚本之前,再三检查:
a. 确保您已经安装了MongoDB企业版
b. 确保mongod/mongo在可执行路径
c. 确保没有mongod运行在27017端口,或者修改端口
d. 在干净的文件夹下运行脚本
./setup-x509.sh
如果一切顺利的话,您应该有一个三节点的复制集,使用X.509作为成员认证运行。您也可以通过客户端认证使用mongo shell连接到复制集。
mongo --ssl --sslPEMKeyFile client1.pem --sslCAFile root-ca.pem --sslAllowInvalidHostnames
之后您可以使用下列命令对自己进行授权:
> db.getSiblingDB("$external").auth(
{
mechanism: "MONGODB-X509",
user: "CN=client1,OU=MyClients,O=MongoDB China,L=Shenzhen,ST=GD,C=CN"
}
);
> db.test.find()
如果您需要向集群中增加新的节点或者增加一台新的客户端机器,您只需要分别按照第5部分和第6部分中描述的步骤进行即可。
Reference
https://docs.mongodb.org/manual/tutorial/configure-x509-client-authentication/
http://www.allanbank.com/blog/security/tls/x.509/2014/10/13/tls-x509-and-mongodb/
评论前必须登录!
注册