MongoDB supports multiple authentication mechanisms, including the default one SCRAM, LDAP, Kerberos, and x.509 Certificate Authentication.
In the X.509 system, which will be the main point of this blog post, an organization can identify its entities using a pair of certificates and private keys signed and trusted by some Certificate Authority (CA).
This model is well-known in the industry, and it’s quite popular for delegating authentication to 3rd party services. The other, similar way of delegating authentication is LDAP, which is also part of the same X.500 standard group called directory services.
While authentication works fine for MongoDB – and in general for every database system – the problem with authorization remains. A MongoDB cluster still needs to obtain information about resources to which a given user has access. Roles help a lot here, but in a zero-trust model, the practice requires having information duplicated: authentication data is stored in LDAP, X.509, or any other system, and authorization models residing in MongoDB.
And here, a cool feature of X.509 and… an undocumented feature of MongoDB comes into play.
An X.509 certificate is de facto a serialized object. It uses Abstract Syntax Notation One (ASN.1), which is a standard interface description language for defining data structures that can be serialized and deserialized in a cross-platform way. It is broadly used in cryptography.
A sample deserialized certificate looks like below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | # openssl asn1parse -in client.crt 0:d=0 hl=4 l= 957 cons: SEQUENCE 4:d=1 hl=4 l= 677 cons: SEQUENCE 8:d=2 hl=2 l= 3 cons: cont [ 0 ] 10:d=3 hl=2 l= 1 prim: INTEGER :02 13:d=2 hl=2 l= 20 prim: INTEGER :22BC2E27B24B5A47123C6CB2FA0904B4F3663322 35:d=2 hl=2 l= 13 cons: SEQUENCE 37:d=3 hl=2 l= 9 prim: OBJECT :sha256WithRSAEncryption 48:d=3 hl=2 l= 0 prim: NULL 50:d=2 hl=2 l= 82 cons: SEQUENCE 52:d=3 hl=2 l= 11 cons: SET 54:d=4 hl=2 l= 9 cons: SEQUENCE 56:d=5 hl=2 l= 3 prim: OBJECT :countryName 61:d=5 hl=2 l= 2 prim: PRINTABLESTRING :AU … |
The main advantage of the above is that almost any piece of information can be stored inside a certificate. As the Certificate Authority needs to sign it, a user that’s presenting the certificate can’t modify it. That makes it secure. It also allows storing authorization information inside it.
How to Use It?
1) Let’s Start with Preparing the Certificate Authority
1 2 | # openssl req -x509 -new -newkey rsa:2048 -nodes -keyout myCA.key -sha256 -days 1825 -out myCA.pem … |
2) Let’s Create a Server Certificate
OpenSSL Configuration:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | [ req ] default_bits = 2048 default_keyfile = server.key encrypt_key = no default_md = sha256 prompt = no utf8 = yes distinguished_name = server_req_distinguished_name req_extensions = server_extensions [ server_req_distinguished_name ] C = AU ST = Some-State O = Percona OU = NA CN = server [ server_extensions ] basicConstraints=CA:FALSE subjectKeyIdentifier = hash keyUsage = keyEncipherment, digitalSignature extendedKeyUsage = serverAuth, clientAuth # openssl req -config server.cnf -new -newkey rsa:2048 -out server.csr # openssl x509 -req -in server.csr -CA myCA.pem -CAkey myCA.key -CAcreateserial -out server.crt -days 825 -sha256 -extensions server_extensions -extfile server.cnf # cat server.crt server.key > server.pem |
3) Let’s Create a Client Certificate
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | [ req ] default_bits = 2048 default_keyfile = client.key encrypt_key = no default_md = sha256 prompt = no utf8 = yes distinguished_name = client_req_distinguished_name req_extensions = client_extensions x509_extensions = client_extensions [ client_req_distinguished_name ] C = AU ST = Some-State L = X O = Percona OU = Clients CN = client [ client_extensions ] basicConstraints=CA:FALSE subjectKeyIdentifier = hash keyUsage = digitalSignature extendedKeyUsage = clientAuth 1.3.6.1.4.1.34601.2.1.1= ASN1:SET:grants [ grants ] grant.1 = SEQUENCE:MongoDBRole grant.2 = SEQUENCE:MongoDBRole2 [ MongoDBRole ] role = UTF8:backup database = UTF8:admin [ MongoDBRole2 ] role = UTF8:readAnyDatabase database = UTF8:admin |
The above configuration will use MongoDBAuthorizationGrant OID 1.3.6.1.4.1.34601.2.1.1 and will grant the user that’s presenting the certificate two roles: backup for database admin and readAnyDatabase for database admin.
It’s important to remember that a client x.509 certificate’s subject, which contains the Distinguished Name (DN), must differ from that of a Member x.509 Certificate. Also, at least one of the Organization (O), Organizational Unit (OU), or Domain Component (DC) attributes in the client certificate must differ from those in the net.tls.clusterFile
and net.tls.certificateKeyFile
server certificates.
Moreover, a client certificate must contain the following fields:
keyUsage = digitalSignature
extendedKeyUsage = clientAuth
If the above requirements are not met, MongoDB will refuse any client x509 authentication with an error:
1 | AuthenticationFailed: The provided certificate can only be used for cluster authentication, not client authentication. The current configuration does not allow x.509 cluster authentication, check the --clusterAuthMode flag |
Let’s continue generating the request and signing the certificate:
1 2 3 | # openssl req -config client.cnf -new -newkey rsa:2048 -out client.csr # openssl x509 -req -in client.csr -CA myCA.pem -CAkey myCA.key -CAcreateserial -out client.crt -days 825 -sha256 -extensions client_extensions -extfile client.cnf # cat client.crt client.key > client.pem |
4) Let’s Try it Out
At this stage MongoDB should allow authentication and authorization using the previously generated certificates:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | $ mongo --tls --tlsCertificateKeyFile client.pem --tlsCAFile ./myCA.pem --authenticationDatabase '$external' --authenticationMechanism MONGODB-X509 server/ > db.runCommand({ connectionStatus: 1, showPrivileges: false }); { "authInfo" : { "authenticatedUsers" : [ { "user" : "CN=client,OU=Clients,O=Percona,L=X,ST=Some-State,C=AU", "db" : "$external" } ], "authenticatedUserRoles" : [ { "role" : "backup", "db" : "admin" }, { "role" : "readAnyDatabase", "db" : "admin" } ] }, "ok" : 1 } |
It works without adding a single user!
Conclusion
- MongoDB allows embedding grants in an x.509 certificate file.
- It’s useful especially in cloud environments because it moves the authorization layer out of MongoDB.
- The certificates can be reused for different clusters.
- It forces using PKI, which is much more secure than just passwords.
Percona Distribution for MongoDB is a freely available MongoDB database alternative, giving you a single solution that combines the best and most important enterprise components from the open source community, designed and tested to work together.
Download Percona Distribution for MongoDB Today!