From e7351f4eeccbe4cba0233e9ccdc1ea9eb79e048d Mon Sep 17 00:00:00 2001 From: Matej Baco Date: Mon, 13 Mar 2023 13:35:34 +0000 Subject: [PATCH] Add logs to rules --- app/config/collections.php | 4 ++-- app/controllers/api/proxy.php | 16 +++++++++++++++- app/controllers/general.php | 15 +++++++++++---- app/workers/certificates.php | 17 ++++++++--------- src/Appwrite/Utopia/Response/Model/Rule.php | 6 ++++++ 5 files changed, 42 insertions(+), 16 deletions(-) diff --git a/app/config/collections.php b/app/config/collections.php index 6964cf7269..9b54b61c6f 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -3064,10 +3064,10 @@ $collections = [ 'filters' => [], ], [ - '$id' => ID::custom('log'), + '$id' => ID::custom('logs'), 'type' => Database::VAR_STRING, 'format' => '', - 'size' => 16384, + 'size' => 1000000, 'signed' => true, 'required' => false, 'default' => null, diff --git a/app/controllers/api/proxy.php b/app/controllers/api/proxy.php index afea34858a..1e668ca6b5 100644 --- a/app/controllers/api/proxy.php +++ b/app/controllers/api/proxy.php @@ -106,6 +106,8 @@ App::post('/v1/proxy/rules') $events->setParam('ruleId', $rule->getId()); + $rule->setAttribute('logs', ''); + $response ->setStatusCode(Response::STATUS_CODE_CREATED) ->dynamic($rule, Response::MODEL_PROXY_RULE); @@ -154,8 +156,14 @@ App::get('/v1/proxy/rules') $filterQueries = Query::groupByType($queries)['filters']; + $rules = $dbForConsole->find('rules', $queries); + foreach ($rules as $rule) { + $certificate = $dbForConsole->getDocument('certificates', $rule->getAttribute('certificateId', '')); + $rule->setAttribute('logs', $certificate->getAttribute('logs', '')); + } + $response->dynamic(new Document([ - 'rules' => $dbForConsole->find('rules', $queries), + 'rules' => $rules, 'total' => $dbForConsole->count('rules', $filterQueries, APP_LIMIT_COUNT), ]), Response::MODEL_PROXY_RULE_LIST); }); @@ -182,6 +190,9 @@ App::get('/v1/proxy/rules/:ruleId') throw new Exception(Exception::RULE_NOT_FOUND); } + $certificate = $dbForConsole->getDocument('certificates', $rule->getAttribute('certificateId', '')); + $rule->setAttribute('logs', $certificate->getAttribute('logs', '')); + $response->dynamic($rule, Response::MODEL_PROXY_RULE); }); @@ -278,5 +289,8 @@ App::patch('/v1/proxy/rules/:ruleId/verification') $events->setParam('ruleId', $rule->getId()); + $certificate = $dbForConsole->getDocument('certificates', $rule->getAttribute('certificateId', '')); + $rule->setAttribute('logs', $certificate->getAttribute('logs', '')); + $response->dynamic($rule, Response::MODEL_PROXY_RULE); }); \ No newline at end of file diff --git a/app/controllers/general.php b/app/controllers/general.php index b9a778972d..efac71dfa3 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -43,7 +43,9 @@ Config::setParam('domainVerification', false); Config::setParam('cookieDomain', 'localhost'); Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE); -function router(Database $dbForConsole, string $host, SwooleRequest $swooleRequest, Response $response) { +function router(Database $dbForConsole, SwooleRequest $swooleRequest, Response $response) { + $host = $swooleRequest->header['host'] ?? ''; + $route = Authorization::skip( fn() => $dbForConsole->find('rules', [ Query::equal('domain', [$host]), @@ -66,6 +68,12 @@ function router(Database $dbForConsole, string $host, SwooleRequest $swooleReque } } + // Skip Appwrite Router for ACME challenge. Nessessary for certificate generation + $path = ($swooleRequest->server['request_uri'] ?? ''); + if(\str_starts_with($path, '/.well-known/acme-challenge')) { + return false; + } + $type = $route->getAttribute('resourceType'); if($type === 'function') { @@ -164,7 +172,7 @@ App::init() $mainDomain = App::getEnv('_APP_DOMAIN', ''); // Only run Router when external domain if($host !== $mainDomain && $host !== 'localhost') { - if(router($dbForConsole, $host, $swooleRequest, $response)) { + if(router($dbForConsole, $swooleRequest, $response)) { return; } } @@ -454,7 +462,7 @@ App::options() $mainDomain = App::getEnv('_APP_DOMAIN', ''); // Only run Router when external domain if($host !== $mainDomain && $host !== 'localhost') { - if(router($dbForConsole, $host, $swooleRequest, $response)) { + if(router($dbForConsole, $swooleRequest, $response)) { return; } } @@ -660,7 +668,6 @@ App::get('/humans.txt') $response->text($template->render(false)); }); -// TODO: @Meldiron this must run before Appwrite Router. Router makes it NOT get here. But it needs to. This must be endpoint reserved by Appwrite for certificate generation and re-generation App::get('/.well-known/acme-challenge') ->desc('SSL Verification') ->label('scope', 'public') diff --git a/app/workers/certificates.php b/app/workers/certificates.php index 6513e6c89a..3a5cebea61 100644 --- a/app/workers/certificates.php +++ b/app/workers/certificates.php @@ -47,7 +47,7 @@ class CertificatesV1 extends Worker * 4. Validate security email. Cannot be empty, required by LetsEncrypt * 5. Validate renew date with certificate file, unless requested to skip by parameter * 6. Issue a certificate using certbot CLI - * 7. Update 'log' attribute on certificate document with Certbot message + * 7. Update 'logs' attribute on certificate document with Certbot message * 8. Create storage folder for certificate, if not ready already * 9. Move certificates from Certbot location to our Storage * 10. Create/Update our Storage with new Traefik config with new certificate paths @@ -57,7 +57,7 @@ class CertificatesV1 extends Worker * If at any point unexpected error occurs, program stops without applying changes to document, and error is thrown into worker * * If code stops with expected error: - * 1. 'log' attribute on document is updated with error message + * 1. 'logs' attribute on document is updated with error message * 2. 'attempts' amount is increased * 3. Console log is shown * 4. Email is sent to security email @@ -113,11 +113,8 @@ class CertificatesV1 extends Worker $letsEncryptData = $this->issueCertificate($folder, $domain->get(), $email); // Command succeeded, store all data into document - // We store stderr too, because it may include warnings - $certificate->setAttribute('log', \json_encode([ - 'stdout' => $letsEncryptData['stdout'], - 'stderr' => $letsEncryptData['stderr'], - ])); + $logs = 'Certificate successfully generated.'; + $certificate->setAttribute('logs', \mb_strcut($logs, 0, 1000000));// Limit to 1MB // Give certificates to Traefik $this->applyCertificateFiles($folder, $domain->get(), $letsEncryptData); @@ -129,8 +126,10 @@ class CertificatesV1 extends Worker $success = true; } catch (Throwable $e) { + $logs = $e->getMessage(); + // Set exception as log in certificate document - $certificate->setAttribute('log', $e->getMessage()); + $certificate->setAttribute('logs', \mb_strcut($logs, 0, 1000000));// Limit to 1MB // Increase attempts count $attempts = $certificate->getAttribute('attempts', 0) + 1; @@ -280,7 +279,7 @@ class CertificatesV1 extends Worker $stderr = ''; $staging = (App::isProduction()) ? '' : ' --dry-run'; - $exit = Console::execute("certbot certonly --webroot --noninteractive --agree-tos{$staging}" + $exit = Console::execute("certbot certonly -v --webroot --noninteractive --agree-tos{$staging}" . " --email " . $email . " --cert-name " . $folder . " -w " . APP_STORAGE_CERTIFICATES diff --git a/src/Appwrite/Utopia/Response/Model/Rule.php b/src/Appwrite/Utopia/Response/Model/Rule.php index ce96fafcac..0f9f996b10 100644 --- a/src/Appwrite/Utopia/Response/Model/Rule.php +++ b/src/Appwrite/Utopia/Response/Model/Rule.php @@ -54,6 +54,12 @@ class Rule extends Model 'default' => false, 'example' => 'verified', ]) + ->addRule('logs', [ + 'type' => self::TYPE_STRING, + 'description' => 'Certificate generation logs. This will return an empty string if generation did not run, or succeeded.', + 'default' => '', + 'example' => 'HTTP challegne failed.', + ]) ; }