MinIO Operator with KES backed by Vault

MinIO Operator with KES backed by Vault

In order to provide functionality for regulatory compliance around secure locking and erasure, MinIO encrypts objects at the storage layer by using Server-Side Encryption (SSE) to protect objects as part of write operations. MinIO does this with extreme efficiency – benchmarks show that MinIO is capable of encrypting/decrypting at close to wire speed.

The secret sauce MinIO uses is Single Instruction Multiple Data (SIMD). Generally, you can only send one CPU instruction at a time and wait for a response before sending the next instruction. This is highly inefficient, especially when performing thousands – if not millions – of encryption and decryption instructions per second. MinIO takes advantage of SIMD so it can send multiple instructions in a single request to be processed by the CPU. We’ve written this in assembly within GoLang so we are as close to the hardware layer as possible to take full advantage of the typically underutilized  CPU power to perform cryptographic operations at scale.

In a previous post we showed you how to get started with MinIO and Vault in a bare-metal environment using KES. In this post we’ll show you how to configure MinIO Operator with KES (Keys Encryption System) and Vault in a cloud native way in Kubernetes. This will enable you to automate the process as you scale and use Kubernetes resources to configure them.

Prerequisites

Before we begin, ensure that you have the following prerequisites in place:

  • A Kubernetes cluster (for this tutorial, we will use kind to create a local cluster)
  • kubectl command-line tool installed on your local machine
  • git for cloning the necessary repositories

Step 1: Deploy a Kubernetes Cluster with kind

To get started, we will create a Kubernetes cluster using kind. This will provide us with a local environment to deploy and test our MinIO setup.

First, create a kind configuration file named kind-config.yaml with the following command: 

$ cat > kind-config.yaml <<EOF

kind: Cluster

apiVersion: kind.x-k8s.io/v1alpha4

nodes:

  - role: control-plane

  - role: worker

  - role: worker

  - role: worker

  - role: worker

EOF


$ kind create cluster --config kind-config.yml

Step 2: Deploy MinIO Operator

With our Kubernetes cluster up and running, we can now deploy the MinIO Operator using the kustomization plugin. The MinIO Operator simplifies the deployment and management of MinIO instances in a Kubernetes environment.

Execute the following command to deploy the MinIO Operator in kind cluster:

$ kubectl apply -k github.com/minio/operator

Wait for all the pods to come online in the minio-operator namespace before proceeding to the next step.

Step 3: Set up HashiCorp Vault

HashiCorp Vault is a powerful secrets management tool that we will use to securely store and manage our encryption keys. To set up Vault in our Kubernetes cluster, we will use the kubernetes-vault repository.

Clone the repository by running the following command:

$ git clone https://github.com/scriptcamp/kubernetes-vault.git

$ cd kubernetes-vault/vault-manifests

Next, set up the necessary RBAC (Role-Based Access Control) rules, create Vault config maps, deploy Vault services, and set up a stateful set:

$ kubectl apply -f rbac.yml

Create Vault config maps

$ kubectl apply -f configmap.yaml

Deploy Vault services

$ kubectl apply -f services.yaml

As Vault is stateful service, we need to set up a stateful set for the same. Execute the below command for the same

$ kubectl apply -f statefulset.yaml

Once the Vault setup is complete, unseal and initialize Vault. 

$ kubectl exec vault-0 -- vault operator init -key-shares=1 -key-threshold=1 -format=json > keys.json


$ VAULT_UNSEAL_KEY=$(cat keys.json | jq -r ".unseal_keys_b64[]")

$ echo $VAULT_UNSEAL_KEY


$ VAULT_ROOT_KEY=$(cat keys.json | jq -r ".root_token")

$ echo $VAULT_ROOT_KEY


$ kubectl exec vault-0 -- vault operator unseal $VAULT_UNSEAL_KEY

Now, enter the vault-0 POS to perform additional configuration:

$ kubectl exec -it vault-0 -- /bin/sh

Inside the pod, enable the K/V backend, create a policy, enable AppRole authentication, create a KES role, and generate an app-role ID and secret:

