Using Vault to Store the Master Key for Data at Rest Encryption on Percona Server for MongoDB

Percona Server MongoDB Encryption

Percona Server MongoDB EncryptionSince the release of Percona Server MongoDB 3.6.13 (PSMDB), you have been able to use Vault to store the encryption keys for data at rest encryption. Here’s how to set it up.

First, you need to have a Vault server up and running. My colleague, Jericho, has an article on setting up Vault for Percona Server titled Using the keyring_vault Plugin with Percona Server for MySQL 5.7. In this post, I will provide the same instructions for installing and setting up Hashicorp Vault for testing (Thank you Jericho!).

Vault Installation(run as root or use sudo):

1. Download, extract and install Vault:

# wget https://releases.hashicorp.com/vault/1.3.2/vault_1.3.2_linux_amd64.zip
# unzip vault_1.3.2_linux_amd64.zip 
# mv vault /usr/sbin

Make sure to place it somewhere on your

PATH

2. Place initial vault configuration in /etc/vault

# mkdir /etc/vault
# cd /etc/vault
# cat vault.hcl
listener "tcp" {
 address = "192.168.0.114:8200"
 tls_cert_file="/etc/vault/vault.crt"
 tls_key_file="/etc/vault/vault.key"
}
storage "file" {
  path = "/var/lib/vault"
}
disable_mlock=true

You can use a port different than 8200.

disable-mlock=true
  is needed if you want to start the Vault server as a non-root user.

3. Generate SSL certificates. To be able to create SSL certificates without going through prompts, we will place those entries in a configuration file:

# cd /etc/vault
# cat ssl.conf
[req]
distinguished_name = req_distinguished_name
x509_extensions = v3_req
prompt = no

[req_distinguished_name]
C = US
ST = NC
L =  R
O = Percona
CN = *

[v3_req]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
basicConstraints = CA:TRUE
subjectAltName = @alt_names

[alt_names]
IP = 192.168.0.114

4. Run the commands below to generate the certificates and store them in /etc/vault.

# cd /etc/vault
# openssl req -config ssl.conf -x509 -days 3650 -batch -nodes -newkey rsa:2048 -keyout vault.key -out vault.crt
# cat vault.key vault.crt > vault.pem

5. Ensure that the keys are only accessible by the owner.

# chmod 400 vault.key
# chmod 400 vault.pem
# chmod 400 vault.crt

6. Set environment variables needed to access the vault.

# export VAULT_CACERT=/etc/vault/vault.crt
# export VAULT_ADDR='https://192.168.0.114:8200'

7. Start Vault in the background.

# vault server -config=/etc/vault/vault.hcl >> /var/log/vault.log 2>&1 &
[1] 7336

8. Initialize Vault and store the unseal keys and the initial root token generated in this step:

# vault operator init
Unseal Key 1: Gdu0HtSSctKUvn0ssi+GWhKEDZBWMAmulKfiAroCt+iw
Unseal Key 2: UEf61dpqPvjD+ftFaCJTy+KzKlSTIsT75qwe4gVLR9w4
Unseal Key 3: am/vq6P/SNs7aIKiLr7gEkeV8Pn/ilkV5r+HbwTpn7Vk
Unseal Key 4: aZYbiu2C3zrDklyWxf0JaqAouGRM95g0a9vz1JIk6jHD
Unseal Key 5: IApVQeN6kxCU1dr/H/Dc+1Afn6CJ5VVhGdAPNC1tGgVL

Initial Root Token: s.AqVdpn2C4NVJgtkZkRrKeFtm

Vault initialized with 5 key shares and a key threshold of 3. Please securely
distribute the key shares printed above. When the Vault is re-sealed,
restarted, or stopped, you must supply at least 3 of these keys to unseal it
before it can start servicing requests.

Vault does not store the generated master key. Without at least 3 key to
reconstruct the master key, Vault will remain permanently sealed!

It is possible to generate new unseal keys, provided you have a quorum of
existing unseal keys shares. See "vault operator rekey" for more information.

9. Unseal the Vault by supplying the three unseal keys generated above. You need to unseal Vault every time the Vault server is started.

# vault operator unseal Gdu0HtSSctKUvn0ssi+GWhKEDZBWMAmulKfiAroCt+iw
Key                Value
---                -----
Seal Type          shamir
Initialized        true
Sealed             true
Total Shares       5
Threshold          3
Unseal Progress    1/3
Unseal Nonce       6be37706-61cb-b372-cc88-b0e89e651aff
Version            1.3.2
HA Enabled         false

