Encrypt data in KubeDB MongoDB with Hashicorp Vault KMIP Secret Engine

MongoDB uses data encryption at rest to protect sensitive data from unauthorized access and meet regulatory compliance. Encryption safeguards data at rest and in transit, reducing the risk of breaches.

KMIP is chosen for its standardized approach to encryption key management, allowing secure generation, storage, and rotation of keys across various platforms. It ensures interoperability and strengthens overall data security.

HashiCorp Vault KMIP secret engine is a powerful solution for managing encryption keys. It offers automated key rotation, fine-grained access controls, and audit logging, making it a scalable and secure choice for MongoDB’s encryption needs.

To demonstrate how to configure KubeDB MongoDB with HashiCorp Vault KMIP secret engine for encryption, you can follow this step-by-step example. This documentation will guide you through setting up Vault, configuring the KMIP secret engine, and then configuring KubeDB to use it for MongoDB data encryption.

Before You Begin

  • At first, you need to have a Kubernetes cluster, and the kubectl command-line tool must be configured to communicate with your cluster. If you do not already have a cluster, you can create one by using kind.

  • Install KubeDB in your cluster following the steps here.

  • Install HashiCorp Vault CLI. You can follow this procedure here

  • To keep things isolated, this tutorial uses a separate namespace called demo throughout this tutorial. Run the following command to prepare your cluster for this tutorial:

$ kubectl create ns demo
namespace/demo created

Setup Hashicorp Vault KMIP secret engine

User can setup Vault KMIP secret engine with Vault Enterprise or HCP Vault Dedicated. For this demo we will use Hashicorp Cloud Provider(HCP) Vault Dedicated.

So First we created a Vault Plus cluster in HCP. Then we need to configure Vault KMIP according to this documentation step by step.

# setup vault environment
$ export VAULT_ADDR=<Public_Cluster_URL>
$ export VAULT_TOKEN=<Generated_Vault_Token>
$ export VAULT_NAMESPACE=admin

# configure kmip secret engine
$ vault secrets enable kmip
Success! Enabled the kmip secrets engine at: kmip/

$ vault write kmip/config \
     listen_addrs=0.0.0.0:5696 \
     server_hostnames=$(echo ${VAULT_ADDR:8} | rev | cut -c6- | rev)
Success! Data written to: kmip/config

# create scope
$ vault write -f kmip/scope/finance
Success! Data written to: kmip/scope/finance

# create role
$ vault write kmip/scope/finance/role/accounting operation_all=true
Success! Data written to: kmip/scope/finance/role/accounting

# store vault-ca.pem
$ vault read kmip/ca -format=json | jq -r '.data | .ca_pem' >> vault-ca.pem

# generate and store client.pem
$ vault write -format=json \
    kmip/scope/finance/role/accounting/credential/generate \
    format=pem > credential.json

$ jq -r .data.certificate < credential.json > cert.pem

$ jq -r .data.private_key < credential.json > key.pem

$ cat cert.pem key.pem > client.pem

We will use this client.pem and vault-ca.pem files to configure KMIP in MongoDB.

Create MongoDB configuration with KMIP

Now we need to make a mongod.conf file to use it as configuration folder for our MongoDB.

$ cat mongod.conf
security:
  enableEncryption: true
  kmip:
    serverName: vault-cluster-doc-public-vault-a33bb761.37131dd1.z1.hashicorp.cloud
    port: 5696
    clientCertificateFile: /etc/certs/client.pem
    serverCAFile: /etc/certs/ca.pem

Here,

  • serverName is the public address of our HCP Vault Plus cluster without port
  • port is listen address of KMIP secret engine
  • clientCertificateFile is path to the client pem file to make connection
  • serverCAFile is path to the ca pem file to verify server.

To set up configuration in mongod.conf file for KMIP encryption, you can look into MongoDB official documentation.

Here /etc/certs/client.pem and /etc/certs/ca.pem will be mounted by secret in KubeDB MongoDB main mongodb container.

Now, create the secret with this configuration file.

$ kubectl create secret generic -n demo mg-configuration --from-file=./mongod.conf
secret/mg-configuration created

Verify the secret has the configuration file.

$ kubectl get secret -n demo mg-configuration -o yaml
apiVersion: v1
data:
  mongod.conf: c2VjdXJpdHk6CiAgZW5hYmxlRW5jcnlwdGlvbjogdHJ1ZQogIGttaXA6CiAgICBzZXJ2ZXJOYW1lOiB2YXVsdC1jbHVzdGVyLWRvYy1wdWJsaWMtdmF1bHQtYTMzYmI3NjEuMzcxMzFkZDEuejEuaGFzaGljb3JwLmNsb3VkCiAgICBwb3J0OiA1Njk2CiAgICBjbGllbnRDZXJ0aWZpY2F0ZUZpbGU6IC9ldGMvY2VydHMvY2xpZW50LnBlbQogICAgc2VydmVyQ0FGaWxlOiAvZXRjL2NlcnRzL2NhLnBlbQ==
kind: Secret
metadata:
  creationTimestamp: "2024-09-24T09:10:55Z"
  name: mg-configuration
  namespace: demo
  resourceVersion: "322831"
  uid: 005f0cac-6bbb-4fb6-a728-87b0ca55785a
