From 5ae148f0260aefcaf137a6ae28cf661040e54a6e Mon Sep 17 00:00:00 2001 From: Hemachandar Date: Tue, 16 Dec 2025 10:59:16 +0530 Subject: [PATCH 1/4] Add logs to rules --- app/config/collections/platform.php | 11 + app/init/constants.php | 6 + src/Appwrite/Network/Validator/DNS.php | 139 +++++-------- .../Modules/Proxy/Http/Rules/API/Create.php | 116 +++-------- .../Modules/Proxy/Http/Rules/Action.php | 188 ++++++++++++++++++ .../Proxy/Http/Rules/Function/Create.php | 116 +++-------- .../Platform/Modules/Proxy/Http/Rules/Get.php | 12 +- .../Proxy/Http/Rules/Redirect/Create.php | 116 +++-------- .../Modules/Proxy/Http/Rules/Site/Create.php | 116 +++-------- .../Proxy/Http/Rules/Verification/Update.php | 113 +++-------- .../Modules/Proxy/Http/Rules/XList.php | 12 +- src/Appwrite/Utopia/Response/Model/Rule.php | 4 +- 12 files changed, 418 insertions(+), 531 deletions(-) create mode 100644 src/Appwrite/Platform/Modules/Proxy/Http/Rules/Action.php diff --git a/app/config/collections/platform.php b/app/config/collections/platform.php index b839e51622..d44d9b725c 100644 --- a/app/config/collections/platform.php +++ b/app/config/collections/platform.php @@ -1317,6 +1317,17 @@ return [ 'array' => false, 'filters' => [], ], + [ + '$id' => ID::custom('logs'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 1000000, + 'signed' => true, + 'required' => false, + 'default' => '', + 'array' => false, + 'filters' => [], + ], ], 'indexes' => [ [ diff --git a/app/init/constants.php b/app/init/constants.php index 9c771edb0a..3168212e0d 100644 --- a/app/init/constants.php +++ b/app/init/constants.php @@ -208,6 +208,12 @@ const DELETE_TYPE_SESSION_TARGETS = 'session_targets'; const DELETE_TYPE_CSV_EXPORTS = 'csv_exports'; const DELETE_TYPE_MAINTENANCE = 'maintenance'; +// Rule statuses +const RULE_STATUS_CREATED = 'created'; // This is also the status when domain DNS verification fails. +const RULE_STATUS_CERTIFICATE_GENERATING = 'verifying'; +const RULE_STATUS_CERTIFICATE_GENERATION_FAILED = 'unverified'; +const RULE_STATUS_VERIFIED = 'verified'; + // Message types const MESSAGE_SEND_TYPE_INTERNAL = 'internal'; const MESSAGE_SEND_TYPE_EXTERNAL = 'external'; diff --git a/src/Appwrite/Network/Validator/DNS.php b/src/Appwrite/Network/Validator/DNS.php index e3c1d38015..2dc3504a96 100644 --- a/src/Appwrite/Network/Validator/DNS.php +++ b/src/Appwrite/Network/Validator/DNS.php @@ -2,115 +2,66 @@ namespace Appwrite\Network\Validator; -use Utopia\DNS\Client; -use Utopia\DNS\Message; -use Utopia\DNS\Message\Question; +use Swoole\Coroutine\WaitGroup; use Utopia\DNS\Message\Record; -use Utopia\Domains\Domain; -use Utopia\System\System; -use Utopia\Validator; +use Utopia\DNS\Validator\DNS as BaseDNS; -class DNS extends Validator +class DNS extends BaseDNS { - public function __construct( - protected string $target, - protected int $type = Record::TYPE_CNAME, - protected string $server = '' - ) { - $this->server = $server ?: System::getEnv('_APP_DNS', '8.8.8.8'); + protected array $dnsServers = []; + + /** + * @param string $target Expected value for the DNS record + * @param int $type Type of DNS record to validate + * For value, use const from Record, such as Record::TYPE_A + * When using CAA type, you can provide exact match, or just issuer domain as $target + * @param array $dnsServers DNS server IP(s) or domain(s) to use for validation + */ + public function __construct(string $target, int $type = Record::TYPE_CNAME, array $dnsServers = []) + { + parent::__construct($target, $type, $dnsServers[0] ?? self::DEFAULT_DNS_SERVER); + + $this->dnsServers = $dnsServers; } - public function getDescription(): string + /** + * Validate DNS record value against multiple DNS servers + * + * @param mixed $value + * @return bool + */ + public function isValid(mixed $value): bool { - return 'Invalid DNS record.'; - } + $wg = new WaitGroup(); + $failedValidator = null; - public function isValid($value): bool - { - if (!is_string($value) || trim($value) === '') { - return false; - } + foreach ($this->dnsServers as $dnsServer) { + $wg->add(); - $client = new Client($this->server); - try { - $response = $client->query(Message::query( - new Question($value, $this->type) - )); - } catch (\Throwable) { - return false; - } + \go(function () use ($value, $dnsServer, $wg, &$failedValidator) { + try { + $validator = new BaseDNS($this->target, $this->type, $dnsServer); + $isValid = $validator->isValid($value); - $typeMatches = array_filter( - $response->answers, - fn (Record $record) => $record->type === $this->type - ); - - if (empty($typeMatches)) { - if ($this->type === Record::TYPE_CAA) { - return $this->validateParentCAA($value); - } - - return false; - } - - foreach ($typeMatches as $record) { - if ($this->type === Record::TYPE_CAA) { - $valuePart = $this->extractCAAValue($record->rdata); - if ($valuePart !== '' && $valuePart === $this->target) { - return true; + if (!$isValid) { + $failedValidator = $validator; + } + } finally { + $wg->done(); } - } - - if ($record->rdata === $this->target) { - return true; - } + }); } - return false; - } + $wg->wait(); - private function validateParentCAA(string $domain): bool - { - try { - $domainInfo = new Domain($domain); - } catch (\Throwable) { + if (!\is_null($failedValidator)) { + $this->count = $failedValidator->count; + $this->value = $failedValidator->value; + $this->reason = $failedValidator->reason; + $this->records = $failedValidator->records; return false; } - if ($domainInfo->get() === $domainInfo->getApex()) { - return true; - } - - $parts = explode('.', $domainInfo->get()); - array_shift($parts); - $parent = implode('.', $parts); - - if ($parent === '') { - return false; - } - - $validator = new self($this->target, Record::TYPE_CAA, $this->server); - return $validator->isValid($parent); - } - - private function extractCAAValue(string $rdata): string - { - $parts = explode(' ', $rdata, 3); - if (count($parts) < 3) { - return ''; - } - - $value = trim($parts[2], '"'); - return explode(';', $value)[0] ?? ''; - } - - public function isArray(): bool - { - return false; - } - - public function getType(): string - { - return self::TYPE_STRING; + return true; } } diff --git a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/API/Create.php b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/API/Create.php index e6805f0759..fc4537301e 100644 --- a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/API/Create.php +++ b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/API/Create.php @@ -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\Http\Rules\Action; use Appwrite\SDK\AuthType; use Appwrite\SDK\Method; use Appwrite\SDK\Response as SDKResponse; @@ -14,14 +14,10 @@ use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Exception\Duplicate; use Utopia\Database\Helpers\ID; -use Utopia\DNS\Message\Record; -use Utopia\Domains\Domain; -use Utopia\Platform\Action; +use Utopia\Logger\Log; 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 { @@ -32,8 +28,10 @@ class Create extends Action return 'createAPIRule'; } - public function __construct() + public function __construct(...$params) { + parent::__construct(...$params); + $this ->setHttpMethod(Action::HTTP_REQUEST_METHOD_POST) ->setHttpPath('/v1/proxy/rules/api') @@ -68,108 +66,43 @@ class Create extends Action ->inject('queueForEvents') ->inject('dbForPlatform') ->inject('platform') + ->inject('log') ->callback($this->action(...)); } - public function action(string $domain, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform, array $platform) + public function action(string $domain, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform, array $platform, Log $log) { - $domains = $platform['hostnames'] ?? []; + $this->validateDomainRestrictions($domain, $platform); + $sitesDomain = System::getEnv('_APP_DOMAIN_SITES', ''); $functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', ''); - $restrictions = []; - if (!empty($sitesDomain)) { - $domainLevel = \count(\explode('.', $sitesDomain)); - $restrictions[] = ValidatorDomain::createRestriction($sitesDomain, $domainLevel + 1, ['commit-', 'branch-']); - } - if (!empty($functionsDomain)) { - $domainLevel = \count(\explode('.', $functionsDomain)); - $restrictions[] = ValidatorDomain::createRestriction($functionsDomain, $domainLevel + 1); - } - $validator = new ValidatorDomain($restrictions); - - if (!$validator->isValid($domain)) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please use a different domain.'); - } - - $deniedDomains = [...$domains]; - - if (!empty($sitesDomain)) { - $deniedDomains[] = $sitesDomain; - } - - if (!empty($functionsDomain)) { - $deniedDomains[] = $functionsDomain; - } - - $denyListDomains = System::getEnv('_APP_CUSTOM_DOMAIN_DENY_LIST', ''); - $denyListDomains = \array_map('trim', explode(',', $denyListDomains)); - foreach ($denyListDomains as $denyListDomain) { - if (empty($denyListDomain)) { - continue; - } - $deniedDomains[] = $denyListDomain; - } - - if (\in_array($domain, $deniedDomains)) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please use a different domain.'); - } - - try { - $domain = new Domain($domain); - } catch (\Throwable) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Domain may not start with http:// or https://.'); - } - // TODO: (@Meldiron) Remove after 1.7.x migration - $isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5'; - $ruleId = $isMd5 ? md5($domain->get()) : ID::unique(); - - $status = 'created'; - 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(), Record::TYPE_CNAME); - } - if ((new IP(IP::V4))->isValid(System::getEnv('_APP_DOMAIN_TARGET_A', ''))) { - $validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_A', ''), Record::TYPE_A); - } - if ((new IP(IP::V6))->isValid(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''))) { - $validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''), Record::TYPE_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'; - } - } + $ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain) : ID::unique(); $owner = ''; if ( - ($functionsDomain != '' && \str_ends_with($domain->get(), $functionsDomain)) || - ($sitesDomain != '' && \str_ends_with($domain->get(), $sitesDomain)) + ($functionsDomain != '' && \str_ends_with($domain, $functionsDomain)) || + ($sitesDomain != '' && \str_ends_with($domain, $sitesDomain)) ) { $owner = 'Appwrite'; } + $status = RULE_STATUS_CREATED; + if (\str_ends_with($domain, $functionsDomain) || \str_ends_with($domain, $sitesDomain)) { + $status = RULE_STATUS_VERIFIED; + } + $rule = new Document([ '$id' => $ruleId, 'projectId' => $project->getId(), 'projectInternalId' => $project->getSequence(), - 'domain' => $domain->get(), + 'domain' => $domain, 'status' => $status, 'type' => 'api', 'trigger' => 'manual', 'certificateId' => '', - 'search' => implode(' ', [$ruleId, $domain->get()]), + 'search' => implode(' ', [$ruleId, $domain]), 'owner' => $owner, 'region' => $project->getAttribute('region') ]); @@ -180,7 +113,16 @@ class Create extends Action throw new Exception(Exception::RULE_ALREADY_EXISTS); } - if ($rule->getAttribute('status', '') === 'verifying') { + if ($rule->getAttribute('status', '') === RULE_STATUS_CREATED) { + try { + $this->verifyRule($rule, $log); + $rule->setAttribute('status', RULE_STATUS_CERTIFICATE_GENERATING); + } catch (Exception $err) { + $rule->setAttribute('logs', $err->getMessage()); + } + } + + if ($rule->getAttribute('status', '') === RULE_STATUS_CERTIFICATE_GENERATING) { $queueForCertificates ->setDomain(new Document([ 'domain' => $rule->getAttribute('domain'), diff --git a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Action.php b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Action.php new file mode 100644 index 0000000000..5ec5b8b8f5 --- /dev/null +++ b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Action.php @@ -0,0 +1,188 @@ +isValid($domain)) { + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please use a different domain.'); + } + + $deniedDomains = [...$domains]; + + if (!empty($sitesDomain)) { + $deniedDomains[] = $sitesDomain; + } + + if (!empty($functionsDomain)) { + $deniedDomains[] = $functionsDomain; + } + + $denyListDomains = System::getEnv('_APP_CUSTOM_DOMAIN_DENY_LIST', ''); + $denyListDomains = \array_map('trim', explode(',', $denyListDomains)); + foreach ($denyListDomains as $denyListDomain) { + if (empty($denyListDomain)) { + continue; + } + $deniedDomains[] = $denyListDomain; + } + + if (\in_array($domain, $deniedDomains)) { + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please use a different domain.'); + } + + try { + $domain = new Domain($domain); + } catch (\Throwable) { + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Domain may not start with http:// or https://.'); + } + } + + /** + * Verify or re-verify a rule + * + * @param Document $rule Rule to verify + * @param Log|null $log Log instance to add timings to + * @return void + */ + protected function verifyRule(Document $rule, ?Log $log = null): void + { + $dnsValidatorClass = $this->dnsValidatorClass; + $dnsEnv = System::getEnv('_APP_DNS', '8.8.8.8'); + $servers = \array_map('trim', \explode(',', $dnsEnv)); + $dnsServers = \array_filter($servers, fn ($server) => !empty($server)); + + $domain = new Domain($rule->getAttribute('domain', '')); + + if (empty($domain->get())) { + throw new Exception(Exception::RULE_VERIFICATION_FAILED, 'DNS verification failed as domain is not valid.'); + } + + if (!$domain->isKnown() || $domain->isTest()) { + throw new Exception(Exception::RULE_VERIFICATION_FAILED, 'DNS verification failed as domain ' . $domain->get() . ' does not resolve to a known public apex domain.'); + } + + // Ensure CAA won't block certificate issuance + $caaTarget = System::getEnv('_APP_DOMAIN_TARGET_CAA', ''); + if (!empty($caaTarget)) { + $validationStart = \microtime(true); + $validator = new $dnsValidatorClass($caaTarget, Record::TYPE_CAA, $dnsServers); + if (!$validator->isValid($domain->get())) { + if (!\is_null($log)) { + $log->addExtra('dnsTimingCaa', \strval(\microtime(true) - $validationStart)); + $log->addTag('dnsDomain', $domain->get()); + } + throw new Exception(Exception::RULE_VERIFICATION_FAILED, $validator->getDescription()); + } + } + + $targetCNAME = null; + $ruleType = $rule->getAttribute('type', ''); + $resourceType = $rule->getAttribute('deploymentResourceType', ''); + + // Ensures different target based on rule's type, as configured by env variables + if ($resourceType === 'function') { + // For example: fra.appwrite.run + $targetCNAME = new Domain(System::getEnv('_APP_DOMAIN_FUNCTIONS', '')); + } 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(System::getEnv('_APP_DOMAIN_TARGET_CNAME', '')); + } elseif ($ruleType === 'redirect') { + // Shouldn't be needed, because redirect should always have resourceTyp too, but just in case we default 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)) { + $validator = new $dnsValidatorClass($targetCNAME->get(), Record::TYPE_CNAME, $dnsServers); + $validators[] = $validator; + + if (\is_null($mainValidator)) { + $mainValidator = $validator; + } + } + + // Ensure at least one of CNAME/A/AAAA record points to our servers properly + $targetA = System::getEnv('_APP_DOMAIN_TARGET_A', ''); + if ((new IP(IP::V4))->isValid($targetA)) { + $validator = new $dnsValidatorClass($targetA, Record::TYPE_A, $dnsServers); + $validators[] = $validator; + + if (\is_null($mainValidator)) { + $mainValidator = $validator; + } + } + + $targetAAAA = System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''); + if ((new IP(IP::V6))->isValid($targetAAAA)) { + $validator = new $dnsValidatorClass($targetAAAA, Record::TYPE_AAAA, $dnsServers); + $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); + + $validationStart = \microtime(true); + if (!$validator->isValid($domain->get())) { + if (!\is_null($log)) { + $log->addExtra('dnsTiming', \strval(\microtime(true) - $validationStart)); + $log->addTag('dnsDomain', $domain->get()); + } + throw new Exception(Exception::RULE_VERIFICATION_FAILED, $mainValidator->getDescription()); + } + } +} diff --git a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Function/Create.php b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Function/Create.php index 575ac9b832..c88e3e5085 100644 --- a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Function/Create.php +++ b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Function/Create.php @@ -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\Http\Rules\Action; use Appwrite\SDK\AuthType; use Appwrite\SDK\Method; use Appwrite\SDK\Response as SDKResponse; @@ -15,14 +15,10 @@ use Utopia\Database\Document; use Utopia\Database\Exception\Duplicate; use Utopia\Database\Helpers\ID; use Utopia\Database\Validator\UID; -use Utopia\DNS\Message\Record; -use Utopia\Domains\Domain; -use Utopia\Platform\Action; +use Utopia\Logger\Log; 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 @@ -34,8 +30,10 @@ class Create extends Action return 'createFunctionRule'; } - public function __construct() + public function __construct(...$params) { + parent::__construct(...$params); + $this ->setHttpMethod(Action::HTTP_REQUEST_METHOD_POST) ->setHttpPath('/v1/proxy/rules/function') @@ -73,59 +71,17 @@ class Create extends Action ->inject('dbForPlatform') ->inject('dbForProject') ->inject('platform') + ->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, array $platform) + public function action(string $domain, string $functionId, string $branch, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform, Database $dbForProject, array $platform, Log $log) { - $domains = $platform['hostnames'] ?? []; + $this->validateDomainRestrictions($domain, $platform); + $sitesDomain = System::getEnv('_APP_DOMAIN_SITES', ''); $functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', ''); - $restrictions = []; - if (!empty($sitesDomain)) { - $domainLevel = \count(\explode('.', $sitesDomain)); - $restrictions[] = ValidatorDomain::createRestriction($sitesDomain, $domainLevel + 1, ['commit-', 'branch-']); - } - if (!empty($functionsDomain)) { - $domainLevel = \count(\explode('.', $functionsDomain)); - $restrictions[] = ValidatorDomain::createRestriction($functionsDomain, $domainLevel + 1); - } - $validator = new ValidatorDomain($restrictions); - - if (!$validator->isValid($domain)) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please use a different domain.'); - } - - $deniedDomains = [...$domains]; - - if (!empty($sitesDomain)) { - $deniedDomains[] = $sitesDomain; - } - - if (!empty($functionsDomain)) { - $deniedDomains[] = $functionsDomain; - } - - $denyListDomains = System::getEnv('_APP_CUSTOM_DOMAIN_DENY_LIST', ''); - $denyListDomains = \array_map('trim', explode(',', $denyListDomains)); - foreach ($denyListDomains as $denyListDomain) { - if (empty($denyListDomain)) { - continue; - } - $deniedDomains[] = $denyListDomain; - } - - if (\in_array($domain, $deniedDomains)) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please use a different domain.'); - } - - try { - $domain = new Domain($domain); - } catch (\Throwable) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Domain may not start with http:// or https://.'); - } - $function = $dbForProject->getDocument('functions', $functionId); if ($function->isEmpty()) { throw new Exception(Exception::RULE_RESOURCE_NOT_FOUND); @@ -134,49 +90,26 @@ class Create extends Action $deployment = $dbForProject->getDocument('deployments', $function->getAttribute('deploymentId', '')); // TODO: (@Meldiron) Remove after 1.7.x migration - $isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5'; - $ruleId = $isMd5 ? md5($domain->get()) : ID::unique(); - - $status = 'created'; - 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(), Record::TYPE_CNAME); - } - if ((new IP(IP::V4))->isValid(System::getEnv('_APP_DOMAIN_TARGET_A', ''))) { - $validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_A', ''), Record::TYPE_A); - } - if ((new IP(IP::V6))->isValid(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''))) { - $validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''), Record::TYPE_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'; - } - } + $ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain) : ID::unique(); $owner = ''; if ( - ($functionsDomain != '' && \str_ends_with($domain->get(), $functionsDomain)) || - ($sitesDomain != '' && \str_ends_with($domain->get(), $sitesDomain)) + ($functionsDomain != '' && \str_ends_with($domain, $functionsDomain)) || + ($sitesDomain != '' && \str_ends_with($domain, $sitesDomain)) ) { $owner = 'Appwrite'; } + $status = RULE_STATUS_CREATED; + if (\str_ends_with($domain, $functionsDomain) || \str_ends_with($domain, $sitesDomain)) { + $status = RULE_STATUS_VERIFIED; + } + $rule = new Document([ '$id' => $ruleId, 'projectId' => $project->getId(), 'projectInternalId' => $project->getSequence(), - 'domain' => $domain->get(), + 'domain' => $domain, 'status' => $status, 'type' => 'deployment', 'trigger' => 'manual', @@ -187,7 +120,7 @@ class Create extends Action 'deploymentResourceInternalId' => $function->getSequence(), 'deploymentVcsProviderBranch' => $branch, 'certificateId' => '', - 'search' => implode(' ', [$ruleId, $domain->get(), $branch]), + 'search' => implode(' ', [$ruleId, $domain, $branch]), 'owner' => $owner, 'region' => $project->getAttribute('region') ]); @@ -198,7 +131,16 @@ class Create extends Action throw new Exception(Exception::RULE_ALREADY_EXISTS); } - if ($rule->getAttribute('status', '') === 'verifying') { + if ($rule->getAttribute('status', '') === RULE_STATUS_CREATED) { + try { + $this->verifyRule($rule, $log); + $rule->setAttribute('status', RULE_STATUS_CERTIFICATE_GENERATING); + } catch (Exception $err) { + $rule->setAttribute('logs', $err->getMessage()); + } + } + + if ($rule->getAttribute('status', '') === RULE_STATUS_CERTIFICATE_GENERATING) { $queueForCertificates ->setDomain(new Document([ 'domain' => $rule->getAttribute('domain'), diff --git a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Get.php b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Get.php index 77aa3df581..4581cb3d08 100644 --- a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Get.php +++ b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Get.php @@ -10,7 +10,6 @@ use Appwrite\Utopia\Response; use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Validator\UID; -use Utopia\Platform\Action; use Utopia\Platform\Scope\HTTP; class Get extends Action @@ -22,8 +21,10 @@ class Get extends Action return 'getRule'; } - public function __construct() + public function __construct(...$params) { + parent::__construct(...$params); + $this ->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET) ->setHttpPath('/v1/proxy/rules/:ruleId') @@ -65,7 +66,12 @@ class Get extends Action } $certificate = $dbForPlatform->getDocument('certificates', $rule->getAttribute('certificateId', '')); - $rule->setAttribute('logs', $certificate->getAttribute('logs', '')); + + // Give priority to certificate generation logs if present + if (!empty($certificate->getAttribute('logs', ''))) { + $rule->setAttribute('logs', $certificate->getAttribute('logs', '')); + } + $rule->setAttribute('renewAt', $certificate->getAttribute('renewDate', '')); $response->dynamic($rule, Response::MODEL_PROXY_RULE); diff --git a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Redirect/Create.php b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Redirect/Create.php index 50b1a4e2df..1f785f858c 100644 --- a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Redirect/Create.php +++ b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Redirect/Create.php @@ -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\Http\Rules\Action; use Appwrite\SDK\AuthType; use Appwrite\SDK\Method; use Appwrite\SDK\Response as SDKResponse; @@ -15,14 +15,10 @@ use Utopia\Database\Document; use Utopia\Database\Exception\Duplicate; use Utopia\Database\Helpers\ID; use Utopia\Database\Validator\UID; -use Utopia\DNS\Message\Record; -use Utopia\Domains\Domain; -use Utopia\Platform\Action; +use Utopia\Logger\Log; 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; @@ -35,8 +31,10 @@ class Create extends Action return 'createRedirectRule'; } - public function __construct() + public function __construct(...$params) { + parent::__construct(...$params); + $this ->setHttpMethod(Action::HTTP_REQUEST_METHOD_POST) ->setHttpPath('/v1/proxy/rules/redirect') @@ -76,59 +74,17 @@ class Create extends Action ->inject('dbForPlatform') ->inject('dbForProject') ->inject('platform') + ->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, array $platform) + 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, array $platform, Log $log) { - $domains = $platform['hostnames'] ?? []; + $this->validateDomainRestrictions($domain, $platform); + $sitesDomain = System::getEnv('_APP_DOMAIN_SITES', ''); $functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', ''); - $restrictions = []; - if (!empty($sitesDomain)) { - $domainLevel = \count(\explode('.', $sitesDomain)); - $restrictions[] = ValidatorDomain::createRestriction($sitesDomain, $domainLevel + 1, ['commit-', 'branch-']); - } - if (!empty($functionsDomain)) { - $domainLevel = \count(\explode('.', $functionsDomain)); - $restrictions[] = ValidatorDomain::createRestriction($functionsDomain, $domainLevel + 1); - } - $validator = new ValidatorDomain($restrictions); - - if (!$validator->isValid($domain)) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please use a different domain.'); - } - - $deniedDomains = [...$domains]; - - if (!empty($sitesDomain)) { - $deniedDomains[] = $sitesDomain; - } - - if (!empty($functionsDomain)) { - $deniedDomains[] = $functionsDomain; - } - - $denyListDomains = System::getEnv('_APP_CUSTOM_DOMAIN_DENY_LIST', ''); - $denyListDomains = \array_map('trim', explode(',', $denyListDomains)); - foreach ($denyListDomains as $denyListDomain) { - if (empty($denyListDomain)) { - continue; - } - $deniedDomains[] = $denyListDomain; - } - - if (\in_array($domain, $deniedDomains)) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please use a different domain.'); - } - - try { - $domain = new Domain($domain); - } catch (\Throwable) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Domain may not start with http:// or https://.'); - } - $collection = match ($resourceType) { 'site' => 'sites', 'function' => 'functions' @@ -139,49 +95,26 @@ class Create extends Action } // TODO: (@Meldiron) Remove after 1.7.x migration - $isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5'; - $ruleId = $isMd5 ? md5($domain->get()) : ID::unique(); - - $status = 'created'; - 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(), Record::TYPE_CNAME); - } - if ((new IP(IP::V4))->isValid(System::getEnv('_APP_DOMAIN_TARGET_A', ''))) { - $validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_A', ''), Record::TYPE_A); - } - if ((new IP(IP::V6))->isValid(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''))) { - $validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''), Record::TYPE_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'; - } - } + $ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain) : ID::unique(); $owner = ''; if ( - ($functionsDomain != '' && \str_ends_with($domain->get(), $functionsDomain)) || - ($sitesDomain != '' && \str_ends_with($domain->get(), $sitesDomain)) + ($functionsDomain != '' && \str_ends_with($domain, $functionsDomain)) || + ($sitesDomain != '' && \str_ends_with($domain, $sitesDomain)) ) { $owner = 'Appwrite'; } + $status = RULE_STATUS_CREATED; + if (\str_ends_with($domain, $functionsDomain) || \str_ends_with($domain, $sitesDomain)) { + $status = RULE_STATUS_VERIFIED; + } + $rule = new Document([ '$id' => $ruleId, 'projectId' => $project->getId(), 'projectInternalId' => $project->getSequence(), - 'domain' => $domain->get(), + 'domain' => $domain, 'status' => $status, 'type' => 'redirect', 'trigger' => 'manual', @@ -191,7 +124,7 @@ class Create extends Action 'deploymentResourceId' => $resource->getId(), 'deploymentResourceInternalId' => $resource->getSequence(), 'certificateId' => '', - 'search' => implode(' ', [$ruleId, $domain->get()]), + 'search' => implode(' ', [$ruleId, $domain]), 'owner' => $owner, 'region' => $project->getAttribute('region') ]); @@ -202,7 +135,16 @@ class Create extends Action throw new Exception(Exception::RULE_ALREADY_EXISTS); } - if ($rule->getAttribute('status', '') === 'verifying') { + if ($rule->getAttribute('status', '') === RULE_STATUS_CREATED) { + try { + $this->verifyRule($rule, $log); + $rule->setAttribute('status', RULE_STATUS_CERTIFICATE_GENERATING); + } catch (Exception $err) { + $rule->setAttribute('logs', $err->getMessage()); + } + } + + if ($rule->getAttribute('status', '') === RULE_STATUS_CERTIFICATE_GENERATING) { $queueForCertificates ->setDomain(new Document([ 'domain' => $rule->getAttribute('domain'), diff --git a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Site/Create.php b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Site/Create.php index 2071477eec..793383e3ba 100644 --- a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Site/Create.php +++ b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Site/Create.php @@ -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\Http\Rules\Action; use Appwrite\SDK\AuthType; use Appwrite\SDK\Method; use Appwrite\SDK\Response as SDKResponse; @@ -15,14 +15,10 @@ use Utopia\Database\Document; use Utopia\Database\Exception\Duplicate; use Utopia\Database\Helpers\ID; use Utopia\Database\Validator\UID; -use Utopia\DNS\Message\Record; -use Utopia\Domains\Domain; -use Utopia\Platform\Action; +use Utopia\Logger\Log; 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 @@ -34,8 +30,10 @@ class Create extends Action return 'createSiteRule'; } - public function __construct() + public function __construct(...$params) { + parent::__construct(...$params); + $this ->setHttpMethod(Action::HTTP_REQUEST_METHOD_POST) ->setHttpPath('/v1/proxy/rules/site') @@ -73,59 +71,17 @@ class Create extends Action ->inject('dbForPlatform') ->inject('dbForProject') ->inject('platform') + ->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, array $platform) + public function action(string $domain, string $siteId, string $branch, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform, Database $dbForProject, array $platform, Log $log) { - $domains = $platform['hostnames'] ?? []; + $this->validateDomainRestrictions($domain, $platform); + $sitesDomain = System::getEnv('_APP_DOMAIN_SITES', ''); $functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', ''); - $restrictions = []; - if (!empty($sitesDomain)) { - $domainLevel = \count(\explode('.', $sitesDomain)); - $restrictions[] = ValidatorDomain::createRestriction($sitesDomain, $domainLevel + 1, ['commit-', 'branch-']); - } - if (!empty($functionsDomain)) { - $domainLevel = \count(\explode('.', $functionsDomain)); - $restrictions[] = ValidatorDomain::createRestriction($functionsDomain, $domainLevel + 1); - } - $validator = new ValidatorDomain($restrictions); - - if (!$validator->isValid($domain)) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please use a different domain.'); - } - - $deniedDomains = [...$domains]; - - if (!empty($sitesDomain)) { - $deniedDomains[] = $sitesDomain; - } - - if (!empty($functionsDomain)) { - $deniedDomains[] = $functionsDomain; - } - - $denyListDomains = System::getEnv('_APP_CUSTOM_DOMAIN_DENY_LIST', ''); - $denyListDomains = \array_map('trim', explode(',', $denyListDomains)); - foreach ($denyListDomains as $denyListDomain) { - if (empty($denyListDomain)) { - continue; - } - $deniedDomains[] = $denyListDomain; - } - - if (\in_array($domain, $deniedDomains)) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please use a different domain.'); - } - - try { - $domain = new Domain($domain); - } catch (\Throwable) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Domain may not start with http:// or https://.'); - } - $site = $dbForProject->getDocument('sites', $siteId); if ($site->isEmpty()) { throw new Exception(Exception::RULE_RESOURCE_NOT_FOUND); @@ -134,49 +90,26 @@ class Create extends Action $deployment = $dbForProject->getDocument('deployments', $site->getAttribute('deploymentId', '')); // TODO: (@Meldiron) Remove after 1.7.x migration - $isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5'; - $ruleId = $isMd5 ? md5($domain->get()) : ID::unique(); - - $status = 'created'; - 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(), Record::TYPE_CNAME); - } - if ((new IP(IP::V4))->isValid(System::getEnv('_APP_DOMAIN_TARGET_A', ''))) { - $validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_A', ''), Record::TYPE_A); - } - if ((new IP(IP::V6))->isValid(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''))) { - $validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''), Record::TYPE_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'; - } - } + $ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain) : ID::unique(); $owner = ''; if ( - ($functionsDomain != '' && \str_ends_with($domain->get(), $functionsDomain)) || - ($sitesDomain != '' && \str_ends_with($domain->get(), $sitesDomain)) + ($functionsDomain != '' && \str_ends_with($domain, $functionsDomain)) || + ($sitesDomain != '' && \str_ends_with($domain, $sitesDomain)) ) { $owner = 'Appwrite'; } + $status = RULE_STATUS_CREATED; + if (\str_ends_with($domain, $functionsDomain) || \str_ends_with($domain, $sitesDomain)) { + $status = RULE_STATUS_VERIFIED; + } + $rule = new Document([ '$id' => $ruleId, 'projectId' => $project->getId(), 'projectInternalId' => $project->getSequence(), - 'domain' => $domain->get(), + 'domain' => $domain, 'status' => $status, 'type' => 'deployment', 'trigger' => 'manual', @@ -187,7 +120,7 @@ class Create extends Action 'deploymentResourceInternalId' => $site->getSequence(), 'deploymentVcsProviderBranch' => $branch, 'certificateId' => '', - 'search' => implode(' ', [$ruleId, $domain->get(), $branch]), + 'search' => implode(' ', [$ruleId, $domain, $branch]), 'owner' => $owner, 'region' => $project->getAttribute('region') ]); @@ -198,7 +131,16 @@ class Create extends Action throw new Exception(Exception::RULE_ALREADY_EXISTS); } - if ($rule->getAttribute('status', '') === 'verifying') { + if ($rule->getAttribute('status', '') === RULE_STATUS_CREATED) { + try { + $this->verifyRule($rule, $log); + $rule->setAttribute('status', RULE_STATUS_CERTIFICATE_GENERATING); + } catch (Exception $err) { + $rule->setAttribute('logs', $err->getMessage()); + } + } + + if ($rule->getAttribute('status', '') === RULE_STATUS_CERTIFICATE_GENERATING) { $queueForCertificates ->setDomain(new Document([ 'domain' => $rule->getAttribute('domain'), diff --git a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Verification/Update.php b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Verification/Update.php index af61f25f05..844f117cd5 100644 --- a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Verification/Update.php +++ b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Verification/Update.php @@ -5,22 +5,17 @@ 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\Http\Rules\Action; use Appwrite\SDK\AuthType; use Appwrite\SDK\Method; use Appwrite\SDK\Response as SDKResponse; use Appwrite\Utopia\Response; use Utopia\Database\Database; +use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Validator\UID; -use Utopia\DNS\Message\Record; -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 { @@ -31,8 +26,10 @@ class Update extends Action return 'updateRuleVerification'; } - public function __construct() + public function __construct(...$params) { + parent::__construct(...$params); + $this ->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH) ->setHttpPath('/v1/proxy/rules/:ruleId/verification') @@ -63,6 +60,7 @@ class Update extends Action ->inject('queueForEvents') ->inject('project') ->inject('dbForPlatform') + ->inject('platform') ->inject('log') ->callback($this->action(...)); } @@ -74,6 +72,7 @@ class Update extends Action Event $queueForEvents, Document $project, Database $dbForPlatform, + array $platform, Log $log ) { $rule = $dbForPlatform->getDocument('rules', $ruleId); @@ -82,83 +81,36 @@ 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; - } + $queueForEvents->setParam('ruleId', $rule->getId()); - $validators = []; - - if (!is_null($targetCNAME)) { - if ($targetCNAME->isKnown() && !$targetCNAME->isTest()) { - $validators[] = new DNS($targetCNAME->get(), Record::TYPE_CNAME); - } - } - - if ((new IP(IP::V4))->isValid(System::getEnv('_APP_DOMAIN_TARGET_A', ''))) { - $validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_A', ''), Record::TYPE_A); - } - if ((new IP(IP::V6))->isValid(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''))) { - $validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''), Record::TYPE_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) { + // If rule is already verified or in certificate generation state, don't queue for verification again + if ($rule->getAttribute('status') === RULE_STATUS_VERIFIED || $rule->getAttribute('status') === RULE_STATUS_CERTIFICATE_GENERATING) { return $response->dynamic($rule, Response::MODEL_PROXY_RULE); } - $validator = new AnyOf($validators, AnyOf::TYPE_STRING); - $domain = new Domain($rule->getAttribute('domain', '')); + try { + $this->verifyRule($rule, $log); + // Reset logs and status for the rule + $rule = $dbForPlatform->updateDocument('rules', $rule->getId(), new Document([ + 'logs' => '', + 'status' => RULE_STATUS_CERTIFICATE_GENERATING, + ])); - $validationStart = \microtime(true); - if (!$validator->isValid($domain->get())) { - $log->addExtra('dnsTiming', \strval(\microtime(true) - $validationStart)); - $log->addTag('dnsDomain', $domain->get()); - throw new Exception(Exception::RULE_VERIFICATION_FAILED); - } - - // 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', ''), Record::TYPE_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.'); + $certificateId = $rule->getAttribute('certificateId', ''); + // Reset logs for the associated certificate. + if (!empty($certificateId)) { + $certificate = $dbForPlatform->updateDocument('certificates', $certificateId, new Document([ + 'logs' => '', + ])); } + } catch (Exception $err) { + $dbForPlatform->updateDocument('rules', $rule->getId(), new Document([ + '$updatedAt' => DateTime::now(), + ])); + throw $err; } - $dbForPlatform->updateDocument('rules', $rule->getId(), $rule->setAttribute('status', 'verifying')); - - // Issue a TLS certificate when domain is verified + // Issue a TLS certificate when DNS verification is successful $queueForCertificates ->setDomain(new Document([ 'domain' => $rule->getAttribute('domain'), @@ -166,10 +118,9 @@ class Update extends Action ])) ->trigger(); - $queueForEvents->setParam('ruleId', $rule->getId()); - - $certificate = $dbForPlatform->getDocument('certificates', $rule->getAttribute('certificateId', '')); - $rule->setAttribute('logs', $certificate->getAttribute('logs', '')); + if (!empty($certificate)) { + $rule->setAttribute('renewAt', $certificate->getAttribute('renewDate', '')); + } $response->dynamic($rule, Response::MODEL_PROXY_RULE); } diff --git a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/XList.php b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/XList.php index 86f63bc258..198bf55a6f 100644 --- a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/XList.php +++ b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/XList.php @@ -13,7 +13,6 @@ use Utopia\Database\Document; use Utopia\Database\Exception\Query as QueryException; use Utopia\Database\Query; use Utopia\Database\Validator\Query\Cursor; -use Utopia\Platform\Action; use Utopia\Platform\Scope\HTTP; use Utopia\Validator\Boolean; use Utopia\Validator\Text; @@ -27,8 +26,10 @@ class XList extends Action return 'listRules'; } - public function __construct() + public function __construct(...$params) { + parent::__construct(...$params); + $this ->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET) ->setHttpPath('/v1/proxy/rules') @@ -109,7 +110,12 @@ class XList extends Action $rules = $dbForPlatform->find('rules', $queries); foreach ($rules as $rule) { $certificate = $dbForPlatform->getDocument('certificates', $rule->getAttribute('certificateId', '')); - $rule->setAttribute('logs', $certificate->getAttribute('logs', '')); + + // Give priority to certificate generation logs if present + if (!empty($certificate->getAttribute('logs', ''))) { + $rule->setAttribute('logs', $certificate->getAttribute('logs', '')); + } + $rule->setAttribute('renewAt', $certificate->getAttribute('renewDate', '')); } diff --git a/src/Appwrite/Utopia/Response/Model/Rule.php b/src/Appwrite/Utopia/Response/Model/Rule.php index d4b8ffd9e7..86ac6f470e 100644 --- a/src/Appwrite/Utopia/Response/Model/Rule.php +++ b/src/Appwrite/Utopia/Response/Model/Rule.php @@ -92,9 +92,9 @@ class Rule extends Model ]) ->addRule('logs', [ 'type' => self::TYPE_STRING, - 'description' => 'Certificate generation logs. This will return an empty string if generation did not run, or succeeded.', + 'description' => 'Logs from rule verification or certificate generation. Certificate generation logs are prioritized if both are available.', 'default' => '', - 'example' => 'HTTP challegne failed.', + 'example' => 'Verification of DNS records failed with DNS resolver 8.8.8.8. Domain stage.myapp.com does not have DNS record.', ]) ->addRule('renewAt', [ 'type' => self::TYPE_DATETIME, From 109967953ebad05cf1562a4f6c4b818abd04ce50 Mon Sep 17 00:00:00 2001 From: Hemachandar Date: Tue, 16 Dec 2025 12:20:59 +0530 Subject: [PATCH 2/4] feedback + tests --- docker-compose.yml | 21 ++ .../Modules/Proxy/Http/Rules/API/Create.php | 23 +- .../Proxy/Http/Rules/Function/Create.php | 14 +- .../Proxy/Http/Rules/Redirect/Create.php | 21 +- .../Modules/Proxy/Http/Rules/Site/Create.php | 21 +- .../Proxy/Http/Rules/Verification/Update.php | 2 - .../Services/Proxy/ProxyCustomServerTest.php | 205 +++++++++++++++++- tests/resources/coredns/Corefile | 73 +++++++ tests/unit/Network/Validators/DNSTest.php | 107 +++------ 9 files changed, 361 insertions(+), 126 deletions(-) create mode 100644 tests/resources/coredns/Corefile diff --git a/docker-compose.yml b/docker-compose.yml index 80fb99c0fd..d4d38d4924 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -63,6 +63,8 @@ services: - 9501:80 networks: - appwrite + dns: + - 172.16.238.100 labels: - "traefik.enable=true" - "traefik.constraint-label-stack=appwrite" @@ -99,6 +101,7 @@ services: depends_on: - mariadb - redis + - coredns # - clamav entrypoint: - php @@ -1079,6 +1082,21 @@ services: volumes: - appwrite-redis:/data:rw + coredns: # DNS server for testing purposes (Proxy APIs) + image: coredns/coredns:1.12.4 + container_name: appwrite-coredns + restart: unless-stopped + <<: *x-logging + command: ["-conf", "/mnt/resources/Corefile"] + # If you need to debug CoreDNS, do it from "appwrite container", or port forward: + # ports: + # - "53:53" + networks: + appwrite: + ipv4_address: 172.16.238.100 + volumes: + - ./tests/resources/coredns:/mnt/resources:ro + # Dev Tools Start ------------------------------------------------------------------------------------------ # # The Appwrite Team uses the following tools to help debug, monitor and diagnose the Appwrite stack @@ -1209,6 +1227,9 @@ networks: name: gateway appwrite: name: appwrite + ipam: + config: + - subnet: 172.16.238.0/24 runtimes: name: runtimes diff --git a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/API/Create.php b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/API/Create.php index fc4537301e..e5bb447ee0 100644 --- a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/API/Create.php +++ b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/API/Create.php @@ -79,18 +79,15 @@ class Create extends Action // TODO: (@Meldiron) Remove after 1.7.x migration $ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain) : ID::unique(); - + $status = RULE_STATUS_CREATED; $owner = ''; + if ( ($functionsDomain != '' && \str_ends_with($domain, $functionsDomain)) || ($sitesDomain != '' && \str_ends_with($domain, $sitesDomain)) ) { - $owner = 'Appwrite'; - } - - $status = RULE_STATUS_CREATED; - if (\str_ends_with($domain, $functionsDomain) || \str_ends_with($domain, $sitesDomain)) { $status = RULE_STATUS_VERIFIED; + $owner = 'Appwrite'; } $rule = new Document([ @@ -107,12 +104,6 @@ class Create extends Action 'region' => $project->getAttribute('region') ]); - try { - $rule = $dbForPlatform->createDocument('rules', $rule); - } catch (Duplicate $e) { - throw new Exception(Exception::RULE_ALREADY_EXISTS); - } - if ($rule->getAttribute('status', '') === RULE_STATUS_CREATED) { try { $this->verifyRule($rule, $log); @@ -122,6 +113,14 @@ class Create extends Action } } + \var_dump($rule); + + try { + $rule = $dbForPlatform->createDocument('rules', $rule); + } catch (Duplicate $e) { + throw new Exception(Exception::RULE_ALREADY_EXISTS); + } + if ($rule->getAttribute('status', '') === RULE_STATUS_CERTIFICATE_GENERATING) { $queueForCertificates ->setDomain(new Document([ diff --git a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Function/Create.php b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Function/Create.php index c88e3e5085..d7e427efe1 100644 --- a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Function/Create.php +++ b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Function/Create.php @@ -101,7 +101,7 @@ class Create extends Action } $status = RULE_STATUS_CREATED; - if (\str_ends_with($domain, $functionsDomain) || \str_ends_with($domain, $sitesDomain)) { + if (($functionsDomain != '' && \str_ends_with($domain, $functionsDomain)) || ($sitesDomain != '' && \str_ends_with($domain, $sitesDomain))) { $status = RULE_STATUS_VERIFIED; } @@ -125,12 +125,6 @@ class Create extends Action 'region' => $project->getAttribute('region') ]); - try { - $rule = $dbForPlatform->createDocument('rules', $rule); - } catch (Duplicate $e) { - throw new Exception(Exception::RULE_ALREADY_EXISTS); - } - if ($rule->getAttribute('status', '') === RULE_STATUS_CREATED) { try { $this->verifyRule($rule, $log); @@ -140,6 +134,12 @@ class Create extends Action } } + try { + $rule = $dbForPlatform->createDocument('rules', $rule); + } catch (Duplicate $e) { + throw new Exception(Exception::RULE_ALREADY_EXISTS); + } + if ($rule->getAttribute('status', '') === RULE_STATUS_CERTIFICATE_GENERATING) { $queueForCertificates ->setDomain(new Document([ diff --git a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Redirect/Create.php b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Redirect/Create.php index 1f785f858c..7ddd501d51 100644 --- a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Redirect/Create.php +++ b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Redirect/Create.php @@ -96,18 +96,15 @@ class Create extends Action // TODO: (@Meldiron) Remove after 1.7.x migration $ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain) : ID::unique(); - + $status = RULE_STATUS_CREATED; $owner = ''; + if ( ($functionsDomain != '' && \str_ends_with($domain, $functionsDomain)) || ($sitesDomain != '' && \str_ends_with($domain, $sitesDomain)) ) { - $owner = 'Appwrite'; - } - - $status = RULE_STATUS_CREATED; - if (\str_ends_with($domain, $functionsDomain) || \str_ends_with($domain, $sitesDomain)) { $status = RULE_STATUS_VERIFIED; + $owner = 'Appwrite'; } $rule = new Document([ @@ -129,12 +126,6 @@ class Create extends Action 'region' => $project->getAttribute('region') ]); - try { - $rule = $dbForPlatform->createDocument('rules', $rule); - } catch (Duplicate $e) { - throw new Exception(Exception::RULE_ALREADY_EXISTS); - } - if ($rule->getAttribute('status', '') === RULE_STATUS_CREATED) { try { $this->verifyRule($rule, $log); @@ -144,6 +135,12 @@ class Create extends Action } } + try { + $rule = $dbForPlatform->createDocument('rules', $rule); + } catch (Duplicate $e) { + throw new Exception(Exception::RULE_ALREADY_EXISTS); + } + if ($rule->getAttribute('status', '') === RULE_STATUS_CERTIFICATE_GENERATING) { $queueForCertificates ->setDomain(new Document([ diff --git a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Site/Create.php b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Site/Create.php index 793383e3ba..7aff9e7e8a 100644 --- a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Site/Create.php +++ b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Site/Create.php @@ -91,18 +91,15 @@ class Create extends Action // TODO: (@Meldiron) Remove after 1.7.x migration $ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain) : ID::unique(); - + $status = RULE_STATUS_CREATED; $owner = ''; + if ( ($functionsDomain != '' && \str_ends_with($domain, $functionsDomain)) || ($sitesDomain != '' && \str_ends_with($domain, $sitesDomain)) ) { - $owner = 'Appwrite'; - } - - $status = RULE_STATUS_CREATED; - if (\str_ends_with($domain, $functionsDomain) || \str_ends_with($domain, $sitesDomain)) { $status = RULE_STATUS_VERIFIED; + $owner = 'Appwrite'; } $rule = new Document([ @@ -125,12 +122,6 @@ class Create extends Action 'region' => $project->getAttribute('region') ]); - try { - $rule = $dbForPlatform->createDocument('rules', $rule); - } catch (Duplicate $e) { - throw new Exception(Exception::RULE_ALREADY_EXISTS); - } - if ($rule->getAttribute('status', '') === RULE_STATUS_CREATED) { try { $this->verifyRule($rule, $log); @@ -140,6 +131,12 @@ class Create extends Action } } + try { + $rule = $dbForPlatform->createDocument('rules', $rule); + } catch (Duplicate $e) { + throw new Exception(Exception::RULE_ALREADY_EXISTS); + } + if ($rule->getAttribute('status', '') === RULE_STATUS_CERTIFICATE_GENERATING) { $queueForCertificates ->setDomain(new Document([ diff --git a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Verification/Update.php b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Verification/Update.php index 844f117cd5..e09c4a6eba 100644 --- a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Verification/Update.php +++ b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Verification/Update.php @@ -60,7 +60,6 @@ class Update extends Action ->inject('queueForEvents') ->inject('project') ->inject('dbForPlatform') - ->inject('platform') ->inject('log') ->callback($this->action(...)); } @@ -72,7 +71,6 @@ class Update extends Action Event $queueForEvents, Document $project, Database $dbForPlatform, - array $platform, Log $log ) { $rule = $dbForPlatform->getDocument('rules', $ruleId); diff --git a/tests/e2e/Services/Proxy/ProxyCustomServerTest.php b/tests/e2e/Services/Proxy/ProxyCustomServerTest.php index 3fbbb7d5e9..ead6be8084 100644 --- a/tests/e2e/Services/Proxy/ProxyCustomServerTest.php +++ b/tests/e2e/Services/Proxy/ProxyCustomServerTest.php @@ -15,6 +15,36 @@ class ProxyCustomServerTest extends Scope use ProjectCustom; use SideServer; + protected function tearDown(): void + { + // Cleanup for testRuleVerification test + // Required as it uses static domain name + $rules = $this->listRules([ + 'queries' => [ + Query::endsWith('domain', 'webapp.com')->toString(), + Query::limit(1000)->toString(), + ] + ]); + $this->assertEquals(200, $rules['headers']['status-code']); + foreach ($rules['body']['rules'] as $rule) { + $ruleId = $rule['$id']; + $response = $this->deleteRule($ruleId); + $this->assertEquals(204, $response['headers']['status-code']); + } + + if ($rules['body']['total'] > 0) { + $rules = $this->listRules([ + 'queries' => [ + Query::endsWith('domain', 'webapp.com')->toString(), + Query::limit(1) + ] + ]); + $this->assertEquals(200, $rules['headers']['status-code']); + $this->assertEquals(0, count($rules['body']['rules'])); + $this->assertEquals(0, $rules['body']['total']); + } + } + public function testCreateRule(): void { $domain = \uniqid() . '-api.myapp.com'; @@ -56,7 +86,7 @@ class ProxyCustomServerTest extends Scope $domain = \uniqid() . '.com'; $rule = $this->createAPIRule($domain); $this->assertEquals(201, $rule['headers']['status-code']); - $this->assertEquals('created', $rule['body']['status']); + $this->assertEquals(RULE_STATUS_CREATED, $rule['body']['status']); } public function testCreateRuleVcs(): void @@ -358,7 +388,7 @@ class ProxyCustomServerTest extends Scope $rule = $this->createAPIRule($domain); $this->assertEquals(201, $rule['headers']['status-code']); - $this->assertEquals('verified', $rule['body']['status']); + $this->assertEquals(RULE_STATUS_VERIFIED, $rule['body']['status']); $this->cleanupRule($rule['body']['$id']); @@ -367,7 +397,7 @@ class ProxyCustomServerTest extends Scope $rule = $this->createAPIRule($domain); $this->assertEquals(201, $rule['headers']['status-code']); - $this->assertEquals('verified', $rule['body']['status']); + $this->assertEquals(RULE_STATUS_VERIFIED, $rule['body']['status']); $this->cleanupRule($rule['body']['$id']); @@ -376,7 +406,7 @@ class ProxyCustomServerTest extends Scope $rule = $this->createAPIRule($domain); $this->assertEquals(201, $rule['headers']['status-code']); - $this->assertEquals('created', $rule['body']['status']); + $this->assertEquals(RULE_STATUS_CREATED, $rule['body']['status']); $ruleId = $rule['body']['$id']; @@ -539,4 +569,171 @@ class ProxyCustomServerTest extends Scope $this->assertEquals(0, $rules['body']['total']); $this->assertCount(0, $rules['body']['rules']); } + + public function testRuleVerification(): void + { + + // 1. Site rule can verify + $site = $this->setupSite(); + $siteId = $site['siteId']; + + $rule = $this->createSiteRule('stage-site.webapp.com', $siteId); + $this->assertEquals(201, $rule['headers']['status-code']); + $this->assertEquals(RULE_STATUS_CERTIFICATE_GENERATING, $rule['body']['status']); + $this->assertEmpty($rule['body']['logs']); + $this->assertNotEmpty($rule['body']['$id']); + $ruleId = $rule['body']['$id']; + + $rule = $this->updateRuleVerification($ruleId); + $this->assertEquals(200, $rule['headers']['status-code']); + $this->assertEquals($ruleId, $rule['body']['$id']); + $this->assertEquals(RULE_STATUS_CERTIFICATE_GENERATING, $rule['body']['status']); + $this->assertEmpty($rule['body']['logs']); + + $this->cleanupRule($rule['body']['$id']); + $this->cleanupSite($siteId); + + // 2. Function rule can verify + $function = $this->setupFunction(); + $functionId = $function['functionId']; + + $rule = $this->createFunctionRule('stage-function.webapp.com', $functionId); + $this->assertEquals(201, $rule['headers']['status-code']); + $this->assertEquals(RULE_STATUS_CERTIFICATE_GENERATING, $rule['body']['status']); + $this->assertEmpty($rule['body']['logs']); + $this->cleanupRule($rule['body']['$id']); + + $rule = $this->createAPIRule('stage-site.webapp.com'); + $this->assertEquals(201, $rule['headers']['status-code']); + $this->assertEquals(RULE_STATUS_CREATED, $rule['body']['status']); + $this->assertStringContainsString('has incorrect CNAME value', $rule['body']['logs']); + $this->cleanupRule($rule['body']['$id']); + + $this->cleanupFunction($functionId); + + // 3. Wrong A record fails to verify + $rule = $this->createAPIRule('wrong-a-webapp.com'); + $this->assertEquals(201, $rule['headers']['status-code']); + $this->assertEquals(RULE_STATUS_CREATED, $rule['body']['status']); + $this->assertStringContainsString('is missing CNAME record', $rule['body']['logs']); + + $ruleId = $rule['body']['$id']; + $rule = $this->updateRuleVerification($ruleId); + $this->assertEquals(400, $rule['headers']['status-code']); + $this->assertStringContainsString('is missing CNAME record', $rule['body']['message']); + + $rule = $this->getRule($ruleId); + $this->assertEquals(200, $rule['headers']['status-code']); + $this->assertEquals(RULE_STATUS_CREATED, $rule['body']['status']); + + $this->cleanupRule($ruleId); + + // 4. Correct A record can verify + $rule = $this->createAPIRule('webapp.com'); + $this->assertEquals(201, $rule['headers']['status-code']); + $this->assertEquals(RULE_STATUS_CERTIFICATE_GENERATING, $rule['body']['status']); + $this->assertEmpty($rule['body']['logs']); + + $this->cleanupRule($rule['body']['$id']); + + // 5. Correct CNAME record can verify (no CAA record) + $rule = $this->createAPIRule('stage.webapp.com'); + $this->assertEquals(201, $rule['headers']['status-code']); + $this->assertEquals(RULE_STATUS_CERTIFICATE_GENERATING, $rule['body']['status']); + $this->assertEmpty($rule['body']['logs']); + + $this->cleanupRule($rule['body']['$id']); + + // 6. Missing CNAME record fails to verify + $rule = $this->createAPIRule('stage-missing-cname.webapp.com'); + $this->assertEquals(201, $rule['headers']['status-code']); + $this->assertEquals(RULE_STATUS_CREATED, $rule['body']['status']); + $this->assertStringContainsString('is missing CNAME record', $rule['body']['logs']); + + $ruleId = $rule['body']['$id']; + $rule = $this->updateRuleVerification($ruleId); + $this->assertEquals(400, $rule['headers']['status-code']); + $this->assertStringContainsString('is missing CNAME record', $rule['body']['message']); + + $rule = $this->getRule($ruleId); + $this->assertEquals(200, $rule['headers']['status-code']); + $this->assertEquals(RULE_STATUS_CREATED, $rule['body']['status']); + + $this->cleanupRule($ruleId); + + // 7. Wrong CNAME record fails to verify + $rule = $this->createAPIRule('stage-wrong-cname.webapp.com'); + $this->assertEquals(201, $rule['headers']['status-code']); + $this->assertEquals(RULE_STATUS_CREATED, $rule['body']['status']); + $this->assertStringContainsString('has incorrect CNAME value', $rule['body']['logs']); + + $ruleId = $rule['body']['$id']; + $rule = $this->updateRuleVerification($ruleId); + $this->assertEquals(400, $rule['headers']['status-code']); + $this->assertStringContainsString('has incorrect CNAME value', $rule['body']['message']); + + $rule = $this->getRule($ruleId); + $this->assertEquals(200, $rule['headers']['status-code']); + $this->assertEquals(RULE_STATUS_CREATED, $rule['body']['status']); + + $this->cleanupRule($ruleId); + + // 8. Wrong CAA record fails to verify + $rule = $this->createAPIRule('stage-wrong-caa.webapp.com'); + $this->assertEquals(201, $rule['headers']['status-code']); + $this->assertEquals(RULE_STATUS_CREATED, $rule['body']['status']); + $this->assertStringContainsString('has incorrect CAA value', $rule['body']['logs']); + + $ruleId = $rule['body']['$id']; + $rule = $this->updateRuleVerification($ruleId); + $this->assertEquals(400, $rule['headers']['status-code']); + $this->assertStringContainsString('has incorrect CAA value', $rule['body']['message']); + + $rule = $this->getRule($ruleId); + $this->assertEquals(200, $rule['headers']['status-code']); + $this->assertEquals(RULE_STATUS_CREATED, $rule['body']['status']); + + $this->cleanupRule($ruleId); + + // 9. Correct CAA record can verify + $rule = $this->createAPIRule('stage-correct-caa.webapp.com'); + $this->assertEquals(201, $rule['headers']['status-code']); + $this->assertEquals(RULE_STATUS_CERTIFICATE_GENERATING, $rule['body']['status']); + $this->assertEmpty($rule['body']['logs']); + + $this->cleanupRule($rule['body']['$id']); + } + + public function testUpdateRuleVerificationWithSameDataUpdatesTimestamp(): void + { + $domain = \uniqid() . '-timestamp-test.webapp.com'; + $rule = $this->createAPIRule($domain); + + $this->assertEquals(201, $rule['headers']['status-code']); + $this->assertEquals(RULE_STATUS_CREATED, $rule['body']['status']); + $this->assertNotEmpty($rule['body']['logs']); + + $ruleId = $rule['body']['$id']; + $initialUpdatedAt = $rule['body']['$updatedAt']; + $initiallogs = $rule['body']['logs']; + + sleep(1); + + $updatedRule = $this->updateRuleVerification($ruleId); + + $this->assertEquals(400, $updatedRule['headers']['status-code']); + $this->assertStringContainsString($initiallogs, $updatedRule['body']['message']); + + $ruleAfterUpdate = $this->getRule($ruleId); + $this->assertEquals(200, $ruleAfterUpdate['headers']['status-code']); + $this->assertEquals(RULE_STATUS_CREATED, $ruleAfterUpdate['body']['status']); + $this->assertEquals($initiallogs, $ruleAfterUpdate['body']['logs']); + $this->assertNotEquals($initialUpdatedAt, $ruleAfterUpdate['body']['$updatedAt']); + + $initialTime = new \DateTime($initialUpdatedAt); + $updatedTime = new \DateTime($ruleAfterUpdate['body']['$updatedAt']); + $this->assertGreaterThan($initialTime, $updatedTime); + + $this->cleanupRule($ruleId); + } } diff --git a/tests/resources/coredns/Corefile b/tests/resources/coredns/Corefile new file mode 100644 index 0000000000..b156ba336d --- /dev/null +++ b/tests/resources/coredns/Corefile @@ -0,0 +1,73 @@ +# Re-use public resolver to answer unknown queries +. { + forward . 1.1.1.1 +} + +# Zones configuration +webapp.com { + template IN A { + match "^webapp\.com\.$" + answer "{{ .Name }} 60 IN A 203.0.0.1" + fallthrough + } + + template IN CNAME { + match "^stage-site\.webapp\.com\.$" + answer "{{ .Name }} 60 IN CNAME sites.localhost." + fallthrough + } + + template IN CNAME { + match "^stage-function\.webapp\.com\.$" + answer "{{ .Name }} 60 IN CNAME functions.localhost." + fallthrough + } + + template IN CNAME { + match "^stage\.webapp\.com\.$" + answer "{{ .Name }} 60 IN CNAME cname.localhost." + fallthrough + } + + template IN CNAME { + match "^stage-wrong-cname\.webapp\.com\.$" + answer "{{ .Name }} 60 IN CNAME cname-wrong.tests.appwrite.io." + fallthrough + } + + template IN A { + match "^stage-wrong-caa\.webapp\.com\.$" + answer "{{ .Name }} 60 IN A 203.0.0.1" + fallthrough + } + + template IN CAA { + match "^stage-wrong-caa\.webapp\.com\.$" + answer "{{ .Name }} 60 IN CAA 0 issue \"unknown-issuer.org\"" + fallthrough + } + + + template IN A { + match "^stage-correct-caa\.webapp\.com\.$" + answer "{{ .Name }} 60 IN A 203.0.0.1" + fallthrough + } + + template IN CAA { + match "^stage-correct-caa\.webapp\.com\.$" + answer "{{ .Name }} 60 IN CAA 0 issue \"digicert.com\"" + fallthrough + } + + forward . 1.1.1.1 +} + +# Zones configuration +wrong-a-webapp.com { + template IN A { + match "^wrong-a-webapp\.com\.$" + answer "{{ .Name }} 60 IN A 203.0.0.5" + fallthrough + } +} \ No newline at end of file diff --git a/tests/unit/Network/Validators/DNSTest.php b/tests/unit/Network/Validators/DNSTest.php index 4611e00f4d..4a07098a31 100644 --- a/tests/unit/Network/Validators/DNSTest.php +++ b/tests/unit/Network/Validators/DNSTest.php @@ -3,98 +3,51 @@ namespace Tests\Unit\Network\Validators; use Appwrite\Network\Validator\DNS; -use Appwrite\Tests\Retry; use PHPUnit\Framework\TestCase; use Utopia\DNS\Message\Record; -/** - * DNS Setup (on Appwrite Labs digital ocean team, network tab): - * - * certainly.caa.appwrite.org: CAA 0 issue "certainly.com" - * certainly-full.caa.appwrite.org: CAA 128 issuewild "certainly.com;account=123456;validationmethods=dns-01" - * letsencrypt.certainly.caa.appwrite.org: CAA 0 issue "letsencrypt.org" - */ class DNSTest extends TestCase { - public function testCNAME(): void + public function testSingleDNSServer(): void { - $validator = new DNS('appwrite.io', Record::TYPE_CNAME); - $this->assertEquals($validator->isValid(''), false); - $this->assertEquals($validator->isValid(null), false); - $this->assertEquals($validator->isValid(false), false); - $this->assertEquals($validator->isValid('cname-unit-test.appwrite.org'), true); - $this->assertEquals($validator->isValid('test1.appwrite.org'), false); + $validator = new DNS('appwrite.io', Record::TYPE_CNAME, ['8.8.8.8']); + + $this->assertEquals(false, $validator->isValid('')); + $this->assertEquals(false, $validator->isValid(null)); + $this->assertEquals('string', $validator->getType()); } - public function testA(): void + public function testMultipleDNSServers(): void { - // IPv4 for documentation purposes - $validator = new DNS('203.0.113.1', Record::TYPE_A); - $this->assertEquals($validator->isValid(''), false); - $this->assertEquals($validator->isValid(null), false); - $this->assertEquals($validator->isValid(false), false); - $this->assertEquals($validator->isValid('a-unit-test.appwrite.org'), true); - $this->assertEquals($validator->isValid('test1.appwrite.org'), false); + $validator = new DNS('appwrite.io', Record::TYPE_CNAME, ['8.8.8.8', '1.1.1.1']); + + $this->assertEquals(false, $validator->isValid('')); + $this->assertEquals(false, $validator->isValid(null)); + $this->assertEquals('string', $validator->getType()); } - public function testAAAA(): void + public function testValidationFailure(): void { - // IPv6 for documentation purposes - $validator = new DNS('2001:db8::1', Record::TYPE_AAAA); - $this->assertEquals($validator->isValid(''), false); - $this->assertEquals($validator->isValid(null), false); - $this->assertEquals($validator->isValid(false), false); - $this->assertEquals($validator->isValid('aaaa-unit-test.appwrite.org'), true); - $this->assertEquals($validator->isValid('test1.appwrite.org'), false); + $validator = new DNS('invalid-target.example.com', Record::TYPE_CNAME, ['8.8.8.8', '1.1.1.1']); + + $result = $validator->isValid('nonexistent-domain-' . \uniqid() . '.com'); + + $this->assertEquals(false, $result); + $this->assertIsInt($validator->count); + $this->assertIsString($validator->value); + $this->assertIsArray($validator->records); + $this->assertIsString($validator->getDescription()); } - #[Retry(count: 5)] - public function testCAA(): void + public function testCoreDNSFailure(): void { - $digitalOceanIp = '172.64.52.210'; // ping ns1.digitalocean.com + // CoreDNS is configured to return cname.localhost. for stage.webapp.com + $validator = new DNS('cname.localhost.', Record::TYPE_CNAME, ['172.16.238.100', '8.8.8.8']); - $certainly = new DNS('certainly.com', Record::TYPE_CAA, $digitalOceanIp); - $letsencrypt = new DNS('letsencrypt.org', Record::TYPE_CAA, $digitalOceanIp); + $result = $validator->isValid('stage.webapp.com'); + $this->assertEquals(false, $result); - // No CAA record succeeds on main domain & subdomains for any issuer - $this->assertEquals($certainly->isValid('caa.appwrite.org'), true); - $this->assertEquals($certainly->isValid('sub.caa.appwrite.org'), true); - $this->assertEquals($certainly->isValid('sub.sub.caa.appwrite.org'), true); - - $this->assertEquals($letsencrypt->isValid('caa.appwrite.org'), true); - $this->assertEquals($letsencrypt->isValid('sub.caa.appwrite.org'), true); - $this->assertEquals($letsencrypt->isValid('sub.sub.caa.appwrite.org'), true); - - // Custom flags and tag is allowed, but only for Certainly - $this->assertEquals($certainly->isValid('certainly-full.caa.appwrite.org'), true); - $this->assertEquals($letsencrypt->isValid('certainly-full.caa.appwrite.org'), false); - - // Custom flags&tag are not allowed if validator includes specific flags&tag - $certainlyFull = new DNS('0 issue "certainly.com"', Record::TYPE_CAA); - $this->assertEquals($certainlyFull->isValid('certainly-full.caa.appwrite.org'), false); - - // Custom flags&tag still allows if they match exactly - $certainlyFull = new DNS('128 issuewild "certainly.com;account=123456;validationmethods=dns-01"', Record::TYPE_CAA); - $this->assertEquals($certainlyFull->isValid('certainly-full.caa.appwrite.org'), true); - - // Certainly CAA allows Certainly, but not LetsEncrypt; Same for subdomains - $this->assertEquals($certainly->isValid('certainly.caa.appwrite.org'), true); - $this->assertEquals($letsencrypt->isValid('certainly.caa.appwrite.org'), false); - - $this->assertEquals($certainly->isValid('sub.certainly.caa.appwrite.org'), true); - $this->assertEquals($letsencrypt->isValid('sub.certainly.caa.appwrite.org'), false); - - $this->assertEquals($certainly->isValid('sub.sub.certainly.caa.appwrite.org'), true); - $this->assertEquals($letsencrypt->isValid('sub.sub.certainly.caa.appwrite.org'), false); - - // LetsEncrypt CAA on subdomain with parent allowing Certainly. Only LetsEncrypt is allowed; Same for subdomains - $this->assertEquals($certainly->isValid('letsencrypt.certainly.caa.appwrite.org'), false); - $this->assertEquals($letsencrypt->isValid('letsencrypt.certainly.caa.appwrite.org'), true); - - $this->assertEquals($certainly->isValid('sub.letsencrypt.certainly.caa.appwrite.org'), false); - $this->assertEquals($letsencrypt->isValid('sub.letsencrypt.certainly.caa.appwrite.org'), true); - - $this->assertEquals($certainly->isValid('sub.sub.letsencrypt.certainly.caa.appwrite.org'), false); - $this->assertEquals($letsencrypt->isValid('sub.sub.letsencrypt.certainly.caa.appwrite.org'), true); + $result = $validator->isValid('stage-wrong-cname.webapp.com'); + $this->assertEquals(false, $result); } -} +} \ No newline at end of file From dfb53895eb582c8891de826afd44995a92fdec35 Mon Sep 17 00:00:00 2001 From: Hemachandar Date: Tue, 16 Dec 2025 12:40:42 +0530 Subject: [PATCH 3/4] tiny --- .env | 6 +++--- .../Platform/Modules/Proxy/Http/Rules/API/Create.php | 2 -- tests/e2e/Services/Proxy/ProxyCustomServerTest.php | 2 +- tests/unit/Network/Validators/DNSTest.php | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.env b/.env index 3a0c4a50cb..64fc7ef10f 100644 --- a/.env +++ b/.env @@ -21,13 +21,13 @@ _APP_OPTIONS_ROUTER_PROTECTION=disabled _APP_OPTIONS_FORCE_HTTPS=disabled _APP_OPTIONS_ROUTER_FORCE_HTTPS=disabled _APP_OPENSSL_KEY_V1=your-secret-key -_APP_DNS=8.8.8.8 +_APP_DNS=172.16.238.100 # CoreDNS _APP_DOMAIN=appwrite.test _APP_CONSOLE_DOMAIN=localhost _APP_DOMAIN_FUNCTIONS=functions.localhost _APP_DOMAIN_SITES=sites.localhost -_APP_DOMAIN_TARGET_CNAME=test.localhost -_APP_DOMAIN_TARGET_A=127.0.0.1 +_APP_DOMAIN_TARGET_CNAME=cname.localhost +_APP_DOMAIN_TARGET_A=203.0.0.1 _APP_DOMAIN_TARGET_AAAA=::1 _APP_DOMAIN_TARGET_CAA=digicert.com _APP_RULES_FORMAT=md5 diff --git a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/API/Create.php b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/API/Create.php index e5bb447ee0..a187622422 100644 --- a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/API/Create.php +++ b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/API/Create.php @@ -113,8 +113,6 @@ class Create extends Action } } - \var_dump($rule); - try { $rule = $dbForPlatform->createDocument('rules', $rule); } catch (Duplicate $e) { diff --git a/tests/e2e/Services/Proxy/ProxyCustomServerTest.php b/tests/e2e/Services/Proxy/ProxyCustomServerTest.php index ead6be8084..dc2f5c72a3 100644 --- a/tests/e2e/Services/Proxy/ProxyCustomServerTest.php +++ b/tests/e2e/Services/Proxy/ProxyCustomServerTest.php @@ -36,7 +36,7 @@ class ProxyCustomServerTest extends Scope $rules = $this->listRules([ 'queries' => [ Query::endsWith('domain', 'webapp.com')->toString(), - Query::limit(1) + Query::limit(1)->toString() ] ]); $this->assertEquals(200, $rules['headers']['status-code']); diff --git a/tests/unit/Network/Validators/DNSTest.php b/tests/unit/Network/Validators/DNSTest.php index 4a07098a31..6e4a78022f 100644 --- a/tests/unit/Network/Validators/DNSTest.php +++ b/tests/unit/Network/Validators/DNSTest.php @@ -50,4 +50,4 @@ class DNSTest extends TestCase $result = $validator->isValid('stage-wrong-cname.webapp.com'); $this->assertEquals(false, $result); } -} \ No newline at end of file +} From 990209b624a01a56f3d8ad7ecd81d92a4036d0a9 Mon Sep 17 00:00:00 2001 From: Hemachandar Date: Tue, 16 Dec 2025 16:17:15 +0530 Subject: [PATCH 4/4] feedback --- .../Proxy/Http/Rules/Function/Create.php | 9 ++-- .../Services/Proxy/ProxyCustomServerTest.php | 42 +++++++++---------- 2 files changed, 24 insertions(+), 27 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Function/Create.php b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Function/Create.php index d7e427efe1..13ee6b9cb5 100644 --- a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Function/Create.php +++ b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Function/Create.php @@ -91,18 +91,15 @@ class Create extends Action // TODO: (@Meldiron) Remove after 1.7.x migration $ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain) : ID::unique(); - + $status = RULE_STATUS_CREATED; $owner = ''; + if ( ($functionsDomain != '' && \str_ends_with($domain, $functionsDomain)) || ($sitesDomain != '' && \str_ends_with($domain, $sitesDomain)) ) { - $owner = 'Appwrite'; - } - - $status = RULE_STATUS_CREATED; - if (($functionsDomain != '' && \str_ends_with($domain, $functionsDomain)) || ($sitesDomain != '' && \str_ends_with($domain, $sitesDomain))) { $status = RULE_STATUS_VERIFIED; + $owner = 'Appwrite'; } $rule = new Document([ diff --git a/tests/e2e/Services/Proxy/ProxyCustomServerTest.php b/tests/e2e/Services/Proxy/ProxyCustomServerTest.php index dc2f5c72a3..1830adbae3 100644 --- a/tests/e2e/Services/Proxy/ProxyCustomServerTest.php +++ b/tests/e2e/Services/Proxy/ProxyCustomServerTest.php @@ -86,7 +86,7 @@ class ProxyCustomServerTest extends Scope $domain = \uniqid() . '.com'; $rule = $this->createAPIRule($domain); $this->assertEquals(201, $rule['headers']['status-code']); - $this->assertEquals(RULE_STATUS_CREATED, $rule['body']['status']); + $this->assertEquals('created', $rule['body']['status']); } public function testCreateRuleVcs(): void @@ -388,7 +388,7 @@ class ProxyCustomServerTest extends Scope $rule = $this->createAPIRule($domain); $this->assertEquals(201, $rule['headers']['status-code']); - $this->assertEquals(RULE_STATUS_VERIFIED, $rule['body']['status']); + $this->assertEquals('verified', $rule['body']['status']); $this->cleanupRule($rule['body']['$id']); @@ -397,7 +397,7 @@ class ProxyCustomServerTest extends Scope $rule = $this->createAPIRule($domain); $this->assertEquals(201, $rule['headers']['status-code']); - $this->assertEquals(RULE_STATUS_VERIFIED, $rule['body']['status']); + $this->assertEquals('verified', $rule['body']['status']); $this->cleanupRule($rule['body']['$id']); @@ -406,7 +406,7 @@ class ProxyCustomServerTest extends Scope $rule = $this->createAPIRule($domain); $this->assertEquals(201, $rule['headers']['status-code']); - $this->assertEquals(RULE_STATUS_CREATED, $rule['body']['status']); + $this->assertEquals('created', $rule['body']['status']); $ruleId = $rule['body']['$id']; @@ -579,7 +579,7 @@ class ProxyCustomServerTest extends Scope $rule = $this->createSiteRule('stage-site.webapp.com', $siteId); $this->assertEquals(201, $rule['headers']['status-code']); - $this->assertEquals(RULE_STATUS_CERTIFICATE_GENERATING, $rule['body']['status']); + $this->assertEquals('verifying', $rule['body']['status']); $this->assertEmpty($rule['body']['logs']); $this->assertNotEmpty($rule['body']['$id']); $ruleId = $rule['body']['$id']; @@ -587,7 +587,7 @@ class ProxyCustomServerTest extends Scope $rule = $this->updateRuleVerification($ruleId); $this->assertEquals(200, $rule['headers']['status-code']); $this->assertEquals($ruleId, $rule['body']['$id']); - $this->assertEquals(RULE_STATUS_CERTIFICATE_GENERATING, $rule['body']['status']); + $this->assertEquals('verifying', $rule['body']['status']); $this->assertEmpty($rule['body']['logs']); $this->cleanupRule($rule['body']['$id']); @@ -599,13 +599,13 @@ class ProxyCustomServerTest extends Scope $rule = $this->createFunctionRule('stage-function.webapp.com', $functionId); $this->assertEquals(201, $rule['headers']['status-code']); - $this->assertEquals(RULE_STATUS_CERTIFICATE_GENERATING, $rule['body']['status']); + $this->assertEquals('verifying', $rule['body']['status']); $this->assertEmpty($rule['body']['logs']); $this->cleanupRule($rule['body']['$id']); $rule = $this->createAPIRule('stage-site.webapp.com'); $this->assertEquals(201, $rule['headers']['status-code']); - $this->assertEquals(RULE_STATUS_CREATED, $rule['body']['status']); + $this->assertEquals('created', $rule['body']['status']); $this->assertStringContainsString('has incorrect CNAME value', $rule['body']['logs']); $this->cleanupRule($rule['body']['$id']); @@ -614,7 +614,7 @@ class ProxyCustomServerTest extends Scope // 3. Wrong A record fails to verify $rule = $this->createAPIRule('wrong-a-webapp.com'); $this->assertEquals(201, $rule['headers']['status-code']); - $this->assertEquals(RULE_STATUS_CREATED, $rule['body']['status']); + $this->assertEquals('created', $rule['body']['status']); $this->assertStringContainsString('is missing CNAME record', $rule['body']['logs']); $ruleId = $rule['body']['$id']; @@ -624,14 +624,14 @@ class ProxyCustomServerTest extends Scope $rule = $this->getRule($ruleId); $this->assertEquals(200, $rule['headers']['status-code']); - $this->assertEquals(RULE_STATUS_CREATED, $rule['body']['status']); + $this->assertEquals('created', $rule['body']['status']); $this->cleanupRule($ruleId); // 4. Correct A record can verify $rule = $this->createAPIRule('webapp.com'); $this->assertEquals(201, $rule['headers']['status-code']); - $this->assertEquals(RULE_STATUS_CERTIFICATE_GENERATING, $rule['body']['status']); + $this->assertEquals('verifying', $rule['body']['status']); $this->assertEmpty($rule['body']['logs']); $this->cleanupRule($rule['body']['$id']); @@ -639,7 +639,7 @@ class ProxyCustomServerTest extends Scope // 5. Correct CNAME record can verify (no CAA record) $rule = $this->createAPIRule('stage.webapp.com'); $this->assertEquals(201, $rule['headers']['status-code']); - $this->assertEquals(RULE_STATUS_CERTIFICATE_GENERATING, $rule['body']['status']); + $this->assertEquals('verifying', $rule['body']['status']); $this->assertEmpty($rule['body']['logs']); $this->cleanupRule($rule['body']['$id']); @@ -647,7 +647,7 @@ class ProxyCustomServerTest extends Scope // 6. Missing CNAME record fails to verify $rule = $this->createAPIRule('stage-missing-cname.webapp.com'); $this->assertEquals(201, $rule['headers']['status-code']); - $this->assertEquals(RULE_STATUS_CREATED, $rule['body']['status']); + $this->assertEquals('created', $rule['body']['status']); $this->assertStringContainsString('is missing CNAME record', $rule['body']['logs']); $ruleId = $rule['body']['$id']; @@ -657,14 +657,14 @@ class ProxyCustomServerTest extends Scope $rule = $this->getRule($ruleId); $this->assertEquals(200, $rule['headers']['status-code']); - $this->assertEquals(RULE_STATUS_CREATED, $rule['body']['status']); + $this->assertEquals('created', $rule['body']['status']); $this->cleanupRule($ruleId); // 7. Wrong CNAME record fails to verify $rule = $this->createAPIRule('stage-wrong-cname.webapp.com'); $this->assertEquals(201, $rule['headers']['status-code']); - $this->assertEquals(RULE_STATUS_CREATED, $rule['body']['status']); + $this->assertEquals('created', $rule['body']['status']); $this->assertStringContainsString('has incorrect CNAME value', $rule['body']['logs']); $ruleId = $rule['body']['$id']; @@ -674,14 +674,14 @@ class ProxyCustomServerTest extends Scope $rule = $this->getRule($ruleId); $this->assertEquals(200, $rule['headers']['status-code']); - $this->assertEquals(RULE_STATUS_CREATED, $rule['body']['status']); + $this->assertEquals('created', $rule['body']['status']); $this->cleanupRule($ruleId); // 8. Wrong CAA record fails to verify $rule = $this->createAPIRule('stage-wrong-caa.webapp.com'); $this->assertEquals(201, $rule['headers']['status-code']); - $this->assertEquals(RULE_STATUS_CREATED, $rule['body']['status']); + $this->assertEquals('created', $rule['body']['status']); $this->assertStringContainsString('has incorrect CAA value', $rule['body']['logs']); $ruleId = $rule['body']['$id']; @@ -691,14 +691,14 @@ class ProxyCustomServerTest extends Scope $rule = $this->getRule($ruleId); $this->assertEquals(200, $rule['headers']['status-code']); - $this->assertEquals(RULE_STATUS_CREATED, $rule['body']['status']); + $this->assertEquals('created', $rule['body']['status']); $this->cleanupRule($ruleId); // 9. Correct CAA record can verify $rule = $this->createAPIRule('stage-correct-caa.webapp.com'); $this->assertEquals(201, $rule['headers']['status-code']); - $this->assertEquals(RULE_STATUS_CERTIFICATE_GENERATING, $rule['body']['status']); + $this->assertEquals('verifying', $rule['body']['status']); $this->assertEmpty($rule['body']['logs']); $this->cleanupRule($rule['body']['$id']); @@ -710,7 +710,7 @@ class ProxyCustomServerTest extends Scope $rule = $this->createAPIRule($domain); $this->assertEquals(201, $rule['headers']['status-code']); - $this->assertEquals(RULE_STATUS_CREATED, $rule['body']['status']); + $this->assertEquals('created', $rule['body']['status']); $this->assertNotEmpty($rule['body']['logs']); $ruleId = $rule['body']['$id']; @@ -726,7 +726,7 @@ class ProxyCustomServerTest extends Scope $ruleAfterUpdate = $this->getRule($ruleId); $this->assertEquals(200, $ruleAfterUpdate['headers']['status-code']); - $this->assertEquals(RULE_STATUS_CREATED, $ruleAfterUpdate['body']['status']); + $this->assertEquals('created', $ruleAfterUpdate['body']['status']); $this->assertEquals($initiallogs, $ruleAfterUpdate['body']['logs']); $this->assertNotEquals($initialUpdatedAt, $ruleAfterUpdate['body']['$updatedAt']);