Skip to content

Commit 2d4e99c

Browse files
committed
Revert revert of CAA validation
1 parent 8fe999d commit 2d4e99c

13 files changed

Lines changed: 205 additions & 45 deletions

File tree

.env

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,15 @@ _APP_OPTIONS_ROUTER_PROTECTION=disabled
2121
_APP_OPTIONS_FORCE_HTTPS=disabled
2222
_APP_OPTIONS_ROUTER_FORCE_HTTPS=disabled
2323
_APP_OPENSSL_KEY_V1=your-secret-key
24+
_APP_DNS=8.8.8.8
2425
_APP_DOMAIN=traefik
2526
_APP_CONSOLE_DOMAIN=localhost
2627
_APP_DOMAIN_FUNCTIONS=functions.localhost
2728
_APP_DOMAIN_SITES=sites.localhost
2829
_APP_DOMAIN_TARGET_CNAME=test.localhost
2930
_APP_DOMAIN_TARGET_A=127.0.0.1
3031
_APP_DOMAIN_TARGET_AAAA=::1
32+
_APP_DOMAIN_TARGET_CAA=digicert.com
3133
_APP_RULES_FORMAT=md5
3234
_APP_REDIS_HOST=redis
3335
_APP_REDIS_PORT=6379

app/config/variables.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,24 @@
151151
'question' => '',
152152
'filter' => ''
153153
],
154+
[
155+
'name' => '_APP_DOMAIN_TARGET_CAA',
156+
'description' => 'A CAA record domain that can be used to validate custom domains. Value should be domain\'s hostname.',
157+
'introduction' => '',
158+
'default' => '',
159+
'required' => false,
160+
'question' => '',
161+
'filter' => ''
162+
],
163+
[
164+
'name' => '_APP_DNS',
165+
'description' => 'DNS server to use for domain validation. Default: 8.8.8.8',
166+
'introduction' => '',
167+
'default' => '8.8.8.8',
168+
'required' => false,
169+
'question' => '',
170+
'filter' => ''
171+
],
154172
[
155173
'name' => '_APP_CONSOLE_WHITELIST_ROOT',
156174
'description' => 'This option allows you to disable the creation of new users on the Appwrite console. When enabled only 1 user will be able to use the registration form. New users can be added by inviting them to your project. By default this option is enabled.',

app/controllers/api/console.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@
7171
'_APP_DOMAIN_TARGET_CNAME' => System::getEnv('_APP_DOMAIN_TARGET_CNAME'),
7272
'_APP_DOMAIN_TARGET_AAAA' => System::getEnv('_APP_DOMAIN_TARGET_AAAA'),
7373
'_APP_DOMAIN_TARGET_A' => System::getEnv('_APP_DOMAIN_TARGET_A'),
74+
// Combine CAA domain with most common flags and tag (no parameters)
75+
'_APP_DOMAIN_TARGET_CAA' => '0 issue "' . System::getEnv('_APP_DOMAIN_TARGET_CAA') . '"',
7476
'_APP_STORAGE_LIMIT' => +System::getEnv('_APP_STORAGE_LIMIT'),
7577
'_APP_COMPUTE_SIZE_LIMIT' => +System::getEnv('_APP_COMPUTE_SIZE_LIMIT'),
7678
'_APP_USAGE_STATS' => System::getEnv('_APP_USAGE_STATS'),

app/controllers/api/proxy.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,19 @@
286286
throw new Exception(Exception::RULE_VERIFICATION_FAILED);
287287
}
288288

289+
// Ensure CAA won't block certificate issuance
290+
if (!empty(System::getEnv('_APP_DOMAIN_TARGET_CAA', ''))) {
291+
$validationStart = \microtime(true);
292+
$validator = new DNS(System::getEnv('_APP_DOMAIN_TARGET_CAA', ''), DNS::RECORD_CAA);
293+
if (!$validator->isValid($domain->get())) {
294+
$log->addExtra('dnsTimingCaa', \strval(\microtime(true) - $validationStart));
295+
$log->addTag('dnsDomain', $domain->get());
296+
$error = $validator->getDescription();
297+
$log->addExtra('dnsResponse', \is_array($error) ? \json_encode($error) : \strval($error));
298+
throw new Exception(Exception::RULE_VERIFICATION_FAILED, 'Domain verification failed because CAA records do not allow certainly.com to issue certificates.');
299+
}
300+
}
301+
289302
$dbForPlatform->updateDocument('rules', $rule->getId(), $rule->setAttribute('status', 'verifying'));
290303

