Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Framework for multi-tenancy support #121

Merged
merged 14 commits into from
Feb 28, 2024
Merged

Conversation

codeforall
Copy link
Collaborator

The PR aims to build multi-tenancy support and contains multiple commits for the following.

  • Establishing a Framework for Master Key.
  • New Shared Cache Management Framework.
  • Adding catalog table for managing key providers.
  • Revamping the Keyring API Interface and Integrating Master Key.

Individual commit messages contain a more detailed description of changes.

Few Notes:

  • A few test cases might fail with PR, but that is because of the file location of the test file vault and not because of any functionality issue. We can take care of that as a separate commit after discussion.
  • Currently, the PR only creates a framework and implements a mechanism to install a key provider and master keys for a database. Altering, Deleting, and handling master-key dependency on key provider will be taken care of as a separate PR
  • Unifying master-key-info with keymap file is also a TODO item for the next PR

This commit introduces a user catalog table, percona_tde.pg_tde_key_provider,
within the percona_tde schema, as part of the pg_tde extension. The purpose of
this table is to store essential provider information. The catalog accommodates
various key providers, present and future, utilizing a JSON type
options field to capture provider-specific details.

To facilitate the creation of key providers,
the commit introduces new SQL interfaces:

- pg_tde_add_key_provider(provider_type VARCHAR(10),
        provider_name VARCHAR(128), options JSON)
- pg_tde_add_key_provider_file(provider_name VARCHAR(128),
        file_path TEXT)
- pg_tde_add_key_provider_vault_v2(provider_name VARCHAR(128),
        vault_token TEXT, vault_url TEXT,
        vault_mount_path TEXT, vault_ca_path TEXT)

Additionally, the commit implements the C interface for catalog
interaction, detailed in the 'tde_keyring.h' file.

These changes lay the foundation for implementing multi-tenancy in pg_tde by
eliminating the necessity of a 'keyring.json' file for configuring a
cluster-wide key provider. With this enhancement, each database can have its
dedicated key provider, added via SQL interface, removing the need
for DBA intervention in TDE setup."
Up until now, pg_tde relied on a hard-coded master key name, primarily for
proof-of-concept purposes. This commit introduces a more robust infrastructure
for configuring the master key and managing a dynamic shared memory-based
master-key cache to enhance accessibility.

For user interaction, a new SQL interface is provided:
- pg_tde_set_master_key(master_key_name VARCHAR(255), provider_name VARCHAR(255));
This interface enables users to set a master key for a specific database and make
further enhancements toward implementing the multi-tenancy.

In addition to the public SQL interface, the commit optimizes the internal
master-key API. It introduces straightforward Get and Set functions,
handling locking, retrieval, caching, and assignment of a master key for
a database seamlessly.

The commit also introduces a unified internal interface for requesting and
utilizing shared memory, contributing to a more cohesive and efficient
master key and cache management system.
This commit unifies the master-key and key-provider modules with the core of
pg_tde, marking a significant evolution in the architecture.
As part of this integration, the keyring API undergoes substantial changes
to enhance flexibility and remove unnecessary components such as the key cache.

As a result of the keyring refactoring, the file keyring is also rewritten,
offering a template for implementing additional key providers for the extension.
The modifications make the keyring API more pluggable, streamlining
interactions and paving the way for future enhancements.
…rements

This commit addresses PostgreSQL core's requirement for upfront information
regarding the number of locks needed by the extension. Given the connection
between locks and the shared memory interface, a new callback routine
is introduced. This routine allows modules to specify
the number of locks they require.

In addition to this functionality, the commit includes code cleanups
and adjustments to nomenclature for improved clarity and consistency.
pg_tde--1.0.sql Show resolved Hide resolved
@@ -1,6 +1,9 @@
-- test pg_tde_move_encrypted_data()
CREATE EXTENSION pg_tde;

SELECT pg_tde_add_key_provider_file('file-valut','/tmp/pg_tde_test_keyring.per');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here and in other files: valut looks like a typo

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed!

@@ -1,6 +1,9 @@
-- test https://github.com/Percona-Lab/pg_tde/issues/63
CREATE EXTENSION pg_tde;

SELECT pg_tde_add_key_provider_file('file-valut','/tmp/pg_tde_test_keyring.per');
--SELECT pg_tde_set_master_key('test-db-master-key','file-valut');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this commented out?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now we need to figure out a way to delete the files created by extension if the extension is deleted. So I have intentionally commented out these lines to take care of that. IMHO we can handle this as a separate PR

keyring_name = TextDatumGetCString(datum);

option_datum = heap_getattr(tuple, PG_TDE_KEY_PROVIDER_OPTIONS_ATTRNUM, tupDesc, &isnull);
/*keyring_options = TextDatumGetCString(option_datum);*/
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this commented out?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed!.

FileKeyring *file_keyring = palloc0(sizeof(FileKeyring));
/*
Datum file_type;
file_type = DirectFunctionCall2(json_object_field_text, keyring_options, CStringGetTextDatum(FILE_KEYRING_TYPE_KEY));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the duplicated commented out code?

{
ProviderType type; /* Must be the first field */
Oid keyId;
char keyName[128];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we add a define for 128?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

{
Oid databaseId;
uint32 keyVersion;
Oid keyringId;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indentation

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

} keyName;