# vault operator unseal UEf61dpqPvjD+ftFaCJTy+KzKlSTIsT75qwe4gVLR9w4
Key                Value
---                -----
Seal Type          shamir
Initialized        true
Sealed             true
Total Shares       5
Threshold          3
Unseal Progress    2/3
Unseal Nonce       6be37706-61cb-b372-cc88-b0e89e651aff
Version            1.3.2
HA Enabled         false

# vault operator unseal am/vq6P/SNs7aIKiLr7gEkeV8Pn/ilkV5r+HbwTpn7Vk
Key             Value
---             -----
Seal Type       shamir
Initialized     true
Sealed          false
Total Shares    5
Threshold       3
Version         1.3.2
Cluster Name    vault-cluster-752be70f
Cluster ID      32ebb89d-2583-23b6-513d-c44220e12ac0
HA Enabled      false

10. Now it’s time to log in to Vault and use the initial root token.

# vault login s.AqVdpn2C4NVJgtkZkRrKeFtm
Success! You are now authenticated. The token information displayed below
is already stored in the token helper. You do NOT need to run "vault login"
again. Future Vault requests will automatically use this token.

Key                  Value
---                  -----
token                s.AqVdpn2C4NVJgtkZkRrKeFtm
token_accessor       EZo7M0JbZjicINWMLee573qL
token_duration       ∞
token_renewable      false
token_policies       ["root"]
identity_policies    []
policies             ["root"]

11. At this point, on Vault, you will need to enable the KV Secrets Engine – Version 2, the only engine supported by PSMDB. To do so, you will need to run the following:

# vault secrets enable -path secret kv-v2
Success! Enabled the kv-v2 secrets engine at: secret/

12. Next, you will need to create a Vault Policy to access this datastore. As per https://www.vaultproject.io/docs/secrets/kv/kv-v2/, reading and writing versions need to be prefixed with the path data/.

# cat mongodb.hcl 
path "secret/data/dc/*" {
  capabilities = ["create","read","update","delete"]
}

13. To upload the policy to the Vault server, run:

# vault policy write mongodb-policy mongodb.hcl
Success! Uploaded policy: mongodb-policy

14. Finally, you will need to create an access token to be used by MongoDB. Remember to create a token per MongoDB instance.

# vault token create -policy=mongodb-policy
Key                  Value
---                  -----
token                s.cFy5NxA72Wk7VhVH45VJ4Rib
token_accessor       yoVBWgUVtDpzIomIRGTvBRd6
token_duration       768h
token_renewable      true
token_policies       ["default" "mongodb-policy"]
identity_policies    []
policies             ["default" "mongodb-policy"]

You will need to use the token above to connect MongoDB to Vault.

15. Done.

Incorporating Vault in MongoDB

In MongoDB, you’ll need to create a token based on the generated token and CA file generated in the Vault. Let’s place them in /etc/mongodb:

1. Create a mongodb config directory that will store both token and CA file.

# mkdir -p /etc/mongodb
# chown mongod:mongod /etc/mongodb

2. Place the token value in the token file and copy the contents of vault.crt from the Vault server.