291304
// Issue a TLS certificate when domain is verified

app/views/install/compose.phtml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ $image = $this->getParam('image', '');
9595
- _APP_DOMAIN_TARGET_CNAME
9696
- _APP_DOMAIN_TARGET_AAAA
9797
- _APP_DOMAIN_TARGET_A
98+
- _APP_DOMAIN_TARGET_CAA
99+
- _APP_DOMAINS_DNS
98100
- _APP_DOMAIN_FUNCTIONS
99101
- _APP_REDIS_HOST
100102
- _APP_REDIS_PORT
@@ -472,6 +474,8 @@ $image = $this->getParam('image', '');
472474
- _APP_DOMAIN_TARGET_CNAME
473475
- _APP_DOMAIN_TARGET_AAAA
474476
- _APP_DOMAIN_TARGET_A
477+
- _APP_DOMAIN_TARGET_CAA
478+
- _APP_DOMAINS_DNS
475479
- _APP_DOMAIN_FUNCTIONS
476480
- _APP_EMAIL_CERTIFICATES
477481
- _APP_REDIS_HOST
@@ -629,6 +633,8 @@ $image = $this->getParam('image', '');
629633
- _APP_DOMAIN_TARGET_CNAME
630634
- _APP_DOMAIN_TARGET_AAAA
631635
- _APP_DOMAIN_TARGET_A
636+
- _APP_DOMAIN_TARGET_CAA
637+
- _APP_DOMAINS_DNS
632638
- _APP_EMAIL_SECURITY
633639
- _APP_REDIS_HOST
634640
- _APP_REDIS_PORT
@@ -660,6 +666,8 @@ $image = $this->getParam('image', '');
660666
- _APP_DOMAIN_TARGET_CNAME
661667
- _APP_DOMAIN_TARGET_AAAA
662668
- _APP_DOMAIN_TARGET_A
669+
- _APP_DOMAIN_TARGET_CAA
670+
- _APP_DOMAINS_DNS
663671
- _APP_DOMAIN_FUNCTIONS
664672
- _APP_OPENSSL_KEY_V1
665673
- _APP_REDIS_HOST

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
"utopia-php/database": "0.71.*",
5656
"utopia-php/detector": "0.1.*",
5757
"utopia-php/domains": "0.8.*",
58+
"utopia-php/dns": "0.3.*",
5859
"utopia-php/dsn": "0.2.1",
5960
"utopia-php/framework": "0.33.*",
6061
"utopia-php/fetch": "0.4.*",

composer.lock

Lines changed: 59 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docker-compose.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ services:
120120
- _APP_DOMAIN_TARGET_CNAME
121121
- _APP_DOMAIN_TARGET_AAAA
122122
- _APP_DOMAIN_TARGET_A
123+
- _APP_DOMAIN_TARGET_CAA
124+
- _APP_DNS
123125
- _APP_DOMAIN_FUNCTIONS
124126
- _APP_REDIS_HOST
125127
- _APP_REDIS_PORT
@@ -535,6 +537,8 @@ services:
535537
- _APP_DOMAIN_TARGET_CNAME
536538
- _APP_DOMAIN_TARGET_AAAA
537539
- _APP_DOMAIN_TARGET_A
540+
- _APP_DOMAIN_TARGET_CAA
541+
- _APP_DNS
538542
- _APP_DOMAIN_FUNCTIONS
539543
- _APP_EMAIL_CERTIFICATES
540544
- _APP_REDIS_HOST
@@ -704,6 +708,8 @@ services:
704708
- _APP_DOMAIN_TARGET_CNAME
705709
- _APP_DOMAIN_TARGET_AAAA
706710
- _APP_DOMAIN_TARGET_A
711+
- _APP_DOMAIN_TARGET_CAA
712+
- _APP_DNS
707713
- _APP_EMAIL_SECURITY
708714
- _APP_REDIS_HOST
709715
- _APP_REDIS_PORT
@@ -738,6 +744,8 @@ services:
738744
- _APP_DOMAIN_TARGET_CNAME
739745
- _APP_DOMAIN_TARGET_AAAA
740746
- _APP_DOMAIN_TARGET_A
747+
- _APP_DOMAIN_TARGET_CAA
748+
- _APP_DNS
741749
- _APP_DOMAIN_FUNCTIONS
742750
- _APP_OPENSSL_KEY_V1
743751
- _APP_REDIS_HOST

src/Appwrite/Network/Validator/DNS.php

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@
22

33
namespace Appwrite\Network\Validator;
44

