1
0
Fork 0
mirror of synced 2024-06-29 11:40:45 +12:00
appwrite/app/workers/certificates.php

192 lines
6.6 KiB
PHP
Raw Normal View History

2020-02-23 21:58:11 +13:00
<?php
use Database\Database;
use Database\Document;
use Database\Validator\Authorization;
use Network\Validators\CNAME;
2020-02-24 06:45:51 +13:00
use Utopia\App;
2020-02-23 21:58:11 +13:00
use Utopia\Domains\Domain;
require_once __DIR__.'/../init.php';
cli_set_process_title('Certificates V1 Worker');
echo APP_NAME.' certificates worker v1 has started';
class CertificatesV1
{
public $args = [];
public function setUp()
{
}
public function perform()
{
2020-02-24 06:45:51 +13:00
global $request, $consoleDB, $env;
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-02-23 21:58:11 +13:00
* 3. Check if certificate is not about to expire skip
* 3.1. Create / renew certificate
* 3.2. Update loadblancer
* 3.3. Update database (domains, change date, expiry)
* 3.4. Set retry on failure
*/
Authorization::disable();
2020-03-01 19:33:19 +13:00
// Args
$document = $this->args['document'];
$domain = $this->args['domain'];
// Validation Args
$validateTarget = (isset($this->args['validateTarget'])) ? $this->args['validateTarget'] : true;
$validateCNAME = (isset($this->args['validateCNAME'])) ? $this->args['validateCNAME'] : true;
// Options
$domain = new Domain((!empty($domain)) ? $domain : '');
$expiry = 60 * 60 * 24 * 30 * 2; // 60 days
$safety = 60 * 60; // 1 hour
$renew = (time() + $expiry);
2020-02-23 21:58:11 +13:00
if(empty($domain->get())) {
throw new Exception('Missing domain');
}
if(!$domain->isKnown() || $domain->isTest()) {
throw new Exception('Unkown public suffix for domain');
}
2020-03-01 19:33:19 +13:00
if($validateTarget) {
$target = new Domain($request->getServer('_APP_DOMAIN_TARGET', ''));
if(!$target->isKnown() || $target->isTest()) {
throw new Exception('Unreachable CNAME target ('.$target->get().'), plesse use a domain with a public suffix.');
}
2020-02-23 21:58:11 +13:00
}
2020-03-01 19:33:19 +13:00
if($validateCNAME) {
$validator = new CNAME($target->get()); // Verify Domain with DNS records
if(!$validator->isValid($domain->get())) {
throw new Exception('Failed to verify domain DNS records');
}
2020-02-23 21:58:11 +13:00
}
$certificate = $consoleDB->getCollection([
'limit' => 1,
'offset' => 0,
'orderField' => 'id',
'orderType' => 'ASC',
'orderCast' => 'string',
'filters' => [
'$collection='.Database::SYSTEM_COLLECTION_CERTIFICATES,
'domain='.$domain->get(),
],
'first' => true,
]);
$certificate = (!empty($certificate) && $certificate instanceof $certificate) ? $certificate->getArrayCopy() : [];
if($certificate
&& $certificate instanceof Document
&& isset($certificate['issueDate'])
2020-02-29 19:24:46 +13:00
&& (($certificate['issueDate'] + ($expiry)) > time())) { // Check last issue time
2020-02-23 21:58:11 +13:00
throw new Exception('Renew isn\'t required. Domain issued at '.date('d.m.Y H:i', (isset($certificate['issueDate']) ? $certificate['issueDate'] : 0)));
}
2020-02-24 06:45:51 +13:00
$staging = ($env === App::ENV_TYPE_PRODUCTION) ? '' : ' --dry-run';
$response = shell_exec("certbot certonly --webroot --noninteractive --agree-tos{$staging} --email security@appwrite.io \
2020-02-23 21:58:11 +13:00
-w ".APP_STORAGE_CERTIFICATES." \
2020-02-29 19:24:46 +13:00
-d {$domain->get()} 2>&1");
2020-02-23 21:58:11 +13:00
if(!$response) {
throw new Exception('Failed to issue a certificate');
}
2020-02-25 22:21:56 +13:00
$path = APP_STORAGE_CERTIFICATES.'/'.$domain->get();
if(!is_readable($path)) {
if (!mkdir($path, 0755, true)) {
throw new Exception('Failed to create path...');
}
}
2020-02-24 06:05:42 +13:00
2020-02-25 20:55:55 +13:00
if(!@rename('/etc/letsencrypt/live/'.$domain->get().'/cert.pem', APP_STORAGE_CERTIFICATES.'/'.$domain->get().'/cert.pem')) {
2020-02-25 21:42:14 +13:00
throw new Exception('Failed to rename certificate cert.pem: '.json_encode($response));
2020-02-24 06:05:42 +13:00
}
2020-02-23 21:58:11 +13:00
2020-02-25 20:55:55 +13:00
if(!@rename('/etc/letsencrypt/live/'.$domain->get().'/chain.pem', APP_STORAGE_CERTIFICATES.'/'.$domain->get().'/chain.pem')) {
2020-02-25 21:42:14 +13:00
throw new Exception('Failed to rename certificate chain.pem: '.json_encode($response));
2020-02-25 06:02:23 +13:00
}
2020-02-25 05:47:45 +13:00
2020-02-25 20:55:55 +13:00
if(!@rename('/etc/letsencrypt/live/'.$domain->get().'/fullchain.pem', APP_STORAGE_CERTIFICATES.'/'.$domain->get().'/fullchain.pem')) {
2020-02-25 21:42:14 +13:00
throw new Exception('Failed to rename certificate fullchain.pem: '.json_encode($response));
2020-02-25 06:02:23 +13:00
}
2020-02-25 05:47:45 +13:00
2020-02-25 20:55:55 +13:00
if(!@rename('/etc/letsencrypt/live/'.$domain->get().'/privkey.pem', APP_STORAGE_CERTIFICATES.'/'.$domain->get().'/privkey.pem')) {
2020-02-25 21:42:14 +13:00
throw new Exception('Failed to rename certificate privkey.pem: '.json_encode($response));
2020-02-25 06:02:23 +13:00
}
2020-02-25 05:47:45 +13:00
2020-02-23 21:58:11 +13:00
$certificate = array_merge($certificate, [
'$collection' => Database::SYSTEM_COLLECTION_CERTIFICATES,
'$permissions' => [
'read' => [],
'write' => [],
],
'domain' => $domain->get(),
'issueDate' => time(),
2020-02-29 19:24:46 +13:00
'renewDate' => $renew,
2020-02-23 21:58:11 +13:00
'attempts' => 0,
2020-02-25 01:55:24 +13:00
'log' => json_encode($response),
2020-02-23 21:58:11 +13:00
]);
$certificate = $consoleDB->createDocument($certificate);
if(!$certificate) {
throw new Exception('Failed saving certificate to DB');
}
2020-02-29 19:24:46 +13:00
if(!empty($document)) {
$document = array_merge($document, [
'updated' => time(),
'certificateId' => $certificate->getId(),
]);
$document = $consoleDB->updateDocument($document);
if(!$document) {
throw new Exception('Failed saving domain to DB');
}
2020-02-23 21:58:11 +13:00
}
2020-02-25 23:04:12 +13:00
$config =
"tls:
certificates:
- certFile: /storage/certificates/{$domain->get()}/fullchain.pem
keyFile: /storage/certificates/{$domain->get()}/privkey.pem";
if(!file_put_contents(APP_STORAGE_CONFIG.'/'.$domain->get().'.yml', $config)) {
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 tearDown()
{
// ... Remove environment for this job
}
}