typedef struct keyData
{
unsigned char data[32]; // maximum 256 bit encryption
unsigned char data[MAX_KEY_DATA_SIZE]; /* maximum 256 bit encryption */
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need this comment at both places

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

extern KeyringReturnCodes KeyringStoreKey(GenericKeyring *keyring, keyInfo *key, bool throw_error);
extern keyInfo *KeyringGetKey(GenericKeyring *keyring, const char *key_name, bool throw_error, KeyringReturnCodes *returnCode);

extern keyInfo *keyringGenerateNewKeyAndStore(GenericKeyring *keyring, const char *key_name, unsigned key_len, bool throw_error);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

KeyringGenerate to match the other functions?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not fixed, the k is still small, this file now has functions with different naming conventions

errmsg("Failed to open keyring file %s :%m", file_keyring->file_name)));
return KEYRING_CODE_RESOURCE_NOT_ACCESSABLE;
}
/* Write key to the end of file */
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this function also report an error if the key already exists in the file?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right! Committed the fix

Copy link
Member

@dAdAbird dAdAbird left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It also makes to run pgindent on new files.

@@ -18,9 +74,12 @@ RETURNS boolean
AS 'MODULE_PATHNAME'
LANGUAGE C;

CREATE FUNCTION pg_tde_set_master_key(master_key_name VARCHAR(255), provider_name VARCHAR(255))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In percona_tde.pg_tde_key_provider, provider_name len is 256 but here is 255

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing it out. I've updated the catalog definition.

src/catalog/tde_master_key.c Outdated Show resolved Hide resolved
src/catalog/tde_master_key.c Outdated Show resolved Hide resolved
* push the master key for current database to the shared memory cache
*/
static void
push_master_key_to_cache(TDEMasterKey *masterKey)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a TODO for the future: set a limit on the cache size (and add eviction policy). As I understand, now cache might grow indefinitely

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a correct observation. At the same time, we would only have one cache entry for each database, so I am not expecting the cache to grow insanely huge, So IMHO we can take care of this at a later stage. For now I have added a TODO note on the function.

codeforall and others added 5 commits February 26, 2024 21:05
Adjusting provider_name length in pg_tde_key_provider catalog
definition and adding a TODO note for implementing an eviction
policy for the master key cache.
This commit enhances the extension by adding a new mechanism to facilitate
cleanup or setup procedures when the extension is installed in a database.
The core addition is a function "pg_tde_extension_initialize" invoked upon
executing the database's 'CREATE EXTENSION' command.

To streamline future development and ensure extensibility,
the commit introduces a callback registration mechanism. This enables any module
to specify a callback function (registered using on_ext_install() ) to be
invoked during extension creation.

As of this commit, the callback functionality is explicitly utilized by the
master key module to handle the cleanup of the master key information file.
This file might persist in the database directory if the extension had been
previously deleted in the same database.

This enhancement paves the way for a more modular and maintainable extension
architecture, allowing individual modules to manage their specific
setup and cleanup tasks seamlessly."

Commit also contains the test case adjustments.
keyring = load_keyring_provider_options(provider_type, option_datum);
if (keyring)
{
strncpy(keyring->keyName, keyring_name, sizeof(keyring->keyName));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are missing a null check for keyring_name

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Catalog definition ensures that we don't get NULL keyring name

Datum file_path;
FileKeyring *file_keyring = palloc0(sizeof(FileKeyring));
file_path = DirectFunctionCall2(json_object_field_text, keyring_options, CStringGetTextDatum(FILE_KEYRING_PATH_KEY));
/* TODO check NULL and verify type */
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't be there an issue about this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adjusted the function definition to ensure we do not get a NULL JSON value

Assert(keyring != NULL);

masterKeyInfo = palloc(sizeof(TDEMasterKeyInfo));
masterKeyInfo->keyId = 0;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@codeforall ^ still a question


masterKey = palloc(sizeof(TDEMasterKey));
masterKey->databaseId = MyDatabaseId;
masterKey->keyVersion = 1;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extern KeyringReturnCodes KeyringStoreKey(GenericKeyring *keyring, keyInfo *key, bool throw_error);
extern keyInfo *KeyringGetKey(GenericKeyring *keyring, const char *key_name, bool throw_error, KeyringReturnCodes *returnCode);

extern keyInfo *keyringGenerateNewKeyAndStore(GenericKeyring *keyring, const char *key_name, unsigned key_len, bool throw_error);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not fixed, the k is still small, this file now has functions with different naming conventions

get_key_by_name(GenericKeyring* keyring, const char* key_name, bool throw_error, KeyringReturnCodes *returnCode)
{
keyInfo* key = NULL;
File file = -1;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indentation (at multiple places in this file)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to fix as many as I could find. It seems like my editor needs some setting fixes as everything appears correctly indented.


datafile = keyringParseStringParam(dataO);
static keyInfo*
get_key_by_name(GenericKeyring* keyring, const char* key_name, bool throw_error, KeyringReturnCodes *returnCode)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And this function also mixes naming styles (key_name and returnCode)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

Copy link
Collaborator

@dutow dutow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see no changes for keyring vault. Will it continue to work after these changes?

@@ -2,6 +2,9 @@
--
CREATE EXTENSION pg_tde;

SELECT pg_tde_add_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realized an issue with these changes: now the tests are hardcoded to only run with keyring file, previously we ran them both with keyring file and vault. With these changes, effectively we no longer execute vault tests.

If we go in this direction, we have to duplicate all test files, and later when we add additional providers, copy and copy them again and again. I don't think this is a good approach, but I do not see how we can do it differently, as the tests would require SQL commands before execution.

@codeforall codeforall merged commit 210c95c into percona:main Feb 28, 2024
8 checks passed
@codeforall codeforall linked an issue Feb 28, 2024 that may be closed by this pull request
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Provide SQL interface for Key management
3 participants