mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Improve UX of dns flows
This commit is contained in:
1 parent
d1918b7c24
commit
60dd501793
11 files changed
+426
-280
No files matched your search
@@ -1185,6 +1185,17 @@ return [
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('verificationLogs'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 2048,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => '',
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('redirectStatusCode'),
|
||||
'type' => Database::VAR_INTEGER,
|
||||
|
||||
@@ -7,9 +7,15 @@ use Utopia\Queue\Publisher;
|
||||
|
||||
class Certificate extends Event
|
||||
{
|
||||
public const ACTION_VERIFICATION = 'verification';
|
||||
public const ACTION_GENERATION = 'generation';
|
||||
public const ACTION_GENERATION_SYNC = 'generation_sync';
|
||||
|
||||
protected bool $skipRenewCheck = false;
|
||||
protected ?Document $domain = null;
|
||||
protected ?string $validationDomain = null;
|
||||
protected ?string $verificationDomainFunction = null; // For example: fra.cloud.appwrite.io
|
||||
protected ?string $verificationDomainApi = null; // For example: fra.appwrite.run
|
||||
protected string $action = self::ACTION_GENERATION;
|
||||
|
||||
public function __construct(protected Publisher $publisher)
|
||||
{
|
||||
@@ -58,26 +64,49 @@ class Certificate extends Event
|
||||
|
||||
|
||||
/**
|
||||
* Set override for main domain used for validation
|
||||
* Set verification domain function.
|
||||
*
|
||||
* @param string|null $validationDomain
|
||||
* @param ?string $verificationDomainFunction
|
||||
* @return self
|
||||
*/
|
||||
public function setValidationDomain(?string $validationDomain): self
|
||||
public function setVerificationDomainFunction(?string $verificationDomainFunction): self
|
||||
{
|
||||
$this->validationDomain = $validationDomain;
|
||||
$this->verificationDomainFunction = $verificationDomainFunction;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get validation domain
|
||||
* Get verification domain function.
|
||||
*
|
||||
* @return string|null
|
||||
* @return ?string
|
||||
*/
|
||||
public function getValidationDomain(): ?string
|
||||
public function getVerificationDomainFunction(): ?string
|
||||
{
|
||||
return $this->validationDomain;
|
||||
return $this->verificationDomainFunction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set verification domain api.
|
||||
*
|
||||
* @param ?string $verificationDomainApi
|
||||
* @return self
|
||||
*/
|
||||
public function setVerificationDomainApi(?string $verificationDomainApi): self
|
||||
{
|
||||
$this->verificationDomainApi = $verificationDomainApi;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get verification domain api.
|
||||
*
|
||||
* @return ?string
|
||||
*/
|
||||
public function getVerificationDomainApi(): ?string
|
||||
{
|
||||
return $this->verificationDomainApi;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,6 +119,28 @@ class Certificate extends Event
|
||||
return $this->skipRenewCheck;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set action for this certificate event.
|
||||
*
|
||||
* @param string $action
|
||||
* @return self
|
||||
*/
|
||||
public function setAction(string $action): self
|
||||
{
|
||||
$this->action = $action;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get action for this certificate event.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getAction(): string
|
||||
{
|
||||
return $this->action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the payload for the event
|
||||
@@ -102,7 +153,9 @@ class Certificate extends Event
|
||||
'project' => $this->project,
|
||||
'domain' => $this->domain,
|
||||
'skipRenewCheck' => $this->skipRenewCheck,
|
||||
'validationDomain' => $this->validationDomain
|
||||
'verificationDomainFunction' => $this->verificationDomainFunction,
|
||||
'verificationDomainApi' => $this->verificationDomainApi,
|
||||
'action' => $this->action
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,10 @@ class DNS extends Validator
|
||||
public const RECORD_CNAME = 'CNAME';
|
||||
public const RECORD_CAA = 'CAA'; // You can provide domain only (as $target) for CAA validation
|
||||
|
||||
protected const FAILURE_REASON_QUERY = 'DNS query failed.';
|
||||
protected const FAILURE_REASON_INTERNAL = 'Internal error occurred.';
|
||||
protected const FAILURE_REASON_UNKNOWN = '';
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
@@ -24,6 +28,11 @@ class DNS extends Validator
|
||||
*/
|
||||
protected string $dnsServer;
|
||||
|
||||
protected string $domain = '';
|
||||
protected array $recordValues = [];
|
||||
protected int $count = 0;
|
||||
protected string $reason = '';
|
||||
|
||||
/**
|
||||
* @param string $target
|
||||
*/
|
||||
@@ -41,7 +50,20 @@ class DNS extends Validator
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Invalid DNS record';
|
||||
if (!empty($this->reason)) {
|
||||
return $this->reason;
|
||||
}
|
||||
|
||||
if ($this->count === 0) {
|
||||
return 'Domain ' . $this->domain . ' does not have ' . $this->type . ' record.';
|
||||
}
|
||||
|
||||
$record = $this->count === 1 ? 'record' : 'records';
|
||||
$value = $this->count === 1 ? 'value' : 'values';
|
||||
|
||||
$recordValuesVerbose = implode(', ', $this->recordValues);
|
||||
|
||||
return "Domain {$this->domain} has {$this->count} {$this->type} {$record} with wrong {$value}: {$recordValuesVerbose}";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,7 +82,13 @@ class DNS extends Validator
|
||||
*/
|
||||
public function isValid($value): bool
|
||||
{
|
||||
$this->count = 0;
|
||||
$this->domain = $value;
|
||||
$this->reason = self::FAILURE_REASON_UNKNOWN;
|
||||
$this->recordValues = [];
|
||||
|
||||
if (!is_string($value)) {
|
||||
$this->reason = self::FAILURE_REASON_INTERNAL;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -77,10 +105,13 @@ class DNS extends Validator
|
||||
|
||||
$this->logs = $query;
|
||||
} catch (\Exception $e) {
|
||||
$this->reason = self::FAILURE_REASON_QUERY;
|
||||
$this->logs = ['error' => $e->getMessage()];
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->count = \count($query);
|
||||
|
||||
if (empty($query)) {
|
||||
// CAA records inherit from parent (custom CAA behaviour)
|
||||
if ($this->type === self::RECORD_CAA) {
|
||||
@@ -109,9 +140,12 @@ class DNS extends Validator
|
||||
$rdata = \trim($rdata, '"'); // certainly.com;validationmethods=tls-alpn-01;retrytimeout=3600
|
||||
$rdata = \explode(';', $rdata, 2)[0] ?? ''; // certainly.com
|
||||
|
||||
$this->recordValues[] = $rdata;
|
||||
if ($rdata === $this->target) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
$this->recordValues[] = $record->getRdata();
|
||||
}
|
||||
|
||||
if ($record->getRdata() === $this->target) {
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Proxy;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Validator\DNS;
|
||||
use Appwrite\Platform\Action;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Domains\Domain;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\AnyOf;
|
||||
use Utopia\Validator\IP;
|
||||
|
||||
class Base extends Action
|
||||
{
|
||||
/**
|
||||
* Verify or re-verify a rule
|
||||
*
|
||||
* @param Document $rule Rule to verify
|
||||
* @param Log|null $log Log instance to add timings to
|
||||
* @param string|null $verificationDomainApi Override for expected API rule value during verification
|
||||
* @param string|null $verificationDomainFunction Override for expected Function rule value during verification
|
||||
* @return void
|
||||
*/
|
||||
public static function verifyRule(Document $rule, ?Log $log = null, ?string $verificationDomainApi = null, ?string $verificationDomainFunction = null): void
|
||||
{
|
||||
if (\is_null($verificationDomainApi)) {
|
||||
$verificationDomainApi = System::getEnv('_APP_DOMAIN_TARGET_CNAME', '');
|
||||
}
|
||||
if (\is_null($verificationDomainFunction)) {
|
||||
$verificationDomainFunction = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
|
||||
}
|
||||
|
||||
$targetCNAME = null;
|
||||
$ruleType = $rule->getAttribute('type', '');
|
||||
$resourceType = $rule->getAttribute('deploymentResourceType', '');
|
||||
|
||||
if ($resourceType === 'function') {
|
||||
// For example: fra.appwrite.run
|
||||
$targetCNAME = new Domain($verificationDomainFunction);
|
||||
} elseif ($resourceType === 'site') {
|
||||
// For example: appwrite.network
|
||||
$targetCNAME = new Domain(System::getEnv('_APP_DOMAIN_SITES', ''));
|
||||
} elseif ($ruleType === 'api') {
|
||||
// For example: fra.cloud.appwrite.io
|
||||
$targetCNAME = new Domain($verificationDomainApi);
|
||||
} elseif ($ruleType === 'redirect') {
|
||||
// Shouldn't be needed, because redirect should always have resourceTyp too, but just in case we defailt to sites
|
||||
// For example: appwrite.network
|
||||
$targetCNAME = new Domain(System::getEnv('_APP_DOMAIN_SITES', ''));
|
||||
}
|
||||
|
||||
$validators = [];
|
||||
$mainValidator = null; // Validator to use for error description
|
||||
|
||||
if (!is_null($targetCNAME)) {
|
||||
if ($targetCNAME->isKnown() && !$targetCNAME->isTest()) {
|
||||
$validator = new DNS($targetCNAME->get(), DNS::RECORD_CNAME);
|
||||
$validators[] = $validator;
|
||||
|
||||
if (\is_null($mainValidator)) {
|
||||
$mainValidator = $validator;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((new IP(IP::V4))->isValid(System::getEnv('_APP_DOMAIN_TARGET_A', ''))) {
|
||||
$validator = new DNS(System::getEnv('_APP_DOMAIN_TARGET_A', ''), DNS::RECORD_A);
|
||||
$validators[] = $validator;
|
||||
|
||||
if (\is_null($mainValidator)) {
|
||||
$mainValidator = $validator;
|
||||
}
|
||||
}
|
||||
|
||||
if ((new IP(IP::V6))->isValid(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''))) {
|
||||
$validator = new DNS(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''), DNS::RECORD_AAAA);
|
||||
$validators[] = $validator;
|
||||
|
||||
if (\is_null($mainValidator)) {
|
||||
$mainValidator = $validator;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($validators)) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'At least one of domain targets environment variable must be configured.');
|
||||
}
|
||||
|
||||
$validator = new AnyOf($validators, AnyOf::TYPE_STRING);
|
||||
$domain = new Domain($rule->getAttribute('domain', ''));
|
||||
|
||||
$validationStart = \microtime(true);
|
||||
if (!$validator->isValid($domain->get())) {
|
||||
if (!\is_null($log)) {
|
||||
$log->addExtra('dnsTiming', \strval(\microtime(true) - $validationStart));
|
||||
$log->addTag('dnsDomain', $domain->get());
|
||||
|
||||
$errors = [];
|
||||
foreach ($validators as $validator) {
|
||||
if (!empty($validator->getLogs())) {
|
||||
$errors[] = $validator->getLogs();
|
||||
}
|
||||
}
|
||||
|
||||
$error = \implode("\n", $errors);
|
||||
$log->addExtra('dnsResponse', \is_array($error) ? \json_encode($error) : \strval($error));
|
||||
}
|
||||
|
||||
$errorVerbose = 'Verification of DNS records failed.' . $mainValidator->getDescription();
|
||||
throw new Exception(Exception::RULE_VERIFICATION_FAILED, $errorVerbose);
|
||||
}
|
||||
|
||||
// Ensure CAA won't block certificate issuance
|
||||
if (!empty(System::getEnv('_APP_DOMAIN_TARGET_CAA', ''))) {
|
||||
$validationStart = \microtime(true);
|
||||
$validator = new DNS(System::getEnv('_APP_DOMAIN_TARGET_CAA', ''), DNS::RECORD_CAA);
|
||||
if (!$validator->isValid($domain->get())) {
|
||||
if (!\is_null($log)) {
|
||||
$log->addExtra('dnsTimingCaa', \strval(\microtime(true) - $validationStart));
|
||||
$log->addTag('dnsDomain', $domain->get());
|
||||
$error = $validator->getDescription();
|
||||
$log->addExtra('dnsResponse', \is_array($error) ? \json_encode($error) : \strval($error));
|
||||
}
|
||||
|
||||
$errorVerbose = 'Verification of DNS records failed.' . $mainValidator->getDescription();
|
||||
throw new Exception(Exception::RULE_VERIFICATION_FAILED, $errorVerbose);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ namespace Appwrite\Platform\Modules\Proxy\Http\Rules\API;
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Validator\DNS;
|
||||
use Appwrite\Platform\Modules\Proxy\Base;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
@@ -15,14 +15,13 @@ use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Duplicate;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Domains\Domain;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\AnyOf;
|
||||
use Utopia\Validator\Domain as ValidatorDomain;
|
||||
use Utopia\Validator\IP;
|
||||
|
||||
class Create extends Action
|
||||
class Create extends Base
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
@@ -66,11 +65,19 @@ class Create extends Action
|
||||
->inject('queueForCertificates')
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForPlatform')
|
||||
->inject('log')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $domain, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform)
|
||||
{
|
||||
public function action(
|
||||
string $domain,
|
||||
Response $response,
|
||||
Document $project,
|
||||
Certificate $queueForCertificates,
|
||||
Event $queueForEvents,
|
||||
Database $dbForPlatform,
|
||||
Log $log
|
||||
) {
|
||||
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
|
||||
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
|
||||
|
||||
@@ -131,28 +138,6 @@ class Create extends Action
|
||||
if (\str_ends_with($domain->get(), $functionsDomain) || \str_ends_with($domain->get(), $sitesDomain)) {
|
||||
$status = 'verified';
|
||||
}
|
||||
if ($status === 'created') {
|
||||
$validators = [];
|
||||
$targetCNAME = new Domain(System::getEnv('_APP_DOMAIN_TARGET_CNAME', ''));
|
||||
if ($targetCNAME->isKnown() && !$targetCNAME->isTest()) {
|
||||
$validators[] = new DNS($targetCNAME->get(), DNS::RECORD_CNAME);
|
||||
}
|
||||
if ((new IP(IP::V4))->isValid(System::getEnv('_APP_DOMAIN_TARGET_A', ''))) {
|
||||
$validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_A', ''), DNS::RECORD_A);
|
||||
}
|
||||
if ((new IP(IP::V6))->isValid(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''))) {
|
||||
$validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''), DNS::RECORD_AAAA);
|
||||
}
|
||||
|
||||
if (empty($validators)) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'At least one of domain targets environment variable must be configured.');
|
||||
}
|
||||
|
||||
$validator = new AnyOf($validators, AnyOf::TYPE_STRING);
|
||||
if ($validator->isValid($domain->get())) {
|
||||
$status = 'verifying';
|
||||
}
|
||||
}
|
||||
|
||||
$owner = '';
|
||||
if (
|
||||
@@ -176,6 +161,15 @@ class Create extends Action
|
||||
'region' => $project->getAttribute('region')
|
||||
]);
|
||||
|
||||
if ($status === 'created') {
|
||||
try {
|
||||
self::verifyRule($rule, $log);
|
||||
$status = 'verifying';
|
||||
} catch (Exception $err) {
|
||||
$rule->setAttribute('verificationLogs', $err->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$rule = $dbForPlatform->createDocument('rules', $rule);
|
||||
} catch (Duplicate $e) {
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace Appwrite\Platform\Modules\Proxy\Http\Rules\Function;
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Validator\DNS;
|
||||
use Appwrite\Platform\Modules\Proxy\Base;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
@@ -16,15 +16,14 @@ use Utopia\Database\Exception\Duplicate;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Domains\Domain;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\AnyOf;
|
||||
use Utopia\Validator\Domain as ValidatorDomain;
|
||||
use Utopia\Validator\IP;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class Create extends Action
|
||||
class Create extends Base
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
@@ -71,11 +70,22 @@ class Create extends Action
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForPlatform')
|
||||
->inject('dbForProject')
|
||||
->inject('log')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $domain, string $functionId, string $branch, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform, Database $dbForProject)
|
||||
{
|
||||
public function action(
|
||||
string $domain,
|
||||
string $functionId,
|
||||
string $branch,
|
||||
Response $response,
|
||||
Document $project,
|
||||
Certificate $queueForCertificates,
|
||||
Event $queueForEvents,
|
||||
Database $dbForPlatform,
|
||||
Database $dbForProject,
|
||||
Log $log
|
||||
) {
|
||||
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
|
||||
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
|
||||
|
||||
@@ -143,28 +153,6 @@ class Create extends Action
|
||||
if (\str_ends_with($domain->get(), $functionsDomain) || \str_ends_with($domain->get(), $sitesDomain)) {
|
||||
$status = 'verified';
|
||||
}
|
||||
if ($status === 'created') {
|
||||
$validators = [];
|
||||
$targetCNAME = new Domain(System::getEnv('_APP_DOMAIN_TARGET_CNAME', ''));
|
||||
if ($targetCNAME->isKnown() && !$targetCNAME->isTest()) {
|
||||
$validators[] = new DNS($targetCNAME->get(), DNS::RECORD_CNAME);
|
||||
}
|
||||
if ((new IP(IP::V4))->isValid(System::getEnv('_APP_DOMAIN_TARGET_A', ''))) {
|
||||
$validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_A', ''), DNS::RECORD_A);
|
||||
}
|
||||
if ((new IP(IP::V6))->isValid(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''))) {
|
||||
$validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''), DNS::RECORD_AAAA);
|
||||
}
|
||||
|
||||
if (empty($validators)) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'At least one of domain targets environment variable must be configured.');
|
||||
}
|
||||
|
||||
$validator = new AnyOf($validators, AnyOf::TYPE_STRING);
|
||||
if ($validator->isValid($domain->get())) {
|
||||
$status = 'verifying';
|
||||
}
|
||||
}
|
||||
|
||||
$owner = '';
|
||||
if (
|
||||
@@ -194,6 +182,15 @@ class Create extends Action
|
||||
'region' => $project->getAttribute('region')
|
||||
]);
|
||||
|
||||
if ($status === 'created') {
|
||||
try {
|
||||
self::verifyRule($rule, $log);
|
||||
$status = 'verifying';
|
||||
} catch (Exception $err) {
|
||||
$rule->setAttribute('verificationLogs', $err->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$rule = $dbForPlatform->createDocument('rules', $rule);
|
||||
} catch (Duplicate $e) {
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace Appwrite\Platform\Modules\Proxy\Http\Rules\Redirect;
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Validator\DNS;
|
||||
use Appwrite\Platform\Modules\Proxy\Base;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
@@ -16,16 +16,15 @@ use Utopia\Database\Exception\Duplicate;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Domains\Domain;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\AnyOf;
|
||||
use Utopia\Validator\Domain as ValidatorDomain;
|
||||
use Utopia\Validator\IP;
|
||||
use Utopia\Validator\URL;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
class Create extends Action
|
||||
class Create extends Base
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
@@ -74,11 +73,24 @@ class Create extends Action
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForPlatform')
|
||||
->inject('dbForProject')
|
||||
->inject('log')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $domain, string $url, int $statusCode, string $resourceId, string $resourceType, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform, Database $dbForProject)
|
||||
{
|
||||
public function action(
|
||||
string $domain,
|
||||
string $url,
|
||||
int $statusCode,
|
||||
string $resourceId,
|
||||
string $resourceType,
|
||||
Response $response,
|
||||
Document $project,
|
||||
Certificate $queueForCertificates,
|
||||
Event $queueForEvents,
|
||||
Database $dbForPlatform,
|
||||
Database $dbForProject,
|
||||
Log $log
|
||||
) {
|
||||
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
|
||||
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
|
||||
|
||||
@@ -148,28 +160,6 @@ class Create extends Action
|
||||
if (\str_ends_with($domain->get(), $functionsDomain) || \str_ends_with($domain->get(), $sitesDomain)) {
|
||||
$status = 'verified';
|
||||
}
|
||||
if ($status === 'created') {
|
||||
$validators = [];
|
||||
$targetCNAME = new Domain(System::getEnv('_APP_DOMAIN_TARGET_CNAME', ''));
|
||||
if ($targetCNAME->isKnown() && !$targetCNAME->isTest()) {
|
||||
$validators[] = new DNS($targetCNAME->get(), DNS::RECORD_CNAME);
|
||||
}
|
||||
if ((new IP(IP::V4))->isValid(System::getEnv('_APP_DOMAIN_TARGET_A', ''))) {
|
||||
$validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_A', ''), DNS::RECORD_A);
|
||||
}
|
||||
if ((new IP(IP::V6))->isValid(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''))) {
|
||||
$validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''), DNS::RECORD_AAAA);
|
||||
}
|
||||
|
||||
if (empty($validators)) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'At least one of domain targets environment variable must be configured.');
|
||||
}
|
||||
|
||||
$validator = new AnyOf($validators, AnyOf::TYPE_STRING);
|
||||
if ($validator->isValid($domain->get())) {
|
||||
$status = 'verifying';
|
||||
}
|
||||
}
|
||||
|
||||
$owner = '';
|
||||
if (
|
||||
@@ -198,6 +188,15 @@ class Create extends Action
|
||||
'region' => $project->getAttribute('region')
|
||||
]);
|
||||
|
||||
if ($status === 'created') {
|
||||
try {
|
||||
self::verifyRule($rule, $log);
|
||||
$status = 'verifying';
|
||||
} catch (Exception $err) {
|
||||
$rule->setAttribute('verificationLogs', $err->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$rule = $dbForPlatform->createDocument('rules', $rule);
|
||||
} catch (Duplicate $e) {
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace Appwrite\Platform\Modules\Proxy\Http\Rules\Site;
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Validator\DNS;
|
||||
use Appwrite\Platform\Modules\Proxy\Base;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
@@ -16,15 +16,14 @@ use Utopia\Database\Exception\Duplicate;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Domains\Domain;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\AnyOf;
|
||||
use Utopia\Validator\Domain as ValidatorDomain;
|
||||
use Utopia\Validator\IP;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class Create extends Action
|
||||
class Create extends Base
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
@@ -71,11 +70,22 @@ class Create extends Action
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForPlatform')
|
||||
->inject('dbForProject')
|
||||
->inject('log')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $domain, string $siteId, string $branch, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform, Database $dbForProject)
|
||||
{
|
||||
public function action(
|
||||
string $domain,
|
||||
string $siteId,
|
||||
string $branch,
|
||||
Response $response,
|
||||
Document $project,
|
||||
Certificate $queueForCertificates,
|
||||
Event $queueForEvents,
|
||||
Database $dbForPlatform,
|
||||
Database $dbForProject,
|
||||
Log $log
|
||||
) {
|
||||
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
|
||||
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
|
||||
|
||||
@@ -143,28 +153,6 @@ class Create extends Action
|
||||
if (\str_ends_with($domain->get(), $functionsDomain) || \str_ends_with($domain->get(), $sitesDomain)) {
|
||||
$status = 'verified';
|
||||
}
|
||||
if ($status === 'created') {
|
||||
$validators = [];
|
||||
$targetCNAME = new Domain(System::getEnv('_APP_DOMAIN_TARGET_CNAME', ''));
|
||||
if ($targetCNAME->isKnown() && !$targetCNAME->isTest()) {
|
||||
$validators[] = new DNS($targetCNAME->get(), DNS::RECORD_CNAME);
|
||||
}
|
||||
if ((new IP(IP::V4))->isValid(System::getEnv('_APP_DOMAIN_TARGET_A', ''))) {
|
||||
$validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_A', ''), DNS::RECORD_A);
|
||||
}
|
||||
if ((new IP(IP::V6))->isValid(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''))) {
|
||||
$validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''), DNS::RECORD_AAAA);
|
||||
}
|
||||
|
||||
if (empty($validators)) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'At least one of domain targets environment variable must be configured.');
|
||||
}
|
||||
|
||||
$validator = new AnyOf($validators, AnyOf::TYPE_STRING);
|
||||
if ($validator->isValid($domain->get())) {
|
||||
$status = 'verifying';
|
||||
}
|
||||
}
|
||||
|
||||
$owner = '';
|
||||
if (
|
||||
@@ -194,6 +182,15 @@ class Create extends Action
|
||||
'region' => $project->getAttribute('region')
|
||||
]);
|
||||
|
||||
if ($status === 'created') {
|
||||
try {
|
||||
self::verifyRule($rule, $log);
|
||||
$status = 'verifying';
|
||||
} catch (Exception $err) {
|
||||
$rule->setAttribute('verificationLogs', $err->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$rule = $dbForPlatform->createDocument('rules', $rule);
|
||||
} catch (Duplicate $e) {
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace Appwrite\Platform\Modules\Proxy\Http\Rules\Verification;
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Validator\DNS;
|
||||
use Appwrite\Platform\Modules\Proxy\Base;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
@@ -17,11 +17,8 @@ use Utopia\Domains\Domain;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\AnyOf;
|
||||
use Utopia\Validator\IP;
|
||||
|
||||
class Update extends Action
|
||||
class Update extends Base
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
@@ -81,92 +78,21 @@ class Update extends Action
|
||||
throw new Exception(Exception::RULE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$targetCNAME = null;
|
||||
switch ($rule->getAttribute('type', '')) {
|
||||
case 'api':
|
||||
// For example: fra.cloud.appwrite.io
|
||||
$targetCNAME = new Domain(System::getEnv('_APP_DOMAIN_TARGET_CNAME', ''));
|
||||
break;
|
||||
case 'redirect':
|
||||
// For example: appwrite.network
|
||||
$targetCNAME = new Domain(System::getEnv('_APP_DOMAIN_SITES', ''));
|
||||
break;
|
||||
case 'deployment':
|
||||
switch ($rule->getAttribute('deploymentResourceType', '')) {
|
||||
case 'function':
|
||||
// For example: fra.appwrite.run
|
||||
$targetCNAME = new Domain(System::getEnv('_APP_DOMAIN_FUNCTIONS', ''));
|
||||
break;
|
||||
case 'site':
|
||||
// For example: appwrite.network
|
||||
$targetCNAME = new Domain(System::getEnv('_APP_DOMAIN_SITES', ''));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// no break
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
$validators = [];
|
||||
|
||||
if (!is_null($targetCNAME)) {
|
||||
if ($targetCNAME->isKnown() && !$targetCNAME->isTest()) {
|
||||
$validators[] = new DNS($targetCNAME->get(), DNS::RECORD_CNAME);
|
||||
}
|
||||
}
|
||||
|
||||
if ((new IP(IP::V4))->isValid(System::getEnv('_APP_DOMAIN_TARGET_A', ''))) {
|
||||
$validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_A', ''), DNS::RECORD_A);
|
||||
}
|
||||
if ((new IP(IP::V6))->isValid(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''))) {
|
||||
$validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''), DNS::RECORD_AAAA);
|
||||
}
|
||||
|
||||
if (empty($validators)) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'At least one of domain targets environment variable must be configured.');
|
||||
}
|
||||
|
||||
if ($rule->getAttribute('verification') === true) {
|
||||
return $response->dynamic($rule, Response::MODEL_PROXY_RULE);
|
||||
}
|
||||
|
||||
$validator = new AnyOf($validators, AnyOf::TYPE_STRING);
|
||||
$domain = new Domain($rule->getAttribute('domain', ''));
|
||||
|
||||
$validationStart = \microtime(true);
|
||||
if (!$validator->isValid($domain->get())) {
|
||||
$log->addExtra('dnsTiming', \strval(\microtime(true) - $validationStart));
|
||||
$log->addTag('dnsDomain', $domain->get());
|
||||
|
||||
$errors = [];
|
||||
foreach ($validators as $validator) {
|
||||
if (!empty($validator->getLogs())) {
|
||||
$errors[] = $validator->getLogs();
|
||||
}
|
||||
}
|
||||
|
||||
$error = \implode("\n", $errors);
|
||||
$log->addExtra('dnsResponse', \is_array($error) ? \json_encode($error) : \strval($error));
|
||||
|
||||
throw new Exception(Exception::RULE_VERIFICATION_FAILED);
|
||||
try {
|
||||
self::verifyRule($rule, $log);
|
||||
$rule = $rule->setAttribute('verificationLogs', '');
|
||||
} catch (Exception $err) {
|
||||
$rule = $rule->setAttribute('verificationLogs', $err->getMessage());
|
||||
throw $err;
|
||||
}
|
||||
|
||||
// Ensure CAA won't block certificate issuance
|
||||
if (!empty(System::getEnv('_APP_DOMAIN_TARGET_CAA', ''))) {
|
||||
$validationStart = \microtime(true);
|
||||
$validator = new DNS(System::getEnv('_APP_DOMAIN_TARGET_CAA', ''), DNS::RECORD_CAA);
|
||||
if (!$validator->isValid($domain->get())) {
|
||||
$log->addExtra('dnsTimingCaa', \strval(\microtime(true) - $validationStart));
|
||||
$log->addTag('dnsDomain', $domain->get());
|
||||
$error = $validator->getDescription();
|
||||
$log->addExtra('dnsResponse', \is_array($error) ? \json_encode($error) : \strval($error));
|
||||
throw new Exception(Exception::RULE_VERIFICATION_FAILED, 'Domain verification failed because CAA records do not allow Appwrite\'s certificate issuer.');
|
||||
}
|
||||
}
|
||||
$rule = $rule->setAttribute('status', 'verifying');
|
||||
|
||||
$dbForPlatform->updateDocument('rules', $rule->getId(), $rule->setAttribute('status', 'verifying'));
|
||||
$dbForPlatform->updateDocument('rules', $rule->getId(), $rule);
|
||||
|
||||
// Issue a TLS certificate when domain is verified
|
||||
$queueForCertificates
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
namespace Appwrite\Platform\Workers;
|
||||
|
||||
use Appwrite\Certificates\Adapter as CertificatesAdapter;
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Func;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Realtime;
|
||||
use Appwrite\Event\Webhook;
|
||||
use Appwrite\Network\Validator\DNS;
|
||||
use Appwrite\Platform\Modules\Proxy\Base;
|
||||
use Appwrite\Template\Template;
|
||||
use Appwrite\Utopia\Response\Model\Rule;
|
||||
use Exception;
|
||||
@@ -22,14 +23,13 @@ use Utopia\Database\Exception\Conflict;
|
||||
use Utopia\Database\Exception\Structure;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization as ValidatorAuthorization;
|
||||
use Utopia\Domains\Domain;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Queue\Message;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\AnyOf;
|
||||
use Utopia\Validator\IP;
|
||||
|
||||
class Certificates extends Action
|
||||
{
|
||||
@@ -85,21 +85,44 @@ class Certificates extends Action
|
||||
array $plan
|
||||
): void {
|
||||
$payload = $message->getPayload() ?? [];
|
||||
|
||||
if (empty($payload)) {
|
||||
throw new Exception('Missing payload');
|
||||
}
|
||||
|
||||
$document = new Document($payload['domain'] ?? []);
|
||||
$domain = new Domain($document->getAttribute('domain', ''));
|
||||
$domain = new Domain($document->getAttribute('domain', ''));
|
||||
$domainType = $document->getAttribute('domainType');
|
||||
|
||||
$skipRenewCheck = $payload['skipRenewCheck'] ?? false;
|
||||
$validationDomain = $payload['validationDomain'] ?? null;
|
||||
$action = $payload['action'] ?? [];
|
||||
$verificationDomainFunction = $payload['verificationDomainFunction'] ?? null;
|
||||
$verificationDomainApi = $payload['verificationDomainApi'] ?? null;
|
||||
|
||||
$log->addTag('domain', $domain->get());
|
||||
|
||||
$domainType = $document->getAttribute('domainType');
|
||||
switch ($action) {
|
||||
case Certificate::ACTION_GENERATION:
|
||||
$this->executeGeneration($domain, $domainType, $dbForPlatform, $queueForMails, $queueForEvents, $queueForWebhooks, $queueForFunctions, $queueForRealtime, $log, $certificates, $skipRenewCheck, $plan, $verificationDomainFunction, $verificationDomainApi);
|
||||
break;
|
||||
case Certificate::ACTION_VERIFICATION:
|
||||
$this->executeVerification();
|
||||
break;
|
||||
case Certificate::ACTION_GENERATION_SYNC:
|
||||
$this->executeGenerationSync();
|
||||
break;
|
||||
default:
|
||||
throw new Exception('Invalid action . ' . $action);
|
||||
}
|
||||
}
|
||||
|
||||
$this->execute($domain, $domainType, $dbForPlatform, $queueForMails, $queueForEvents, $queueForWebhooks, $queueForFunctions, $queueForRealtime, $log, $certificates, $skipRenewCheck, $plan, $validationDomain);
|
||||
private function executeGenerationSync(): void
|
||||
{
|
||||
throw new \Exception('To be implemented.');
|
||||
}
|
||||
|
||||
private function executeVerification(): void
|
||||
{
|
||||
throw new \Exception('To be implemented.');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -113,12 +136,13 @@ class Certificates extends Action
|
||||
* @param CertificatesAdapter $certificates
|
||||
* @param bool $skipRenewCheck
|
||||
* @param array $plan
|
||||
* @param string|null $validationDomain
|
||||
* @param ?string $verificationDomainFunction
|
||||
* @param ?string $verificationDomainApi
|
||||
* @return void
|
||||
* @throws Throwable
|
||||
* @throws \Utopia\Database\Exception
|
||||
*/
|
||||
private function execute(
|
||||
private function executeGeneration(
|
||||
Domain $domain,
|
||||
?string $domainType,
|
||||
Database $dbForPlatform,
|
||||
@@ -131,7 +155,8 @@ class Certificates extends Action
|
||||
CertificatesAdapter $certificates,
|
||||
bool $skipRenewCheck = false,
|
||||
array $plan = [],
|
||||
?string $validationDomain = null
|
||||
?string $verificationDomainFunction = null,
|
||||
?string $verificationDomainApi = null
|
||||
): void {
|
||||
/**
|
||||
* 1. Read arguments and validate domain
|
||||
@@ -176,9 +201,37 @@ class Certificates extends Action
|
||||
try {
|
||||
// Validate domain and DNS records. Skip if job is forced
|
||||
if (!$skipRenewCheck) {
|
||||
$mainDomain = $validationDomain ?? $this->getMainDomain();
|
||||
$mainDomain = $this->getMainDomain();
|
||||
$isMainDomain = !isset($mainDomain) || $domain->get() === $mainDomain;
|
||||
$this->validateDomain($domain, $isMainDomain, $log);
|
||||
|
||||
if (empty($domain->get())) {
|
||||
throw new Exception('Missing certificate domain.');
|
||||
}
|
||||
|
||||
if (!$domain->isKnown() || $domain->isTest()) {
|
||||
throw new Exception('Unknown public suffix for domain.');
|
||||
}
|
||||
|
||||
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
|
||||
if (System::getEnv('_APP_RULES_FORMAT') === 'md5') {
|
||||
$rule = ValidatorAuthorization::skip(fn () => $dbForPlatform->getDocument('rules', md5($domain->get())));
|
||||
} else {
|
||||
$rule = ValidatorAuthorization::skip(
|
||||
fn () => $dbForPlatform->find('rules', [
|
||||
Query::equal('domain', [$domain->get()]),
|
||||
Query::limit(1)
|
||||
])
|
||||
)[0] ?? new Document();
|
||||
}
|
||||
|
||||
if ($rule->isEmpty()) {
|
||||
$rule = new Document([
|
||||
'domain' => $domain->get(),
|
||||
'type' => 'api'
|
||||
]);
|
||||
}
|
||||
|
||||
$this->validateDomain($rule, $isMainDomain, $log, $verificationDomainApi, $verificationDomainFunction);
|
||||
|
||||
// If certificate exists already, double-check expiry date. Skip if job is forced
|
||||
if (!$certificates->isRenewRequired($domain->get(), $domainType, $log)) {
|
||||
@@ -286,73 +339,18 @@ class Certificates extends Action
|
||||
* - Domain needs to be public and valid (prevents NFT domains that are not supported)
|
||||
* - Domain must have proper DNS record
|
||||
*
|
||||
* @param Domain $domain Domain which we validate
|
||||
* @param Document $rule Rule which we validate
|
||||
* @param bool $isMainDomain In case of master domain, we look for different DNS configurations
|
||||
* @param string|null $verificationDomainApi Function to verify domain
|
||||
* @param string|null $verificationDomainFunction Function to verify domain
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
private function validateDomain(Domain $domain, bool $isMainDomain, Log $log): void
|
||||
private function validateDomain(Document $rule, bool $isMainDomain, Log $log, ?string $verificationDomainApi = null, ?string $verificationDomainFunction = null): void
|
||||
{
|
||||
if (empty($domain->get())) {
|
||||
throw new Exception('Missing certificate domain.');
|
||||
}
|
||||
|
||||
if (!$domain->isKnown() || $domain->isTest()) {
|
||||
throw new Exception('Unknown public suffix for domain.');
|
||||
}
|
||||
|
||||
if (!$isMainDomain) {
|
||||
$validationStart = \microtime(true);
|
||||
|
||||
$validators = [];
|
||||
$targetCNAME = new Domain(System::getEnv('_APP_DOMAIN_TARGET_CNAME', ''));
|
||||
if ($targetCNAME->isKnown() && !$targetCNAME->isTest()) {
|
||||
$validators[] = new DNS($targetCNAME->get(), DNS::RECORD_CNAME);
|
||||
}
|
||||
if ((new IP(IP::V4))->isValid(System::getEnv('_APP_DOMAIN_TARGET_A', ''))) {
|
||||
$validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_A', ''), DNS::RECORD_A);
|
||||
}
|
||||
if ((new IP(IP::V6))->isValid(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''))) {
|
||||
$validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''), DNS::RECORD_AAAA);
|
||||
}
|
||||
|
||||
// Validate if domain target is properly configured
|
||||
if (empty($validators)) {
|
||||
throw new Exception('At least one of domain targets environment variable must be configured.');
|
||||
}
|
||||
|
||||
// Verify domain with DNS records
|
||||
$validator = new AnyOf($validators, AnyOf::TYPE_STRING);
|
||||
if (!$validator->isValid($domain->get())) {
|
||||
$log->addExtra('dnsTiming', \strval(\microtime(true) - $validationStart));
|
||||
$log->addTag('dnsDomain', $domain->get());
|
||||
|
||||
$errors = [];
|
||||
foreach ($validators as $validator) {
|
||||
if (!empty($validator->getLogs())) {
|
||||
$errors[] = $validator->getLogs();
|
||||
}
|
||||
}
|
||||
|
||||
$error = \implode("\n", $errors);
|
||||
$log->addExtra('dnsResponse', \is_array($error) ? \json_encode($error) : \strval($error));
|
||||
|
||||
throw new Exception('Failed to verify domain DNS records.');
|
||||
}
|
||||
|
||||
// Ensure CAA won't block certificate issuance
|
||||
if (!empty(System::getEnv('_APP_DOMAIN_TARGET_CAA', ''))) {
|
||||
$validationStart = \microtime(true);
|
||||
$validator = new DNS(System::getEnv('_APP_DOMAIN_TARGET_CAA', ''), DNS::RECORD_CAA);
|
||||
if (!$validator->isValid($domain->get())) {
|
||||
$log->addExtra('dnsTimingCaa', \strval(\microtime(true) - $validationStart));
|
||||
$log->addTag('dnsDomain', $domain->get());
|
||||
$error = $validator->getDescription();
|
||||
$log->addExtra('dnsResponse', \is_array($error) ? \json_encode($error) : \strval($error));
|
||||
throw new Exception('Failed to verify domain DNS records. CAA records do not allow Appwrite\'s certificate issuer.');
|
||||
}
|
||||
}
|
||||
Base::verifyRule($rule, $log, $verificationDomainApi, $verificationDomainFunction);
|
||||
} else {
|
||||
// Main domain validation
|
||||
// TODO: Would be awesome to check A/AAAA record here. Maybe dry run?
|
||||
|
||||
@@ -88,6 +88,12 @@ class Rule extends Model
|
||||
'default' => false,
|
||||
'example' => 'verified',
|
||||
])
|
||||
->addRule('verificationLogs', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'DNS verification logs. This contains error from last verification attempt.',
|
||||
'default' => '',
|
||||
'example' => 'Verification of DNS records failed. Domain stage.myapp.com does not have DNS record.',
|
||||
])
|
||||
->addRule('logs', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Certificate generation logs. This will return an empty string if generation did not run, or succeeded.',
|
||||
|
||||
Reference in new issue
Block a user