Add logs to rules

This commit is contained in:
Hemachandar
2025-12-16 10:59:16 +05:30
parent 2a61cacdf9
commit 5ae148f026
12 changed files with 418 additions and 531 deletions
+11
View File
@@ -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' => [
[
+6
View File
@@ -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';
+45 -94
View File
@@ -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<string> $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;
}
}
@@ -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'),
@@ -0,0 +1,188 @@
<?php
namespace Appwrite\Platform\Modules\Proxy\Http\Rules;
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\Domains\Domain;
use Utopia\Logger\Log;
use Utopia\System\System;
use Utopia\Validator\AnyOf;
use Utopia\Validator\Domain as ValidatorDomain;
use Utopia\Validator\IP;
class Action extends PlatformAction
{
public function __construct(protected string $dnsValidatorClass = ValidatorDNS::class)
{
}
/**
* Ensures domain is not in the deny list and is a valid domain
*
* @param string $domain Domain to validate
* @param array $platform Platform configuration which has internal domains
* @throws Exception
* @return void
*/
protected function validateDomainRestrictions(string $domain, array $platform): void
{
$domains = $platform['hostnames'] ?? [];
$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://.');
}
}
/**
* 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());
}
}
}
@@ -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'),
@@ -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);
@@ -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'),
@@ -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'),
@@ -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);
}
@@ -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', ''));
}
+2 -2
View File
@@ -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,