meface/docs/article/db/mongodb_advance2.md

557 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: MongoDB 安全认证
date: 2021-01-10
author: ac
tags:
- MongoDB
categories:
- Database
---
> `MongoDB`的安全认证
<!-- more -->
## 安全认证
### 1.用户和角色权限简介
默认情况下,`MongoDB`实例启动运行时是没有启用用户访问权限控制的,也就是说,在实例本机服务器上都可以随意连接到实例进行各种操作,`MongoDB`不会对连接客户端进行用户验证,这是非常危险的。
`MongoDB`官网上说,为了能保障`MongoDB`的安全可以做以下几个步骤:
1. 使用新的端口,默认的`27017`端口。
2. 设置`MongoDB`的网络环境最好将mongodb部署到公司服务器内网。
3. 开启安全认证。认证要同时设置服务器之间的内部认证方式,同时要设置客户端连接到集群的账号密码认证方式。
为了强制开启用户访问控制(用户验证),则需要在`MongoDB`实例启动时使用选项`--auth`或在指定的启动文件中添加选项`auth=true`。
**在开始之前需要了解的概念**
1. 启用访问控制
`MongoDB`使用的是基于角色的访问控制(`Role-Based Access Control``RBAC`)来管理用户对实例的访问。通过对用户授予一个或多个角色来控制用户访问数据库资源的权限和数据库操作的权限,在对用户分配角色之前,用户无法访问实例。
2. 角色
在`MongoDB`中通过角色对用户授予相应数据库资源的操作权限,每个角色当中的权限可以显示指定,也可以通过继承其他角色的权限,或者两者都存在的权限。
3. 权限
权限由指定的数据库资源resource以及允许在指定资源上进行的操作action组成。
- 资源resource数据库、集合、部分集合和集群
- 操作action对资源进行的增删改查CRUD操作
在角色定义时可以包含一个或多个已经存在的角色,新创建的角色会继承包含的角色所有的权限。在同一个数据库中新创建角色可以继承其他角色的权限,在`admin`数据库中创建的角色可以继承在其它任意数据库中角色的权限。
常用的内置角色:
- 数据库用户角色:`read`、`readWrite`
- 所有数据库用户角色:`readAnyDatabase`、`readWriteAnyDatabase`、`userAdminAnyDatabase`、`dbAdminAnyDatabase`
- 数据库管理角色:`dbAdmin`、`dbOwner`、`userAdmin`
- 集群管理角色:`clusterAdmin`、`clusterManager`、`clusterMonitor`、`hostManager`;
- 备份恢复角色:`backup`、`restore`
- 超级用户角色:`root`
- 内部角色:`system`
关于角色权限的查看:
```shell
# 查询所有角色权限(仅用户自定义角色)
db.runCommand({rolesInfo:1})
#查询所有角色权限(包含内置角色)
db.runCommand({rolesInfo:1,showBuiltinRoles:true})
#查询当前数据库的某角色的权限
db.runCommand({rolesInfo:"<rolename>"})
#查询其他数据库中指定的角色权限
db.runCommand({rolesInfo:{role:"<rolename>",db:"<database>"}})
```
**角色说明**
| 角色 | 权限描述 |
| ---------------------- | ------------------------------------------------------------ |
| `read` | 可以读取指定数据库中任何数据。 |
| `readWrite` | 可以读写指定数据库中任何数据,包括创建、重命名、删除集合 |
| `readAnyDatabase` | 可以读取所有数据库中任何数据(除了数据库`config`和`local`之外) |
| `userAdminAnyDatabase` | 可以在指定数据库创建和修改用户(除了数据库`config`和`local`之外) |
| `readWriteAnyDatabase` | 可以读写所有数据库中任何数据(除了数据库`config`和`local`之外) |
| `dbAdminAnyDatabase` | 可以读取任何数据库以及对数据库进行清理、修改、压缩、获取统计信息、执行检查等操作(除了数据库`config`和`local`之外) |
| `dbAdmin` | 可以读取指定数据库以及对数据库进行清理、修改、压缩、获取统计信息、执行检查等操作。 |
| `userAdmin` | 可以在指定数据库创建和修改用户。 |
| `clusterAdmin` | 可以对整个集群或数据库系统进行管理操作。 |
| `backup` | 备份`MongoDB`数据最小的权限。 |
| `restore` | 从备份文件中还原恢复`MongoDB`数据(除了`system.profile`集合)的权限。 |
| `root` | 超级账号,超级权限 |
### 2.单实例环境
> 对单实例的`MongoDB`服务开启安全认证,这里的单实例指的是未开启副本集或分片的`MongoDB`实例
#### 关闭已开启的服务(可选)
增加`mongod`的单实例的安全认证功能,可以在服务搭建的时候直接添加,也可以在之前搭建好的服务上添加。
这里使用之前搭建好的服务,因此,先停止之前的服务。
关闭服务有两种方式:
- 快速关闭:使用`kill`命令直接杀死进程
```shell
#通过进程编号关闭节点
kill -2 进程号
```
- 标准关闭:通过`mongo`个客户端中的`shutdownServer`命令来关闭服务
```shell
#客户端登录服务注意这里通过localhost登录如果需要远程登录必须先登录认证才行。
mongo --port 27017
#切换到admin库
use admin
#如果不是admin数据库,则会报如下错误
shutdown command only works with the admin database; try 'use admin'
#关闭服务
db.shutdownServer()
```
> 必须是在`admin`库下执行该关闭命令;
>
> 如果没有开启认证,必须是从`localhost`登陆才能执行关闭服务命令;
>
> 非`localhost`的、通过远程登陆的,必须登陆且必须登陆用户有对`admin`操作权限才可以。
#### 添加用户权限
1. 先按照普通无授权认证的配置,来配置服务端的配置文件`/mongodb/single/mongod.conf`
```yaml
systemLog:
destination: file
path: "/home/qiusj/.local/mongodb/singlesite/log/mongod.log"
logAppend: true
storage:
dbPath: "/home/qiusj/.local/mongodb/singlesite/data/db"
journal:
enabled: true
net:
#添加本机在局域网内的ip
bindIp: 127.0.0.1,192.168.0.152
port: 27017
setParameter:
enableLocalhostAuthBypass: false
processManagement:
fork: true
pidFilePath: "/home/qiusj/.local/mongodb/singlesite/log/mongod.pid"
```
2. 按照之前未开启认证的方式(不添加`--auth`参数)来启动`MongoDB`服务:
```shell
qiusj@u20:~$ /usr/local/mongodb/bin/mongod -f /home/qiusj/.local/mongodb/singlesite/mongod.conf
```
3. 使用`Mongo`客户端登陆
```shell
qiusj@u20:~$ /usr/local/mongodb/bin/mongo --port=27017
```
4. 创建两个管理员用户,一个系统的超级管理员`myroot`,一个是`admin`库的管理员用户`myadmin`:
```shell
#切换到admin库
> use admin
switched to db admin
#创建系统超级用户myroot,密码123456角色root
#> db.createUser({user:"myroot",pwd:"123456",roles:[{"role":"root","db":"admin"}]})
> db.createUser({user:"myroot",pwd:"123456",roles:["root"]})
Successfully added user: { "user" : "myroot", "roles" : [ "root" ] }
#创建专门用来管理admin库的账号myadmin,只用来作为用户权限的管理
> db.createUser({user:"myadmin",pwd:"123456",roles:[{role:"userAdminAnyDatabase",db:"admin"}]})
Successfully added user: {
"user" : "myadmin",
"roles" : [
{
"role" : "userAdminAnyDatabase",
"db" : "admin"
}
]
}
#查看已经创建了的用户的情况
> db.system.users.find()
{ "_id" : "admin.myroot", "userId" : UUID("9ae2bb4d-d289-4135-884f-2082cb6b20aa"), "user" : "myroot", "db" : "admin", "credentials" : { "SCRAM-SHA-1" : { "iterationCount" : 10000, "salt" : "I2oNFoKRK0ktT9pM4to5fw==", "storedKey" : "Nxuoez/RCQ1AeyQxF2FKEyTsopE=", "serverKey" : "L740F4turO7raGtNr92H1GoRErk=" }, "SCRAM-SHA-256" : { "iterationCount" : 15000, "salt" : "iZ7PX1mFDZdCKwyOIrvyvLW7SywqR8sJANp+SA==", "storedKey" : "ngkeL6kE94IKXuuHjjSZjo87fo4yZ3ivxGeFsWK0/V4=", "serverKey" : "SpcYUVj+J3ZO3Iv3SU4AOx9POmWc7bc2WLreAxFtypI=" } }, "roles" : [ { "role" : "root", "db" : "admin" } ] }
{ "_id" : "admin.myadmin", "userId" : UUID("4a901dd2-75ce-4ea4-ab2e-5c06de29ba20"), "user" : "myadmin", "db" : "admin", "credentials" : { "SCRAM-SHA-1" : { "iterationCount" : 10000, "salt" : "eCn7OmqaJHgX4kxGij2eew==", "storedKey" : "qXWbRh5vgKQJQ4Ri4in1W7KyTq0=", "serverKey" : "tattdTXuWm2wsozuaWgtZgn2I4M=" }, "SCRAM-SHA-256" : { "iterationCount" : 15000, "salt" : "z3gBxcUUIgV4BjZk1RPWkjWHlDZF6r5ZDcSV6A==", "storedKey" : "2oQpybEAs0mdvNTZnIuKA3hVRHJxKcR6UZXq6At/fkg=", "serverKey" : "cMuUfnaDScuIJOQ4Y/g2y/1s/1W7FZNrM8T9H/pMbAI=" } }, "roles" : [ { "role" : "userAdminAnyDatabase", "db" : "admin" } ] }
```
5. 用户相关操作
```shell
#删除用户
> db.dropuser("myadmin")
#修改密码
> db.changeUserPassword("myroot","123456")
#认证测试
> db.auth("myroot","123456")
1
```
和其他数据库一样,权限的管理都差不多一样,也是将用户和权限信息保存在数据库对应的表中。`MongoDB`存储所有用户信息在`admin`数据库中的`system.users`集合中,保存着用户名、密码和数据库信息。
如果不指定数据库,则创建的指定的权限的用户在所有的数据库上有效。如`{role:"userAdmin",db:""}`
6. 创建普通用户
创建普通用户可以在没有开启认证的时候添加,也可以在开启认证后添加,但开启之后,必须使用有操作`admin`库的用户登录认证后才能操作。底层都是将用户信息保存在`admin`数据库的`system.users`集合中
```shell
> use meface
> db.createUser({user:"star",pwd:"star",roles:[{role:"readWrite",db:"meface"}]})
Successfully added user: {
"user" : "star",
"roles" : [
{
"role" : "readWrite",
"db" : "meface"
}
]
}
> db.auth("star","star")
1
```
> 如果开启了认证,登陆的客户端的用户必须使用`admin`库的角色,如果拥有`root`劫色的`myadmin`用户,再通过`myadmin`用户去创建其他角色的用户。
#### 服务端开启认证和客户端连接登陆
有两种方式开启权限认证启动服务:参数方式、配置文件方式
1. 参数方式
在启动时指定参数`--auth`,示例:
```shell
/usr/local/mongodb/bin/mongd -f /mongodb/single/mongod.conf --auth
```
2. 配置文件方式
在`mongod.conf`配置文件中加入:
```yaml
security:
#开启授权认证
authorization: enabled
```
启动时可以不加`--auth`参数
开启了认证的情况下的客户端登陆,有两种方式:先连接再登陆、登陆时直接认证
1. 开启认证后再登陆,如查询`admin`库中的`system.user`集合的用户:
```shell
> use admin
switched to db admin
> db.auth("myadmin","123456")
1
> db.system.users.find()
```
查询`meface`库中的 `article`集合的内容:
```shell
> use meface
switched to db meface
> db.article.find()
Error: error: {
"ok" : 0,
"errmsg" : "not authorized on meface to execute command { find: \"article\", filter: {}, lsid: { id: UUID(\"a2215a94-d86a-4bdc-bdef-1e62033aee89\") }, $db: \"meface\" }",
"code" : 13,
"codeName" : "Unauthorized"
}
```
需要退出来,再使用`star`用户去操作
```shell
> exit
bye
qiusj@u20:~$ /usr/local/mongodb/bin/mongo --port=27017
> db.auth("star","star")
Error: Authentication failed.
0
> use meface
switched to db meface
> db.auth("star","star")
1
> db.article.find()
```
2. 登陆时直接认证:
`/usr/local/mongodb/bin/mongo --port=27017 --username=star --password=star --authenticationDatabase=meface`
```shell
qiusj@u20:~$ /usr/local/mongodb/bin/mongo --port=27017 --username=star --password=star --authenticationDatabase=meface
MongoDB shell version v4.4.2
connecting to: mongodb://127.0.0.1:27017/?authSource=meface&compressors=disabled&gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("0cabba54-185e-4332-a8f8-2a1b1b8753a3") }
MongoDB server version: 4.4.2
> db
test
> use meface
switched to db meface
> db.article.find()
{ "_id" : ObjectId("5fed2b1b232584d8ba357db0"), "articleid" : "100000", "content" : "今天天气真好,阳光明 媚", "userid" : "1001", "nickname" : "Rose", "createdatetime" : ISODate("2020-12-31T01:36:27.784Z"), "likenum" : 10, "state" : null }
{ "_id" : "1", "likenum" : 1001 }
{ "_id" : "2", "articleid" : "100001", "content" : "我夏天空腹喝凉开水,冬天喝温开水", "userid" : "1005", "nickname" : "伊人憔 悴", "createdatetime" : ISODate("2019-08-05T23:58:51.485Z"), "likenum" : 666, "state" : "1" }
{ "_id" : "3", "articleid" : "100001", "content" : "我一直喝凉开水,冬天夏天都喝。", "userid" : "1004", "nickname" : "杰克船 长", "createdatetime" : ISODate("2019-08-06T01:05:06.321Z"), "likenum" : 666, "state" : "1" }
{ "_id" : "4", "articleid" : "100001", "content" : "专家说不能空腹吃饭,影响健康。", "userid" : "1003", "nickname" : "小李", "createdatetime" : ISODate("2019-08-06T08:18:35.288Z"), "likenum" : 2000, "state" : "1" }
{ "_id" : "5", "articleid" : "100001", "content" : "研究表明,刚烧开的水千万不能喝,因为烫 嘴。", "userid" : "1003", "nickname" : "小李", "createdatetime" : ISODate("1970-01-01T00:00:00Z"), "likenum" : 3000, "state" : "1" }
>
```
#### Compass连接认证
`mongodb://star:star@192.168.0.152:27017/?authSource=meface`
![image-20210121161359189](./images/image-20210121161359189.png)
#### `SpringData`连接认证
使用用户名和密码连接到`MongoDB`服务器,必须使用`username:password@hostname/dbname`格式。
`application.yml`
```yaml
spring:
#数据源配置
data:
mongodb:
#连接分片集群的路由节点多个路由用逗号分隔mongodb会有其负载均衡的策略
# uri: mongodb://192.168.0.152:27017,192.168.0.152:27117/meface
# host: 192.168.0.152
# database: meface
# port: 27017
#使用uri的方式
# uri:mongodb://192.168.0.152:27017/meface
uri: mongodb://star:star@192.168.0.152:27017/meface
```
### 3.副本集环境
对于搭建好的`mongodb`副本集,为了安全,启动安全认证,使用账号密码登陆。
副本集环境使用之前搭建好的,架构如下:
![image-20210121164640605](./images/image-20210121164640605.png)
#### 先通过主节点增加一个管理员账号
在开启认证之前,创建超管用户:`myroot/123456`
只需要在主节点上添加用户,副本集会自动同步。
```shell
myrs:PRIMARY> use admin
switched to db admin
myrs:PRIMARY> db.createUser({user:"myroot",pwd:"123456",roles:[{role:"root",db:"admin"}]})
Successfully added user: {
"user" : "myroot",
"roles" : [
{
"role" : "root",
"db" : "admin"
}
]
}
```
#### 关闭副本集
主节点必须最后一个成员关闭义避免潜在的回滚。
```shell
$ ps -ef|grep mongo
qiusj 4004546 1 0 10:38 ? 00:00:02 /usr/local/mongodb/bin/mongod -f /home/qiusj/.local/mongodb/replica_sets/myrs_27017/mongod.conf
qiusj 4004640 1 0 10:38 ? 00:00:02 /usr/local/mongodb/bin/mongod -f /home/qiusj/.local/mongodb/replica_sets/myrs_27018/mongod.conf
qiusj 4004739 1 0 10:38 ? 00:00:01 /usr/local/mongodb/bin/mongod -f /home/qiusj/.local/mongodb/replica_sets/myrs_27019/mongod.conf
qiusj 4006158 3989038 0 10:45 pts/1 00:00:00 grep --color=auto mongo
$ kill -2 4004640 4004739 4004546
```
#### 创建副本集认证的key文件
**第一步**生成一个key文件到当前文件夹中。
可以使用任何方法生成密钥文件。例如,以下操作使用`openssl`生成加密文件,然后使用`chmod`来更改文件权限,仅为文件所有者提供读取权限。
```shell
# 查看安装的ssl版本
qiusj@u20:~$ openssl version -a
OpenSSL 1.1.1f 31 Mar 2020
qiusj@u20:~$ openssl rand -out /home/qiusj/.local/mongodb/mongo.keyfile -base64 90
```
> 所有副本集节点都必须要用同一份`keyfile`,一般是在一台机器上生成,然后拷贝到其他机器上,且必须有读取权限,否则将来会报错。
一定要保证密钥文件一致,文件位置随便。
**第二步**:将生成的加密文件拷贝到节点的目录下
```shell
$ cp mongo.keyfile /home/qiusj/.local/mongodb/replica_sets/myrs_27017
$ cp mongo.keyfile /home/qiusj/.local/mongodb/replica_sets/myrs_27018
$ cp mongo.keyfile /home/qiusj/.local/mongodb/replica_sets/myrs_27019
```
**第三步**:修改加密文件的权限
```shell
$ ll
-rw-r--r-- 1 qiusj intplanet 122 1月 21 17:44 mongo.keyfile
#如果这种权限启动服务会报错“permissions on ../mongo.keyfile are too open”当前加密文件的权限过大
#修改成只对当前用户只读
$ chmod 400 ./mongo.keyfile
-r-------- 1 qiusj intplanet 122 1月 21 17:44 mongo.keyfile
```
#### 修改配置文件指定keyfile
分别编辑几个服务的`mongod.conf`文件,添加相关内容:
`$ vi /home/qiusj/.local/mongodb/replica_sets/myrs_27017/mongod.conf`
```yaml
security:
# KeyFile鉴权文件
keyFile: /home/qiusj/.local/mongodb/replica_sets/myrs_27017/mongo.keyfile
# 开启认证方式
authorization: enabled
```
```shell
security:
# KeyFile鉴权文件
keyFile: /home/qiusj/.local/mongodb/replica_sets/myrs_27018/mongo.keyfile
# 开启认证方式
authorization: enabled
```
```shell
security:
# KeyFile鉴权文件
keyFile: /home/qiusj/.local/mongodb/replica_sets/myrs_27019/mongo.keyfile
# 开启认证方式
authorization: enabled
```
#### 启动副本集
如果副本集是开启状态,则分别关闭副本集中的每个`mongod`进程,从次节点开始,直到副本集的所有成员都离线,包括仲裁者。主节点必须最后一个成员关闭义避免潜在的回滚。
分别启动副本集节点:
```shell
$ /usr/local/mongodb/bin/mongod -f /home/qiusj/.local/mongodb/replica_sets/myrs_27017/mongod.conf
$ /usr/local/mongodb/bin/mongod -f /home/qiusj/.local/mongodb/replica_sets/myrs_27018/mongod.conf
$ /usr/local/mongodb/bin/mongod -f /home/qiusj/.local/mongodb/replica_sets/myrs_27019/mongod.conf
$ /usr/local/mongodb/bin/mongo --port=27017
```
#### 在主节点上添加普通账号
```shell
myrs:PRIMARY> use admin
switched to db admin
myrs:PRIMARY> db.auth("myroot","123456")
1
myrs:PRIMARY> show dbs
admin 0.000GB
article 0.000GB
config 0.000GB
local 0.002GB
#切换到article库
myrs:PRIMARY> use article
switched to db article
#创建操作article库的普通用户moon
myrs:PRIMARY> db.createUser({user:"moon",pwd:"moon",roles:[{role:"readWrite",db:"article"}]})
Successfully added user: {
"user" : "moon",
"roles" : [
{
"role" : "readWrite",
"db" : "article"
}
]
}
```
#### SpringData连接副本集
使用用户名和密码连接到MongoDB服务器必须使用`username:password@hostname/dbname`格式
application.yml
```yaml
spring:
#数据源配置
data:
mongodb:
#连接分片集群的路由节点多个路由用逗号分隔mongodb会有其负载均衡的策略
# uri: mongodb://192.168.0.152:27017,192.168.0.152:27117/meface
# host: 192.168.0.152
# database: meface
# port: 27017
#使用uri的方式
# uri:mongodb://192.168.0.152:27017/meface
#开启安全认证后的单机连接uri
# uri: mongodb://star:star@192.168.0.152:27017/meface
#开启安全认证后的副本集连接uri
uri: mongodb://moon:moon@192.168.0.152:27017,192.168.0.152:27018,192.168.0.152:27019/article?connect=replicaSet&slaveOk=true&replicaSet=myrs
```
## 参考文章
[1] `Replication` https://docs.mongodb.com/manual/replication/
[2] `Sharding` https://docs.mongodb.com/manual/sharding/
[3] `Sharding reference` https://docs.mongodb.com/manual/reference/sharding/
[4] `Security Reference` https://docs.mongodb.com/manual/reference/security/