# cd /etc/mongodb
# cat token 
s.cFy5NxA72Wk7VhVH45VJ4Rib
# cat vault.crt 
-----BEGIN CERTIFICATE-----
MIIDbDCCAlSgAwIBAgIJAOsptGVHJfKYMA0GCSqGSIb3DQEBCwUAMEQxCzAJBgNV
BAYTAlVTMQswCQYDVQQIDAJOQzEKMAgGA1UEBwwBUjEQMA4GA1UECgwHUGVyY29u
YTEKMAgGA1UEAwwBKjAeFw0yMDAzMzAxODE4NDlaFw0zMDAzMjgxODE4NDlaMEQx
CzAJBgNVBAYTAlVTMQswCQYDVQQIDAJOQzEKMAgGA1UEBwwBUjEQMA4GA1UECgwH
UGVyY29uYTEKMAgGA1UEAwwBKjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBAKSRMI4/rbiWitDZtnDzlCVi0BDalmn4ieG93mt1Vn0IfN4NOmQeZ5fVRBve
Y26ugAT7Nq92puvV6vLk3ky8K8dkNPLe4wPEMPWnrHJIS4zqcmJWbw618yymVufm
h59cI/jyYCPMyLXkcZLuOnSZszoKnN/jEZq6WKNxgFuc6evzfxN4AbM7ffRk2mj5
FfMbtpToBgJS/U9UMS7EvzXYslUyq4bgBx3ygcmUyas7Ej7aie2fb0tzW/dMJ6/F
M2kYnHTM+pxr+Eq5QndBcjgptv/iqdnmIbNHogY+4TOCQwyke83SphgIwtI9MCLN
xUDN5nZEeRY8RINk3+irL/Kq0ocCAwEAAaNhMF8wHQYDVR0OBBYEFFS+R00NplQg
c6XFyYNLmrMJFa4lMB8GA1UdIwQYMBaAFFS+R00NplQgc6XFyYNLmrMJFa4lMAwG
A1UdEwQFMAMBAf8wDwYDVR0RBAgwBocEwKgAcjANBgkqhkiG9w0BAQsFAAOCAQEA
h9dKQoTILcv43mVbowCENm0ujPCW74CM/VXc+k8s3A5+h2tHtrO6NYATQBA07L+k
dwuethgqaqzbaEJqF766UiKv00hCkY8fMSyptLRtYG3WtxDWN/kGGvSjkzbYYqDP
RK1X5eI7PT7rtyu+FMWEiFLgQE6e5pz8MOITvdtQviJ35jUD1FfCpzTWppqeCc0g
9dVhzej1KDYTpoTcUkGLYDF1AMwEdXXqI77zdbhzQfmSlt1hAJGBKJFvAGTM2XHb
vd0QhgGbfzryv1b+eDTL7wWsYckNEcADpizHinxBpKC9/cnJZZtt6D35NvVAMkjy
+8wuOopSb6PCjrxVAz75FA==
-----END CERTIFICATE-----

3. Ensure the files are readable by mongod only, as MongoDB will complain and fail during startup with lax permissions.

# chown mongod:mongod /etc/mongodb/token
# chown mongod:mongod /etc/mongodb/vault.crt
# chmod 400 /etc/mongodb/token
# chmod 400 /etc/mongodb/vault.crt

4. Place in /etc/mongod.conf the Vault configuration under the security section. As a sample naming convention, given that the hostname of this server is psmongodb1, the secret path will be secret/data/dc/psmongodb1 as well:

# cat /etc/mongod.conf
# mongod.conf, Percona Server for MongoDB
# for documentation of all options, see:
#   http://docs.mongodb.org/manual/reference/configuration-options/

*** redacted ***
processManagement:
  fork: true
  pidFilePath: /var/run/mongod.pid

# network interfaces
net:
  port: 27017
  bindIp: 127.0.0.1

#security:
security:
  enableEncryption: true
  vault:
    serverName: 192.168.0.114
    port: 8200
    secret: secret/data/dc/psmongodb1
    tokenFile: /etc/mongodb/token
    serverCAFile: /etc/mongodb/vault.crt

*** redacted ***

5. Then start MongoDB:

# systemctl start mongod

You can only enable encryption on an empty database. It’s not possible to re-encrypt an existing, previously unencrypted database. One way to do it is to create a backup, stop mongod, delete data files, enable encryption, start mongod, and restore the database from backup. If you have a replica set, you can apply changes in a rolling manner (one member at the time). Data will be synced automatically from other nodes. Each node has to be encrypted separately.

6. Check the logs if using the key was successful:

