Transparent Data Encryption (TDE)
=================================

..  Important::
   TDE is available *only* for operands that support it: EPAS and PG Extended, versions 15 and newer.

Transparent Data Encryption, or TDE, is a technology used by several
database vendors to **encrypt data at rest**, i.e. database files on
disk. TDE does not however encrypt data in use.

TDE is included in EDB Postgres Advanced Server and EDB Postgres
Extended Server from version 15, and is supported by the EDB
CloudNativePG Cluster operator.

..  Important::
   Before you proceed, please take some time to familiarize with the :ref:`TDE feature in the EPAS documentation <Transparent Data Encryption (TDE)>`  .

With TDE activated, both WAL files and files for tables will be
encrypted. Data encryption/decryption is entirely transparent to the
user, as it is managed by the database without requiring any application
changes or updated client drivers.

..  Note::
   In the code samples shown below, the `epas`  sub-section of `postgresql`  in the YAML manifests is used to activate TDE. The `epas`  section can be used to enable TDE for PG Extended images as well as for EPAS images.

EDB CloudNativePG Cluster provides 3 ways to use TDE:

- using a secret containing the passphrase

- using a secret containing a custom passphrase command

- using a pair of secrets containing custom wrap/unwrap commands

Passphrase secret
-----------------

The basic approach is to store the passphrase in a Kubernetes secret.
Such a passphrase will be used to encrypt the EPAS binary key.

..  Seealso::
   Please refer to  :ref:`the EPAS documentation <Transparent Data Encryption (TDE)>` 

for details on the EPAS encryption key.

Activating TDE on the operator is simple. In the ``epas`` section of the
manifest, use the ``tde`` stanza to enable TDE, and set the Kubernetes
secret that will hold the TDE encryption key.

For example:

.. code:: yaml

     […]
     postgresql:
       epas:
         tde:
           enabled: true
           secretKeyRef:
             name: tde-key
             key: key

You can find an example in

::

   - --
   apiVersion: v1
   kind: Secret
   metadata:
     name: tde-key
   data:
     key: bG9zcG9sbGl0b3NkaWNlbnBpb3Bpb3Bpb2N1YW5kb3RpZW5lbmhhbWJyZWN1YW5kb3RpZW5lbmZyaW8=

   - --
   apiVersion: postgresql.k8s.enterprisedb.io/v1
   kind: Cluster
   metadata:
     name: cluster-example
   spec:
     instances: 3
     imageName: docker.enterprisedb.com/k8s_enterprise/edb-postgres-advanced:16
     postgresql:
       epas:
         tde:
           enabled: true
           secretKeyRef:
             name: tde-key
             key: key

     storage:
       size: 1Gi

.

..  Note::
   This file also contains the definition of the secret to hold the encryption key. Look at the following section for an example on how to create a secret for this purpose.

The key stored in the secret will be used as the pass-phrase to invoke
``openssl`` to wrap/unwrap the EPAS encryption key.

How to create the secret containing the passphrase
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

First choose the passphrase. While it is recommended to use a randomly
generated passphrase, in this example we will use ``PostgresRocks`` as
passphrase, and rely on ``kubectl`` to generate for us the secret
definition:

.. code:: shell

   kubectl create secret generic -o yaml tde-key \
       --from-literal=key=PostgresRocks

This should return something like this:

.. code:: yaml

   apiVersion: v1
   data:
     key: UG9zdGdyZXNSb2Nrcw==
   kind: Secret
   metadata:
     creationTimestamp: "YYYY-MM-DDTHH:MM:SSZ"
     name: tde-key
     namespace: default
     resourceVersion: ....
     uid: ....
   type: Opaque

Remember to run ``kubectl apply`` or remove the ``-o yaml`` option to
the ``create`` command above to actually create the secret in the
cluster.

Custom passphrase command
-------------------------

Instead of the ``secretKeyRef`` in the cluster manifest snippet above,
it is possible to specify a ``passphraseCommand`` stored in a secret.
The passphrase command can be run to generate a passphrase to be used
with ``openssl`` .