$ vault secrets enable -version=1 kv

Create policy

$ cat > kes-policy.hcl <<EOF

path "kv/*" {

   capabilities = [ "create", "read", "delete" ]

}


$ vault policy write kes-policy kes-policy.hcl

Enable AppRole authentication

$ vault auth enable approle

Create KES role and attach policy to it

$ vault write auth/approle/role/kes-server token_num_uses=0  secret_id_num_uses=0  period=5m

$ vault write auth/approle/role/kes-server policies=kes-policy

Generate app-role ID and secret, and take a note of values

$ vault read auth/approle/role/kes-server/role-id

Key    Value

---    -----

role_id b484633b-8965-08dd-0e24-d6973e6be2d2


$ vault write -f auth/approle/role/kes-server/secret-id

Key               Value

---               -----

secret_id         7fd44d44-a3f1-a013-1f40-e1952496c416

secret_id_accessor b5ee0c97-5ffc-b9a7-fe0b-763757fe1033

secret_id_ttl     0s

Take note of the generated app-role ID and secret, as we will need them in the next step.

Step 4: Deploy KES using kustomization plugin

To deploy KES (Key Encryption Service) using the kustomization plugin, we first need to clone the MinIO repository:

$ git clone https://github.com/minio/operator.git

$ cd operator

Update the examples/kustomization/tenant-kes-encryption/kes-configuration-secret.yaml file with the appropriate KES values, including the Vault endpoint, namespace, prefix, and the generated app-role ID and secret.

keystore:

   ## KES configured with fs (File System mode) doesnt work in Kubernetes environments and it's not recommended

   ## use a real KMS

   # fs:

   #   path: "./keys" # Path to directory. Keys will be stored as files. Not Recommended for Production.

   vault:

     endpoint: "http://vault.default.svc.cluster.local:8200" # The Vault endpoint

     namespace: "default" # An optional Vault namespace. See: https://www.vaultproject.io/docs/enterprise/namespaces/index.html

     prefix: "my-minio" # An optional K/V prefix. The server will store keys under this prefix.

     approle: # AppRole credentials. See: https://www.vaultproject.io/docs/auth/approle.html

       id: "b484633b-8965-08dd-0e24-d6973e6be2d2"  # Your AppRole Role ID

       secret: "7fd44d44-a3f1-a013-1f40-e1952496c416"  # Your AppRole Secret ID

       retry: 15s  # Duration until the server tries to re-authenticate after connection loss.

     tls:    # The Vault client TLS configuration for mTLS authentication and certificate verification

       key: "" # Path to the TLS client private key for mTLS authentication to Vault

       cert: "" # Path to the TLS client certificate for mTLS authentication to Vault

       ca: ""  # Path to one or multiple PEM root CA certificates

     status: # Vault status configuration. The server will periodically reach out to Vault to check its status.

       ping: 10s   # Duration until the server checks Vault's status again.

Now, deploy the KES service along with the MinIO tenant:

$ kubectl apply -k operator/examples/kustomization/tenant-kes-encryption

This would deploy KES and MinIO pods as below

$ kubectl get pods -n tenant-kms-encrypted

NAME           READY   STATUS RESTARTS   AGE

myminio-kes-0  1/1 Running   0          104m

myminio-kes-1  1/1 Running   0          104m

myminio-pool-0-0   2/2 Running   0          101m

myminio-pool-0-1   2/2 Running   0          102m

myminio-pool-0-2   2/2 Running   3 (102m ago)   103m

myminio-pool-0-3   2/2 Running   4 (102m ago)   104m

Your MinIO deployment with KES is now up and running.

Step 5: Verify the Deployment

To ensure that our deployment is running smoothly, let's check the status of the MinIO tenant pods, KES pods, and operator pods:

$ kubectl get pods -n minio-operator                                                                                                                        NAME                          READY   STATUS RESTARTS   AGE

console-6459d44b76-fgwbt      1/1 Running   0      5h29m

minio-operator-5668d46f98-9p2wm   1/1 Running   0      5h29m