2020-03-30T19:05:48.929+0000 I ACCESS   [main] Initialized External Auth Session
2020-03-30T19:05:48.933+0000 I CONTROL  [initandlisten] MongoDB starting : pid=8026 port=27017 dbpath=/var/lib/mongo 64-bit host=psmongodb1.example.com
2020-03-30T19:05:48.933+0000 I CONTROL  [initandlisten] db version v3.6.17-4.0
2020-03-30T19:05:48.933+0000 I CONTROL  [initandlisten] git version: 96e9c7218eebc2995dd847d6185f20f102a86055
2020-03-30T19:05:48.933+0000 I CONTROL  [initandlisten] OpenSSL version: OpenSSL 1.0.2k-fips  26 Jan 2017
2020-03-30T19:05:48.933+0000 I CONTROL  [initandlisten] allocator: tcmalloc
2020-03-30T19:05:48.933+0000 I CONTROL  [initandlisten] modules: none
2020-03-30T19:05:48.933+0000 I CONTROL  [initandlisten] build environment:
2020-03-30T19:05:48.933+0000 I CONTROL  [initandlisten]     distarch: x86_64
2020-03-30T19:05:48.933+0000 I CONTROL  [initandlisten]     target_arch: x86_64
2020-03-30T19:05:48.933+0000 I CONTROL  [initandlisten] options: { config: "/etc/mongod.conf", net: { bindIp: "127.0.0.1", port: 27017 }, processManagement: { fork: true, pidFilePath: "/var/run/mongod.pid" }, security: { enableEncryption: true, vault: { disableTLSForTesting: false, port: 8200, secret: "secret/data/dc/psmongodb1", serverCAFile: "/etc/mongodb/vault.crt", serverName: "192.168.0.114", tokenFile: "/etc/mongodb/token" } }, storage: { dbPath: "/var/lib/mongo", journal: { enabled: true } }, systemLog: { destination: "file", logAppend: true, path: "/var/log/mongo/mongod.log" } }
2020-03-30T19:05:49.024+0000 I STORAGE  [initandlisten] Master key is absent in the Vault. Generating and writing one.
2020-03-30T19:05:49.076+0000 I STORAGE  [initandlisten] Initializing KeyDB with wiredtiger_open config: create,config_base=false,extensions=[local=(entry=percona_encryption_extension_init,early_load=true,config=(cipher=AES256-CBC,rotation=false))],encryption=(name=percona,keyid=""),log=(enabled,file_max=5MB),transaction_sync=(enabled=true,method=fsync),
2020-03-30T19:05:49.556+0000 I STORAGE  [initandlisten] Encryption keys DB is initialized successfully
2020-03-30T19:05:49.556+0000 I STORAGE  [initandlisten] wiredtiger_open config: create,cache_size=256M,cache_overflow=(file_max=0M),session_max=20000,eviction=(threads_min=4,threads_max=4),config_base=false,statistics=(fast),compatibility=(release="3.0",require_max="3.0"),log=(enabled=true,archive=true,path=journal,compressor=snappy),file_manager=(close_idle_time=100000),statistics_log=(wait=0),verbose=(recovery_progress),encryption=(name=percona,keyid="/default"),extensions=[local=(entry=percona_encryption_extension_init,early_load=true,config=(cipher=AES256-CBC)),local=(entry=percona_encryption_extension_init,early_load=true,config=(cipher=AES256-CBC)),],

As per the logs, it was able to generate a master key and use the Percona encryption. At this point, data at rest encryption has been configured on this server.

7. Done.

Take note that if the Vault server is down, you will not be able to start up your server. Given this new dependency on obtaining the master key from Vault, be sure to design your architecture for fault tolerance.

Encryption Key Rotation

Each encryption key can be used only for a limited time. Rotating master key re-encrypts the keystore using a new master key. The newly generated master key is then stored in the Vault. The entire dataset isn’t re-encrypted.

1. Place in /etc/mongod.conf an extra line in the security section:

# cat /etc/mongod.conf
security:
  enableEncryption: true
  vault:
    serverName: 192.168.0.114
    port: 8200
    secret: secret/data/dc/psmongodb1
    tokenFile: /etc/mongodb/token
    serverCAFile: /etc/mongodb/vault.crt
    rotateMasterKey: true

2. Then restart MongoDB:

# systemctl restart mongod

You’ll get an error message (but that’s expected, as mongod process didn’t start successfully). You can check logs if the encryption key was successfully rotated:

# tail /var/log/mongo/mongod.log
2020-03-30T19:05:48.929+0000 I STORAGE [initandlisten] Encryption keys DB is initialized successfully
2020-03-30T19:05:48.933+0000 I STORAGE [initandlisten] exception in initAndListend std::exception: master key rotation finished successfully, terminating
2020-03-30T19:05:48.933+0000 I NETWORK [initandlisten] shutdown: going to close listening sockets...
2020-03-30T19:05:48.933+0000 I -       [initandlisten] Stopping further Flow Control ticket acquisitions.
2020-03-30T19:05:48.933+0000 I CONTROL [initandlisten] now exiting
2020-03-30T19:05:48.933+0000 I CONTROL [initandlisten] shutting down with code:100

3. Remove the extra line in /etc/mongod.conf configuration

rotateMasterKey: true

4. Done. You can start mongod again, your key was rotated.

Please be advised that this is a simple guideline on how to set up a basic Vault server and should not be used as a template for production usage. We recommend using Percona Consulting Services to assist you in that matter.


by Jaime Sicam via Percona Database Performance Blog

Comments