.. code:: yaml

     […]
     postgresql:
       epas:
         tde:
           enabled: true
           passphraseCommand:
             name: tde-passphrase
             key: command

The passphrase command should write to standard output. For example, we
could simply use ``echo my-passphrase`` .

The passphrase generated by the command will be used the same way the
secretKeyRef was used, i.e. as a passphrase argument for ``openssl`` .

Custom wrap/unwrap commands
---------------------------

It is also possible to specify the wrap and unwrap commands, rather than
rely on the default invocation of ``openssl`` . This can be done by
creating secrets containing the custom commands, and declaring those
secrets in the ``tde`` stanza.

The snippet below shows a cluster with TDE enabled using custom
commands.

.. code:: yaml

     […]
     postgresql:
       epas:
         tde:
           enabled: true
           wrapCommand:
             name: tde-wrap-command
             key: command
           unwrapCommand:
             name: tde-unwrap-command
             key: command

The custom commands need to obey the following conventions:

1. The custom wrap command should accept input from standard input,
   which EPAS will use to feed it the binary key. It should write to a
   file via an explicit argument (not shell redirections). Moreover, the
   file argument should be given the string “%p”, which is a placeholder
   EPAS will use to pass the file path of the new, wrapped encryption
   key file.

2. The custom unwrap command should write to standard output. It should
   have an explicit file path argument for input (not shell
   redirections). Again, the file argument should be given the string
   “%p”, which is the placeholder EPAS will fill in with the wrapped
   encryption key file path.

For example:

- wrap command:
  ``openssl enc -aes-128-cbc -pass pass:temp-pass -e -out %p``

- unwrap command:
  ``openssl enc -aes-128-cbc -pass pass:temp-pass -d -in %p``

Example using HashiCorp Vault
-----------------------------

The following example shows how to use HashiCorp Vault to store the
encryption key and use it to activate TDE. The ``vault`` CLI is used to
interact with Vault and is included by default in the EDB Postgres
Advanced Server (EPAS) image.

First, wherever you have vault running you must enable the Transit
secrets engine and create a key:

.. code:: shell

   vault secrets enable transit
   vault write -f transit/keys/pg-tde

Then, create a secret containing the custom wrap/unwrap commands. The
wrap and unwrap commands will ‘wrap’ a binary that is in the EPAS image.
The binary will interact with the vault API to encrypt/decrypt the EPAS
encryption.

The binary needs 4 flags: ``--host`` , ``--secret`` , ``--key`` and
``--vault-endpoint`` . The ``--host`` flag is in the format of
``http://vault-host:vault-port`` and needs to be provided to reach the
Vault. The server\ ``--secret`` flag is the name of the Kubernetes
secret that contains the vault token and the ``--key`` flag is the key
in that secret pointing the vault token. The ``--vault-endpoint`` flag
is the name of the key that was created inside vault; in the example
above it is ``pg-tde`` .

If running the Vault operator in Kubernetes the root token can be
obtained from the following two commands:

.. code:: shell

   kubectl exec vault-0 -- vault operator init -key-shares=1 -key-threshold=1 -format=json > cluster-keys.json
   cat cluster-keys.json | jq -r ".root_token"

.. code:: shell

   kubectl create secret generic -o yaml vault-token \
       --from-literal=wrap="/bin/vault wrap --file %p --host http://vault:8200 --secret vault-token --key token --vault-endpoint pg-tde" \
       --from-literal=unwrap="/bin/vault unwrap --file %p --host http://vault:8200 --secret vault-token --key token --vault-endpoint pg-tde" \
       --from-literal=token="hvs.whatever"

You can now create a Cluster that is referencing the secrets:

.. code:: yaml

   apiVersion: postgresql.k8s.enterprisedb.io/v1
   kind: Cluster
   metadata:
     name: hashicorp-vault-tde
   spec:
     instances: 3
     storage:
       size: 1Gi
     postgresql:
       epas:
         tde:
           enabled: true
           wrapCommand:
             name: vault-token
             key: wrap
           unwrapCommand:
             name: vault-token
             key: unwrap
