1
0
Fork 0
mirror of synced 2024-06-29 19:50:26 +12:00
appwrite/app/workers/certificates.php

214 lines
7.6 KiB
PHP
Raw Normal View History

2020-02-23 21:58:11 +13:00
<?php
use Appwrite\Database\Database;
2020-07-06 07:46:04 +12:00
use Appwrite\Database\Adapter\MySQL as MySQLAdapter;
use Appwrite\Database\Adapter\Redis as RedisAdapter;
use Appwrite\Database\Validator\Authorization;
2020-06-12 07:36:10 +12:00
use Appwrite\Network\Validator\CNAME;
use Appwrite\Resque\Worker;
use Utopia\App;
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\Domains\Domain;
2020-02-23 21:58:11 +13:00
2021-09-01 21:13:23 +12:00
require_once __DIR__ . '/../workers.php';
2020-02-23 21:58:11 +13:00
2021-01-15 19:02:48 +13:00
Console::title('Certificates V1 Worker');
2021-09-01 21:13:23 +12:00
Console::success(APP_NAME . ' certificates worker v1 has started');
2020-02-23 21:58:11 +13:00
class CertificatesV1 extends Worker
2020-02-23 21:58:11 +13:00
{
public function init(): void
2020-02-23 21:58:11 +13:00
{
}
public function run(): void
2020-02-23 21:58:11 +13:00
{
2020-07-06 07:46:04 +12:00
global $register;
2021-06-28 19:19:33 +12:00
$db = $register->get('db');
$cache = $register->get('cache');
2020-07-06 07:46:04 +12:00
$consoleDB = new Database();
2021-06-28 19:19:33 +12:00
$consoleDB->setAdapter(new RedisAdapter(new MySQLAdapter($db, $cache), $cache));
2020-07-06 07:46:04 +12:00
$consoleDB->setNamespace('app_console'); // Main DB
$consoleDB->setMocks(Config::getParam('collections', []));
2020-02-23 21:58:11 +13:00
/**
2020-02-24 06:05:42 +13:00
* 1. Get new domain document - DONE
* 1.1. Validate domain is valid, public suffix is known and CNAME records are verified - DONE
* 2. Check if a certificate already exists - DONE
2020-05-28 19:00:19 +12:00
* 3. Check if certificate is about to expire, if not - skip it
2020-02-23 21:58:11 +13:00
* 3.1. Create / renew certificate
* 3.2. Update loadblancer
* 3.3. Update database (domains, change date, expiry)
* 3.4. Set retry on failure
2020-05-28 19:00:19 +12:00
* 3.5. Schedule to renew certificate in 60 days
2020-02-23 21:58:11 +13:00
*/
Authorization::disable();
2020-03-01 19:33:19 +13:00
// Args
$document = $this->args['document'];
$domain = $this->args['domain'];
// Validation Args
2020-10-15 10:34:57 +13:00
$validateTarget = $this->args['validateTarget'] ?? true;
$validateCNAME = $this->args['validateCNAME'] ?? true;
2021-09-01 21:13:23 +12:00
2020-03-01 19:33:19 +13:00
// Options
$domain = new Domain((!empty($domain)) ? $domain : '');
$expiry = 60 * 60 * 24 * 30 * 2; // 60 days
$safety = 60 * 60; // 1 hour
2020-06-20 23:20:49 +12:00
$renew = (\time() + $expiry);
2020-02-23 21:58:11 +13:00
2021-09-01 21:13:23 +12:00
if (empty($domain->get())) {
2020-02-23 21:58:11 +13:00
throw new Exception('Missing domain');
}
2021-09-01 21:13:23 +12:00
if (!$domain->isKnown() || $domain->isTest()) {
2020-10-26 03:43:23 +13:00
throw new Exception('Unknown public suffix for domain');
2020-02-23 21:58:11 +13:00
}
2021-09-01 21:13:23 +12:00
if ($validateTarget) {
2020-06-29 08:45:36 +12:00
$target = new Domain(App::getEnv('_APP_DOMAIN_TARGET', ''));
2021-09-01 21:13:23 +12:00
if (!$target->isKnown() || $target->isTest()) {
throw new Exception('Unreachable CNAME target (' . $target->get() . '), please use a domain with a public suffix.');
2020-03-01 19:33:19 +13:00
}
2020-02-23 21:58:11 +13:00
}
2021-09-01 21:13:23 +12:00
if ($validateCNAME) {
2020-03-01 19:33:19 +13:00
$validator = new CNAME($target->get()); // Verify Domain with DNS records
2021-09-01 21:13:23 +12:00
if (!$validator->isValid($domain->get())) {
2020-03-01 19:33:19 +13:00
throw new Exception('Failed to verify domain DNS records');
}
2020-02-23 21:58:11 +13:00
}
$certificate = $consoleDB->getCollectionFirst([
2020-02-23 21:58:11 +13:00
'limit' => 1,
'offset' => 0,
'filters' => [
2021-09-01 21:13:23 +12:00
'$collection=' . Database::SYSTEM_COLLECTION_CERTIFICATES,
'domain=' . $domain->get(),
2020-02-23 21:58:11 +13:00
],
]);
2020-03-02 01:04:29 +13:00
// $condition = ($certificate
// && $certificate instanceof Document
// && isset($certificate['issueDate'])
// && (($certificate['issueDate'] + ($expiry)) > time())) ? 'true' : 'false';
// throw new Exception('cert issued at'.date('d.m.Y H:i', $certificate['issueDate']).' | renew date is: '.date('d.m.Y H:i', ($certificate['issueDate'] + ($expiry))).' | condition is '.$condition);
2020-03-02 00:20:39 +13:00
2020-02-23 21:58:11 +13:00
$certificate = (!empty($certificate) && $certificate instanceof $certificate) ? $certificate->getArrayCopy() : [];
2021-09-01 21:13:23 +12:00
if (
!empty($certificate)
2020-02-23 21:58:11 +13:00
&& isset($certificate['issueDate'])
2021-09-01 21:13:23 +12:00
&& (($certificate['issueDate'] + ($expiry)) > \time())
) { // Check last issue time
throw new Exception('Renew isn\'t required');
2020-02-23 21:58:11 +13:00
}
2020-06-30 09:42:21 +12:00
$staging = (App::isProduction()) ? '' : ' --dry-run';
2021-02-19 05:48:11 +13:00
$email = App::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS');
2020-02-24 06:45:51 +13:00
2021-09-01 21:13:23 +12:00
if (empty($email)) {
2021-02-19 05:48:11 +13:00
throw new Exception('You must set a valid security email address (_APP_SYSTEM_SECURITY_EMAIL_ADDRESS) to issue an SSL certificate');
}
$stdout = '';
$stderr = '';
$exit = Console::execute("certbot certonly --webroot --noninteractive --agree-tos{$staging}"
2021-09-01 21:13:23 +12:00
. " --email " . $email
. " -w " . APP_STORAGE_CERTIFICATES
. " -d {$domain->get()}", '', $stdout, $stderr);
2020-02-23 21:58:11 +13:00
2021-09-01 21:13:23 +12:00
if ($exit !== 0) {
throw new Exception('Failed to issue a certificate with message: ' . $stderr);
2020-02-23 21:58:11 +13:00
}
2020-02-25 22:21:56 +13:00
2021-09-01 21:13:23 +12:00
$path = APP_STORAGE_CERTIFICATES . '/' . $domain->get();
2020-02-25 22:21:56 +13:00
2021-09-01 21:13:23 +12:00
if (!\is_readable($path)) {
2020-06-20 23:20:49 +12:00
if (!\mkdir($path, 0755, true)) {
2020-02-25 22:21:56 +13:00
throw new Exception('Failed to create path...');
}
}
2021-09-01 21:13:23 +12:00
if (!@\rename('/etc/letsencrypt/live/' . $domain->get() . '/cert.pem', APP_STORAGE_CERTIFICATES . '/' . $domain->get() . '/cert.pem')) {
throw new Exception('Failed to rename certificate cert.pem: ' . \json_encode($stdout));
2020-02-24 06:05:42 +13:00
}
2020-02-23 21:58:11 +13:00
2021-09-01 21:13:23 +12:00
if (!@\rename('/etc/letsencrypt/live/' . $domain->get() . '/chain.pem', APP_STORAGE_CERTIFICATES . '/' . $domain->get() . '/chain.pem')) {
throw new Exception('Failed to rename certificate chain.pem: ' . \json_encode($stdout));
2020-02-25 06:02:23 +13:00
}
2020-02-25 05:47:45 +13:00
2021-09-01 21:13:23 +12:00
if (!@\rename('/etc/letsencrypt/live/' . $domain->get() . '/fullchain.pem', APP_STORAGE_CERTIFICATES . '/' . $domain->get() . '/fullchain.pem')) {
throw new Exception('Failed to rename certificate fullchain.pem: ' . \json_encode($stdout));
2020-02-25 06:02:23 +13:00
}
2020-02-25 05:47:45 +13:00
2021-09-01 21:13:23 +12:00
if (!@\rename('/etc/letsencrypt/live/' . $domain->get() . '/privkey.pem', APP_STORAGE_CERTIFICATES . '/' . $domain->get() . '/privkey.pem')) {
throw new Exception('Failed to rename certificate privkey.pem: ' . \json_encode($stdout));
2020-02-25 06:02:23 +13:00
}
2020-02-25 05:47:45 +13:00
2020-06-20 23:20:49 +12:00
$certificate = \array_merge($certificate, [
2020-02-23 21:58:11 +13:00
'$collection' => Database::SYSTEM_COLLECTION_CERTIFICATES,
'$permissions' => [
'read' => [],
'write' => [],
],
'domain' => $domain->get(),
2020-06-20 23:20:49 +12:00
'issueDate' => \time(),
2020-02-29 19:24:46 +13:00
'renewDate' => $renew,
2020-02-23 21:58:11 +13:00
'attempts' => 0,
2021-02-19 05:48:11 +13:00
'log' => \json_encode($stdout),
2020-02-23 21:58:11 +13:00
]);
$certificate = $consoleDB->createDocument($certificate);
2021-09-01 21:13:23 +12:00
if (!$certificate) {
2020-02-23 21:58:11 +13:00
throw new Exception('Failed saving certificate to DB');
}
2021-09-01 21:13:23 +12:00
if (!empty($document)) {
2020-06-20 23:20:49 +12:00
$document = \array_merge($document, [
'updated' => \time(),
2020-02-29 19:24:46 +13:00
'certificateId' => $certificate->getId(),
]);
2021-09-01 21:13:23 +12:00
2020-02-29 19:24:46 +13:00
$document = $consoleDB->updateDocument($document);
2021-09-01 21:13:23 +12:00
if (!$document) {
2020-02-29 19:24:46 +13:00
throw new Exception('Failed saving domain to DB');
}
2020-02-23 21:58:11 +13:00
}
2021-09-01 21:13:23 +12:00
$config =
"tls:
2020-02-25 23:04:12 +13:00
certificates:
- certFile: /storage/certificates/{$domain->get()}/fullchain.pem
keyFile: /storage/certificates/{$domain->get()}/privkey.pem";
2021-09-01 21:13:23 +12:00
if (!\file_put_contents(APP_STORAGE_CONFIG . '/' . $domain->get() . '.yml', $config)) {
2020-02-25 23:04:12 +13:00
throw new Exception('Failed to save SSL configuration');
}
2020-02-23 21:58:11 +13:00
2020-02-29 19:24:46 +13:00
ResqueScheduler::enqueueAt($renew + $safety, 'v1-certificates', 'CertificatesV1', [
'document' => [],
2020-03-02 00:14:55 +13:00
'domain' => $domain->get(),
'validateTarget' => $validateTarget,
'validateCNAME' => $validateCNAME,
2020-02-29 19:24:46 +13:00
]); // Async task rescheduale
2020-02-23 21:58:11 +13:00
Authorization::reset();
}
public function shutdown(): void
2020-02-23 21:58:11 +13:00
{
}
2021-09-01 21:13:23 +12:00
}