Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@ _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_DOMAIN=traefik
_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_AAAA=::1
_APP_DOMAIN_TARGET_CAA=digicert.com
_APP_RULES_FORMAT=md5
_APP_REDIS_HOST=redis
_APP_REDIS_PORT=6379
Expand Down
18 changes: 18 additions & 0 deletions app/config/variables.php
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,24 @@
'question' => '',
'filter' => ''
],
[
'name' => '_APP_DOMAIN_TARGET_CAA',
'description' => 'A CAA record domain that can be used to validate custom domains. Value should be domain\'s hostname.',
'introduction' => '',
'default' => '',
Comment thread
vermakhushboo marked this conversation as resolved.
'required' => false,
'question' => '',
'filter' => ''
],
[
'name' => '_APP_DNS',
'description' => 'DNS server to use for domain validation. Default: 8.8.8.8',
'introduction' => '',
'default' => '8.8.8.8',
'required' => false,
'question' => '',
'filter' => ''
],
[
'name' => '_APP_CONSOLE_WHITELIST_ROOT',
'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.',
Expand Down
2 changes: 2 additions & 0 deletions app/controllers/api/console.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@
'_APP_DOMAIN_TARGET_CNAME' => System::getEnv('_APP_DOMAIN_TARGET_CNAME'),
'_APP_DOMAIN_TARGET_AAAA' => System::getEnv('_APP_DOMAIN_TARGET_AAAA'),
'_APP_DOMAIN_TARGET_A' => System::getEnv('_APP_DOMAIN_TARGET_A'),
// Combine CAA domain with most common flags and tag (no parameters)
'_APP_DOMAIN_TARGET_CAA' => '0 issue "' . System::getEnv('_APP_DOMAIN_TARGET_CAA') . '"',
'_APP_STORAGE_LIMIT' => +System::getEnv('_APP_STORAGE_LIMIT'),
'_APP_COMPUTE_SIZE_LIMIT' => +System::getEnv('_APP_COMPUTE_SIZE_LIMIT'),
'_APP_USAGE_STATS' => System::getEnv('_APP_USAGE_STATS'),
Expand Down
13 changes: 13 additions & 0 deletions app/controllers/api/proxy.php
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,19 @@
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', ''), DNS::RECORD_CAA);
if (!$validator->isValid($domain->get())) {
$log->addExtra('dnsTimingCaa', \strval(\microtime(true) - $validationStart));
$log->addTag('dnsDomain', $domain->get());
$error = $validator->getDescription();
$log->addExtra('dnsResponse', \is_array($error) ? \json_encode($error) : \strval($error));
throw new Exception(Exception::RULE_VERIFICATION_FAILED, 'Domain verification failed because CAA records do not allow certainly.com to issue certificates.');
}
}

$dbForPlatform->updateDocument('rules', $rule->getId(), $rule->setAttribute('status', 'verifying'));