type: Opaque

Create MongoDB

Before creating MongoDB, we need to create a secret with client.pem and vault-ca.pem to use as volume for our MongoDB

$ kubectl create secret generic vault-tls-secret -n demo \
        --from-file=client.pem=client.pem \
        --from-file=ca.pem=vault-ca.pem
secret/vault-tls-secret created

Now lets create KubeDB MongoDB. Currently, we have KMIP encryption support for percona-4.2.24,percona-4.2.26,percona-5.0.23,percona-6.0.12 and percona-7.0.4 version of KubeDB managed MongoDB.

We will use mongodb version percona-5.0.23 for our demo purpose.

apiVersion: kubedb.com/v1
kind: MongoDB
metadata:
  name: mg-kmip
  namespace: demo
spec:
  podTemplate:
    spec:
      containers:
        - name: "mongodb"
          volumeMounts:
            - name: certs
              mountPath: /etc/certs
      volumes:
      - name: certs
        secret:
          secretName: vault-tls-secret
  storage:
    accessModes:
      - ReadWriteOnce
    resources:
      requests:
        storage: 1Gi
  storageType: Durable
  deletionPolicy: WipeOut
  version: "percona-5.0.23"
  configSecret:
    name: mg-configuration
$ kubectl create -f https://github.com/kubedb/docs/raw/v2024.9.30/docs/guids/mongodb/vault-integration/kmip-enryption/examples/mg.yaml
mongodb.kubedb.com/mg-kmip created

Now, wait a few minutes. KubeDB operator will create necessary PVC, petset, services, secret etc. If everything goes well, we will see that a pod with the name mg-kmip-0 has been created.

Check that the petset’s pod is running

$ kubectl get pod -n demo mg-kmip-0
NAME                  READY     STATUS    RESTARTS   AGE
mg-kmip-0             1/1       Running   0          1m

Now, we will check if the database has started with the custom configuration we have provided.

To make sure that this mg-kmip MongoDB is KMIP encrypted, we can check the log of this mg-kmip-0 pod

kubectl logs -f --all-containers -n demo mg-kmip-0

We should see these logs which confirm that this MongoDB is setup with KMIP

{"t":{"$date":"2024-09-24T09:26:13.551+00:00"},"s":"I",  "c":"STORAGE",  "id":29116,   "ctx":"initandlisten","msg":"Master encryption key has been created on the key management facility","attr":{"keyManagementFacilityType":"KMIP server","keyIdentifier":{"kmipKeyIdentifier":"73ORm3aFQxGKZtJQ3196VXV5NmfT3AlG"}}}
{"t":{"$date":"2024-09-24T09:26:13.551+00:00"},"s":"I",  "c":"STORAGE",  "id":29037,   "ctx":"initandlisten","msg":"Initializing KeyDB with wiredtiger_open config: {cfg}","attr":{"cfg":"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),"}}
{"t":{"$date":"2024-09-24T09:26:13.799+00:00"},"s":"I",  "c":"STORAGE",  "id":29039,   "ctx":"initandlisten","msg":"Encryption keys DB is initialized successfully"}

Now, we can connect to this database through mongo-shell. In this tutorial, we are connecting to the MongoDB server from inside the pod.

$ kubectl get secrets -n demo mg-kmip-auth -o jsonpath='{.data.\username}' | base64 -d
root

$ kubectl get secrets -n demo mg-kmip-auth -o jsonpath='{.data.\password}' | base64 -d
bJI!1H!)V7!2U.wJ

$ kubectl exec -it mg-kmip-0 -n demo -- bash

> mongo admin

> db.auth("root","bJI!1H!)V7!2U.wJ")
1

> db._adminCommand( {getCmdLineOpts: 1})
{
	"argv" : [
		"mongod",
		"--dbpath=/data/db",
		"--auth",
		"--port=27017",
		"--ipv6",
		"--bind_ip=::,0.0.0.0",
		"--tlsMode=disabled",
		"-f",
		"/data/configdb/mongod.conf"
	],
	"parsed" : {
		"config" : "/data/configdb/mongod.conf",
		"net" : {
			"bindIp" : "::,0.0.0.0",
			"ipv6" : true,
			"port" : 27017,
			"tls" : {
				"mode" : "disabled"
			}
		},
		"security" : {
			"authorization" : "enabled",
			"enableEncryption" : true,
			"kmip" : {
				"clientCertificateFile" : "/etc/certs/client.pem",
				"port" : 5696,
				"serverCAFile" : "/etc/certs/ca.pem",
				"serverName" : "vault-cluster-doc-public-vault-a33bb761.37131dd1.z1.hashicorp.cloud"
			}
		},
		"storage" : {
			"dbPath" : "/data/db"
		}
	},
	"ok" : 1
}
> exit
bye

We can see that in parsed.security field, encryption is enabled.

Cleaning up

To cleanup the Kubernetes resources created by this tutorial, run:

kubectl delete -n demo mg/mg-kmip

kubectl delete -n demo secret mg-configuration
kubectl delete -n demo secret vault-tls-secret

kubectl delete ns demo

Next Steps