5+
use Utopia\DNS\Client;
6+
use Utopia\System\System;
57
use Utopia\Validator;
68

79
class DNS extends Validator
810
{
9-
public const RECORD_A = 'a';
10-
public const RECORD_AAAA = 'aaaa';
11-
public const RECORD_CNAME = 'cname';
11+
public const RECORD_A = 'A';
12+
public const RECORD_AAAA = 'AAAA';
13+
public const RECORD_CNAME = 'CNAME';
14+
public const RECORD_CAA = 'CAA'; // You can provide domain only (as $target) for CAA validation
1215

1316
/**
1417
* @var mixed
@@ -42,33 +45,22 @@ public function getLogs(): mixed
4245
* Check if DNS record value matches specific value
4346
*
4447
* @param mixed $domain
45-
*
4648
* @return bool
4749
*/
4850
public function isValid($value): bool
4951
{
50-
$typeNative = match ($this->type) {
51-
self::RECORD_A => DNS_A,
52-
self::RECORD_AAAA => DNS_AAAA,
53-
self::RECORD_CNAME => DNS_CNAME,
54-
default => throw new \Exception('Record type not supported.')
55-
};
56-
57-
$dnsKey = match ($this->type) {
58-
self::RECORD_A => 'ip',
59-
self::RECORD_AAAA => 'ipv6',
60-
self::RECORD_CNAME => 'target',
61-
default => throw new \Exception('Record type not supported.')
62-
};
63-
6452
if (!is_string($value)) {
6553
return false;
6654
}
6755

56+
$dnsServer = System::getEnv('_APP_DNS', '8.8.8.8');
57+
$dns = new Client($dnsServer);
58+
6859
try {
69-
$records = \dns_get_record($value, $typeNative);
70-
$this->logs = $records;
71-
} catch (\Throwable $th) {
60+
$query = $dns->query($value, $this->type);
61+
$this->logs = $query;
62+
} catch (\Exception $e) {
63+
$this->logs = ['error' => $e->getMessage()];
7264
return false;
7365
}
7466

@@ -90,8 +82,21 @@ public function isValid($value): bool
9082
return false;
9183
}
9284

93-
foreach ($records as $record) {
94-
if (isset($record[$dnsKey]) && $record[$dnsKey] === $this->target) {
85+
foreach ($query as $record) {
86+
// CAA validation only needs to ensure domain
87+
if ($this->type === self::RECORD_CAA) {
88+
// Extract domain; comments showcase extraction steps in most complex scenario
89+
$rdata = $record->getRdata(); // 255 issuewild "certainly.com;validationmethods=tls-alpn-01;retrytimeout=3600"
90+
$rdata = \explode(' ', $rdata, 3)[2] ?? ''; // "certainly.com;validationmethods=tls-alpn-01;retrytimeout=3600"
91+
$rdata = \trim($rdata, '"'); // certainly.com;validationmethods=tls-alpn-01;retrytimeout=3600
92+
$rdata = \explode(';', $rdata, 2)[0] ?? ''; // certainly.com
93+
94+
if ($rdata === $this->target) {
95+
return true;
96+
}
97+
}
98+
99+
if ($record->getRdata() === $this->target) {
95100
return true;
96101
}
97102
}

src/Appwrite/Platform/Workers/Certificates.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,19 @@ private function validateDomain(Domain $domain, bool $isMainDomain, Log $log): v
337337

338338
throw new Exception('Failed to verify domain DNS records.');
339339
}
340+
341+
// Ensure CAA won't block certificate issuance
342+
if (!empty(System::getEnv('_APP_DOMAIN_TARGET_CAA', ''))) {
343+
$validationStart = \microtime(true);
344+
$validator = new DNS(System::getEnv('_APP_DOMAIN_TARGET_CAA', ''), DNS::RECORD_CAA);
345+
if (!$validator->isValid($domain->get())) {
346+
$log->addExtra('dnsTimingCaa', \strval(\microtime(true) - $validationStart));
347+
$log->addTag('dnsDomain', $domain->get());
348+
$error = $validator->getDescription();
349+
$log->addExtra('dnsResponse', \is_array($error) ? \json_encode($error) : \strval($error));
350+
throw new Exception('Failed to verify domain DNS records. CAA records do not allow certificates from certainly.com to issue certificates.');
351+
}
352+
}
340353
} else {
341354
// Main domain validation
342355
// TODO: Would be awesome to check A/AAAA record here. Maybe dry run?

0 commit comments

Comments
 (0)