From 8a199ca97f00a0ad9f74e216d1af85b3c375e969 Mon Sep 17 00:00:00 2001 From: Anastasia Alexandrova Date: Wed, 11 Dec 2024 14:55:52 +0100 Subject: [PATCH 01/19] PG-1206 Reworked intro to pg_tde (#353) * PG-1206 Reworked intro to pg_tde --------- Co-authored-by: Artem Gavrilov --- documentation/docs/faq.md | 30 ++++++++++++++++++++++++++++++ documentation/docs/index.md | 17 ++++++----------- documentation/docs/replication.md | 2 -- documentation/docs/setup.md | 10 +++++++--- documentation/docs/tde.md | 16 ++++++++-------- documentation/docs/test.md | 4 ---- documentation/mkdocs.yml | 1 + 7 files changed, 52 insertions(+), 28 deletions(-) create mode 100644 documentation/docs/faq.md delete mode 100644 documentation/docs/replication.md diff --git a/documentation/docs/faq.md b/documentation/docs/faq.md new file mode 100644 index 00000000..9e9d5afd --- /dev/null +++ b/documentation/docs/faq.md @@ -0,0 +1,30 @@ +# FAQ + +## Why do I need TDE? + +- Compliance to security and legal regulations like GDPR, PCI DSS and others +- Encryption of backups +- Granular encryption of specific data sets and reducing the performance overhead that encryption brings +- Additional layer of security to existing security measures + +## I use disk-level encryption. Why should I care about TDE? + +Encrypting a hard drive encrypts all data including system and application files that are there. However, disk encryption doesn’t protect your data after the boot-up of your system. During runtime, the files are decrypted with disk-encryption. + +TDE focuses specifically on data files and offers a more granular control over encrypted data. It also ensures that files are encrypted on disk during runtime and when moved to another system or storage. + +Consider using TDE and storage-level encryption together to add another layer of data security + +## Is TDE enough to ensure data security? + +No. TDE is an additional layer to ensure data security. It protects data at rest. Consider introducing also these measures: + +* Access control and authentication +* Strong network security like TLS +* Disk encryption +* Regular monitoring and auditing +* Additional data protection for sensitive fields (e.g., application-layer encryption) + +## What happens to my data if I lose a principal key? + +If you lose encryption keys, especially, the principal key, the data is lost. That's why it's critical to back up your encryption keys securely. \ No newline at end of file diff --git a/documentation/docs/index.md b/documentation/docs/index.md index a377798e..49ca25e1 100644 --- a/documentation/docs/index.md +++ b/documentation/docs/index.md @@ -1,8 +1,10 @@ # `pg_tde` documentation -`pg_tde` is the extension that brings in [Transparent Data Encryption (TDE)](tde.md) to PostgreSQL and enables users to keep sensitive data safe and secure. The encryption is transparent for users allowing them to access and manipulate the data and not to worry about the encryption process. +`pg_tde` is the open source PostgreSQL extension that provides Transparent Data Encryption (TDE) to protect data at rest. This ensures that the data stored on disk is encrypted, and no one can read it without the proper encryption keys, even if they gain access to the physical storage media. -Users can configure encryption differently for each database, encrypting specific tables in some databases with different encryption keys, while keeping others non encrypted. +You can configure encryption differently for each database, encrypting specific tables in some databases with different encryption keys while keeping others unencrypted. + +Lear more [what is Transparent Data Encryption](tde.md#how-does-it-work) and [why you need it](tde.md#why-do-you-need-tde). !!! important @@ -22,7 +24,7 @@ Users can configure encryption differently for each database, encrypting specifi ## Known limitations -* Keys in the local keyfile are stored unencrypted. +* Keys in the local keyfile are stored unencrypted. For better security we recommend using the Key management storage. * System tables are currently not encrypted. :material-alert: Warning: Note that introducing encryption/decryption affects performance. Our benchmark tests show less than 10% performance overhead for most situations. However, in some specific applications such as those using JSONB operations, performance degradation might be higher. @@ -41,7 +43,7 @@ The `pg_tde` extension comes in two distinct versions with specific access metho ### Which version to chose? -The answer is pretty straightforward: if you don't use indexes and don't need index encryption, use the community version and the `tde_heap_basic` access method. Check the [upstream documentation :octicons-link-external-16:](https://github.com/percona/pg_tde/blob/main/README.md) how to get started. +The answer is pretty straightforward: for data sets where indexing is not mandatory or index encryption is not required, use the community version and the `tde_heap_basic` access method. Check the [upstream documentation :octicons-link-external-16:](https://github.com/percona/pg_tde/blob/main/README.md) how to get started. Otherwise, enjoy full encryption with the Percona Server for PostgreSQL version and the `tde_heap` access method. @@ -55,10 +57,3 @@ The following is planned for future releases of `pg_tde`: * KMIP integration for key management * Global principal key management - - - -## Useful links - -* [What is Transparent Data Encryption](tde.md) - diff --git a/documentation/docs/replication.md b/documentation/docs/replication.md deleted file mode 100644 index f0e505df..00000000 --- a/documentation/docs/replication.md +++ /dev/null @@ -1,2 +0,0 @@ -# Streaming replication configuration - diff --git a/documentation/docs/setup.md b/documentation/docs/setup.md index bd4ce103..1f7509f5 100644 --- a/documentation/docs/setup.md +++ b/documentation/docs/setup.md @@ -42,10 +42,12 @@ Load the `pg_tde` at the start time. The extension requires additional shared me ## Key provider configuration -1. Set up a key provider for the database where you have enabled the extension +1. Set up a key provider for the database where you have enabled the extension. === "With HashiCorp Vault" + The Vault server setup is out of scope of this document. + ``` SELECT pg_tde_add_key_provider_vault_v2('provider-name',:'secret_token','url','mount','ca_path'); ``` @@ -66,7 +68,7 @@ Load the `pg_tde` at the start time. The extension requires additional shared me SELECT pg_tde_add_key_provider_file('provider-name','/path/to/the/keyring/data.file'); ``` - :material-information: Warning: Example for testing purposes only: + :material-information: Warning: This example is for testing purposes only: ``` SELECT pg_tde_add_key_provider_file('file-vault','/tmp/pg_tde_test_local_keyring.per'); @@ -79,12 +81,14 @@ Load the `pg_tde` at the start time. The extension requires additional shared me SELECT pg_tde_set_principal_key('name-of-the-principal-key', 'provider-name'); ``` - :material-information: Warning: Example for testing purposes only: + :material-information: Warning: This example is for testing purposes only: ``` SELECT pg_tde_set_principal_key('test-db-master-key','file-vault'); ``` + The key is auto-generated. + :material-information: Info: The key provider configuration is stored in the database catalog in an unencrypted table. See [how to use external reference to parameters](external-parameters.md) to add an extra security layer to your setup. diff --git a/documentation/docs/tde.md b/documentation/docs/tde.md index bd90b958..fea9136c 100644 --- a/documentation/docs/tde.md +++ b/documentation/docs/tde.md @@ -1,13 +1,13 @@ # What is Transparent Data Encryption (TDE) -Transparent Data Encryption offers encryption at the file level and solves the problem of protecting data at rest. The encryption is transparent for users allowing them to access and manipulate the data and not to worry about the encryption process. +Transparent Data Encryption is a technology to protect data at rest. The encryption process happens transparently in the background, without affecting database operations. Data is automatically encrypted as it's written to the disk and decrypted as it's read, all in real-time. Users and applications interact with the data as usual without noticing any difference. ## How does it work? To encrypt the data, two types of keys are used: -* Database keys to encrypt user data. These are stored internally, near the data that they encrypt. -* The principal key to encrypt database keys. It is kept separately from the database keys and is managed externally. +* Table encryption keys (TEK) to encrypt user data. These keys are stored internally, near the data that they encrypt. +* The principal key to encrypt table keys. It is kept separately from the table keys and is managed externally. `pg_tde` is integrated with HashiCorp Vault server to store and manage principal keys. Only the back end KV Secrets Engine - Version 2 (API) is supported. @@ -15,7 +15,7 @@ The encryption process is the following: ![image](_images/tde-flow.png) -When a user creates an encrypted table using `pg_tde`, a new random key is generated for that table. This key is used to encrypt all data the user inserts in that table. Eventually the encrypted data gets stored in the underlying storage. +When a user creates an encrypted table using `pg_tde`, a new random key is generated for that table using the AES128 (AES-ECB) cipher algorithm. This key is used to encrypt all data the user inserts in that table. Eventually the encrypted data gets stored in the underlying storage. The table itself is encrypted using the principal key. The principal key is stored externally in the Vault key management store. @@ -27,13 +27,13 @@ Using TDE has the following benefits: * For organizations: - - Ensure data safety when at rest and in motion - - Comply with security standards like HIPAA, PCI DSS, SOC 2, ISO 27001 + - Ensure data safety when it is stored on disk and in backups + - Comply with security and legal standards like HIPAA, PCI DSS, SOC 2, ISO 27001 * For DBAs: - - Allows defining what to encrypt in the table and with what key - - Encryption on storage level is not a must to provide data safety. However, using TDE and storage-level encryption together adds another layer of data security + - Granular encryption of specific tables and reducing the performance overhead that encryption brings + - Additional layer of security to existing security measures like storage-level encryption, data encryption in transit using TLS, access control and more. !!! admonition "See also" diff --git a/documentation/docs/test.md b/documentation/docs/test.md index c0936e90..ac610953 100644 --- a/documentation/docs/test.md +++ b/documentation/docs/test.md @@ -2,10 +2,6 @@ Enabling `pg_tde` extension for a database creates the table access method `tde_heap` . This access method enables you to encrypt the data. -!!! warning - - This is the tech preview functionality. Its scope is not yet finalized and can change anytime. **Use it only for testing purposes.** - Here's how to do it: 1. Create a table in the database for which you have [enabled `pg_tde`](setup.md) using the `tde_heap` access method as follows: diff --git a/documentation/mkdocs.yml b/documentation/mkdocs.yml index 4334f73a..3a66f920 100644 --- a/documentation/mkdocs.yml +++ b/documentation/mkdocs.yml @@ -169,6 +169,7 @@ nav: - How to: - Use reference to external parameters: external-parameters.md - Decrypt an encrypted table: decrypt.md + - faq.md - Release notes: - "pg_tde release notes": release-notes/release-notes.md - uninstall.md From c62e23c06a7d420adbd83e149cca213e9979cdd6 Mon Sep 17 00:00:00 2001 From: Artem Gavrilov Date: Wed, 11 Dec 2024 17:43:43 +0200 Subject: [PATCH 02/19] PG-1238 Remove redundant code checkout and add required permission (#377) * PG-1238 Remove redundant code checkout * PG-1238 Add required permission for Perf results --- .github/workflows/postgresql-perf-results.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/postgresql-perf-results.yml b/.github/workflows/postgresql-perf-results.yml index fb32d1bc..65d4e35e 100644 --- a/.github/workflows/postgresql-perf-results.yml +++ b/.github/workflows/postgresql-perf-results.yml @@ -8,6 +8,7 @@ on: permissions: contents: read + pull-requests: write jobs: download: @@ -38,12 +39,6 @@ jobs: run: | unzip pr_perf_results.zip - - name: Clone pg_tde repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - path: 'src' - ref: ${{ github.event.workflow_run.head_branch }} - - name: 'Create comment' run: | gh pr comment ${PR_NUMBER} -F ../pr_perf_results --edit-last || \ From 69f83375eb573a24159a2ea1dc13a54b29586d68 Mon Sep 17 00:00:00 2001 From: Artem Gavrilov Date: Thu, 12 Dec 2024 09:24:12 +0200 Subject: [PATCH 03/19] Fix perf results CI (#379) --- .github/workflows/postgresql-perf-results.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/postgresql-perf-results.yml b/.github/workflows/postgresql-perf-results.yml index 65d4e35e..21228d83 100644 --- a/.github/workflows/postgresql-perf-results.yml +++ b/.github/workflows/postgresql-perf-results.yml @@ -41,9 +41,8 @@ jobs: - name: 'Create comment' run: | - gh pr comment ${PR_NUMBER} -F ../pr_perf_results --edit-last || \ - gh pr comment ${PR_NUMBER} -F ../pr_perf_results + gh pr comment ${PR_NUMBER} -F pr_perf_results --edit-last || \ + gh pr comment ${PR_NUMBER} -F pr_perf_results env: PR_NUMBER: ${{ github.event.number }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - working-directory: src From dfbf158de43698b1144ca480f818f3a2ac72f995 Mon Sep 17 00:00:00 2001 From: Artem Gavrilov Date: Thu, 12 Dec 2024 17:15:55 +0200 Subject: [PATCH 04/19] Fix perf test results workflow (#380) --- .github/workflows/postgresql-perf-results.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/postgresql-perf-results.yml b/.github/workflows/postgresql-perf-results.yml index 21228d83..5124b9d2 100644 --- a/.github/workflows/postgresql-perf-results.yml +++ b/.github/workflows/postgresql-perf-results.yml @@ -41,8 +41,8 @@ jobs: - name: 'Create comment' run: | - gh pr comment ${PR_NUMBER} -F pr_perf_results --edit-last || \ - gh pr comment ${PR_NUMBER} -F pr_perf_results + gh pr comment ${PR_NUMBER} -R ${{ github.repository }} -F pr_perf_results --edit-last || \ + gh pr comment ${PR_NUMBER} -R ${{ github.repository }} -F pr_perf_results env: PR_NUMBER: ${{ github.event.number }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 28e88b4c7eb2248acf86aee05e8a2583ce25badc Mon Sep 17 00:00:00 2001 From: Artem Gavrilov Date: Thu, 12 Dec 2024 18:04:42 +0200 Subject: [PATCH 05/19] Fix perf report once again (#381) * Fix perf report once again * Fix * Fix --- .github/workflows/postgresql-perf-results.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/postgresql-perf-results.yml b/.github/workflows/postgresql-perf-results.yml index 5124b9d2..830ed8e6 100644 --- a/.github/workflows/postgresql-perf-results.yml +++ b/.github/workflows/postgresql-perf-results.yml @@ -41,8 +41,8 @@ jobs: - name: 'Create comment' run: | - gh pr comment ${PR_NUMBER} -R ${{ github.repository }} -F pr_perf_results --edit-last || \ - gh pr comment ${PR_NUMBER} -R ${{ github.repository }} -F pr_perf_results + gh pr comment ${PR_NUMBER} -F pr_perf_results --repo ${{ github.repository }} --edit-last || \ + gh pr comment ${PR_NUMBER} -F pr_perf_results --repo ${{ github.repository }} env: PR_NUMBER: ${{ github.event.number }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From a4b61d1861d388e781b9c59353289126f1382337 Mon Sep 17 00:00:00 2001 From: Zsolt Parragi Date: Mon, 16 Dec 2024 13:10:56 +0000 Subject: [PATCH 06/19] PG-1254, PG-1253, PG-1250: Improve diagnostic messages (#382) * Event triggers dumped a debug log message with the LOG level. This commit completely removes it, this is a debug functionality for development, it can be simply re-added temporarily during event trigger development * Many, but not all FATAL messages are now ERROR messages. A few messages dealing with data corruption are still FATAL. * Some of these also got a TODO comment, as it looks like those would result in a corrupted internal key file, resulting in data loss. These parts should be improved. * Key generation on keyring was missing some error messages, and also displayed some of the messages as WARNING instead of ERROR. This is now all fixed, functions like pg_tde_set_principal key only display one specific error message explaining the problem. --- expected/vault_v2_test.out | 17 ++++++++++++++++- expected/vault_v2_test_basic.out | 17 ++++++++++++++++- sql/vault_v2_test.inc | 11 +++++++++++ src/access/pg_tde_tdemap.c | 16 ++++++++++------ src/catalog/tde_principal_key.c | 8 +++++--- src/keyring/keyring_api.c | 2 ++ src/pg_tde_event_capture.c | 1 - 7 files changed, 60 insertions(+), 12 deletions(-) diff --git a/expected/vault_v2_test.out b/expected/vault_v2_test.out index 789a6ec7..b1037d72 100644 --- a/expected/vault_v2_test.out +++ b/expected/vault_v2_test.out @@ -2,12 +2,27 @@ \i sql/vault_v2_test.inc CREATE EXTENSION pg_tde; \getenv root_token ROOT_TOKEN -SELECT pg_tde_add_key_provider_vault_v2('vault-v2',:'root_token','http://127.0.0.1:8200','secret',NULL); +SELECT pg_tde_add_key_provider_vault_v2('vault-incorrect',:'root_token','http://127.0.0.1:8200','DUMMY-TOKEN',NULL); pg_tde_add_key_provider_vault_v2 ---------------------------------- 1 (1 row) +-- FAILS +SELECT pg_tde_set_principal_key('vault-v2-principal-key','vault-incorrect'); +psql:sql/vault_v2_test.inc:7: ERROR: Failed to store key on keyring. Please check the keyring configuration. +CREATE TABLE test_enc( + id SERIAL, + k INTEGER DEFAULT '0' NOT NULL, + PRIMARY KEY (id) + ) USING :tde_am; +psql:sql/vault_v2_test.inc:13: ERROR: failed to retrieve principal key. Create one using pg_tde_set_principal_key before using encrypted tables. +SELECT pg_tde_add_key_provider_vault_v2('vault-v2',:'root_token','http://127.0.0.1:8200','secret',NULL); + pg_tde_add_key_provider_vault_v2 +---------------------------------- + 2 +(1 row) + SELECT pg_tde_set_principal_key('vault-v2-principal-key','vault-v2'); pg_tde_set_principal_key -------------------------- diff --git a/expected/vault_v2_test_basic.out b/expected/vault_v2_test_basic.out index c3cc0204..5fcedd36 100644 --- a/expected/vault_v2_test_basic.out +++ b/expected/vault_v2_test_basic.out @@ -2,12 +2,27 @@ \i sql/vault_v2_test.inc CREATE EXTENSION pg_tde; \getenv root_token ROOT_TOKEN -SELECT pg_tde_add_key_provider_vault_v2('vault-v2',:'root_token','http://127.0.0.1:8200','secret',NULL); +SELECT pg_tde_add_key_provider_vault_v2('vault-incorrect',:'root_token','http://127.0.0.1:8200','DUMMY-TOKEN',NULL); pg_tde_add_key_provider_vault_v2 ---------------------------------- 1 (1 row) +-- FAILS +SELECT pg_tde_set_principal_key('vault-v2-principal-key','vault-incorrect'); +psql:sql/vault_v2_test.inc:7: ERROR: Failed to store key on keyring. Please check the keyring configuration. +CREATE TABLE test_enc( + id SERIAL, + k INTEGER DEFAULT '0' NOT NULL, + PRIMARY KEY (id) + ) USING :tde_am; +psql:sql/vault_v2_test.inc:13: ERROR: failed to retrieve principal key. Create one using pg_tde_set_principal_key before using encrypted tables. +SELECT pg_tde_add_key_provider_vault_v2('vault-v2',:'root_token','http://127.0.0.1:8200','secret',NULL); + pg_tde_add_key_provider_vault_v2 +---------------------------------- + 2 +(1 row) + SELECT pg_tde_set_principal_key('vault-v2-principal-key','vault-v2'); pg_tde_set_principal_key -------------------------- diff --git a/sql/vault_v2_test.inc b/sql/vault_v2_test.inc index 221e6390..70c71906 100644 --- a/sql/vault_v2_test.inc +++ b/sql/vault_v2_test.inc @@ -1,6 +1,17 @@ CREATE EXTENSION pg_tde; \getenv root_token ROOT_TOKEN + +SELECT pg_tde_add_key_provider_vault_v2('vault-incorrect',:'root_token','http://127.0.0.1:8200','DUMMY-TOKEN',NULL); +-- FAILS +SELECT pg_tde_set_principal_key('vault-v2-principal-key','vault-incorrect'); + +CREATE TABLE test_enc( + id SERIAL, + k INTEGER DEFAULT '0' NOT NULL, + PRIMARY KEY (id) + ) USING :tde_am; + SELECT pg_tde_add_key_provider_vault_v2('vault-v2',:'root_token','http://127.0.0.1:8200','secret',NULL); SELECT pg_tde_set_principal_key('vault-v2-principal-key','vault-v2'); diff --git a/src/access/pg_tde_tdemap.c b/src/access/pg_tde_tdemap.c index a3cb8c5a..366e8716 100644 --- a/src/access/pg_tde_tdemap.c +++ b/src/access/pg_tde_tdemap.c @@ -177,7 +177,7 @@ pg_tde_create_key_map_entry(const RelFileLocator *newrlocator, uint32 entry_type if (!RAND_bytes(int_key.key, INTERNAL_KEY_LEN)) { LWLockRelease(lock_pk); - ereport(FATAL, + ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("could not generate internal key for relation \"%s\": %s", "TODO", ERR_error_string(ERR_get_error(), NULL)))); @@ -439,8 +439,9 @@ pg_tde_write_one_map_entry(int fd, const RelFileLocator *rlocator, uint32 flags, { char db_map_path[MAXPGPATH] = {0}; + // TODO: this seems like a bad idea? pg_tde_set_db_file_paths(rlocator->dbOid, db_map_path, NULL); - ereport(FATAL, + ereport(ERROR, (errcode_for_file_access(), errmsg("could not write tde map file \"%s\": %m", db_map_path))); @@ -449,6 +450,7 @@ pg_tde_write_one_map_entry(int fd, const RelFileLocator *rlocator, uint32 flags, { char db_map_path[MAXPGPATH] = {0}; + // TODO: this seems like a bad idea? pg_tde_set_db_file_paths(rlocator->dbOid, db_map_path, NULL); ereport(data_sync_elevel(ERROR), (errcode_for_file_access(), @@ -500,7 +502,8 @@ pg_tde_write_one_keydata(int fd, int32 key_index, RelKeyData *enc_rel_key_data) /* TODO: pgstat_report_wait_start / pgstat_report_wait_end */ if (pg_pwrite(fd, &enc_rel_key_data->internal_key, INTERNAL_KEY_DAT_LEN, curr_pos) != INTERNAL_KEY_DAT_LEN) { - ereport(FATAL, + // TODO: what now? File is corrupted + ereport(ERROR, (errcode_for_file_access(), errmsg("could not write tde key data file: %m"))); } @@ -1022,10 +1025,11 @@ pg_tde_process_map_entry(const RelFileLocator *rlocator, uint32 key_type, char * if (curr_pos == -1) { - ereport(FATAL, + ereport(ERROR, (errcode_for_file_access(), errmsg("could not seek in tde map file \"%s\": %m", db_map_path))); + return curr_pos; } } else @@ -1117,7 +1121,7 @@ tde_decrypt_rel_key(TDEPrincipalKey *principal_key, RelKeyData *enc_rel_key_data * Open and Validate File Header [pg_tde.*]: * header: {Format Version, Principal Key Name} * - * Returns the file descriptor in case of a success. Otherwise, fatal error + * Returns the file descriptor in case of a success. Otherwise, error * is raised. * * Also, it sets the is_new_file to true if the file is just created. This is @@ -1160,7 +1164,7 @@ pg_tde_open_file(char *tde_filename, TDEPrincipalKeyInfo *principal_key_info, bo /* * Open a TDE file [pg_tde.*]: * - * Returns the file descriptor in case of a success. Otherwise, fatal error + * Returns the file descriptor in case of a success. Otherwise, error * is raised except when ignore_missing is true and the file does not exit. */ static int diff --git a/src/catalog/tde_principal_key.c b/src/catalog/tde_principal_key.c index c3326c5a..5316cbe5 100644 --- a/src/catalog/tde_principal_key.c +++ b/src/catalog/tde_principal_key.c @@ -262,7 +262,7 @@ set_principal_key_with_keyring(const char *key_name, GenericKeyring *keyring, keyInfo = load_latest_versioned_key_name(&principalKey->keyInfo, keyring, ensure_new_key); if (keyInfo == NULL) - keyInfo = KeyringGenerateNewKeyAndStore(keyring, principalKey->keyInfo.keyId.versioned_name, INTERNAL_KEY_LEN, false); + keyInfo = KeyringGenerateNewKeyAndStore(keyring, principalKey->keyInfo.keyId.versioned_name, INTERNAL_KEY_LEN, true); if (keyInfo == NULL) { @@ -424,7 +424,7 @@ RotatePrincipalKey(TDEPrincipalKey *current_key, const char *new_key_name, const keyInfo = load_latest_versioned_key_name(&new_principal_key.keyInfo, keyring, ensure_new_key); if (keyInfo == NULL) - keyInfo = KeyringGenerateNewKeyAndStore(keyring, new_principal_key.keyInfo.keyId.versioned_name, INTERNAL_KEY_LEN, false); + keyInfo = KeyringGenerateNewKeyAndStore(keyring, new_principal_key.keyInfo.keyId.versioned_name, INTERNAL_KEY_LEN, true); if (keyInfo == NULL) { @@ -496,9 +496,10 @@ load_latest_versioned_key_name(TDEPrincipalKeyInfo *principal_key_info, GenericK */ if (kr_ret != KEYRING_CODE_SUCCESS && kr_ret != KEYRING_CODE_RESOURCE_NOT_AVAILABLE) { - ereport(FATAL, + ereport(ERROR, (errmsg("failed to retrieve principal key from keyring provider :\"%s\"", keyring->provider_name), errdetail("Error code: %d", kr_ret))); + return NULL; } if (keyInfo == NULL) { @@ -531,6 +532,7 @@ load_latest_versioned_key_name(TDEPrincipalKeyInfo *principal_key_info, GenericK { ereport(ERROR, (errmsg("failed to retrieve principal key. %d versions already exist", MAX_PRINCIPAL_KEY_VERSION_NUM))); + return NULL; } } return NULL; /* Just to keep compiler quite */ diff --git a/src/keyring/keyring_api.c b/src/keyring/keyring_api.c index 076779db..8fa56716 100644 --- a/src/keyring/keyring_api.c +++ b/src/keyring/keyring_api.c @@ -164,6 +164,8 @@ KeyringGenerateNewKeyAndStore(GenericKeyring *keyring, const char *key_name, uns if (KeyringStoreKey(keyring, key, throw_error) != KEYRING_CODE_SUCCESS) { pfree(key); + ereport(ereport_level, + (errmsg("Failed to store key on keyring. Please check the keyring configuration."))); return NULL; } return key; diff --git a/src/pg_tde_event_capture.c b/src/pg_tde_event_capture.c index 8d7794ae..1a7518f5 100644 --- a/src/pg_tde_event_capture.c +++ b/src/pg_tde_event_capture.c @@ -68,7 +68,6 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS) trigdata = (EventTriggerData *) fcinfo->context; parsetree = trigdata->parsetree; - elog(LOG, "EVENT TRIGGER (%s) %s", trigdata->event, nodeToString(parsetree)); reset_current_tde_create_event(); if (IsA(parsetree, IndexStmt)) From 7bcda21fb779c659fc0d1fdc3c903ea373d555b2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Dec 2024 17:52:57 +0200 Subject: [PATCH 07/19] Bump github/codeql-action from 3.27.6 to 3.27.9 (#384) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.27.6 to 3.27.9. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/aa578102511db1f4524ed59b8cc2bae4f6e88195...df409f7d9260372bd5f19e5b04e83cb3c43714ae) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/scorecard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 70634cd2..6744f9c9 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -43,6 +43,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@aa578102511db1f4524ed59b8cc2bae4f6e88195 # v3.27.6 + uses: github/codeql-action/upload-sarif@df409f7d9260372bd5f19e5b04e83cb3c43714ae # v3.27.9 with: sarif_file: results.sarif From b9726e7d0675300018738847307853b3dc5391a3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Dec 2024 17:53:19 +0200 Subject: [PATCH 08/19] Bump docker/setup-buildx-action from 3.7.1 to 3.8.0 (#383) Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3.7.1 to 3.8.0. - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/c47758b77c9736f4b2ef4073d4d51994fabfe349...6524bf65af31da8d45b59e8c27de4bd072b392f5) --- updated-dependencies: - dependency-name: docker/setup-buildx-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docker.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml index ac501d00..7225dbbb 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/docker.yaml @@ -17,7 +17,7 @@ jobs: steps: - name: Set up Docker Buildx - uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1 + uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 - name: Build uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6.10.0 From 1907c42e764d8231b2cdc9f9230e6a251640d1e0 Mon Sep 17 00:00:00 2001 From: Zsolt Parragi Date: Tue, 17 Dec 2024 13:01:41 +0000 Subject: [PATCH 09/19] PG-1259: Correctly use the beta tag everywhere where we deal with versions (#386) --- Makefile | 2 +- meson.build | 2 +- pg_tde--1.0.sql => pg_tde--1.0-beta2.sql | 0 pg_tde.control | 2 +- t/001_basic.pl | 3 +++ t/expected/001_basic.out | 2 ++ 6 files changed, 8 insertions(+), 3 deletions(-) rename pg_tde--1.0.sql => pg_tde--1.0-beta2.sql (100%) diff --git a/Makefile b/Makefile index f17f0ae5..4b6a32cb 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ PGFILEDESC = "pg_tde access method" MODULE_big = pg_tde EXTENSION = pg_tde -DATA = pg_tde--1.0.sql +DATA = pg_tde--1.0-beta2.sql REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/pg_tde/pg_tde.conf REGRESS = toast_decrypt_basic \ diff --git a/meson.build b/meson.build index e1566e6a..f4da8bac 100644 --- a/meson.build +++ b/meson.build @@ -81,7 +81,7 @@ endif install_data( 'pg_tde.control', - 'pg_tde--1.0.sql', + 'pg_tde--1.0-beta2.sql', kwargs: contrib_data_args, ) diff --git a/pg_tde--1.0.sql b/pg_tde--1.0-beta2.sql similarity index 100% rename from pg_tde--1.0.sql rename to pg_tde--1.0-beta2.sql diff --git a/pg_tde.control b/pg_tde.control index e72bb8d4..b36c1429 100644 --- a/pg_tde.control +++ b/pg_tde.control @@ -1,5 +1,5 @@ # pg_tde extension comment = 'pg_tde access method' -default_version = '1.0' +default_version = '1.0-beta2' module_pathname = '$libdir/pg_tde' relocatable = true diff --git a/t/001_basic.pl b/t/001_basic.pl index 45b885f6..914f4af9 100644 --- a/t/001_basic.pl +++ b/t/001_basic.pl @@ -30,6 +30,9 @@ ok($cmdret == 0, "CREATE PGTDE EXTENSION"); PGTDE::append_to_file($stdout); +my ($cmdret, $stdout, $stderr) = $node->psql('postgres', 'SELECT extname, extversion FROM pg_extension WHERE extname = \'pg_tde\';', extra_params => ['-a']); +ok($cmdret == 0, "SELECT PGTDE VERSION"); +PGTDE::append_to_file($stdout); $rt_value = $node->psql('postgres', 'CREATE TABLE test_enc(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap_basic;', extra_params => ['-a']); ok($rt_value == 3, "Failing query"); diff --git a/t/expected/001_basic.out b/t/expected/001_basic.out index c91f77dc..ab41e680 100644 --- a/t/expected/001_basic.out +++ b/t/expected/001_basic.out @@ -1,4 +1,6 @@ CREATE EXTENSION pg_tde; +SELECT extname, extversion FROM pg_extension WHERE extname = 'pg_tde'; +pg_tde|1.0-beta2 -- server restart CREATE TABLE test_enc(id SERIAL,k VARCHAR(32),PRIMARY KEY (id)) USING tde_heap_basic; INSERT INTO test_enc (k) VALUES ('foobar'),('barfoo'); From 8596e4385702bbb74d12a1b72b06fc6fb75a56e6 Mon Sep 17 00:00:00 2001 From: Andrew Pogrebnoi Date: Tue, 17 Dec 2024 18:55:40 +0200 Subject: [PATCH 10/19] Fix memory usage in the internal keys cache (#385) There was a mistake in the new cap calculations during the cache extension. It popped up only when a new cache size was multiple of a cache record (every 256 records). Which lead to the usage of the memory beyond an allocated size. This commit fixes it along with mlock size for reallocated pages. Also fixed a typo in a variable name. Fixes PG-1248 --- expected/cache_alloc.out | 125 +++++++++++++++++++++++++++++++++++++ meson.build | 1 + sql/cache_alloc.sql | 17 +++++ src/access/pg_tde_tdemap.c | 35 +++++++---- 4 files changed, 165 insertions(+), 13 deletions(-) create mode 100644 expected/cache_alloc.out create mode 100644 sql/cache_alloc.sql diff --git a/expected/cache_alloc.out b/expected/cache_alloc.out new file mode 100644 index 00000000..7b6bf201 --- /dev/null +++ b/expected/cache_alloc.out @@ -0,0 +1,125 @@ +-- We test cache so AM doesn't matter +-- Just checking there are no mem debug WARNINGs during the cache population +CREATE EXTENSION pg_tde; +SELECT pg_tde_add_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); + pg_tde_add_key_provider_file +------------------------------ + 1 +(1 row) + +SELECT pg_tde_set_principal_key('test-db-principal-key','file-vault'); + pg_tde_set_principal_key +-------------------------- + t +(1 row) + +do $$ + DECLARE idx integer; +begin + for idx in 0..700 loop + EXECUTE format('CREATE TABLE t%s (c1 int) USING tde_heap_basic', idx); + end loop; +end; $$; +DROP EXTENSION pg_tde cascade; +NOTICE: drop cascades to 701 other objects +DETAIL: drop cascades to table t0 +drop cascades to table t1 +drop cascades to table t2 +drop cascades to table t3 +drop cascades to table t4 +drop cascades to table t5 +drop cascades to table t6 +drop cascades to table t7 +drop cascades to table t8 +drop cascades to table t9 +drop cascades to table t10 +drop cascades to table t11 +drop cascades to table t12 +drop cascades to table t13 +drop cascades to table t14 +drop cascades to table t15 +drop cascades to table t16 +drop cascades to table t17 +drop cascades to table t18 +drop cascades to table t19 +drop cascades to table t20 +drop cascades to table t21 +drop cascades to table t22 +drop cascades to table t23 +drop cascades to table t24 +drop cascades to table t25 +drop cascades to table t26 +drop cascades to table t27 +drop cascades to table t28 +drop cascades to table t29 +drop cascades to table t30 +drop cascades to table t31 +drop cascades to table t32 +drop cascades to table t33 +drop cascades to table t34 +drop cascades to table t35 +drop cascades to table t36 +drop cascades to table t37 +drop cascades to table t38 +drop cascades to table t39 +drop cascades to table t40 +drop cascades to table t41 +drop cascades to table t42 +drop cascades to table t43 +drop cascades to table t44 +drop cascades to table t45 +drop cascades to table t46 +drop cascades to table t47 +drop cascades to table t48 +drop cascades to table t49 +drop cascades to table t50 +drop cascades to table t51 +drop cascades to table t52 +drop cascades to table t53 +drop cascades to table t54 +drop cascades to table t55 +drop cascades to table t56 +drop cascades to table t57 +drop cascades to table t58 +drop cascades to table t59 +drop cascades to table t60 +drop cascades to table t61 +drop cascades to table t62 +drop cascades to table t63 +drop cascades to table t64 +drop cascades to table t65 +drop cascades to table t66 +drop cascades to table t67 +drop cascades to table t68 +drop cascades to table t69 +drop cascades to table t70 +drop cascades to table t71 +drop cascades to table t72 +drop cascades to table t73 +drop cascades to table t74 +drop cascades to table t75 +drop cascades to table t76 +drop cascades to table t77 +drop cascades to table t78 +drop cascades to table t79 +drop cascades to table t80 +drop cascades to table t81 +drop cascades to table t82 +drop cascades to table t83 +drop cascades to table t84 +drop cascades to table t85 +drop cascades to table t86 +drop cascades to table t87 +drop cascades to table t88 +drop cascades to table t89 +drop cascades to table t90 +drop cascades to table t91 +drop cascades to table t92 +drop cascades to table t93 +drop cascades to table t94 +drop cascades to table t95 +drop cascades to table t96 +drop cascades to table t97 +drop cascades to table t98 +drop cascades to table t99 +and 601 other objects (see server log for list) diff --git a/meson.build b/meson.build index f4da8bac..800a8b8a 100644 --- a/meson.build +++ b/meson.build @@ -104,6 +104,7 @@ sql_tests = [ 'kmip_test_basic', 'alter_index_basic', 'merge_join_basic', + 'cache_alloc', ] tap_tests = [ diff --git a/sql/cache_alloc.sql b/sql/cache_alloc.sql new file mode 100644 index 00000000..de791ec1 --- /dev/null +++ b/sql/cache_alloc.sql @@ -0,0 +1,17 @@ +-- We test cache so AM doesn't matter +-- Just checking there are no mem debug WARNINGs during the cache population + +CREATE EXTENSION pg_tde; + +SELECT pg_tde_add_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_set_principal_key('test-db-principal-key','file-vault'); + +do $$ + DECLARE idx integer; +begin + for idx in 0..700 loop + EXECUTE format('CREATE TABLE t%s (c1 int) USING tde_heap_basic', idx); + end loop; +end; $$; + +DROP EXTENSION pg_tde cascade; diff --git a/src/access/pg_tde_tdemap.c b/src/access/pg_tde_tdemap.c index 366e8716..198f6a12 100644 --- a/src/access/pg_tde_tdemap.c +++ b/src/access/pg_tde_tdemap.c @@ -1460,40 +1460,49 @@ pg_tde_put_key_into_cache(RelFileNumber rel_num, RelKeyData *key) elog(ERROR, "could not mlock internal key initial cache page: %m"); tde_rel_key_cache->len = 0; - tde_rel_key_cache->cap = pageSize / sizeof(RelKeyCacheRec); + tde_rel_key_cache->cap = (pageSize - 1) / sizeof(RelKeyCacheRec); } /* * Add another mem page if there is no more room left for another key. We * allocate `current_memory_size` + 1 page and copy data there. */ - if (tde_rel_key_cache->len + 1 > - (tde_rel_key_cache->cap * sizeof(RelKeyCacheRec)) / sizeof(RelKeyCacheRec)) + if (tde_rel_key_cache->len == tde_rel_key_cache->cap) { size_t size; size_t old_size; - RelKeyCacheRec *chachePage; + RelKeyCacheRec *cachePage; - size = TYPEALIGN(pageSize, (tde_rel_key_cache->cap + 1) * sizeof(RelKeyCacheRec)); old_size = TYPEALIGN(pageSize, (tde_rel_key_cache->cap) * sizeof(RelKeyCacheRec)); + /* TODO: consider some formula for less allocations when caching a lot + * of objects. But on the other, hand it'll use more memory... + * E.g.: + * if (old_size < 0x8000) + * size = old_size * 2; + * else + * size = TYPEALIGN(pageSize, old_size + ((old_size + 3*256) >> 2)); + * + */ + size = old_size + pageSize; + #ifndef FRONTEND oldCtx = MemoryContextSwitchTo(TopMemoryContext); - chachePage = palloc_aligned(size, pageSize, MCXT_ALLOC_ZERO); + cachePage = palloc_aligned(size, pageSize, MCXT_ALLOC_ZERO); MemoryContextSwitchTo(oldCtx); #else - chachePage = aligned_alloc(pageSize, size); - memset(chachePage, 0, size); + cachePage = aligned_alloc(pageSize, size); + memset(cachePage, 0, size); #endif - memcpy(chachePage, tde_rel_key_cache->data, old_size); + memcpy(cachePage, tde_rel_key_cache->data, old_size); pfree(tde_rel_key_cache->data); - tde_rel_key_cache->data = chachePage; + tde_rel_key_cache->data = cachePage; - if (mlock(tde_rel_key_cache->data, pageSize) == -1) - elog(ERROR, "could not mlock internal key cache page: %m"); + if (mlock(tde_rel_key_cache->data, size) == -1) + elog(WARNING, "could not mlock internal key cache pages: %m"); - tde_rel_key_cache->cap = size / sizeof(RelKeyCacheRec); + tde_rel_key_cache->cap = (size - 1) / sizeof(RelKeyCacheRec); } rec = tde_rel_key_cache->data + tde_rel_key_cache->len; From e5074711698ef0b0e4dc08542a79a13944bf9fa5 Mon Sep 17 00:00:00 2001 From: Zsolt Parragi Date: Tue, 17 Dec 2024 17:31:20 +0000 Subject: [PATCH 11/19] Fixing documentation push permissions (#388) The default GITHUB_TOKEN should have proper permissions to push to the gh-pages branch, the ROBOT_TOKEN is deprecated. --- .github/workflows/docs.yaml | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 136d59ac..499bc45d 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -1,5 +1,6 @@ name: Docs on: + workflow_dispatch: {} push: branches: - main @@ -7,7 +8,7 @@ on: - "documentation/**" permissions: - contents: read + contents: write jobs: release: @@ -26,16 +27,6 @@ jobs: with: python-version: "3.x" - - name: Configure git - env: - ROBOT_TOKEN: ${{ secrets.ROBOT_TOKEN }} - run: | - git config --global url."https://percona-platform-robot:${ROBOT_TOKEN}@github.com".insteadOf "https://github.com" - git config user.name "GitHub Action" - git config user.email "github-action@users.noreply.github.com" - git config user.password "${ROBOT_TOKEN}" - echo "GIT_USER=percona-platform-robot:${ROBOT_TOKEN}" >> $GITHUB_ENV - - name: Install MkDocs run: | python -m pip install --upgrade pip From d0cf4791d8005d263e7e4c68b05430af293ef6f6 Mon Sep 17 00:00:00 2001 From: Zsolt Parragi Date: Tue, 17 Dec 2024 17:39:44 +0000 Subject: [PATCH 12/19] Documentation push fix: re-added user name and email (#389) --- .github/workflows/docs.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 499bc45d..a9c78173 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -27,6 +27,11 @@ jobs: with: python-version: "3.x" + - name: Configure git + run: | + git config user.name "GitHub Action" + git config user.email "github-action@users.noreply.github.com" + - name: Install MkDocs run: | python -m pip install --upgrade pip From 17c4bcf89242775ff5e37bf1ba85ea49ac72ac45 Mon Sep 17 00:00:00 2001 From: Zsolt Parragi Date: Tue, 17 Dec 2024 17:49:55 +0000 Subject: [PATCH 13/19] Documentation github action fix: mike order and check depth (#390) --- .github/workflows/docs.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index a9c78173..8ef6d4c4 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -21,6 +21,8 @@ jobs: steps: - name: Chekout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 # fetch all commits/branches - name: Set up Python uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 @@ -40,6 +42,6 @@ jobs: - name: Deploy run: | - mike deploy main -p mike set-default main -p mike retitle main "Beta" -p + mike deploy main -p From da25ee09c71a93e06d3d6c5afecc5be564d46928 Mon Sep 17 00:00:00 2001 From: Anastasia Alexandrova Date: Wed, 18 Dec 2024 12:27:08 +0100 Subject: [PATCH 14/19] PG-1013 Updated the functions document (#370) * PG-1013 Updated the functions document * Removed the pg_alter_keyring_provider --------- Co-authored-by: Artem Gavrilov --- documentation/docs/functions.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/documentation/docs/functions.md b/documentation/docs/functions.md index 2ddd4006..c91d3c8d 100644 --- a/documentation/docs/functions.md +++ b/documentation/docs/functions.md @@ -72,12 +72,19 @@ SELECT pg_tde_rotate_principal_key('name-of-the-new-principal-key', NULL); SELECT pg_tde_rotate_principal_key(NULL, 'name-of-the-new-provider'); ``` + ## pg_tde_is_encrypted -Tells if a table is using the `pg_tde` access method or not. +Tells if a table is encrypted using the `tde_heap` access method or not. + +To verify a table encryption, run the following statement: ``` SELECT pg_tde_is_encrypted('table_name'); ``` +You can also verify if the table in a custom schema is encrypted. Pass teh schema name for the function as follows: +``` +SELECT pg_tde_is_encrypted('schema.table_name'); +``` From 7997d3abde045c08843a103e8d687649a9dbb03d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Dec 2024 15:11:01 +0200 Subject: [PATCH 15/19] Bump actions/upload-artifact from 4.4.3 to 4.5.0 (#392) * Bump actions/upload-artifact from 4.4.3 to 4.5.0 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.4.3 to 4.5.0. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v4.4.3...6f51ac03b9356f520e9adb1b1b7802705f340c2b) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Update .github/workflows/scorecard.yml --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Artem Gavrilov --- .github/workflows/postgresql-16-ppg-package-pgxs.yml | 6 +++--- .github/workflows/postgresql-16-src-make-ssl11.yml | 2 +- .github/workflows/postgresql-16-src-make.yml | 2 +- .github/workflows/postgresql-16-src-meson.yml | 2 +- .github/workflows/postgresql-17-src-make.yml | 2 +- .github/workflows/postgresql-17-src-meson-perf.yml | 4 ++-- .github/workflows/postgresql-17-src-meson.yml | 2 +- .github/workflows/postgresql-pgdg-package-pgxs.yml | 6 +++--- .github/workflows/scorecard.yml | 2 +- 9 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/postgresql-16-ppg-package-pgxs.yml b/.github/workflows/postgresql-16-ppg-package-pgxs.yml index 03759366..93c3b015 100644 --- a/.github/workflows/postgresql-16-ppg-package-pgxs.yml +++ b/.github/workflows/postgresql-16-ppg-package-pgxs.yml @@ -107,7 +107,7 @@ jobs: working-directory: src/pg_tde - name: Report on test fail - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 if: ${{ failure() }} with: name: Regressions diff and postgresql log @@ -130,7 +130,7 @@ jobs: sudo cp /usr/lib/postgresql/16/lib/pg_tde* pgtde-ppg16/usr/lib/postgresql/16/lib/ - name: Upload tgz - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: pg_tde_ppg16_binary path: pgtde-ppg16 @@ -152,7 +152,7 @@ jobs: sudo dpkg -i --debug=7777 pgtde-ppg16.deb - name: Upload deb - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: pg_tde_deb path: pgtde-ppg16.deb diff --git a/.github/workflows/postgresql-16-src-make-ssl11.yml b/.github/workflows/postgresql-16-src-make-ssl11.yml index 05ab327b..adfae4ea 100644 --- a/.github/workflows/postgresql-16-src-make-ssl11.yml +++ b/.github/workflows/postgresql-16-src-make-ssl11.yml @@ -97,7 +97,7 @@ jobs: working-directory: src/contrib/pg_tde - name: Report on test fail - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 if: ${{ failure() }} with: name: Regressions diff and postgresql log diff --git a/.github/workflows/postgresql-16-src-make.yml b/.github/workflows/postgresql-16-src-make.yml index 994dd561..0f20716d 100644 --- a/.github/workflows/postgresql-16-src-make.yml +++ b/.github/workflows/postgresql-16-src-make.yml @@ -97,7 +97,7 @@ jobs: working-directory: src/contrib/pg_tde - name: Report on test fail - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 if: ${{ failure() }} with: name: Regressions diff and postgresql log diff --git a/.github/workflows/postgresql-16-src-meson.yml b/.github/workflows/postgresql-16-src-meson.yml index a2f35eb0..c305ebed 100644 --- a/.github/workflows/postgresql-16-src-meson.yml +++ b/.github/workflows/postgresql-16-src-meson.yml @@ -85,7 +85,7 @@ jobs: working-directory: src/build - name: Report on test fail - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 if: ${{ failure() }} with: name: Regressions diff and postgresql log diff --git a/.github/workflows/postgresql-17-src-make.yml b/.github/workflows/postgresql-17-src-make.yml index cc3fbcba..6dd648bc 100644 --- a/.github/workflows/postgresql-17-src-make.yml +++ b/.github/workflows/postgresql-17-src-make.yml @@ -97,7 +97,7 @@ jobs: working-directory: src/contrib/pg_tde - name: Report on test fail - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 if: ${{ failure() }} with: name: Regressions diff and postgresql log diff --git a/.github/workflows/postgresql-17-src-meson-perf.yml b/.github/workflows/postgresql-17-src-meson-perf.yml index 9409140d..91e5b8a9 100644 --- a/.github/workflows/postgresql-17-src-meson-perf.yml +++ b/.github/workflows/postgresql-17-src-meson-perf.yml @@ -79,7 +79,7 @@ jobs: working-directory: src/build - name: Report on test fail - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 if: ${{ failure() }} with: name: Regressions diff and postgresql log @@ -139,7 +139,7 @@ jobs: echo "EOF" >> $GITHUB_ENV working-directory: inst - - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: pr_perf_results path: inst/pr_perf_results diff --git a/.github/workflows/postgresql-17-src-meson.yml b/.github/workflows/postgresql-17-src-meson.yml index fdb5daf8..0c6a374a 100644 --- a/.github/workflows/postgresql-17-src-meson.yml +++ b/.github/workflows/postgresql-17-src-meson.yml @@ -80,7 +80,7 @@ jobs: working-directory: src/build - name: Report on test fail - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 if: ${{ failure() }} with: name: Regressions diff and postgresql log diff --git a/.github/workflows/postgresql-pgdg-package-pgxs.yml b/.github/workflows/postgresql-pgdg-package-pgxs.yml index 6fa071e1..55439095 100644 --- a/.github/workflows/postgresql-pgdg-package-pgxs.yml +++ b/.github/workflows/postgresql-pgdg-package-pgxs.yml @@ -104,7 +104,7 @@ jobs: working-directory: src/pg_tde - name: Report on test fail - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 if: ${{ failure() }} with: name: Regressions diff and postgresql log @@ -131,7 +131,7 @@ jobs: - name: Upload tgz env: POSTGRESQL_VERSION: ${{ matrix.postgresql-version }} - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: pg_tde_pgdg$POSTGRESQL_VERSION_binary path: pgtde-pgdg$POSTGRESQL_VERSION @@ -159,7 +159,7 @@ jobs: - name: Upload deb env: POSTGRESQL_VERSION: ${{ matrix.postgresql-version }} - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: pg_tde_deb path: pgtde-pgdg$POSTGRESQL_VERSION.deb diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 6744f9c9..df29dac5 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -35,7 +35,7 @@ jobs: publish_results: true - name: Upload results - uses: actions/upload-artifact@97a0fba1372883ab732affbe8f94b823f91727db # v3.pre.node20 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: SARIF file path: results.sarif From f36bac0779a3100220fdcd681892f4f688dd0c86 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Dec 2024 15:11:27 +0200 Subject: [PATCH 16/19] Bump github/codeql-action from 3.27.9 to 3.28.0 (#393) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.27.9 to 3.28.0. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/df409f7d9260372bd5f19e5b04e83cb3c43714ae...48ab28a6f5dbc2a99bf1e0131198dd8f1df78169) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/scorecard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index df29dac5..95de836e 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -43,6 +43,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@df409f7d9260372bd5f19e5b04e83cb3c43714ae # v3.27.9 + uses: github/codeql-action/upload-sarif@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 with: sarif_file: results.sarif From bf7f6ea8d6ec5b8746744d04eecd2ea7dc2a682a Mon Sep 17 00:00:00 2001 From: Anastasia Alexandrova Date: Thu, 26 Dec 2024 14:41:57 +0100 Subject: [PATCH 17/19] PG-1241 Documented KMIP integration and setup (#368) PG-1241 Documented KMIP integration and setup modified: documentation/docs/_images/tde-flow.png modified: documentation/docs/setup.md modified: documentation/docs/tde.md --- documentation/docs/_images/tde-flow.png | Bin 27733 -> 31099 bytes documentation/docs/functions.md | 20 +++- documentation/docs/setup.md | 121 +++++++++++++----------- documentation/docs/tde.md | 11 ++- documentation/mkdocs.yml | 1 - 5 files changed, 91 insertions(+), 62 deletions(-) diff --git a/documentation/docs/_images/tde-flow.png b/documentation/docs/_images/tde-flow.png index 08e3bdbb4f4e6c88d7a23d0b84b4583625ee58eb..09c4da2eba5a3c30a9538893507ebb41d29981c3 100644 GIT binary patch delta 21518 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 diff --git a/documentation/docs/functions.md b/documentation/docs/functions.md index c91d3c8d..a83d812a 100644 --- a/documentation/docs/functions.md +++ b/documentation/docs/functions.md @@ -21,7 +21,7 @@ Creates a new key provider for the database using a remote HashiCorp Vault serve The specified access parameters require permission to read and write keys at the location. ``` -SELECT pg_tde_add_key_provider_vault_v2('provider-name',:'secret_token','url','mount','ca_path'); +SELECT pg_tde_add_key_provider_vault_v2('provider-name','secret_token','url','mount','ca_path'); ``` where: @@ -33,6 +33,24 @@ where: All parameters can be either strings, or JSON objects [referencing remote parameters](external-parameters.md). +## pg_tde_add_key_provider_kmip + +Creates a new key provider for the database using a remote KMIP server. + +The specified access parameters require permission to read and write keys at the server. + +``` +SELECT pg_tde_add_key_provider_kmip('provider-name','kmip-IP', 5696, '/path_to/server_certificate.pem', '/path_to/client_key.pem'); +``` + +where: + +* `provider-name` is the name of the provider. You can specify any name, it's for you to identify the provider. +* `kmip-IP` is the IP address of a domain name of the KMIP server +* The port to communicate with the KMIP server. The default port is `5696`. +* `server-certificate` is the path to the certificate file for the KMIP server. +* `client key` is the path to the client key. + ## pg_tde_set_principal_key Sets the principal key for the database using the specified key provider. diff --git a/documentation/docs/setup.md b/documentation/docs/setup.md index 1f7509f5..e6e6c7e0 100644 --- a/documentation/docs/setup.md +++ b/documentation/docs/setup.md @@ -4,7 +4,7 @@ Load the `pg_tde` at the start time. The extension requires additional shared memory; therefore, add the `pg_tde` value for the `shared_preload_libraries` parameter and restart the `postgresql` instance. -1. Use the [ALTER SYSTEM](https://www.postgresql.org/docs/current/sql-altersystem.html) command from `psql` terminal to modify the `shared_preload_libraries` parameter. +1. Use the [ALTER SYSTEM](https://www.postgresql.org/docs/current/sql-altersystem.html) command from `psql` terminal to modify the `shared_preload_libraries` parameter. This requires superuser privileges. ``` ALTER SYSTEM SET shared_preload_libraries = 'pg_tde'; @@ -14,14 +14,14 @@ Load the `pg_tde` at the start time. The extension requires additional shared me * On Debian and Ubuntu: - ```{.bash data-prompt="$"} - $ sudo systemctl restart postgresql.service + ```sh + sudo systemctl restart postgresql.service ``` * On RHEL and derivatives - ```{.bash data-prompt="$"} - $ sudo systemctl restart postgresql-17 + ```sh + sudo systemctl restart postgresql-17 ``` 3. Create the extension using the [CREATE EXTENSION](https://www.postgresql.org/docs/current/sql-createextension.html) command. You must have the privileges of a superuser or a database owner to use this command. Connect to `psql` as a superuser for a database and run the following command: @@ -36,7 +36,7 @@ Load the `pg_tde` at the start time. The extension requires additional shared me You can have the `pg_tde` extension automatically enabled for every newly created database. Modify the template `template1` database as follows: - ``` + ```sh psql -d template1 -c 'CREATE EXTENSION pg_tde;' ``` @@ -44,65 +44,95 @@ Load the `pg_tde` at the start time. The extension requires additional shared me 1. Set up a key provider for the database where you have enabled the extension. - === "With HashiCorp Vault" + === "With KMIP server" + + Make sure you have obtained the root certificate for the KMIP server and the keypair for the client. The client key needs permissions to create / read keys on the server. Find the [configuration guidelines for the HashiCorp Vault Enterprise KMIP Secrets Engine](https://developer.hashicorp.com/vault/tutorials/enterprise/kmip-engine). + + For testing purposes, you can use the PyKMIP server which enables you to set up required certificates. To use a real KMIP server, make sure to obtain the valid certificates issued by the key management appliance. + + ``` + SELECT pg_tde_add_key_provider_kmip('provider-name','kmip-IP', 5696, '/path_to/server_certificate.pem', '/path_to/client_key.pem'); + ``` + + where: + + * `provider-name` is the name of the provider. You can specify any name, it's for you to identify the provider. + * `kmip-IP` is the IP address of a domain name of the KMIP server + * `port` is the port to communicate with the KMIP server. Typically used port is 5696. + * `server-certificate` is the path to the certificate file for the KMIP server. + * `client key` is the path to the client key. + + :material-information: Warning: This example is for testing purposes only: The Vault server setup is out of scope of this document. ``` - SELECT pg_tde_add_key_provider_vault_v2('provider-name',:'secret_token','url','mount','ca_path'); + SELECT pg_tde_add_key_provider_kmip('kmip','127.0.0.1', 5696, '/tmp/server_certificate.pem', '/tmp/client_key_jane_doe.pem'); + ``` + + === "With HashiCorp Vault" + + The Vault server setup is out of scope of this document. + + ```sql + SELECT pg_tde_add_key_provider_vault_v2('provider-name','root_token','url','mount','ca_path'); ``` where: * `url` is the URL of the Vault server * `mount` is the mount point where the keyring should store the keys - * `secret_token` is an access token with read and write access to the above mount point + * `root_token` is an access token with read and write access to the above mount point * [optional] `ca_path` is the path of the CA file used for SSL verification + :material-information: Warning: This example is for testing purposes only: - === "With keyring file" + ``` + SELECT pg_tde_add_key_provider_file_vault_v2('my-vault','https://vault.example.com','secret/data','hvs.zPuyktykA...example...ewUEnIRVaKoBzs2', NULL); + ``` + + === "With a keyring file" This setup is intended for development and stores the keys unencrypted in the specified data file. - ``` + ```sql SELECT pg_tde_add_key_provider_file('provider-name','/path/to/the/keyring/data.file'); ``` - :material-information: Warning: This example is for testing purposes only: + :material-information: Warning: This example is for testing purposes only: - ``` - SELECT pg_tde_add_key_provider_file('file-vault','/tmp/pg_tde_test_local_keyring.per'); - ``` + ```sql + SELECT pg_tde_add_key_provider_file('file-keyring','/tmp/pg_tde_test_local_keyring.per'); + ``` 2. Add a principal key - ``` + ```sql SELECT pg_tde_set_principal_key('name-of-the-principal-key', 'provider-name'); ``` :material-information: Warning: This example is for testing purposes only: - ``` + ```sql SELECT pg_tde_set_principal_key('test-db-master-key','file-vault'); ``` The key is auto-generated. - :material-information: Info: The key provider configuration is stored in the database catalog in an unencrypted table. See [how to use external reference to parameters](external-parameters.md) to add an extra security layer to your setup. + :material-information: Info: The key provider configuration is stored in the database catalog in an unencrypted table. See [how to use external reference to parameters](external-parameters.md) to add an extra security layer to your setup. -## WAL encryption configuration (tech preview) -After you [enabled `pg_tde`](#enable-extension) and started the Percona Server for PostgreSQL, a principal key and internal keys for WAL encryption are created. They are stored in the data directory so that after WAL encryption is enabled, any process that requires access to WAL (a recovery or a checkpointer) can use them for decryption. +## WAL encryption configuration -Now you need to instruct `pg_tde ` to encrypt WAL files by configuring WAL encryption. Here's how to do it: +After you [enabled `pg_tde`](#enable-extension) and started the Percona Server for PostgreSQL, a principal key and a keyring for WAL are created. Now you need to instruct `pg_tde ` to encrypt WAL files by configuring WAL encryption. -### Enable WAL level encryption +Here's how to do it: -1. Use the `ALTER SYSTEM SET` command. You need the privileges of the superuser to run this command: +1. Enable WAL level encryption using the `ALTER SYSTEM SET` command. You need the privileges of the superuser to run this command: - ``` + ```sql ALTER SYSTEM set pg_tde.wal_encrypt = on; ``` @@ -110,29 +140,23 @@ Now you need to instruct `pg_tde ` to encrypt WAL files by configuring WAL encry * On Debian and Ubuntu: - ```{.bash data-prompt="$"} - $ sudo systemctl restart postgresql.service + ```sh + sudo systemctl restart postgresql.service ``` * On RHEL and derivatives - ```{.bash data-prompt="$"} - $ sudo systemctl restart postgresql-17 + ```sh + sudo systemctl restart postgresql-17 ``` -On the server start +3. We highly recommend you to create your own keyring and rotate the principal key. This is because the default principal key is created from the local keyfile and is stored unencrypted. -### Rotate the principal key - -We highly recommend you to create your own keyring and rotate the principal key. This is because the default principal key is created from the local keyfile and is stored unencrypted. - -Rotating the principal key means re-encrypting internal keys used for WAL encryption with the new principal key. This process doesn't stop the database operation meaning that reads and writes can take place as usual during key rotation. - -1. Set up the key provider for WAL encryption + Set up the key provider for WAL encryption === "With HashiCorp Vault" - ``` + ```sql SELECT pg_tde_add_key_provider_vault_v2('PG_TDE_GLOBAL','provider-name',:'secret_token','url','mount','ca_path'); ``` @@ -150,32 +174,17 @@ Rotating the principal key means re-encrypting internal keys used for WAL encryp This setup is intended for development and stores the keys unencrypted in the specified data file. - ``` + ```sql SELECT pg_tde_add_key_provider_file('provider-name','/path/to/the/keyring/data.file'); ``` -2. Rotate the principal key. Don't forget to specify the `PG_TDE_GLOBAL` constant to rotate only the principal key for WAL. +4. Rotate the principal key. Don't forget to specify the `PG_TDE_GLOBAL` constant to rotate only the principal key for WAL. - ``` + ```sql SELECT pg_tde_rotate_principal_key('PG_TDE_GLOBAL', 'new-principal-key', 'provider-name'); ``` - Now all WAL files are encrypted for both encrypted and unencrypted tables. - -3. Verify the encryption by checking the `pg_tde.wal_encrypt` GUC (Grand Unified Configuration) parameter as follows: - - ``` - SELECT name, setting FROM pg_settings WHERE name = 'pg_tde.wal_encrypt'; - ``` - - ??? example "Sample output" - - ```{.text .no-copy} - - name | setting - --------------------+--------- - pg_tde.wal_encrypt | on - ``` +Now all WAL files are encrypted for both encrypted and unencrypted tables. ## Next steps diff --git a/documentation/docs/tde.md b/documentation/docs/tde.md index fea9136c..a33ea97f 100644 --- a/documentation/docs/tde.md +++ b/documentation/docs/tde.md @@ -6,10 +6,13 @@ Transparent Data Encryption is a technology to protect data at rest. The encrypt To encrypt the data, two types of keys are used: -* Table encryption keys (TEK) to encrypt user data. These keys are stored internally, near the data that they encrypt. -* The principal key to encrypt table keys. It is kept separately from the table keys and is managed externally. +* Internal encryption keys to encrypt user data. They are stored internally, near the data that they encrypt. +* The principal key to encrypt database keys. It is kept separately from the database keys and is managed externally in the key management store. -`pg_tde` is integrated with HashiCorp Vault server to store and manage principal keys. Only the back end KV Secrets Engine - Version 2 (API) is supported. +You have the following options to store and manage principal keys externally: + +* Use the HashiCorp Vault server. Only the back end KV Secrets Engine - Version 2 (API) is supported. +* Use the KMIP-compatible server. `pg_tde` has been tested with the [PyKMIP](https://pykmip.readthedocs.io/en/latest/server.html) server and [the HashiCorp Vault Enterprise KMIP Secrets Engine](https://www.vaultproject.io/docs/secrets/kmip). The encryption process is the following: @@ -17,7 +20,7 @@ The encryption process is the following: When a user creates an encrypted table using `pg_tde`, a new random key is generated for that table using the AES128 (AES-ECB) cipher algorithm. This key is used to encrypt all data the user inserts in that table. Eventually the encrypted data gets stored in the underlying storage. -The table itself is encrypted using the principal key. The principal key is stored externally in the Vault key management store. +The table itself is encrypted using the principal key. The principal key is stored externally in the key management store. Similarly when the user queries the encrypted table, the principal key is retrieved from the key store to decrypt the table. Then the same unique internal key for that table is used to decrypt the data, and unencrypted data gets returned to the user. So, effectively, every TDE table has a unique key, and each table key is encrypted using the principal key. diff --git a/documentation/mkdocs.yml b/documentation/mkdocs.yml index 3a66f920..60fdd44d 100644 --- a/documentation/mkdocs.yml +++ b/documentation/mkdocs.yml @@ -164,7 +164,6 @@ nav: - functions.md - Concepts: - "What is TDE": tde.md - # - wal-encryption.md - table-access-method.md - How to: - Use reference to external parameters: external-parameters.md From 69232561b73285f18fde5879c999dbcab136804c Mon Sep 17 00:00:00 2001 From: Anastasia Alexandrova Date: Thu, 26 Dec 2024 14:43:29 +0100 Subject: [PATCH 18/19] PG-1261 Documented pg_rewind limitation with (#387) WAL modified: documentation/docs/index.md --- documentation/docs/index.md | 1 + 1 file changed, 1 insertion(+) diff --git a/documentation/docs/index.md b/documentation/docs/index.md index 49ca25e1..55abaa7a 100644 --- a/documentation/docs/index.md +++ b/documentation/docs/index.md @@ -26,6 +26,7 @@ Lear more [what is Transparent Data Encryption](tde.md#how-does-it-work) and [wh * Keys in the local keyfile are stored unencrypted. For better security we recommend using the Key management storage. * System tables are currently not encrypted. +* `pg_rewind` doesn't work with encrypted WAL for now. We plan to fix it in future releases. :material-alert: Warning: Note that introducing encryption/decryption affects performance. Our benchmark tests show less than 10% performance overhead for most situations. However, in some specific applications such as those using JSONB operations, performance degradation might be higher. From ba8c576c4d4c75bdcb4e141de37c5e3dbec282e5 Mon Sep 17 00:00:00 2001 From: Anastasia Alexandrova Date: Thu, 26 Dec 2024 14:45:51 +0100 Subject: [PATCH 19/19] PG-1244 Added a limitation about KMS configuration update (#373) * PG-1244 Added a limitation about KMS configuration update --- documentation/docs/index.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/documentation/docs/index.md b/documentation/docs/index.md index 55abaa7a..23b8da07 100644 --- a/documentation/docs/index.md +++ b/documentation/docs/index.md @@ -26,8 +26,13 @@ Lear more [what is Transparent Data Encryption](tde.md#how-does-it-work) and [wh * Keys in the local keyfile are stored unencrypted. For better security we recommend using the Key management storage. * System tables are currently not encrypted. +* Currently you cannot update the configuration of an existing Key Management Store (KMS). If its configuration changes (e.g. your Vault server has a new URL), you must set up a new key provider in `pg_tde` and create new keys there. Both the KMS and PostgreSQL servers must be up and running during these changes. [Reach out to our experts](https://www.percona.com/about/contact) for assistance and to outline the best update path for you. + + We plan to introduce the way to update the configuration of an existing KMS in future releases. + * `pg_rewind` doesn't work with encrypted WAL for now. We plan to fix it in future releases. + :material-alert: Warning: Note that introducing encryption/decryption affects performance. Our benchmark tests show less than 10% performance overhead for most situations. However, in some specific applications such as those using JSONB operations, performance degradation might be higher. ## Versions and supported PostgreSQL deployments