// Issue a TLS certificate when domain is verified
Expand Down
8 changes: 8 additions & 0 deletions app/views/install/compose.phtml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ $image = $this->getParam('image', '');
- _APP_DOMAIN_TARGET_CNAME
- _APP_DOMAIN_TARGET_AAAA
- _APP_DOMAIN_TARGET_A
- _APP_DOMAIN_TARGET_CAA
- _APP_DOMAINS_DNS
- _APP_DOMAIN_FUNCTIONS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
Expand Down Expand Up @@ -472,6 +474,8 @@ $image = $this->getParam('image', '');
- _APP_DOMAIN_TARGET_CNAME
- _APP_DOMAIN_TARGET_AAAA
- _APP_DOMAIN_TARGET_A
- _APP_DOMAIN_TARGET_CAA
- _APP_DOMAINS_DNS
- _APP_DOMAIN_FUNCTIONS
- _APP_EMAIL_CERTIFICATES
- _APP_REDIS_HOST
Expand Down Expand Up @@ -629,6 +633,8 @@ $image = $this->getParam('image', '');
- _APP_DOMAIN_TARGET_CNAME
- _APP_DOMAIN_TARGET_AAAA
- _APP_DOMAIN_TARGET_A
- _APP_DOMAIN_TARGET_CAA
- _APP_DOMAINS_DNS
- _APP_EMAIL_SECURITY
- _APP_REDIS_HOST
- _APP_REDIS_PORT
Expand Down Expand Up @@ -660,6 +666,8 @@ $image = $this->getParam('image', '');
- _APP_DOMAIN_TARGET_CNAME
- _APP_DOMAIN_TARGET_AAAA
- _APP_DOMAIN_TARGET_A
- _APP_DOMAIN_TARGET_CAA
- _APP_DOMAINS_DNS
- _APP_DOMAIN_FUNCTIONS
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"utopia-php/database": "0.71.*",
"utopia-php/detector": "0.1.*",
"utopia-php/domains": "0.8.*",
"utopia-php/dns": "0.3.*",
"utopia-php/dsn": "0.2.1",
"utopia-php/framework": "0.33.*",
"utopia-php/fetch": "0.4.*",
Expand Down
62 changes: 59 additions & 3 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ services:
- _APP_DOMAIN_TARGET_CNAME
- _APP_DOMAIN_TARGET_AAAA
- _APP_DOMAIN_TARGET_A
- _APP_DOMAIN_TARGET_CAA
- _APP_DNS
- _APP_DOMAIN_FUNCTIONS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
Expand Down Expand Up @@ -535,6 +537,8 @@ services:
- _APP_DOMAIN_TARGET_CNAME
- _APP_DOMAIN_TARGET_AAAA
- _APP_DOMAIN_TARGET_A
- _APP_DOMAIN_TARGET_CAA
- _APP_DNS
- _APP_DOMAIN_FUNCTIONS
- _APP_EMAIL_CERTIFICATES
- _APP_REDIS_HOST
Expand Down Expand Up @@ -704,6 +708,8 @@ services:
- _APP_DOMAIN_TARGET_CNAME
- _APP_DOMAIN_TARGET_AAAA
- _APP_DOMAIN_TARGET_A
- _APP_DOMAIN_TARGET_CAA
- _APP_DNS
- _APP_EMAIL_SECURITY
- _APP_REDIS_HOST
- _APP_REDIS_PORT
Expand Down Expand Up @@ -738,6 +744,8 @@ services:
- _APP_DOMAIN_TARGET_CNAME
- _APP_DOMAIN_TARGET_AAAA
- _APP_DOMAIN_TARGET_A
- _APP_DOMAIN_TARGET_CAA
- _APP_DNS
- _APP_DOMAIN_FUNCTIONS
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
Expand Down
58 changes: 34 additions & 24 deletions src/Appwrite/Network/Validator/DNS.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@

namespace Appwrite\Network\Validator;

use Utopia\DNS\Client;
use Utopia\System\System;
use Utopia\Validator;

