encryption worker and base encryption key rotation flow

This commit is contained in:
Damodar Lohani
2022-07-25 08:09:55 +00:00
parent 1f73c3e639
commit 394296f277
11 changed files with 206 additions and 34 deletions
+2
View File
@@ -152,6 +152,7 @@ ENV _APP_SERVER=swoole \
_APP_OPTIONS_ABUSE=enabled \
_APP_OPTIONS_FORCE_HTTPS=disabled \
_APP_OPENSSL_KEY_V1=your-secret-key \
_APP_KEY_ROTATION_INTERVAL=7776000 \
_APP_STORAGE_LIMIT=10000000 \
_APP_STORAGE_ANTIVIRUS=enabled \
_APP_STORAGE_ANTIVIRUS_HOST=clamav \
@@ -308,6 +309,7 @@ RUN chmod +x /usr/local/bin/doctor && \
chmod +x /usr/local/bin/worker-builds && \
chmod +x /usr/local/bin/worker-mails && \
chmod +x /usr/local/bin/worker-messaging && \
chmod +x /usr/local/bin/worker-encryption && \
chmod +x /usr/local/bin/worker-webhooks
# Letsencrypt Permissions
+11
View File
@@ -723,6 +723,17 @@ $collections = [
'array' => false,
'filters' => [],
],
[
'$id' => 'keyRotationDate',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => false,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
]
],
'indexes' => [
[
+1 -9
View File
@@ -92,15 +92,6 @@ App::post('/v1/projects')
'secret' => OpenSSL::secretString(),
])));
// create new secret for the project
// project should save keyId
// need setFilter to set instance level filter in dbforconsole
// saving keyId in project doesn't make sense, as we only need the secret to
// read and write the project document
// The problem is we need to know the projectId to set the filter
// but when we get dbForConsole we will never know the project id to work with
$project = $dbForConsole->createDocument('projects', new Document([
'$id' => $projectId,
'$read' => ['team:' . $teamId],
@@ -110,6 +101,7 @@ App::post('/v1/projects')
'teamId' => $team->getId(),
'description' => $description,
'keyId' => $secret->getId(),
'keyRotationDate' => time() + App::getEnv('_APP_KEY_ROTATION_INTERVAL', 60 * 60 * 24 * 90),
'logo' => $logo,
'url' => $url,
'version' => APP_VERSION_STABLE,
+2
View File
@@ -154,6 +154,8 @@ const APP_AUTH_TYPE_SESSION = 'Session';
const APP_AUTH_TYPE_JWT = 'JWT';
const APP_AUTH_TYPE_KEY = 'Key';
const APP_AUTH_TYPE_ADMIN = 'Admin';
// Encryption rotation types
const APP_ENCRYPTION_TYPE_PROJECT_MASTER_KEY = 'projectMasterKey';
// Response related
const MAX_OUTPUT_CHUNK_SIZE = 2 * 1024 * 1024; // 2MB
+25 -1
View File
@@ -6,6 +6,7 @@ global $register;
use Appwrite\Auth\Auth;
use Appwrite\Event\Certificate;
use Appwrite\Event\Delete;
use Appwrite\Event\Encrypt;
use Utopia\App;
use Utopia\Cache\Cache;
use Utopia\CLI\Console;
@@ -102,7 +103,7 @@ $cli
->trigger();
}
function renewCertificates($dbForConsole)
function renewCertificates(Database $dbForConsole)
{
$time = date('d-m-Y H:i:s', time());
$certificates = $dbForConsole->find('certificates', [
@@ -127,6 +128,29 @@ $cli
}
}
function rotateKeys(Database $dbForConsole)
{
$time = date('d-m-Y H:i:s', time());
$projects = $dbForConsole->find('projects', [
new Query('keyRotationDate', Query::TYPE_LESSEREQUAL, [\time()])
], 200);
if (\count($projects) > 0) {
Console::info("[{$time}] Found " . \count($projects) . " projects for key rotation, scheduling jobs.");
$event = new Encrypt();
foreach ($projects as $project) {
$event
->setType(APP_ENCRYPTION_TYPE_PROJECT_MASTER_KEY)
->setProject($project)
->trigger();
}
} else {
Console::info("[{$time}] No projects for key rotation.");
}
}
// # of days in seconds (1 day = 86400s)
$interval = (int) App::getEnv('_APP_MAINTENANCE_INTERVAL', '86400');
$executionLogsRetention = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_EXECUTION', '1209600');
+59
View File
@@ -0,0 +1,59 @@
<?php
use Appwrite\OpenSSL\OpenSSL;
use Appwrite\Resque\Worker;
use Utopia\App;
use Utopia\CLI\Console;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
require_once __DIR__ . '/../init.php';
Console::title('Encryption V1 Worker');
Console::success(APP_NAME . ' Encryption worker V1 has started', "\n");
class EncryptionV1 extends Worker
{
public function getName(): string
{
return 'encryption';
}
public function init(): void
{
}
public function run(): void
{
$type = $this->args['type'] ?? '';
switch($type) {
case APP_ENCRYPTION_TYPE_PROJECT_MASTER_KEY:
$project = new Document($this->args['project'] ?? []);
$this->rotateMasterKeyForProject($project);
break;
default:
Console::error('No encryption operation type: ' . $type);
}
}
protected function rotateMasterKeyForProject(Document $project): void
{
$projectId = $project->getId();
$dbForConsole = $this->getConsoleDB();
$oldKey = $project->getAttribute('keyId', '');
$secret = Authorization::skip(fn() => $dbForConsole->createDocument('secrets', new Document([
'$id' => $dbForConsole->getId(),
'$read' => [],
'$write' => [],
'$collection' => 'secrets',
'secret' => OpenSSL::secretString(),
])));
$dbForConsole->updateDocument('projects', $projectId,
$project
->setAttribute('keyId', $secret->getId())
->setAttribute('keyRotationDate', time() + App::getEnv('_APP_KEY_ROTATION_INTERVAL', 60 * 60 * 24 * 90))
);
}
}
+10
View File
@@ -0,0 +1,10 @@
#!/bin/sh
if [ -z "$_APP_REDIS_USER" ] && [ -z "$_APP_REDIS_PASS" ]
then
REDIS_BACKEND="${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
else
REDIS_BACKEND="redis://${_APP_REDIS_USER}:${_APP_REDIS_PASS}@${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
fi
INTERVAL=1 QUEUE='v1-encryption' APP_INCLUDE='/usr/src/code/app/workers/encryption.php' php /usr/src/code/vendor/bin/resque -dopcache.preload=opcache.preload=/usr/src/code/app/preload.php
Generated
+15 -24
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "380d806f7540199698d12a7abeb13534",
"content-hash": "677b1b47c8567f0b7b05645e2bbc7bc7",
"packages": [
{
"name": "adhocore/jwt",
@@ -2051,16 +2051,16 @@
},
{
"name": "utopia-php/database",
"version": "0.18.7",
"version": "0.18.9",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/database.git",
"reference": "d542ee433f1a545d926ffaf707bdf952dc18a52e"
"reference": "227b3ca919149b7b0d6556c8effe9ee46ed081e6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/database/zipball/d542ee433f1a545d926ffaf707bdf952dc18a52e",
"reference": "d542ee433f1a545d926ffaf707bdf952dc18a52e",
"url": "https://api.github.com/repos/utopia-php/database/zipball/227b3ca919149b7b0d6556c8effe9ee46ed081e6",
"reference": "227b3ca919149b7b0d6556c8effe9ee46ed081e6",
"shasum": ""
},
"require": {
@@ -2109,9 +2109,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/database/issues",
"source": "https://github.com/utopia-php/database/tree/0.18.7"
"source": "https://github.com/utopia-php/database/tree/0.18.9"
},
"time": "2022-07-11T10:20:33+00:00"
"time": "2022-07-19T09:42:53+00:00"
},
{
"name": "utopia-php/domains",
@@ -2387,16 +2387,16 @@
},
{
"name": "utopia-php/orchestration",
"version": "dev-cli-lib-upgrade",
"version": "0.6.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/orchestration.git",
"reference": "06f2afef516aca900ddb483689ebe6f8e7037d28"
"reference": "94263976413871efb6b16157a7101a81df3b6d78"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/orchestration/zipball/06f2afef516aca900ddb483689ebe6f8e7037d28",
"reference": "06f2afef516aca900ddb483689ebe6f8e7037d28",
"url": "https://api.github.com/repos/utopia-php/orchestration/zipball/94263976413871efb6b16157a7101a81df3b6d78",
"reference": "94263976413871efb6b16157a7101a81df3b6d78",
"shasum": ""
},
"require": {
@@ -2436,9 +2436,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/orchestration/issues",
"source": "https://github.com/utopia-php/orchestration/tree/cli-lib-upgrade"
"source": "https://github.com/utopia-php/orchestration/tree/0.6.0"
},
"time": "2022-07-13T14:55:12+00:00"
"time": "2022-07-13T16:47:18+00:00"
},
{
"name": "utopia-php/preloader",
@@ -5346,18 +5346,9 @@
"time": "2022-05-17T05:48:52+00:00"
}
],
"aliases": [
{
"package": "utopia-php/orchestration",
"version": "dev-cli-lib-upgrade",
"alias": "0.4.1",
"alias_normalized": "0.4.1.0"
}
],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {
"utopia-php/orchestration": 20
},
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
+26
View File
@@ -550,6 +550,32 @@ services:
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
appwrite-worker-encryption:
entrypoint: worker-encryption
<<: *x-logging
container_name: appwrite-worker-encryption
build:
context: .
networks:
- appwrite
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
- redis
environment:
- _APP_ENV
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
appwrite-maintenance:
entrypoint: maintenance
<<: *x-logging
+52
View File
@@ -0,0 +1,52 @@
<?php
namespace Appwrite\Event;
use Resque;
class Encrypt extends Event
{
protected string $type = '';
public function __construct()
{
parent::__construct(Event::ENCRYPTION_QUEUE_NAME, Event::ENCRYPTION_CLASS_NAME);
}
/**
* Sets the type for the delete event (use the constants starting with DELETE_TYPE_*).
*
* @param string $type
* @return self
*/
public function setType(string $type): self
{
$this->type = $type;
return $this;
}
/**
* Returns the set type for the delete event.
*
* @return string
*/
public function getType(): string
{
return $this->type;
}
/**
* Executes this event and sends it to the deletes worker.
*
* @return string|bool
* @throws \InvalidArgumentException
*/
public function trigger(): string|bool
{
return Resque::enqueue($this->queue, $this->class, [
'project' => $this->project,
'type' => $this->type,
]);
}
}
+3
View File
@@ -35,6 +35,9 @@ class Event
public const MESSAGING_QUEUE_NAME = 'v1-messaging';
public const MESSAGING_CLASS_NAME = 'MessagingV1';
public const ENCRYPTION_QUEUE_NAME = 'v1-encryption';
public const ENCRYPTION_CLASS_NAME = 'EncryptionV1';
protected string $queue = '';
protected string $class = '';
protected string $event = '';