Extend DNS validator for concurrency

This commit is contained in:
Khushboo Verma
2025-11-13 16:39:05 +05:30
parent 790c440799
commit eed36f36bf
5 changed files with 173 additions and 13 deletions
+1 -1
View File
@@ -55,7 +55,7 @@
"utopia-php/detector": "0.2.*",
"utopia-php/domains": "0.9.*",
"utopia-php/emails": "0.6.*",
"utopia-php/dns": "1.1.*",
"utopia-php/dns": "1.3.*",
"utopia-php/dsn": "0.2.1",
"utopia-php/framework": "0.33.*",
"utopia-php/fetch": "0.4.*",
Generated
+7 -7
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "ad28b7155175986191bd19bbcd13d623",
"content-hash": "001835bef99a3dc73ab54cf4b22fc5e8",
"packages": [
{
"name": "adhocore/jwt",
@@ -3947,16 +3947,16 @@
},
{
"name": "utopia-php/dns",
"version": "1.1.3",
"version": "1.3.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/dns.git",
"reference": "1e6b4bac735329c9e5ec69a6a5d899ec2d050707"
"reference": "7644276913f68648515228d68684feca8742a645"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/dns/zipball/1e6b4bac735329c9e5ec69a6a5d899ec2d050707",
"reference": "1e6b4bac735329c9e5ec69a6a5d899ec2d050707",
"url": "https://api.github.com/repos/utopia-php/dns/zipball/7644276913f68648515228d68684feca8742a645",
"reference": "7644276913f68648515228d68684feca8742a645",
"shasum": ""
},
"require": {
@@ -3998,9 +3998,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/dns/issues",
"source": "https://github.com/utopia-php/dns/tree/1.1.3"
"source": "https://github.com/utopia-php/dns/tree/1.3.0"
},
"time": "2025-11-06T19:08:29+00:00"
"time": "2025-11-12T19:24:16+00:00"
},
{
"name": "utopia-php/domains",
+94
View File
@@ -0,0 +1,94 @@
<?php
namespace Appwrite\Network\Validator;
use Swoole\Coroutine\WaitGroup;
use Utopia\DNS\Message\Record;
use Utopia\DNS\Validator\DNS as BaseDNS;
class DNS extends BaseDNS
{
/**
* @var array<string>
*/
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 string|array<string> $dnsServers DNS server IP(s) or domain(s) to use for validation
*/
public function __construct(string $target, int $type = Record::TYPE_CNAME, string|array $dnsServers = self::DEFAULT_DNS_SERVER)
{
parent::__construct($target, $type, is_array($dnsServers) ? $dnsServers[0] : $dnsServers);
$this->dnsServers = is_array($dnsServers) ? $dnsServers : [$dnsServers];
}
/**
* Validate DNS record value against multiple DNS servers
*
* @param mixed $value
* @return bool
*/
public function isValid(mixed $value): bool
{
if (\count($this->dnsServers) === 1) {
return $this->isValidWithDNSServer($value, $this->dnsServers[0]);
}
$wg = new WaitGroup();
$failedValidator = null;
foreach ($this->dnsServers as $dnsServer) {
$wg->add();
\go(function () use ($value, $dnsServer, $wg, &$failedValidator) {
try {
$validator = new BaseDNS($this->target, $this->type, $dnsServer);
$isValid = $validator->isValid($value);
if (!$isValid) {
$failedValidator = $validator;
}
} finally {
$wg->done();
}
});
}
$wg->wait();
if (!\is_null($failedValidator)) {
$this->count = $failedValidator->count;
$this->value = $failedValidator->value;
$this->reason = $failedValidator->reason;
$this->records = $failedValidator->records;
return false;
}
return true;
}
/**
* Validate with a specific DNS server
*
* @param mixed $value
* @param string $dnsServer
* @return bool
*/
protected function isValidWithDNSServer(mixed $value, string $dnsServer): bool
{
$validator = new BaseDNS($this->target, $this->type, $dnsServer);
$result = $validator->isValid($value);
$this->count = $validator->count;
$this->value = $validator->value;
$this->reason = $validator->reason;
$this->records = $validator->records;
return $result;
}
}
+18 -5
View File
@@ -3,10 +3,10 @@
namespace Appwrite\Platform\Modules\Proxy;
use Appwrite\Extend\Exception;
use Appwrite\Network\Validator\DNS as ValidatorDNS;
use Appwrite\Platform\Action as PlatformAction;
use Utopia\Database\Document;
use Utopia\DNS\Message\Record;
use Utopia\DNS\Validator\DNS as ValidatorDNS;
use Utopia\Domains\Domain;
use Utopia\Logger\Log;
use Utopia\System\System;
@@ -19,6 +19,18 @@ class Action extends PlatformAction
{
}
/**
* Parse DNS servers from environment variable
*
* @return array<string>
*/
protected function getDNSServers(): array
{
$dnsEnv = System::getEnv('_APP_DNS', '8.8.8.8');
$servers = \array_map('trim', \explode(',', $dnsEnv));
return \array_filter($servers, fn ($server) => !empty($server));
}
/**
* Verify or re-verify a rule
*
@@ -31,6 +43,7 @@ class Action extends PlatformAction
public function verifyRule(Document $rule, ?Log $log = null, ?string $verificationDomainAPI = null, ?string $verificationDomainFunction = null): void
{
$dnsValidatorClass = $this->dnsValidatorClass;
$dnsServers = $this->getDNSServers();
$domain = new Domain($rule->getAttribute('domain', ''));
@@ -46,7 +59,7 @@ class Action extends PlatformAction
$caaTarget = System::getEnv('_APP_DOMAIN_TARGET_CAA', '');
if (!empty($caaTarget)) {
$validationStart = \microtime(true);
$validator = new $dnsValidatorClass($caaTarget, Record::TYPE_CAA, System::getEnv('_APP_DNS', '8.8.8.8'));
$validator = new $dnsValidatorClass($caaTarget, Record::TYPE_CAA, $dnsServers);
if (!$validator->isValid($domain->get())) {
if (!\is_null($log)) {
$log->addExtra('dnsTimingCaa', \strval(\microtime(true) - $validationStart));
@@ -88,7 +101,7 @@ class Action extends PlatformAction
$mainValidator = null; // Validator to use for error description
if (!is_null($targetCNAME)) {
$validator = new $dnsValidatorClass($targetCNAME->get(), Record::TYPE_CNAME, System::getEnv('_APP_DNS', '8.8.8.8'));
$validator = new $dnsValidatorClass($targetCNAME->get(), Record::TYPE_CNAME, $dnsServers);
$validators[] = $validator;
if (\is_null($mainValidator)) {
@@ -98,7 +111,7 @@ class Action extends PlatformAction
$targetA = System::getEnv('_APP_DOMAIN_TARGET_A', '');
if ((new IP(IP::V4))->isValid($targetA)) {
$validator = new $dnsValidatorClass($targetA, Record::TYPE_A, System::getEnv('_APP_DNS', '8.8.8.8'));
$validator = new $dnsValidatorClass($targetA, Record::TYPE_A, $dnsServers);
$validators[] = $validator;
if (\is_null($mainValidator)) {
@@ -108,7 +121,7 @@ class Action extends PlatformAction
$targetAAAA = System::getEnv('_APP_DOMAIN_TARGET_AAAA', '');
if ((new IP(IP::V6))->isValid($targetAAAA)) {
$validator = new $dnsValidatorClass($targetAAAA, Record::TYPE_AAAA, System::getEnv('_APP_DNS', '8.8.8.8'));
$validator = new $dnsValidatorClass($targetAAAA, Record::TYPE_AAAA, $dnsServers);
$validators[] = $validator;
if (\is_null($mainValidator)) {
+53
View File
@@ -0,0 +1,53 @@
<?php
namespace Tests\Unit\Network\Validators;
use Appwrite\Network\Validator\DNS;
use PHPUnit\Framework\TestCase;
use Utopia\DNS\Message\Record;
class DNSTest extends TestCase
{
public function testSingleDNSServer(): void
{
$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 testMultipleDNSServers(): void
{
$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 testValidationFailure(): void
{
$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());
}
public function testCoreDNSFailure(): void
{
// 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']);
$result = $validator->isValid('stage.webapp.com');
$this->assertEquals(false, $result);
$result = $validator->isValid('stage-wrong-cname.webapp.com');
$this->assertEquals(false, $result);
}
}