class DNS extends Validator
{
public const RECORD_A = 'a';
public const RECORD_AAAA = 'aaaa';
public const RECORD_CNAME = 'cname';
public const RECORD_A = 'A';
public const RECORD_AAAA = 'AAAA';
public const RECORD_CNAME = 'CNAME';
public const RECORD_CAA = 'CAA'; // You can provide domain only (as $target) for CAA validation

/**
* @var mixed
Expand Down Expand Up @@ -42,42 +45,49 @@ public function getLogs(): mixed
* Check if DNS record value matches specific value
*
* @param mixed $domain
*
* @return bool
*/
public function isValid($value): bool
{
$typeNative = match ($this->type) {
self::RECORD_A => DNS_A,
self::RECORD_AAAA => DNS_AAAA,
self::RECORD_CNAME => DNS_CNAME,
default => throw new \Exception('Record type not supported.')
};

$dnsKey = match ($this->type) {
self::RECORD_A => 'ip',
self::RECORD_AAAA => 'ipv6',
self::RECORD_CNAME => 'target',
default => throw new \Exception('Record type not supported.')
};

if (!is_string($value)) {
return false;
}

$dnsServer = System::getEnv('_APP_DNS', '8.8.8.8');
$dns = new Client($dnsServer);

try {
$records = \dns_get_record($value, $typeNative);
$this->logs = $records;
} catch (\Throwable $th) {
$query = $dns->query($value, $this->type);
$this->logs = $query;
} catch (\Exception $e) {
$this->logs = ['error' => $e->getMessage()];
return false;
}

if (!$records) {
if (empty($query)) {
// No CAA records means anyone can issue certificate
if ($this->type === self::RECORD_CAA) {
return true;
}

return false;
}

foreach ($records as $record) {
if (isset($record[$dnsKey]) && $record[$dnsKey] === $this->target) {
foreach ($query as $record) {
// CAA validation only needs to ensure domain
Comment thread
Meldiron marked this conversation as resolved.
if ($this->type === self::RECORD_CAA) {
// Extract domain; comments showcase extraction steps in most complex scenario
$rdata = $record->getRdata(); // 255 issuewild "certainly.com;validationmethods=tls-alpn-01;retrytimeout=3600"
$rdata = \explode(' ', $rdata, 3)[2] ?? ''; // "certainly.com;validationmethods=tls-alpn-01;retrytimeout=3600"
$rdata = \trim($rdata, '"'); // certainly.com;validationmethods=tls-alpn-01;retrytimeout=3600
$rdata = \explode(';', $rdata, 2)[0] ?? ''; // certainly.com

if ($rdata === $this->target) {
return true;
}
}
Comment thread
Meldiron marked this conversation as resolved.

if ($record->getRdata() === $this->target) {
Comment thread
Meldiron marked this conversation as resolved.
return true;
}
}
Expand Down
13 changes: 13 additions & 0 deletions src/Appwrite/Platform/Workers/Certificates.php
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,19 @@ private function validateDomain(Domain $domain, bool $isMainDomain, Log $log): v

throw new Exception('Failed to verify domain DNS records.');
}

// Ensure CAA won't block certificate issuance
if (!empty(System::getEnv('_APP_DOMAIN_TARGET_CAA', ''))) {
$validationStart = \microtime(true);
$validator = new DNS(System::getEnv('_APP_DOMAIN_TARGET_CAA', ''), DNS::RECORD_CAA);
if (!$validator->isValid($domain->get())) {
$log->addExtra('dnsTimingCaa', \strval(\microtime(true) - $validationStart));
$log->addTag('dnsDomain', $domain->get());
$error = $validator->getDescription();
$log->addExtra('dnsResponse', \is_array($error) ? \json_encode($error) : \strval($error));
throw new Exception('Failed to verify domain DNS records. CAA records do not allow certificates from certainly.com to issue certificates.');
}
}
} else {
// Main domain validation
// TODO: Would be awesome to check A/AAAA record here. Maybe dry run?
Expand Down
42 changes: 24 additions & 18 deletions src/Appwrite/Utopia/Response/Model/ConsoleVariables.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,30 @@ class ConsoleVariables extends Model
public function __construct()
{
$this
->addRule('_APP_DOMAIN_TARGET_CNAME', [
'type' => self::TYPE_STRING,
'description' => 'CNAME target for your Appwrite custom domains.',
'default' => '',
'example' => 'appwrite.io',
])
->addRule('_APP_DOMAIN_TARGET_A', [
'type' => self::TYPE_STRING,
'description' => 'A target for your Appwrite custom domains.',
'default' => '',
'example' => '127.0.0.1',
])
->addRule('_APP_DOMAIN_TARGET_AAAA', [
'type' => self::TYPE_STRING,
'description' => 'AAAA target for your Appwrite custom domains.',
'default' => '',
'example' => '::1',
])
->addRule('_APP_DOMAIN_TARGET_CNAME', [
'type' => self::TYPE_STRING,
'description' => 'CNAME target for your Appwrite custom domains.',
'default' => '',
'example' => 'appwrite.io',
])
->addRule('_APP_DOMAIN_TARGET_A', [
'type' => self::TYPE_STRING,
'description' => 'A target for your Appwrite custom domains.',
'default' => '',
'example' => '127.0.0.1',
])
->addRule('_APP_DOMAIN_TARGET_AAAA', [
'type' => self::TYPE_STRING,
'description' => 'AAAA target for your Appwrite custom domains.',
'default' => '',
'example' => '::1',
])
->addRule('_APP_DOMAIN_TARGET_CAA', [
'type' => self::TYPE_STRING,
'description' => 'CAA target for your Appwrite custom domains.',
'default' => '',
'example' => 'digicert.com',
])
->addRule('_APP_STORAGE_LIMIT', [
'type' => self::TYPE_INTEGER,
'description' => 'Maximum file size allowed for file upload in bytes.',
Expand Down
Loading