diff --git a/app/config/collections.php b/app/config/collections.php index c671941968..e65f1fe3b3 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -481,6 +481,17 @@ $collections = [ 'array' => false, 'filters' => [], ], + [ + '$id' => 'keyId', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], [ '$id' => 'name', 'type' => Database::VAR_STRING, @@ -611,7 +622,7 @@ $collections = [ 'required' => false, 'default' => [], 'array' => false, - 'filters' => ['json', 'encrypt'], + 'filters' => ['json', 'masterEncrypt'], ], [ '$id' => 'jwtSecrets', @@ -622,7 +633,7 @@ $collections = [ 'required' => false, 'default' => [], 'array' => false, - 'filters' => ['encrypt'], + 'filters' => ['masterEncrypt'], ], [ '$id' => 'services', @@ -724,6 +735,56 @@ $collections = [ ], ], + 'secrets' => [ + '$collection' => Database::METADATA, + '$id' => 'secrets', + 'name' => 'Secrets', + 'attributes' => [ + [ + '$id' => 'projectInternalId', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'projectId', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'default' => 0, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'secrets', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16384, + 'signed' => true, + 'required' => false, + 'default' => '', + 'array' => false, + 'filters' => [], + ], + ], + 'indexes' => [ + [ + '$id' => '_key_project', + 'type' => Database::INDEX_KEY, + 'attributes' => ['projectInternalId'], + 'lengths' => [Database::LENGTH_KEY], + 'orders' => [Database::ORDER_ASC], + ], + ], + ], + 'platforms' => [ '$collection' => Database::METADATA, '$id' => 'platforms', diff --git a/app/init.php b/app/init.php index a2a319d8a0..24a163f30c 100644 --- a/app/init.php +++ b/app/init.php @@ -377,6 +377,43 @@ Database::addFilter( } ); +Database::addFilter( + 'masterEncrypt', + function(mixed $value, Document $document, Database $database) { + $keyId = $document->getAttribute('masterKeyId', ''); + $database->setNamespace('_console'); + $key = $database->getDocument('secrets', $keyId); + if($key->isEmpty()) { + throw new Exception("Unable to find master key with ID ($keyId} to encrypt."); + } + + $secret = $key->getAttribute('secret'); + $iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM)); + $tag = null; + return json_encode([ + 'data' => OpenSSL::encrypt($value, OpenSSL::CIPHER_AES_128_GCM, $secret, 0, $iv, $tag), + 'method' => OpenSSL::CIPHER_AES_128_GCM, + 'iv' => \bin2hex($iv), + 'tag' => \bin2hex($tag ?? ''), + 'version' => $keyId, + ]); + }, + function(mixed $value, Document $document, Database $database) { + if(is_null($value)) { + return null; + } + $value = json_decode($value, true); + $keyId = $value['version']; + $database->setNamespace('_console'); + $key = $database->getDocument('secrets', $keyId); + if($key->isEmpty()) { + throw new Exception("Unable to find master key with ID {$keyId} to decrypt."); + } + $secret = $key->getAttribute('secret'); + return OpenSSL::decrypt($value['data'], $value['method'], $secret, 0, hex2bin($value['iv']), hex2bin($value['tag'])); + } +); + Database::addFilter( 'encrypt', function (mixed $value) {