To protect your MongoDB cluster and the data it holds, you will want to employ the following security measures:
Enable authorization and enforce authentication
Encrypt communication
Encrypt data
This chapter demonstrates how to address the first two security measures with a tutorial on using MongoDB’s support for x.509 to configure authentication and transport layer encryption to ensure secure communications among clients and servers in a MongoDB replica set. We will touch on encrypting data at the storage layer in a later chapter.
While authentication and authorization are closely connected, it is important to note that authentication is distinct from authorization. The purpose of authentication is to verify the identity of a user, while authorization determines the verified user’s access to resources and operations.
Enabling authorization on a MongoDB cluster enforces authentication and ensures users can only perform actions they are authorized for, as determined by their roles. The Community version of MongoDB provides support for SCRAM (Salted Challenge Response Authentication Mechanism) and x.509 certificate authentication. In addition to SCRAM and x.509, MongoDB Enterprise supports Kerberos authentication and LDAP proxy authentication. See the documentation for details on the various authentication mechanisms that MongoDB supports. In this chapter, we will focus on x.509 authentication. An x.509 digital certificate uses the widely accepted x.509 public key infrastructure (PKI) standard to verify that a public key belongs to the presenter.
When adding a user in MongoDB, you must create the user in a specific database. That database is the authentication database for the user; you can use any database for this purpose. The username and authentication database serves as a unique identifier for a user. However, a user’s privileges are not limited to their authentication database. When creating a user, you can specify the operations the user may perform on any resources to which they should have access. Resources include the cluster, databases, and collections.
MongoDB provides a number of built-in roles that grant commonly needed permissions for database users. These include the following:
read
Read data on all nonsystem collections and on the following system collections: system.indexes, system.js, and system.namespaces.
readWrite
Provides same privileges as read
, plus the ability to modify data on
all nonsystem collections and the system.js
collection.
dbAdmin
Perform administrative tasks such as schema-related tasks, indexing, and gathering statistics (does not grant privileges for user and role management).
userAdmin
dbOwner
Combines the privileges granted by the readWrite, dbAdmin, and userAdmin roles.
clusterManager
clusterMonitor
Provides read-only access to monitoring tools such as the MongoDB Cloud Manager and Ops Manager monitoring agent.
hostManager
clusterAdmin
Combines the privileges granted by the clusterManager, clusterMonitor, and hostManager roles, plus the dropDatabase action.
backup
Provides sufficient privileges to use the MongoDB Cloud Manager backup agent or the Ops Manager backup agent, or to use mongodump to back up an entire mongod instance.
restore
Provides privileges needed to restore data from backups that do not include system.profile collection data.
readAnyDatabase
Provides same privileges as read on all databases except local and config, plus the listDatabases action on the cluster as a whole.
readWriteAnyDatabase
Provides same privileges as readWrite on all databases except local and config, plus the listDatabases action on the cluster as a whole.
userAdminAnyDatabase
Provides same privileges as userAdmin on all databases except local and config (effectively a superuser role).
dbAdminAnyDatabase
Provides same privileges as dbAdmin on all databases except local and config, plus the listDatabases action on the cluster as a whole.
root
Provides access to the operations and all the resources of the readWriteAnyDatabase, dbAdminAnyDatabase, userAdminAnyDatabase, clusterAdmin, restore, and backup roles combined.
You may also create what are known as “user-defined roles,” which are custom roles that group together authorization to perform specific operations and label them with a name so that you may grant this set of permissions to multiple users easily.
A deep dive on built-in roles or user-defined roles is beyond the scope of this chapter. However, this introduction should give you a pretty good idea of what’s possible with MongoDB authorization. For greater detail, please see the authorization section of the MongoDB documentation.
To ensure that you can add new users as needed, you must first create an admin user. MongoDB does not create a default root or admin user when enabling authentication and authorization, regardless of the authentication mode you are using (x.509 is no exception).
In MongoDB, authentication and authorization are not enabled by
default. You must explicitly enable them by using the
--auth
option to the mongod
command
or specifying a value of "enabled"
for the security.authorization
setting in a
MongoDB config file.
To configure a replica set, first bring it up without authentication and authorization enabled, then create the admin user and the users you’ll need for each client.
Given that all production MongoDB clusters are composed of multiple members, to secure a cluster, it is essential that all services communicating within the cluster authenticate with one another. Each member of a replica set must authenticate with the others in order to exchange data. Likewise, clients must authenticate with the primary and any secondaries that they communicate with.
For x.509, it’s necessary that a trusted certification authority (CA) sign all certificates. Signing certifies that the named subject of a certificate owns the public key associated with that certificate. A CA acts as a trusted third party to prevent man-in-the-middle attacks.
Figure 19-1 depicts x.509 authentication used to secure a three-member MongoDB replica set. Note the authentication among the client and members of the replica set and the trust relationships with the CA.
The members and the client each have their own certificate signed by the CA. For production use, your MongoDB deployment should use valid certificates generated and signed by a single certificate authority. You or your organization can generate and maintain an independent certificate authority, or you can use certificates generated by a third-party TLS/SSL vendor.
We will refer to certificates used for internal authentication to verify membership in a cluster as member certificates. Both member certificates and client certificates (used to authenticate clients) have a structure resembling the following:
Certificate: Data: Version: 1 (0x0) Serial Number: 1 (0x1) Signature Algorithm: sha256WithRSAEncryption Issuer: C=US, ST=NY, L=New York, O=MongoDB, CN=CA-SIGNER Validity Not Before: Nov 11 22:00:03 2018 GMT Not After : Nov 11 22:00:03 2019 GMT Subject: C=US, ST=NY, L=New York, O=MongoDB, OU=MyServers, CN=server1 Subject Public Key Info: Public Key Algorithm: rsaEncryption Public-Key: (2048 bit) Modulus: 00:d3:1c:29:ba:3d:29:44:3b:2b:75:60:95:c8:83: fc:32:1a:fa:29:5c:56:f3:b3:66:88:7f:f9:f9:89: ff:c2:51:b9:ca:1d:4c:d8:b8:5a:fd:76:f5:d3:c9: 95:9c:74:52:e9:8d:5f:2e:6b:ca:f8:6a:16:17:98: dc:aa:bf:34:d0:44:33:33:f3:9d:4b:7e:dd:7a:19: 1b:eb:3b:9e:21:d9:d9:ba:01:9c:8b:16:86:a3:52: a3:e6:e4:5c:f7:0c:ab:7a:1a:be:c6:42:d3:a6:01: 8e:0a:57:b2:cd:5b:28:ee:9d:f5:76:ca:75:7a:c1: 7c:42:d1:2a:7f:17:fe:69:17:49:91:4b:ca:2e:39: b4:a5:e0:03:bf:64:86:ca:15:c7:b2:f7:54:00:f7: 02:fe:cf:3e:12:6b:28:58:1c:35:68:86:3f:63:46: 75:f1:fe:ac:1b:41:91:4f:f2:24:99:54:f2:ed:5b: fd:01:98:65:ac:7a:7a:57:2f:a8:a5:5a:85:72:a6: 9e:fb:44:fb:3b:1c:79:88:3f:60:85:dd:d1:5c:1c: db:62:8c:6a:f7:da:ab:2e:76:ac:af:6d:7d:b1:46: 69:c1:59:db:c6:fb:6f:e1:a3:21:0c:5f:2e:8e:a7: d5:73:87:3e:60:26:75:eb:6f:10:c2:64:1d:a6:19: f3:0b Exponent: 65537 (0x10001) Signature Algorithm: sha256WithRSAEncryption 5d:dd:b2:35:be:27:c2:41:4a:0d:c7:8c:c9:22:05:cd:eb:88: 9d:71:4f:28:c1:79:71:3c:6d:30:19:f4:9c:3d:48:3a:84:d0: 19:00:b1:ec:a9:11:02:c9:a6:9c:74:e7:4e:3c:3a:9f:23:30: 50:5a:d2:47:53:65:06:a7:22:0b:59:71:b0:47:61:62:89:3d: cf:c6:d8:b3:d9:cc:70:20:35:bf:5a:2d:14:51:79:4b:7c:00: 30:39:2d:1d:af:2c:f3:32:fe:c2:c6:a5:b8:93:44:fa:7f:08: 85:f0:01:31:29:00:d4:be:75:7e:0d:f9:1a:f5:e9:75:00:9a: 7b:d0:eb:80:b1:01:00:c0:66:f8:c9:f0:35:6e:13:80:70:08: 5b:95:53:4b:34:ec:48:e3:02:88:5c:cd:a0:6c:b4:bc:65:15: 4d:c8:41:9d:00:f5:e7:f2:d7:f5:67:4a:32:82:2a:04:ae:d7: 25:31:0f:34:e8:63:a5:93:f2:b5:5a:90:71:ed:77:2a:a6:15: eb:fc:c3:ac:ef:55:25:d1:a1:31:7a:2c:80:e3:42:c2:b3:7d: 5e:9a:fc:e4:73:a8:39:50:62:db:b1:85:aa:06:1f:42:27:25: 4b:24:cf:d0:40:ca:51:13:94:97:7f:65:3e:ed:d9:3a:67:08: 79:64:a1:ba -----BEGIN CERTIFICATE----- MIIDODCCAiACAQEwDQYJKoZIhvcNAQELBQAwWTELMAkGA1UEBhMCQ04xCzAJBgNV BAgMAkdEMREwDwYDVQQHDAhTaGVuemhlbjEWMBQGA1UECgwNTW9uZ29EQiBDaGlu YTESMBAGA1UEAwwJQ0EtU0lHTkVSMB4XDTE4MTExMTIyMDAwM1oXDTE5MTExMTIy MDAwM1owazELMAkGA1UEBhMCQ04xCzAJBgNVBAgMAkdEMREwDwYDVQQHDAhTaGVu emhlbjEWMBQGA1UECgwNTW9uZ29EQiBDaGluYTESMBAGA1UECwwJTXlTZXJ2ZXJz MRAwDgYDVQQDDAdzZXJ2ZXIxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC AQEA0xwpuj0pRDsrdWCVyIP8Mhr6KVxW87NmiH/5+Yn/wlG5yh1M2Lha/Xb108mV nHRS6Y1fLmvK+GoWF5jcqr800EQzM/OdS37dehkb6zueIdnZugGcixaGo1Kj5uRc 9wyrehq+xkLTpgGOCleyzVso7p31dsp1esF8QtEqfxf+aRdJkUvKLjm0peADv2SG yhXHsvdUAPcC/s8+EmsoWBw1aIY/Y0Z18f6sG0GRT/IkmVTy7Vv9AZhlrHp6Vy+o pVqFcqae+0T7Oxx5iD9ghd3RXBzbYoxq99qrLnasr219sUZpwVnbxvtv4aMhDF8u jqfVc4c+YCZ1628QwmQdphnzCwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBd3bI1 vifCQUoNx4zJIgXN64idcU8owXlxPG0wGfScPUg6hNAZALHsqRECyaacdOdOPDqf IzBQWtJHU2UGpyILWXGwR2FiiT3Pxtiz2cxwIDW/Wi0UUXlLfAAwOS0dryzzMv7C xqW4k0T6fwiF8AExKQDUvnV+Dfka9el1AJp70OuAsQEAwGb4yfA1bhOAcAhblVNL NOxI4wKIXM2gbLS8ZRVNyEGdAPXn8tf1Z0oygioErtclMQ806GOlk/K1WpBx7Xcq phXr/MOs71Ul0aExeiyA40LCs31emvzkc6g5UGLbsYWqBh9CJyVLJM/QQMpRE5SX f2U+7dk6Zwh5ZKG6 -----END CERTIFICATE-----
For use with x.509 authentication in MongoDB, member certificates must have the following properties:
A single CA must issue all x.509 certificates for the members of the cluster.
The Distinguished Name (DN
), found in the subject of the member
certificate, must specify a nonempty value for at least one of the
following attributes: Organization (O
), Organizational Unit (OU
), or Domain Component (DC
).
The O
, OU
, and DC
attributes must match those from the
certificates for the other cluster members.
The Common Name (CN
) or a
Subject Alternative Name (SAN
)
must match the hostname of the server used by the other members
of the cluster.
In this tutorial we will set up a root CA and an intermediate CA. Best practice recommends signing the server and client certificates with the intermediate CA.
Before we can generate signed certificates for the members of our replica set, we must first address the issue of a certificate authority. As mentioned previously, we can either generate and maintain an independent certificate authority or use certificates generated by a third-party TLS/SSL vendor. We will generate our own CA to use for the running example in this chapter. Note that you may access all the code examples in this chapter from the GitHub repository maintained for this book. The examples are drawn from a script you can use to deploy a secure replica set. You’ll see comments from this script throughout these examples.
To generate our CA, we will use OpenSSL. To follow along, please make sure you have access to OpenSSL on your local machine.
A root CA is at the top of the certificate chain. This is the ultimate source of trust. Ideally, a third-party CA should be used. However, in the case of an isolated network (typical in a large enterprise environment) or for testing purposes, you’ll need to use a local CA.
First, we’ll initialize some variables:
dn_prefix="/C=US/ST=NY/L=New York/O=MongoDB" ou_member="MyServers" ou_client="MyClients" mongodb_server_hosts=( "server1" "server2" "server3" ) mongodb_client_hosts=( "client1" "client2" ) mongodb_port=27017
Then, we’ll create a key pair and store it in the file root-ca.key:
# !!! In production you will want to password-protect the keys # openssl genrsa -aes256 -out root-ca.key 4096 openssl genrsa -out root-ca.key 4096
Next, we’ll create a configuration file to hold our OpenSSL settings that we will use to generate the certificates:
# For the CA policy [ policy_match ] countryName = match stateOrProvinceName = match organizationName = match organizationalUnitName = optional commonName = supplied emailAddress = optional [ req ] default_bits = 4096 default_keyfile = server-key.pem default_md = sha256 distinguished_name = req_dn req_extensions = v3_req x509_extensions = v3_ca # The extensions to add to the self-signed cert [ v3_req ] subjectKeyIdentifier = hash basicConstraints = CA:FALSE keyUsage = critical, digitalSignature, keyEncipherment nsComment = "OpenSSL Generated Certificate" extendedKeyUsage = serverAuth, clientAuth [ req_dn ] countryName = Country Name (2-letter code) countryName_default = US countryName_min = 2 countryName_max = 2 stateOrProvinceName = State or Province Name (full name) stateOrProvinceName_default = NY stateOrProvinceName_max = 64 localityName = Locality Name (eg, city) localityName_default = New York localityName_max = 64 organizationName = Organization Name (eg, company) organizationName_default = MongoDB organizationName_max = 64 organizationalUnitName = Organizational Unit Name (eg, section) organizationalUnitName_default = Education organizationalUnitName_max = 64 commonName = Common Name (eg, YOUR name) commonName_max = 64 [ v3_ca ] # Extensions for a typical CA subjectKeyIdentifier = hash basicConstraints = critical,CA:true authorityKeyIdentifier = keyid:always,issuer:always # Key usage: this is typical for a CA certificate. However, since it will # prevent it being used as a test self-signed certificate it is best # left out by default. keyUsage = critical,keyCertSign,cRLSign
Then, using the openssl req
command, we will create the root certificate. Since the root is the
very top of the authority chain, we’ll self-sign this certificate
using the private key we created in the previous step (stored in
root-ca.key). The
-x509
option tells the openssl req
command we want to self-sign the
certificate using the private key supplied to the
-key
option. The output is a file called root-ca.crt:
openssl req -new -x509 -days 1826 -key root-ca.key -out root-ca.crt -config openssl.cnf -subj "$dn_prefix/CN=ROOTCA"
If you take a look at the root-ca.crt file, you’ll find that it contains the public certificate for the root CA. You can verify the contents by taking a look at a human-readable version of the certificate produced by this command:
openssl x509 -noout -text -in root-ca.crt
The output from this command will resemble the following:
Certificate: Data: Version: 3 (0x2) Serial Number: 1e:83:0d:9d:43:75:7c:2b:d6:2a:dc:7e:a2:a2:25:af:5d:3b:89:43 Signature Algorithm: sha256WithRSAEncryption Issuer: C = US, ST = NY, L = New York, O = MongoDB, CN = ROOTCA Validity Not Before: Sep 11 21:17:24 2019 GMT Not After : Sep 10 21:17:24 2024 GMT Subject: C = US, ST = NY, L = New York, O = MongoDB, CN = ROOTCA Subject Public Key Info: Public Key Algorithm: rsaEncryption RSA Public-Key: (4096 bit) Modulus: 00:e3:de:05:ae:ba:c9:e0:3f:98:37:18:77:02:35: e7:f6:62:bc:c3:ae:38:81:8d:04:88:da:6c:e0:57: c2:90:86:05:56:7b:d2:74:23:54:f8:ca:02:45:0f: 38:e7:e2:0b:69:ea:f6:c8:13:8f:6c:2d:d6:c1:72: 64:17:83:4e:68:47:cf:de:37:ed:6e:38:b2:ab:3a: e4:45:a8:fa:08:90:a0:f3:0d:3a:14:d8:9a:8d:69: e7:cf:93:1a:71:53:4f:13:29:50:b0:2f:b6:b8:19: 2a:40:21:15:90:43:e7:d8:d8:f3:51:e5:95:58:87: 6c:45:9f:61:fc:b5:97:cf:5b:4e:4a:1f:72:c9:0c: e9:8c:4c:d1:ca:df:b3:a4:da:b4:10:83:81:01:b1: c8:09:22:76:c7:1e:96:c7:e6:56:27:8d:bc:fb:17: ed:d9:23:3f:df:9c:ef:03:20:cc:c3:c4:55:cc:9f: ad:d4:8d:81:95:c3:f1:87:f8:d4:5a:5e:e0:a8:41: 27:c8:0d:52:91:e4:2b:db:25:d6:b7:93:8d:82:33: 7a:a7:b8:e8:cd:a8:e2:94:3d:d6:16:e1:4e:13:63: 3f:77:08:10:cf:23:f6:15:7c:71:24:97:ef:1c:a2: 68:0f:82:e2:f7:24:b3:aa:70:1a:4a:b4:ca:4d:05: 92:5e:47:a2:3d:97:82:f6:d8:c8:04:a7:91:6c:a4: 7d:15:8e:a8:57:70:5d:50:1c:0b:36:ba:78:28:f2: da:5c:ed:4b:ea:60:8c:39:e6:a1:04:26:60:b3:e2: ee:4f:9b:f9:46:3c:7e:df:82:88:29:c2:76:3e:1a: a4:81:87:1f:ce:9e:41:68:de:6c:f3:89:df:ae:02: e7:12:ee:93:20:f1:d2:d6:3d:36:58:ee:71:bf:b3: c5:e7:5a:4b:a0:12:89:ed:f7:cc:ec:34:c7:b2:28: a8:1a:87:c6:8b:5e:d2:c8:25:71:ba:ff:d0:82:1b: 5e:50:a9:8a:c6:0c:ea:4b:17:a6:cc:13:0a:53:36: c6:9d:76:f2:95:cc:ac:b9:64:d5:72:fc:ab:ce:6b: 59:b1:3a:f2:49:2f:2c:09:d0:01:06:e4:f2:49:85: 79:82:e8:c8:bb:1a:ab:70:e3:49:97:9f:84:e0:96: c2:6d:41:ab:59:0c:2e:70:9a:2e:11:c8:83:69:4b: f1:19:97:87:c3:76:0e:bb:b0:2c:92:4a:07:03:6f: 57:bf:a9:ec:19:85:d6:3d:f8:de:03:7f:1b:9a:2f: 6c:02:72:28:b0:69:d5:f9:fb:3d:2e:31:8f:61:50: 59:a6:dd:43:4b:89:e9:68:4b:a6:0d:9b:00:0f:9a: 94:61:71 Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Subject Key Identifier: 8B:D6:F8:BD:B7:82:FC:13:BC:61:3F:8B:FA:84:24:3F:A2:14:C8:27 X509v3 Basic Constraints: critical CA:TRUE X509v3 Authority Key Identifier: keyid:8B:D6:F8:BD:B7:82:FC:13:BC:61:3F:8B:FA:84:24:3F:A2:14:C8:27 DirName:/C=US/ST=NY/L=New York/O=MongoDB/CN=ROOTCA serial:1E:83:0D:9D:43:75:7C:2B:D6:2A:DC:7E:A2:A2:25:AF:5D:3B:89:43 X509v3 Key Usage: critical Certificate Sign, CRL Sign Signature Algorithm: sha256WithRSAEncryption c2:cc:79:40:8b:7b:a1:87:3a:ec:4a:71:9d:ab:69:00:bb:6f: 56:0a:25:3b:8f:bd:ca:4d:4b:c5:27:28:3c:7c:e5:cf:84:ec: 2e:2f:0d:37:35:52:6d:f9:4b:07:fb:9b:da:ea:5b:31:0f:29: 1f:3c:89:6a:10:8e:ae:20:30:8f:a0:cf:f1:0f:41:99:6a:12: 5f:5c:ce:15:d5:f1:c9:0e:24:c4:81:70:df:ad:a0:e1:0a:cc: 52:d4:3e:44:0b:61:48:a9:26:3c:a3:3d:2a:c3:ca:4f:19:60: da:f7:7a:4a:09:9e:26:42:50:05:f8:74:13:4b:0c:78:f1:59: 39:1e:eb:2e:e1:e2:6c:cc:4d:96:95:79:c2:8b:58:41:e8:7a: e6:ad:37:e4:87:d7:ed:bb:7d:fa:47:dd:46:dd:e7:62:5f:e9: fe:17:4b:e3:7a:0e:a1:c5:80:78:39:b7:6c:a6:85:cf:ba:95: d2:8d:09:ab:2d:cb:be:77:9b:3c:22:12:ca:12:86:42:d8:c5: 3c:31:a0:ed:92:bc:7f:3f:91:2d:ec:db:01:bd:26:65:56:12: a3:56:ba:d8:d3:6e:f3:c3:13:84:98:2a:c7:b3:22:05:68:fa: 8e:48:6f:36:8e:3f:e5:4d:88:ef:15:26:4c:b1:d3:7e:25:84: 8c:bd:5b:d2:74:55:cb:b3:fa:45:3f:ee:ef:e6:80:e9:f7:7f: 25:a6:6e:f2:c4:22:f7:b8:40:29:02:f1:5e:ea:8e:df:80:e0: 60:f1:e5:3a:08:81:25:d5:cc:00:8f:5c:ac:a6:02:da:27:c0: cc:4e:d3:f3:14:60:c1:12:3b:21:b4:f7:29:9b:4c:34:39:3c: 2a:d1:4b:86:cc:c7:de:f3:f7:5e:8f:9d:47:2e:3d:fe:e3:49: 70:0e:1c:61:1c:45:a0:5b:d6:48:49:be:6d:f9:3c:49:26:d8: 8b:e6:a1:b2:61:10:fe:0c:e8:44:2c:33:cd:3c:1d:c2:de:c2: 06:98:7c:92:7b:c4:06:a5:1f:02:8a:03:53:ec:bd:b7:fc:31: f3:2a:c1:0e:6a:a5:a8:e4:ea:4d:cc:1d:07:a9:3f:f6:0e:35: 5d:99:31:35:b3:43:90:f3:1c:92:8e:99:15:13:2b:8f:f6:a6: 01:c9:18:05:15:2a:e3:d0:cc:45:66:d3:48:11:a2:b9:b1:20: 59:42:f7:88:15:9f:e0:0c:1d:13:ae:db:09:3d:bf:7a:9d:cf: b2:41:1e:7a:fa:6b:35:20:03:58:a1:6c:02:19:21:5f:25:fc: ba:2f:fc:79:d7:92:e7:37:77:14:10:d9:33:b6:e5:fb:7a:46: ab:d1:86:70:88:92:59:c3
Now that we’ve created our root CA, we will create an intermediate CA for signing member and client certificates. An intermediate CA is nothing more than a certificate signed using our root certificate. It is a best practice to use an intermediate CA to sign server (i.e., member) and client certificates. Typically, a CA will use different intermediate CAs for signing different categories of certificates. If the intermediate CA is compromised and the certificate needs to be revoked, only a portion of the trust tree is affected instead of all certificates signed by the CA, as would be the case if the root CA were used to sign all certificates.
# again, in production you would want to password protect your signing key: # openssl genrsa -aes256 -out signing-ca.key 4096 openssl genrsa -out signing-ca.key 4096 openssl req -new -key signing-ca.key -out signing-ca.csr -config openssl.cnf -subj "$dn_prefix/CN=CA-SIGNER" openssl x509 -req -days 730 -in signing-ca.csr -CA root-ca.crt -CAkey root-ca.key -set_serial 01 -out signing-ca.crt -extfile openssl.cnf -extensions v3_ca
Note that in the statements above we are using the
openssl req
command followed by the
openssl ca
command to sign our signing certificate
using our root certificate. The openssl req
command
creates a signing request and the openssl ca
command uses that request as input to create a signed intermediate
(signing) certificate.
As a last step in creating our signing CA, we will concatenate
our root certificate (containing our root public key) and signing
certificate (containing our signing public key) into a single pem
file. This file will be supplied to our mongod or client process later
as the value of the --tlsCAFile
option.
cat root-ca.crt > root-ca.pem cat signing-ca.crt >> root-ca.pem
With the root CA and signing CA set up, we are now ready to create the member and client certificates used for authentication in our MongoDB cluster.
Member certificates are typically referred to as x.509 server certificates. Use this type of certificate for mongod and mongos processes. Members of a MongoDB cluster use these certificates to verify membership in the cluster. Stated another way, one mongod authenticates itself with other members of a replica set using a server certificate.
To generate certificates for the members of our replica set, we
will use a for
loop to generate multiple
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 4096 openssl req -new -key ${host}.key -out ${host}.csr -config openssl.cnf -subj "$dn_prefix/OU=$ou_member/CN=${host}" openssl x509 -req -days 365 -in ${host}.csr -CA signing-ca.crt -CAkey signing-ca.key -CAcreateserial -out ${host}.crt -extfile openssl.cnf -extensions v3_req cat ${host}.crt > ${host}.pem cat ${host}.key >> ${host}.pem done
Three steps are involved with each certificate:
Use the openssl genrsa command to create a new key pair.
Use the openssl req command to generate a signing request for the key.
Use the openssl x509 command to sign and output a certificate using the signing CA.
Notice the variable $ou_member
. This signifies
the difference between server certificates and client certificates.
Server and client certificates must differ in the organization part of
the Distinguished Names. More specifically, they must differ in at least
one of the O, OU, or DC values.
Client certificates are used by the mongo shell, MongoDB
Compass, MongoDB utilities and tools and, of course, by applications
using a MongoDB driver. Generating client certificates follows
essentially the same process as for member certificates. The one
difference is our use of the variable $ou_client
.
This ensure that the combination of the O, OU, and DC values will be
different from those of the server certificates generated above.
# 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 4096 openssl req -new -key ${host}.key -out ${host}.csr -config openssl.cnf -subj "$dn_prefix/OU=$ou_client/CN=${host}" openssl x509 -req -days 365 -in ${host}.csr -CA signing-ca.crt -CAkey signing-ca.key -CAcreateserial -out ${host}.crt -extfile openssl.cnf -extensions v3_req cat ${host}.crt > ${host}.pem cat ${host}.key >> ${host}.pem done
We can start each member of our replica set without auth enabled as follows. Previously, when working with replica sets we’ve not enabled auth so this should look familiar. Here again we are making use of a few variables we defined in “Generate a root CA” (or see the full script for this chapter) and a loop to launch each member (mongod) of our replica set.
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
Once each mongod has started, we can then initialize a replica set using these mongods.
myhostname=`hostname` cat > init_set.js <<EOF rs.initiate(); mport=$mongodb_port; mport++; rs.add("localhost:" + mport); mport++; rs.add("localhost:" + mport); EOF mongo localhost:$mongodb_port init_set.js
Note that the code above simply constructs a series of commands,
stores these commands in a JavaScript file, and then runs the mongo shell to execute the small script that
was created. Together, these commands, when executed in the mongo shell, will connect to the mongod running on port 27017 (value of the
$mongodb_port
variable set in “Generate a root CA”), initiate the replica set, and then add
each of the other two mongods (on
ports 27018 and 27019) to the replica set.
Now, we’ll create an admin user based on one of the client certificates we created in “Generate and Sign Client Certificates”. We will authenticate as this user when connecting from the mongo shell or another client to perform administrative tasks. To authenticate with a client certificate, you must first add the value of the subject from the client certificate as a MongoDB user. Each unique x.509 client certificate corresponds to a single MongoDB user; i.e., you cannot use a single client certificate to authenticate more than one MongoDB user. We must add the user in the $external database; i.e., the authentication database is the $external database.
First, we’ll get the subject from our client certificate using the openssl x509 command.
openssl x509 -in client1.pem -inform PEM -subject -nameopt RFC2253 | grep subject
This should result in the following output:
subject= CN=client1,OU=MyClients,O=MongoDB,L=New York,ST=NY,C=US
To create our admin user, we’ll first connect to the primary of our replica set using the mongo shell.
mongo --norc localhost:27017
From within the mongo shell, we will issue the following command:
db.getSiblingDB("$external").runCommand( { createUser: "CN=client1,OU=MyClients,O=MongoDB,L=New York,ST=NY,C=US", roles: [ { role: "readWrite", db: 'test' }, { role: "userAdminAnyDatabase", db: "admin" }, { role: "clusterAdmin", db:"admin"} ], writeConcern: { w: "majority" , wtimeout: 5000 } } );
Note the use of the $external database in this command and the fact that we’ve specified the subject of our client certificate as the user name.
Now that we have an admin user, we can restart the replica set with authentication and authorization enabled and connect as a client. Without a user of any kind, it would be impossible to connect to a replica set with auth enabled.
Let’s stop the replica set in it’s current form (without auth enabled).
kill $(ps -ef | grep mongod | grep set509 | awk '{print $2}')
We are now ready to restart the replica set with auth enabled. In a production environment, we would copy each of the certificate and key files to their corresponding hosts. Here we’re doing everything on localhost to make things easier. To initiate a secure replica set we will add the following command-line options to each invocation of mongod:
--tlsMode
--clusterAuthMode
--tlsCAFile
—root CA file
(root-ca.key)
--tlsCertificateKeyFile
—certificate file
for the mongod
--tlsAllowInvalidHostnames
—only used for
testing; allows invalid hostnames
Here the file we provide as the value of the tlsCAFile
option is used to establish a trust
chain. As you recall the root-ca.key file contains
the certificate of the root CA as well as the signing CA. By providing
this file to the mongod process, we are stating our
desire to trust the certificate contained in this file as well as all
other certificates signed by these certificates.
Okay, let’s do this.
mport=$mongodb_port for host in "${mongodb_server_hosts[@]}"; do echo "Starting server $host" mongod --replSet set509 --port $mport --dbpath ./db/$host --tlsMode requireTLS --clusterAuthMode x509 --tlsCAFile root-ca.pem --tlsAllowInvalidHostnames --fork --logpath ./db/${host}.log --tlsCertificateKeyFile ${host}.pem --tlsClusterFile ${host}.pem --bind_ip 127.0.0.1 let "mport++" done
And with that, we have a three-member replica set secured using x.509 certificates for authentication and transport-layer encryption. The only thing left to do is to connect with the mongo shell. We’ll use the client1 certificate to authenticate, because that is the certificate for which we created an admin user.
mongo --norc --tls --tlsCertificateKeyFile client1.pem --tlsCAFile root-ca.pem --tlsAllowInvalidHostnames --authenticationDatabase "$external" --authenticationMechanism MONGODB-X509
Once connected, we encourage you to experiment by inserting some
data to a collection. You should also attempt to connect using any other
user (e.g., using the client2.pem
). Connections
attempts will result in errors like the following.
mongo --norc --tls --tlsCertificateKeyFile client2.pem --tlsCAFile root-ca.pem --tlsAllowInvalidHostnames --authenticationDatabase "$external" --authenticationMechanism MONGODB-X509 MongoDB shell version v4.2.0 2019-09-11T23:18:31.696+0100 W NETWORK [js] The server certificate does not match the host name. Hostname: 127.0.0.1 does not match 2019-09-11T23:18:31.702+0100 E QUERY [js] Error: Could not find user "CN=client2,OU=MyClients,O=MongoDB,L=New York,ST=NY,C=US" for db "$external" : connect@src/mongo/shell/mongo.js:341:17 @(connect):3:6 2019-09-11T23:18:31.707+0100 F - [main] exception: connect failed 2019-09-11T23:18:31.707+0100 E - [main] exiting with code 1
In the tutorial in this chapter, we’ve looked at an example of using x.509 certificates as a basis for authentication and to encrypt communication among clients and members of a replica set. The same procedure works for sharded clusters as well. With respect to securing a MongoDB cluster, please keep the following in mind:
The directories, root CA and signing CA, as well as the host itself where you generate and sign certificates for the member machines or clients, should be protected from unauthorized access.
For simplicity, the root CA and signing CA keys are not password protected in this tutorial. In production it is necessary to use passwords to protect the key from unauthorized use.
We encourage you to download and experiment with the demo scripts we have provided for this chapter in the book’s GitHub repository.
18.117.227.194