minio-operator-5668d46f98-pm98s   1/1 Running   0      5h29m


$ kubectl get pods -n tenant-kms-encrypted

NAME           READY   STATUS RESTARTS   AGE

myminio-kes-0  1/1 Running   0          116m

myminio-kes-1  1/1 Running   0          116m

myminio-pool-0-0   2/2 Running   0          113m

myminio-pool-0-1   2/2 Running   0          114m

myminio-pool-0-2   2/2 Running   3 (114m ago)   115m

myminio-pool-0-3   2/2 Running   4 (114m ago)   116m


$ k get pods -n default

NAME     READY   STATUS RESTARTS   AGE

vault-0  1/1 Running   0      4h39m

Verify the KES pod logs to ensure that there are no errors and that the pods are running as expected as below:

$ kubectl logs myminio-kes-0 -n tenant-kms-encrypted                                                                                                        Copyright   MinIO, Inc.       https://min.io

License GNU AGPLv3        https://www.gnu.org/licenses/agpl-3.0.html

Version 2023-04-18T19-36-09Z  linux/amd64


KMS     Hashicorp Vault: http://vault.default.svc.cluster.local:8200

Endpoints   https://127.0.0.1:7373

         https://10.244.3.55:7373


Admin   _                 [ disabled ]

Mem Lock off               Failed to lock RAM pages. Consider granting CAP_IPC_LOCK


{"time":"2024-03-08T07:51:31.333681336Z","request":{"ip":"10.244.1.47","path":"/v1/key/create/my-minio-key","identity":"f9daf353b4bb9bba772a369b621a258c9464322c85d1ac9d2aafd29afdb96ea6"},"response":{"code":400,"time":2871000}}

{"time":"2024-03-08T07:51:32.191375099Z","request":{"ip":"10.244.3.54","path":"/v1/identity/self/describe","identity":"f9daf353b4bb9bba772a369b621a258c9464322c85d1ac9d2aafd29afdb96ea6"},"response":{"code":200,"time":18000}}

{"time":"2024-03-08T07:51:32.191784146Z","request":{"ip":"10.244.3.54","path":"/v1/key/create/my-minio-key","identity":"f9daf353b4bb9bba772a369b621a258c9464322c85d1ac9d2aafd29afdb96ea6"},"response":{"code":400,"time":1739000}}

{"time":"2024-03-08T07:52:01.890935905Z","request":{"ip":"10.244.3.54","path":"/v1/key/generate/my-minio-key","identity":"f9daf353b4bb9bba772a369b621a258c9464322c85d1ac9d2aafd29afdb96ea6"},"response":{"code":200,"time":1888000}}

{"time":"2024-03-08T07:52:02.145905494Z","request":{"ip":"10.244.1.47","path":"/v1/key/generate/my-minio-key","identity":"f9daf353b4bb9bba772a369b621a258c9464322c85d1ac9d2aafd29afdb96ea6"},"response":{"code":200,"time":62000}}

{"time":"2024-03-08T07:52:02.162627602Z","request":{"ip":"10.244.1.47","path":"/v1/key/generate/my-minio-key","identity":"f9daf353b4bb9bba772a369b621a258c9464322c85d1ac9d2aafd29afdb96ea6"},"response":{"code":200,"time":62000}}

{"time":"2024-03-08T07:52:04.850766874Z","request":{"ip":"10.244.3.54","path":"/v1/key/decrypt/my-minio-key","identity":"f9daf353b4bb9bba772a369b621a258c9464322c85d1ac9d2aafd29afdb96ea6"},"response":{"code":200,"time":68000}}

{"time":"2024-03-08T07:52:04.857285115Z","request":{"ip":"10.244.3.54","path":"/v1/key/decrypt/my-minio-key","identity":"f9daf353b4bb9bba772a369b621a258c9464322c85d1ac9d2aafd29afdb96ea6"},"response":{"code":200,"time":53000}}

