From 109df3a6602dcbb3dfc34610fe010e4ee971651e Mon Sep 17 00:00:00 2001 From: Ted Salmon Date: Mon, 22 Jul 2024 17:46:32 -0400 Subject: [PATCH 1/3] [IMP] Gene_EncryptionKeyManager: Add support for JSON fields * Add ability to re-encrypt columns with JSON blobs in them using dot notation. * Update docs --- Console/ReencryptColumn.php | 43 ++++++++++++++++++++++++++++--------- README.md | 2 +- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/Console/ReencryptColumn.php b/Console/ReencryptColumn.php index 2fcd439..3135f0b 100644 --- a/Console/ReencryptColumn.php +++ b/Console/ReencryptColumn.php @@ -6,6 +6,7 @@ use Magento\Framework\App\CacheInterface; use Magento\Framework\App\DeploymentConfig; use Magento\Framework\Encryption\EncryptorInterface; +use Magento\Framework\Serialize\Serializer\Json as JsonSerializer; use Magento\Framework\Console\Cli; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; @@ -21,14 +22,17 @@ class ReencryptColumn extends Command public const INPUT_KEY_COLUMN = 'column'; /** - * @param DeploymentConfig $deploymentConfig - * @param ResourceConnection $resourceConnection - * @param EncryptorInterface $encryptor - * @param CacheInterface $cache + * @param Magento\Framework\App\DeploymentConfig $deploymentConfig + * @param Magento\Framework\App\ResourceConnection $resourceConnection + * @param Magento\Framework\Serialize\Serializer\Json $jsonSerializer + * @param Magento\Framework\Encryption\EncryptorInterface $encryptor + * @param Magento\Framework\App\CacheInterface $cache + * @return void */ public function __construct( private readonly DeploymentConfig $deploymentConfig, private readonly ResourceConnection $resourceConnection, + private readonly JsonSerializer $jsonSerializer, private readonly EncryptorInterface $encryptor, private readonly CacheInterface $cache ) { @@ -105,8 +109,15 @@ protected function execute(InputInterface $input, OutputInterface $output): int if (!strlen($column)) { throw new \Exception('Provide an column'); } - $output->writeln("Looking for '$column' in '$table', identified by '$identifier'"); - + $jsonField = null; + if (strpos($column, '.') !== false) { + list($column, $jsonField) = explode('.', $column); + $output->writeln( + "Looking for JSON field '$jsonField.$column' in '$table', identified by '$identifier'" + ); + } else { + $output->writeln("Looking for '$column' in '$table', identified by '$identifier'"); + } /** * @see \Magento\Framework\Model\ResourceModel\Db\AbstractDb::_getLoadSelect() */ @@ -116,9 +127,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int $select = $connection->select() ->from($tableName, [$identifier, "$column"]) - ->where("($field LIKE '_:_:____%' OR $field LIKE '__:_:____%')") - ->where("$field NOT LIKE ?", "$latestKeyNumber:_:__%"); - + ->where("($field LIKE '%_:_:____%' OR $field LIKE '%__:_:____%')") + ->where("$field NOT LIKE ?", "%$latestKeyNumber:_:__%"); $result = $connection->fetchAll($select); if (empty($result)) { $output->writeln('No old entries found'); @@ -127,14 +137,27 @@ protected function execute(InputInterface $input, OutputInterface $output): int $connection->beginTransaction(); foreach ($result as $row) { $output->writeln(str_pad('', 120, '#')); - $output->writeln("$identifier: {$row[$identifier]}"); $value = $row[$column]; + $fieldData = []; + if ($jsonField !== null) { + $fieldData = $this->jsonSerializer->unserialize($value); + $value = $fieldData[$jsonField] ?? ''; + } + if ($value === '') { + continue; + } + $output->writeln("$identifier: {$row[$identifier]}"); $output->writeln("ciphertext_old: " . $value); $valueDecrypted = $this->encryptor->decrypt($value); $output->writeln("plaintext: " . $valueDecrypted); $valueEncrypted = $this->encryptor->encrypt($valueDecrypted); $output->writeln("ciphertext_new: " . $valueEncrypted); + if ($jsonField !== null) { + $fieldData[$jsonField] = $valueEncrypted; + $valueEncrypted = $this->jsonSerializer->serialize($fieldData); + } + if ($input->getOption(self::INPUT_KEY_FORCE)) { $connection->update( $tableName, diff --git a/README.md b/README.md index 9e419cf..7e1492b 100644 --- a/README.md +++ b/README.md @@ -176,7 +176,7 @@ Done ## bin/magento gene:encryption-key-manager:reencrypt-column -This allows you to target a specific column for re-encryption. +This allows you to target a specific column for re-encryption. If the column contains JSON, you can target it using dot notation: `column.field`. This command runs in dry run mode by default, do that as a first pass to see the changes that will be made. When you are happy run with `--force`. From ddfb0f7f2d27ac974fb2fec3bf6c3aff9c3a2ee8 Mon Sep 17 00:00:00 2001 From: Ted Salmon Date: Tue, 23 Jul 2024 17:03:46 -0400 Subject: [PATCH 2/3] [IMP] Gene_EncryptionKeyManager: Add conditional WHERE clauses --- Console/ReencryptColumn.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Console/ReencryptColumn.php b/Console/ReencryptColumn.php index 3135f0b..9779600 100644 --- a/Console/ReencryptColumn.php +++ b/Console/ReencryptColumn.php @@ -124,11 +124,15 @@ protected function execute(InputInterface $input, OutputInterface $output): int $tableName = $this->resourceConnection->getTableName($table); $connection = $this->resourceConnection->getConnection(); $field = $connection->quoteIdentifier(sprintf('%s.%s', $tableName, $column)); - $select = $connection->select() - ->from($tableName, [$identifier, "$column"]) - ->where("($field LIKE '%_:_:____%' OR $field LIKE '%__:_:____%')") - ->where("$field NOT LIKE ?", "%$latestKeyNumber:_:__%"); + ->from($tableName, [$identifier, "$column"]); + if ($jsonField === null) { + $select = $select->where("($field LIKE '_:_:____%' OR $field LIKE '__:_:____%')") + ->where("$field NOT LIKE ?", "$latestKeyNumber:_:__%"); + } else { + $select = $select->where("($field LIKE '{%_:_:____%}' OR $field LIKE '{%__:_:____%}')") + ->where("$field NOT LIKE ?", "{%$latestKeyNumber:_:__%}"); + } $result = $connection->fetchAll($select); if (empty($result)) { $output->writeln('No old entries found'); From 31c3eb4f3b54cdf68a3d27ce73ced8ef6ae23c91 Mon Sep 17 00:00:00 2001 From: Luke Rodgers Date: Wed, 24 Jul 2024 05:36:16 +0100 Subject: [PATCH 3/3] Add test for reencrypt-column json syntax. --- dev/test.sh | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/dev/test.sh b/dev/test.sh index a728487..57176d0 100755 --- a/dev/test.sh +++ b/dev/test.sh @@ -30,6 +30,13 @@ FAKE_RP_TOKEN=$(vendor/bin/n98-magerun2 dev:encrypt 'abc123') vendor/bin/n98-magerun2 db:query "update admin_user set rp_token='$FAKE_RP_TOKEN' where username='$ADMIN'" echo "Generated FAKE_RP_TOKEN=$FAKE_RP_TOKEN and assigned to $ADMIN" +echo "Generating a fake json column" +FAKE_JSON_PASSWORD=$(vendor/bin/n98-magerun2 dev:encrypt 'jsonpasswordabc123') +FAKE_JSON_PAYLOAD="{\"user\": \"foobar\", \"password\": \"$FAKE_JSON_PASSWORD\", \"request_url\": \"\"}" +vendor/bin/n98-magerun2 db:query 'DROP TABLE IF EXISTS fake_json_table; CREATE TABLE fake_json_table (id INT AUTO_INCREMENT PRIMARY KEY, text_column TEXT);' +vendor/bin/n98-magerun2 db:query "insert into fake_json_table(text_column) values ('$FAKE_JSON_PAYLOAD');" +vendor/bin/n98-magerun2 db:query "select * from fake_json_table"; + echo "";echo ""; echo "Verifying commands need to use --force" @@ -124,6 +131,18 @@ php bin/magento gene:encryption-key-manager:reencrypt-column admin_user user_id echo "PASS" echo "";echo ""; +echo "Running reencrypt-column on JSON column" +php bin/magento gene:encryption-key-manager:reencrypt-column fake_json_table id text_column.password --force > test.txt +cat test.txt +grep -q "$FAKE_JSON_PASSWORD" test.txt +grep -q jsonpasswordabc123 test.txt +echo "PASS" +echo "";echo ""; +echo "Running reencrypt-column on JSON column - again to verify it was all processed" +php bin/magento gene:encryption-key-manager:reencrypt-column fake_json_table id text_column.password --force | grep --context 999 'No old entries found' +echo "PASS" +echo "";echo ""; + echo "Running invalidate" php bin/magento gene:encryption-key-manager:invalidate --force grep -q invalidated_key app/etc/env.php