{"time":"2024-03-08T07:52:07.753077729Z","request":{"ip":"10.244.2.35","path":"/v1/identity/self/describe","identity":"f9daf353b4bb9bba772a369b621a258c9464322c85d1ac9d2aafd29afdb96ea6"},"response":{"code":200,"time":7000}}

Step 6: Test the Deployment

To test our deployment, we will create a sample bucket and load/retrieve objects from a debug pod with mc (MinIO Client) installed.

First, start a new terminal and run the following command to exec into the debug pod:

kubectl exec -it pod/ubuntu-pod -n default -- bash

Inside the debug pod, run the following commands to create a bucket, create an encryption key, upload a file, and verify the encryption as demonstrated below:

mc alias ls myminio
mc ls myminio
mc mb myminio/encryptedbucket
mc admin kms key create myminio encrypted-bucket-key

echo "Hello" >> file1.txt
mc cp file1.txt myminio/encryptedbucket
mc ls myminio/encryptedbucket
mc cat myminio/encryptedbucket/file1.txt
mc admin kms key status myminio encrypted-bucket-key
mc stat myminio/encryptedbucket/file1.txt

If everything is set up correctly, you should see that the uploaded file is encrypted, and the encryption key status is valid.

The following demonstrates this process as expected:

$ kubectl exec -it pod/ubuntu-pod -n default -- bash                                                                                               


root@ubuntu-pod:/# mc alias ls myminio

myminio

  URL   : https://myminio-pool-0-0.myminio-hl.tenant-kms-encrypted.svc.cluster.local:9000

  AccessKey : minio

  SecretKey : minio123

  API   : s3v4

  Path  : auto


root@ubuntu-pod:/# mc ls myminio


root@ubuntu-pod:/# mc mb myminio/encryptedbucket

Bucket created successfully `myminio/encryptedbucket`.


root@ubuntu-pod:/# mc admin kms key create myminio encrypted-bucket-key

Created master key `encrypted-bucket-key` successfully


root@ubuntu-pod:/# echo "Hello" >> file1.txt


root@ubuntu-pod:/# mc cp file1.txt myminio/encryptedbucket

/file1.txt:                        6 B / 6 B ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 344 B/s 0sroot@ubuntu-pod:/# mc ls myminio/encryptedbucket

[2024-03-08 10:25:01 UTC] 6B STANDARD file1.txt


root@ubuntu-pod:/# mc cat myminio/encryptedbucket/file1.txt

Hello


root@ubuntu-pod:/# mc admin kms key status myminio encrypted-bucket-key

Key: encrypted-bucket-key

   - Encryption ✔

   - Decryption ✔


root@ubuntu-pod:/# mc stat myminio/encryptedbucket/file1.txt

Name  : file1.txt

Date  : 2024-03-08 10:25:01 UTC

Size  : 6 B    

ETag  : 27e775e1a5d22463e4cd39f12ce14ea0

Type  : file

Metadata  :

  Content-Type: text/plain

Encrypted :

  X-Amz-Server-Side-Encryption           : aws:kms

  X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id: arn:aws:kms:encrypted-bucket-key


If everything is set up correctly, you should see that the uploaded file is encrypted, and the encryption key status is valid, confirming that the object has been encrypted.

Final Thoughts

In this blog post, we explored how to deploy MinIO Operator with KES backed by Vault in a Kubernetes environment, with focus on Server-Side Encryption (SSE). By leveraging the power of Kubernetes and the kustomization plugin, we can automate the deployment process and easily scale our MinIO setup as needed.

Embracing a "shift left" approach, we underscore the importance of embedding security and data protection practices from the earliest stages of development and planning. The intent is clear: to elevate security from a mere consideration to a foundational element of your infrastructure. By spotlighting the straightforwardness and accessibility of SSE, our goal is to encourage its adoption as a standard across all MinIO deployments. The path to robust security is both simple and attainable, paving the way for a safer and more secure digital environment.

If you have questions, face any issues, or just want to talk about your experiences with MinIO, our community is ready to assist. Join us on Slack to meet other users, get advice from experts, and keep up with the latest news in object storage. 

Happy encrypting!