Compare commits

...
Author SHA1 Message Date
Khushboo Verma fe15741217 Move rules and certificates logic to Interval 2025-11-20 12:54:42 +05:30
Khushboo Verma 606b666aed Merge branch 'fix-dns-flows' of github.com:appwrite/appwrite into fix-dns-flows 2025-11-20 11:49:36 +05:30
Khushboo Verma 1413a66f0a Fix merge conflicts 2025-11-20 11:49:15 +05:30
Khushboo VermaandGitHub 2262494f87 Merge branch '1.8.x' into fix-dns-flows 2025-11-18 14:03:58 +05:30
Khushboo Verma c66a6a14b4 Resolve merge conflicts 2025-11-18 13:24:21 +05:30
Khushboo Verma a233c3b82f Override rule updated at when certificate is updated 2025-11-18 13:20:30 +05:30
Khushboo Verma 1e28f0b2fb Merge branch 'fix-dns-flows' of github.com:appwrite/appwrite into fix-dns-flows 2025-11-17 13:15:49 +05:30
Khushboo Verma a5f44aebc3 Resolve merge conflicts 2025-11-17 13:15:27 +05:30
Khushboo VermaandGitHub ea15cafc34 Merge branch '1.8.x' into fix-dns-flows 2025-11-14 16:23:38 +05:30
Khushboo Verma 9cc152d173 Change param to accept only array 2025-11-14 16:23:14 +05:30
Khushboo Verma c8d069f35e Resolve merge conflicts 2025-11-14 15:34:40 +05:30
Khushboo Verma 6ce46e29d0 Replace verificationLogs with logs 2025-11-14 13:58:20 +05:30
Khushboo VermaandGitHub 4962a1d7e0 Merge branch '1.8.x' into fix-dns-flows 2025-11-13 16:41:45 +05:30
Khushboo Verma eed36f36bf Extend DNS validator for concurrency 2025-11-13 16:39:05 +05:30
Khushboo Verma 790c440799 Delete DNS validator from Appwrite 2025-11-13 13:34:52 +05:30
Khushboo VermaandGitHub ac42bb04f4 Merge branch '1.8.x' into fix-dns-flows 2025-11-13 13:16:43 +05:30
Khushboo Verma 0b83b5b22b Resolve merge conflicts 2025-11-13 13:10:30 +05:30
Khushboo Verma 80397374bc Fix proxy tests 2025-11-13 13:08:44 +05:30
Matej Bačo 8ce47393e8 Fix CoreDNS proxying logic 2025-11-12 13:15:31 +01:00
Khushboo Verma 2d10819875 Merge branch '1.8.x' into fix-dns-flows 2025-11-07 17:45:30 +05:30
Khushboo Verma 6335363853 Fix some tests 2025-11-07 17:44:05 +05:30
Khushboo Verma 9c8202df52 Sync changes 2025-11-07 13:53:21 +05:30
Khushboo Verma d9d6270a15 Merge branch '1.8.x' into fix-dns-flows 2025-11-06 19:05:15 +05:30
Khushboo Verma ae6e608e25 Resolve merge conflicts 2025-11-06 19:03:55 +05:30
Khushboo Verma 293d52715e Merge branch '1.8.x' into fix-dns-flows 2025-11-03 12:45:54 +05:30
Khushboo Verma eeb163b1c7 Resolve merge conflicts 2025-11-03 10:56:42 +05:30
Khushboo Verma 1c2819fcad Merge branch '1.8.x' into fix-dns-flows 2025-10-21 10:12:31 +05:30
Khushboo Verma 6615375b3b Merge branch '1.8.x' into fix-dns-flows 2025-10-15 11:55:28 +05:30
Khushboo VermaandGitHub 37ddaa78c9 Update tests/resources/coredns/webapp.com.zone 2025-10-15 11:04:23 +05:30
Khushboo VermaandGitHub 4b16da92d8 Update tests/resources/coredns/wrong-a-webapp.com.zone 2025-10-15 11:04:09 +05:30
Khushboo VermaandGitHub 6589efa87f Merge branch '1.8.x' into fix-dns-flows 2025-10-14 19:24:42 +05:30
Khushboo Verma d81beb9c99 Update specs 2025-10-14 16:55:31 +05:30
Khushboo Verma 779fb01b96 Merge branch '1.8.x' into fix-dns-flows 2025-10-14 16:45:48 +05:30
Khushboo Verma 3313d78fad Update when verification is re-attempted 2025-10-09 12:24:46 +05:30
Khushboo Verma 2b09805957 Logs for debugging 2025-10-09 11:17:19 +05:30
Khushboo Verma dbae081225 Resolve merge conflicts 2025-10-08 15:56:02 +05:30
Matej Bačo 1ac35054fe FIx missing event data; improve test 2025-09-17 18:26:19 +02:00
Matej Bačo ad795b4fd1 Improve extensability of certificate validator 2025-09-17 17:42:36 +02:00
Matej Bačo 47cf9aa0fc fix syntax bug 2025-09-17 13:26:19 +02:00
Matej Bačo 0af3338cab Improve extensibility of DNS validation 2025-09-17 13:15:50 +02:00
Matej Bačo 00fb346b7a Improve extensability of DNS validator 2025-09-17 13:01:51 +02:00
Khushboo VermaandGitHub daacc4d437 Merge branch '1.8.x' into fix-dns-flows 2025-09-17 11:07:57 +05:30
Matej BačoandGitHub 04adfd1e04 Merge branch '1.8.x' into fix-dns-flows 2025-09-16 16:18:55 +02:00
Matej Bačo db80b74f1f Fix success+error logs 2025-09-16 16:12:44 +02:00
Matej Bačo 3f46b06634 Issue status exception (to be used for logging) 2025-09-16 15:36:32 +02:00
Matej Bačo 91bac63dbd Add support for issue status sychronization 2025-09-16 14:37:02 +02:00
Matej Bačo e93b06acb5 Improved code quality 2025-09-16 13:25:39 +02:00
Matej Bačo 4a3a74d284 AI review fixes 2025-09-16 12:34:06 +02:00
Matej Bačo 7b46197ffe consts for rule status 2025-09-16 11:57:45 +02:00
Matej Bačo 824d90edc0 Fix bug (override status) 2025-09-15 16:58:46 +02:00
Matej Bačo e6de282275 AI review fixes 2025-09-15 16:41:17 +02:00
Matej Bačo 616fef17a8 Support for background issue certificate issue status sync 2025-09-15 16:41:03 +02:00
Matej Bačo 448dd9696c Merge branch 'fix-dns-flows' of https://github.com/appwrite/appwrite into fix-dns-flows 2025-09-15 15:40:24 +02:00
Matej Bačo 9fe68dbdde Grammar fixes 2025-09-15 15:40:15 +02:00
Matej BačoandGitHub 8b38dc7326 Merge pull request #10484 from appwrite/feat-proxy-e2e-tests
Feat: Proxy verification tests
2025-09-15 15:36:03 +02:00
Khushboo VermaandGitHub 83811c0c33 Merge branch '1.8.x' into fix-dns-flows 2025-09-15 13:15:35 +05:30
Matej Bačo 6463892ba9 Fix tests 2025-09-13 11:33:48 +02:00
Matej Bačo 636ce72e52 Implement E2E proxy tests 2025-09-12 21:19:36 +02:00
Matej Bačo db664318c4 Remove unnessessary check (double check) 2025-09-12 20:52:49 +02:00
Matej Bačo cf0838d75b Formatting fix 2025-09-12 20:37:46 +02:00
Matej Bačo e508f049bf Prepare coreDNS for proxy tests 2025-09-12 20:37:36 +02:00
Matej Bačo cfbb6d9453 AI review fixes 2025-09-12 19:16:51 +02:00
Matej Bačo 72e936a004 improve invalid domain validation 2025-09-12 16:51:28 +02:00
Matej Bačo 613065b074 PR review changes 2025-09-12 16:28:58 +02:00
Matej Bačo 0d594de9d7 Add concurrency to dns validator 2025-09-12 15:58:39 +02:00
Matej Bačo 3eb1d74ba9 Improved DNS copy further 2025-09-12 15:41:58 +02:00
Matej Bačo 7d7e866ddf More AI review fixes 2025-09-12 14:51:02 +02:00
Matej Bačo 3e905f9d6b AI review fixes 2025-09-12 14:48:56 +02:00
Matej Bačo b4a49107f1 Remove generation status sync 2025-09-12 14:44:06 +02:00
Matej Bačo 4c4892a214 implement certificate bg verification 2025-09-12 14:39:04 +02:00
Matej Bačo 951baa1392 Fix warnings, unconsistant response format, fix maintenance concurrency 2025-09-12 14:38:53 +02:00
Matej Bačo 921c0344a0 Multi-dns support, improved DNS error description, improved $updatedAt of rule 2025-09-12 13:55:26 +02:00
Matej Bačo cdb400b538 AI review bug fixing 2025-09-11 15:02:44 +02:00
Matej Bačo 039a2361b4 Prioritize CAA validation for better UX 2025-09-11 14:49:44 +02:00
Matej Bačo c2faf18da6 Improve DNS validation copy further 2025-09-11 13:10:50 +02:00
Matej Bačo b391c2ac74 Instant-save logs to certificate 2025-09-11 12:48:58 +02:00
Matej Bačo 64fe298f91 Merge branch '1.8.x' into fix-dns-flows 2025-09-11 12:41:38 +02:00
Matej Bačo c81b2ebcae Fix maintenance worker 2025-09-11 12:32:49 +02:00
Matej Bačo 60dd501793 Improve UX of dns flows 2025-09-11 12:32:37 +02:00
94 changed files with 1344 additions and 770 deletions
+3 -3
View File
@@ -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=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_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
+11
View File
@@ -1185,6 +1185,17 @@ return [
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('logs'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 65535,
'signed' => true,
'required' => false,
'default' => '',
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('redirectStatusCode'),
'type' => Database::VAR_INTEGER,
@@ -59174,13 +59174,12 @@
"status": {
"type": "string",
"description": "Domain verification status. Possible values are \"created\", \"verifying\", \"verified\" and \"unverified\"",
"x-example": "verified",
"enum": [
"created",
"verifying",
"verified",
"unverified"
]
"x-example": "verified"
},
"verificationLogs": {
"type": "string",
"description": "DNS verification logs. This contains error from last verification attempt.",
"x-example": "Verification of DNS records failed with DNS resolver 8.8.8.8. Domain stage.myapp.com does not have DNS record."
},
"logs": {
"type": "string",
@@ -59207,6 +59206,7 @@
"deploymentResourceId",
"deploymentVcsProviderBranch",
"status",
"verificationLogs",
"logs",
"renewAt"
],
@@ -59224,6 +59224,7 @@
"deploymentResourceId": "n3u9feiwmf",
"deploymentVcsProviderBranch": "main",
"status": "verified",
"verificationLogs": "Verification of DNS records failed with DNS resolver 8.8.8.8. Domain stage.myapp.com does not have DNS record.",
"logs": "HTTP challegne failed.",
"renewAt": "datetime"
}
@@ -59105,13 +59105,12 @@
"status": {
"type": "string",
"description": "Domain verification status. Possible values are \"created\", \"verifying\", \"verified\" and \"unverified\"",
"x-example": "verified",
"enum": [
"created",
"verifying",
"verified",
"unverified"
]
"x-example": "verified"
},
"verificationLogs": {
"type": "string",
"description": "DNS verification logs. This contains error from last verification attempt.",
"x-example": "Verification of DNS records failed with DNS resolver 8.8.8.8. Domain stage.myapp.com does not have DNS record."
},
"logs": {
"type": "string",
@@ -59138,6 +59137,7 @@
"deploymentResourceId",
"deploymentVcsProviderBranch",
"status",
"verificationLogs",
"logs",
"renewAt"
],
@@ -59155,6 +59155,7 @@
"deploymentResourceId": "n3u9feiwmf",
"deploymentVcsProviderBranch": "main",
"status": "verified",
"verificationLogs": "Verification of DNS records failed with DNS resolver 8.8.8.8. Domain stage.myapp.com does not have DNS record.",
"logs": "HTTP challegne failed.",
"renewAt": "datetime"
}
+3 -3
View File
@@ -356,7 +356,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
'deploymentResourceId' => $resourceId,
'deploymentResourceInternalId' => $resourceInternalId,
'deploymentVcsProviderBranch' => $providerBranch,
'status' => 'verified',
'status' => RULE_STATUS_SUCCESSFUL,
'certificateId' => '',
'search' => implode(' ', [$ruleId, $domain]),
'owner' => 'Appwrite',
@@ -389,7 +389,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
'deploymentResourceId' => $resourceId,
'deploymentResourceInternalId' => $resourceInternalId,
'deploymentVcsProviderBranch' => $providerBranch,
'status' => 'verified',
'status' => RULE_STATUS_SUCCESSFUL,
'certificateId' => '',
'search' => implode(' ', [$ruleId, $domain]),
'owner' => 'Appwrite',
@@ -420,7 +420,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
'deploymentResourceId' => $resourceId,
'deploymentResourceInternalId' => $resourceInternalId,
'deploymentVcsProviderBranch' => $providerBranch,
'status' => 'verified',
'status' => RULE_STATUS_SUCCESSFUL,
'certificateId' => '',
'search' => implode(' ', [$ruleId, $domain]),
'owner' => 'Appwrite',
+1 -1
View File
@@ -973,7 +973,7 @@ App::init()
'$id' => $ruleId,
'domain' => $domain->get(),
'type' => 'api',
'status' => 'verifying',
'status' => RULE_STATUS_GENERATING_CERTIFICATE,
'projectId' => $console->getId(),
'projectInternalId' => $console->getSequence(),
'search' => implode(' ', [$ruleId, $domain->get()]),
+5 -1
View File
@@ -140,7 +140,11 @@ const DELETE_TYPE_EXPIRED_TARGETS = 'invalid_targets';
const DELETE_TYPE_SESSION_TARGETS = 'session_targets';
const DELETE_TYPE_CSV_EXPORTS = 'csv_exports';
const DELETE_TYPE_MAINTENANCE = 'maintenance';
// Rule types
const RULE_STATUS_VERIFICATION_FAILED = 'created';
const RULE_STATUS_GENERATING_CERTIFICATE = 'verifying';
const RULE_STATUS_GENERATION_FAILED = 'unverified';
const RULE_STATUS_SUCCESSFUL = 'verified';
// Message types
const MESSAGE_SEND_TYPE_INTERNAL = 'internal';
const MESSAGE_SEND_TYPE_EXTERNAL = 'external';
+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
+9 -9
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": "dc4eb1888275a24d596468924e272e25",
"content-hash": "98b10a56d6dc9787512620f14b3c22a0",
"packages": [
{
"name": "adhocore/jwt",
@@ -3943,16 +3943,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": {
@@ -3994,9 +3994,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",
@@ -8893,7 +8893,7 @@
],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"stability-flags": {},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
@@ -8917,5 +8917,5 @@
"platform-overrides": {
"php": "8.3"
},
"plugin-api-version": "2.3.0"
"plugin-api-version": "2.6.0"
}
+24
View File
@@ -89,9 +89,12 @@ services:
- ./public:/usr/src/code/public
- ./src:/usr/src/code/src
- ./dev:/usr/src/code/dev
dns:
- 172.16.238.100
depends_on:
- mariadb
- redis
- coredns
# - clamav
entrypoint:
- php
@@ -524,9 +527,12 @@ services:
image: appwrite-dev
networks:
- appwrite
dns:
- 172.16.238.100
depends_on:
- redis
- mariadb
- coredns
volumes:
- appwrite-config:/storage/config:rw
- appwrite-certificates:/storage/certificates:rw
@@ -1069,6 +1075,21 @@ services:
- appwrite
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 ------------------------------------------------------------------------------------------
#
@@ -1147,6 +1168,9 @@ networks:
name: gateway
appwrite:
name: appwrite
ipam:
config:
- subnet: 172.16.238.0/24
runtimes:
name: runtimes
+4
View File
@@ -6,9 +6,13 @@ use Utopia\Logger\Log;
interface Adapter
{
public function isIssueInstant(string $domain, ?string $domainType): bool;
public function issueCertificate(string $certName, string $domain, ?string $domainType): ?string;
public function isRenewRequired(string $domain, ?string $domainType, Log $log): bool;
public function deleteCertificate(string $domain): void;
public function getIssueStatus(string $domain, ?string $domainType): ?string;
}
@@ -0,0 +1,11 @@
<?php
namespace Appwrite\Certificates\Exceptions;
use Exception;
// Exception thrown during certificate issue status checking
//
class IssueStatus extends Exception
{
}
+10
View File
@@ -2,6 +2,7 @@
namespace Appwrite\Certificates;
use Appwrite\Certificates\Exceptions\IssueStatus as IssueStatusException;
use Exception;
use Utopia\App;
use Utopia\CLI\Console;
@@ -17,6 +18,15 @@ class LetsEncrypt implements Adapter
$this->email = $email;
}
public function isIssueInstant(string $domain, ?string $domainType): bool
{
return true;
}
public function getIssueStatus(string $domain, ?string $domainType): ?string
{
throw new IssueStatusException('Issue status synchronization is not supported in this adapter.');
}
public function issueCertificate(string $certName, string $domain, ?string $domainType): ?string
{
+62 -10
View File
@@ -7,9 +7,14 @@ use Utopia\Queue\Publisher;
class Certificate extends Event
{
public const ACTION_VERIFICATION = 'verification';
public const ACTION_GENERATION = 'generation';
protected bool $skipRenewCheck = false;
protected ?Document $domain = null;
protected ?string $validationDomain = null;
protected ?string $verificationDomainFunction = null; // For example: fra.cloud.appwrite.io
protected ?string $verificationDomainAPI = null; // For example: fra.appwrite.run
protected string $action = self::ACTION_GENERATION;
public function __construct(protected Publisher $publisher)
{
@@ -58,26 +63,49 @@ class Certificate extends Event
/**
* Set override for main domain used for validation
* Set verification domain function.
*
* @param string|null $validationDomain
* @param ?string $verificationDomainFunction
* @return self
*/
public function setValidationDomain(?string $validationDomain): self
public function setVerificationDomainFunction(?string $verificationDomainFunction): self
{
$this->validationDomain = $validationDomain;
$this->verificationDomainFunction = $verificationDomainFunction;
return $this;
}
/**
* Get validation domain
* Get verification domain function.
*
* @return string|null
* @return ?string
*/
public function getValidationDomain(): ?string
public function getVerificationDomainFunction(): ?string
{
return $this->validationDomain;
return $this->verificationDomainFunction;
}
/**
* Set verification domain api.
*
* @param ?string $verificationDomainAPI
* @return self
*/
public function setVerificationDomainAPI(?string $verificationDomainAPI): self
{
$this->verificationDomainAPI = $verificationDomainAPI;
return $this;
}
/**
* Get verification domain api.
*
* @return ?string
*/
public function getVerificationDomainAPI(): ?string
{
return $this->verificationDomainAPI;
}
/**
@@ -90,6 +118,28 @@ class Certificate extends Event
return $this->skipRenewCheck;
}
/**
* Set action for this certificate event.
*
* @param string $action
* @return self
*/
public function setAction(string $action): self
{
$this->action = $action;
return $this;
}
/**
* Get action for this certificate event.
*
* @return string
*/
public function getAction(): string
{
return $this->action;
}
/**
* Prepare the payload for the event
@@ -102,7 +152,9 @@ class Certificate extends Event
'project' => $this->project,
'domain' => $this->domain,
'skipRenewCheck' => $this->skipRenewCheck,
'validationDomain' => $this->validationDomain
'verificationDomainFunction' => $this->verificationDomainFunction,
'verificationDomainAPI' => $this->verificationDomainAPI,
'action' => $this->action
];
}
}
+48 -94
View File
@@ -2,115 +2,69 @@
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');
/**
* @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 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;
}
}
@@ -4,7 +4,7 @@ namespace Appwrite\Platform\Modules\Compute;
use Appwrite\Event\Build;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Action;
use Appwrite\Platform\Action as PlatformAction;
use Appwrite\Platform\Modules\Compute\Validator\Specification as SpecificationValidator;
use Utopia\Config\Config;
use Utopia\Database\Database;
@@ -19,7 +19,7 @@ use Utopia\System\System;
use Utopia\VCS\Adapter\Git\GitHub;
use Utopia\VCS\Exception\RepositoryNotFound;
class Base extends Action
class Action extends PlatformAction
{
/**
* Get default specification based on plan and available specifications.
@@ -252,7 +252,7 @@ class Base extends Action
'deploymentResourceId' => $site->getId(),
'deploymentResourceInternalId' => $site->getSequence(),
'deploymentVcsProviderBranch' => $providerBranch,
'status' => 'verified',
'status' => RULE_STATUS_SUCCESSFUL,
'certificateId' => '',
'search' => implode(' ', [$ruleId, $domain]),
'owner' => 'Appwrite',
@@ -278,7 +278,7 @@ class Base extends Action
'deploymentResourceId' => $site->getId(),
'deploymentResourceInternalId' => $site->getSequence(),
'deploymentVcsProviderBranch' => $providerBranch,
'status' => 'verified',
'status' => RULE_STATUS_SUCCESSFUL,
'certificateId' => '',
'search' => implode(' ', [$ruleId, $domain]),
'owner' => 'Appwrite',
@@ -315,7 +315,7 @@ class Base extends Action
'deploymentResourceId' => $site->getId(),
'deploymentResourceInternalId' => $site->getSequence(),
'deploymentVcsProviderBranch' => $providerBranch,
'status' => 'verified',
'status' => RULE_STATUS_SUCCESSFUL,
'certificateId' => '',
'search' => implode(' ', [$ruleId, $domain]),
'owner' => 'Appwrite',
@@ -5,6 +5,7 @@ namespace Appwrite\Platform\Modules\Functions\Http\Deployments;
use Appwrite\Event\Build;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
@@ -18,7 +19,6 @@ use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Storage\Device;
use Utopia\Storage\Validator\File;
@@ -5,6 +5,7 @@ namespace Appwrite\Platform\Modules\Functions\Http\Deployments;
use Appwrite\Event\Delete as DeleteEvent;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
@@ -14,7 +15,6 @@ use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Storage\Device;
@@ -3,6 +3,7 @@
namespace Appwrite\Platform\Modules\Functions\Http\Deployments\Download;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
@@ -11,7 +12,6 @@ use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Storage\Device;
use Utopia\Swoole\Request;
@@ -5,6 +5,7 @@ namespace Appwrite\Platform\Modules\Functions\Http\Deployments\Duplicate;
use Appwrite\Event\Build;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
@@ -12,7 +13,6 @@ use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Storage\Device;
@@ -3,13 +3,13 @@
namespace Appwrite\Platform\Modules\Functions\Http\Deployments;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\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\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
class Get extends Action
@@ -4,6 +4,7 @@ namespace Appwrite\Platform\Modules\Functions\Http\Deployments\Status;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
@@ -13,7 +14,6 @@ use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
class Update extends Action
@@ -5,7 +5,7 @@ namespace Appwrite\Platform\Modules\Functions\Http\Deployments\Template;
use Appwrite\Event\Build;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
@@ -16,7 +16,6 @@ use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Swoole\Request;
use Utopia\Validator\Boolean;
@@ -24,7 +23,7 @@ use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
use Utopia\VCS\Adapter\Git\GitHub;
class Create extends Base
class Create extends Action
{
use HTTP;
@@ -5,7 +5,7 @@ namespace Appwrite\Platform\Modules\Functions\Http\Deployments\Vcs;
use Appwrite\Event\Build;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
@@ -13,7 +13,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;
use Utopia\Swoole\Request;
use Utopia\Validator\Boolean;
@@ -21,7 +20,7 @@ use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
use Utopia\VCS\Adapter\Git\GitHub;
class Create extends Base
class Create extends Action
{
use HTTP;
@@ -3,7 +3,7 @@
namespace Appwrite\Platform\Modules\Functions\Http\Deployments;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
@@ -17,12 +17,11 @@ use Utopia\Database\Exception\Query as QueryException;
use Utopia\Database\Query;
use Utopia\Database\Validator\Query\Cursor;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\Boolean;
use Utopia\Validator\Text;
class XList extends Base
class XList extends Action
{
use HTTP;
@@ -10,7 +10,7 @@ use Appwrite\Event\StatsUsage;
use Appwrite\Extend\Exception;
use Appwrite\Extend\Exception as AppwriteException;
use Appwrite\Functions\Validator\Headers;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
@@ -29,7 +29,6 @@ use Utopia\Database\Helpers\Role;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Datetime as DatetimeValidator;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Swoole\Request;
use Utopia\System\System;
@@ -40,7 +39,7 @@ use Utopia\Validator\Nullable;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
class Create extends Base
class Create extends Action
{
use HTTP;
@@ -4,7 +4,7 @@ namespace Appwrite\Platform\Modules\Functions\Http\Executions;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
@@ -15,10 +15,9 @@ use Utopia\Database\DateTime;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
class Delete extends Base
class Delete extends Action
{
use HTTP;
@@ -4,7 +4,7 @@ namespace Appwrite\Platform\Modules\Functions\Http\Executions;
use Appwrite\Auth\Auth;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
@@ -12,10 +12,9 @@ use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
class Get extends Base
class Get extends Action
{
use HTTP;
@@ -4,7 +4,7 @@ namespace Appwrite\Platform\Modules\Functions\Http\Executions;
use Appwrite\Auth\Auth;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
@@ -18,11 +18,10 @@ use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Query\Cursor;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\Boolean;
class XList extends Base
class XList extends Action
{
use HTTP;
@@ -9,7 +9,7 @@ use Appwrite\Event\Realtime;
use Appwrite\Event\Validator\FunctionEvent;
use Appwrite\Event\Webhook;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\Platform\Modules\Compute\Validator\Specification;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
@@ -29,7 +29,6 @@ use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Roles;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Request;
use Utopia\System\System;
@@ -40,7 +39,7 @@ use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
use Utopia\VCS\Adapter\Git\GitHub;
class Create extends Base
class Create extends Action
{
use HTTP;
@@ -371,7 +370,7 @@ class Create extends Base
'projectId' => $project->getId(),
'projectInternalId' => $project->getSequence(),
'domain' => $domain,
'status' => 'verified',
'status' => RULE_STATUS_SUCCESSFUL,
'type' => 'deployment',
'trigger' => 'manual',
'deploymentId' => !isset($deployment) || $deployment->isEmpty() ? '' : $deployment->getId(),
@@ -5,7 +5,7 @@ namespace Appwrite\Platform\Modules\Functions\Http\Functions;
use Appwrite\Event\Delete as DeleteEvent;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
@@ -15,10 +15,9 @@ use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
class Delete extends Base
class Delete extends Action
{
use HTTP;
@@ -4,7 +4,7 @@ namespace Appwrite\Platform\Modules\Functions\Http\Functions\Deployment;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
@@ -15,10 +15,9 @@ use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
class Update extends Base
class Update extends Action
{
use HTTP;
@@ -3,17 +3,16 @@
namespace Appwrite\Platform\Modules\Functions\Http\Functions;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\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\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
class Get extends Base
class Get extends Action
{
use HTTP;
@@ -6,7 +6,7 @@ use Appwrite\Event\Build;
use Appwrite\Event\Event;
use Appwrite\Event\Validator\FunctionEvent;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\Platform\Modules\Compute\Validator\Specification;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
@@ -25,7 +25,6 @@ use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Roles;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Swoole\Request;
use Utopia\System\System;
@@ -37,7 +36,7 @@ use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
use Utopia\VCS\Adapter\Git\GitHub;
class Update extends Base
class Update extends Action
{
use HTTP;
@@ -3,7 +3,7 @@
namespace Appwrite\Platform\Modules\Functions\Http\Functions;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
@@ -15,12 +15,11 @@ use Utopia\Database\Exception\Order as OrderException;
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;
class XList extends Base
class XList extends Action
{
use HTTP;
@@ -2,18 +2,17 @@
namespace Appwrite\Platform\Modules\Functions\Http\Runtimes;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Config\Config;
use Utopia\Database\Document;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\System\System;
class XList extends Base
class XList extends Action
{
use HTTP;
@@ -2,18 +2,17 @@
namespace Appwrite\Platform\Modules\Functions\Http\Specifications;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Config\Config;
use Utopia\Database\Document;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\System\System;
class XList extends Base
class XList extends Action
{
use HTTP;
@@ -3,18 +3,17 @@
namespace Appwrite\Platform\Modules\Functions\Http\Templates;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Config\Config;
use Utopia\Database\Document;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\Text;
class Get extends Base
class Get extends Action
{
use HTTP;
@@ -2,21 +2,20 @@
namespace Appwrite\Platform\Modules\Functions\Http\Templates;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Config\Config;
use Utopia\Database\Document;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Boolean;
use Utopia\Validator\Range;
use Utopia\Validator\WhiteList;
class XList extends Base
class XList extends Action
{
use HTTP;
@@ -3,7 +3,7 @@
namespace Appwrite\Platform\Modules\Functions\Http\Usage;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
@@ -14,11 +14,10 @@ use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\WhiteList;
class Get extends Base
class Get extends Action
{
use HTTP;
@@ -2,7 +2,7 @@
namespace Appwrite\Platform\Modules\Functions\Http\Usage;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
@@ -12,11 +12,10 @@ use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\WhiteList;
class XList extends Base
class XList extends Action
{
use HTTP;
@@ -3,7 +3,7 @@
namespace Appwrite\Platform\Modules\Functions\Http\Variables;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
@@ -17,12 +17,11 @@ use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\Boolean;
use Utopia\Validator\Text;
class Create extends Base
class Create extends Action
{
use HTTP;
@@ -3,7 +3,7 @@
namespace Appwrite\Platform\Modules\Functions\Http\Variables;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
@@ -13,10 +13,9 @@ use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
class Delete extends Base
class Delete extends Action
{
use HTTP;
@@ -3,17 +3,16 @@
namespace Appwrite\Platform\Modules\Functions\Http\Variables;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\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\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
class Get extends Base
class Get extends Action
{
use HTTP;
@@ -3,7 +3,7 @@
namespace Appwrite\Platform\Modules\Functions\Http\Variables;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
@@ -13,13 +13,12 @@ use Utopia\Database\DateTime;
use Utopia\Database\Exception\Duplicate as DuplicateException;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
use Utopia\Validator\Text;
class Update extends Base
class Update extends Action
{
use HTTP;
@@ -3,7 +3,7 @@
namespace Appwrite\Platform\Modules\Functions\Http\Variables;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
@@ -11,10 +11,9 @@ 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 XList extends Base
class XList extends Action
{
use HTTP;
@@ -9,6 +9,7 @@ use Appwrite\Event\Realtime;
use Appwrite\Event\StatsUsage;
use Appwrite\Event\Webhook;
use Appwrite\Permission;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\Role;
use Appwrite\Utopia\Response\Model\Deployment;
use Appwrite\Vcs\Comment;
@@ -33,7 +34,6 @@ use Utopia\Detector\Detection\Rendering\XStatic;
use Utopia\Detector\Detector\Rendering;
use Utopia\Fetch\Client as FetchClient;
use Utopia\Logger\Log;
use Utopia\Platform\Action;
use Utopia\Queue\Message;
use Utopia\Storage\Compression\Compression;
use Utopia\Storage\Device;
@@ -1226,7 +1226,7 @@ class Builds extends Action
'deploymentResourceId' => $deployment->getId(),
'deploymentResourceInternalId' => $deployment->getSequence(),
'deploymentVcsProviderBranch' => $branchName,
'status' => 'verified',
'status' => RULE_STATUS_SUCCESSFUL,
'certificateId' => '',
'search' => implode(' ', [$ruleId, $domain]),
'owner' => 'Appwrite',
@@ -3,9 +3,9 @@
namespace Appwrite\Platform\Modules\Projects;
use Appwrite\Platform\Modules\Projects\Services\Http;
use Utopia\Platform\Module as Base;
use Utopia\Platform\Module as PlatformModule;
class Module extends Base
class Module extends PlatformModule
{
public function __construct()
{
@@ -0,0 +1,137 @@
<?php
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\Domains\Domain;
use Utopia\Logger\Log;
use Utopia\System\System;
use Utopia\Validator\AnyOf;
use Utopia\Validator\IP;
class Action extends PlatformAction
{
public function __construct(protected string $dnsValidatorClass = ValidatorDNS::class)
{
}
/**
* Verify or re-verify a rule
*
* @param Document $rule Rule to verify
* @param Log|null $log Log instance to add timings to
* @param string|null $verificationDomainAPI Override for expected API rule value during verification
* @param string|null $verificationDomainFunction Override for expected Function rule value during verification
* @return void
*/
public function verifyRule(Document $rule, ?Log $log = null, ?string $verificationDomainAPI = null, ?string $verificationDomainFunction = 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 because domain is not valid.');
}
if (!$domain->isKnown() || $domain->isTest()) {
throw new Exception(Exception::RULE_VERIFICATION_FAILED, 'DNS verification failed because domain ' . $domain->get() . ' is not known public suffix.');
}
// 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());
}
}
// Ensure at least one of CNAME/A/AAAA record points to our servers properly
// Ensures different target based on rule's type, as configured by env variables
if (\is_null($verificationDomainAPI)) {
$verificationDomainAPI = System::getEnv('_APP_DOMAIN_TARGET_CNAME', '');
}
if (\is_null($verificationDomainFunction)) {
$verificationDomainFunction = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
}
$targetCNAME = null;
$ruleType = $rule->getAttribute('type', '');
$resourceType = $rule->getAttribute('deploymentResourceType', '');
if ($resourceType === 'function') {
// For example: fra.appwrite.run
$targetCNAME = new Domain($verificationDomainFunction);
} 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($verificationDomainAPI);
} elseif ($ruleType === 'redirect') {
// Shouldn't be needed, because redirect should always have resourceTyp too, but just in case we defailt 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;
}
}
$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\API;
use Appwrite\Event\Certificate;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Network\Validator\DNS;
use Appwrite\Platform\Modules\Proxy\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
@@ -14,14 +14,11 @@ 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 +29,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')
@@ -67,11 +66,19 @@ class Create extends Action
->inject('queueForCertificates')
->inject('queueForEvents')
->inject('dbForPlatform')
->inject('log')
->callback($this->action(...));
}
public function action(string $domain, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform)
{
public function action(
string $domain,
Response $response,
Document $project,
Certificate $queueForCertificates,
Event $queueForEvents,
Database $dbForPlatform,
Log $log
) {
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
@@ -128,31 +135,9 @@ class Create extends Action
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
$ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain->get()) : ID::unique();
$status = 'created';
$status = RULE_STATUS_VERIFICATION_FAILED;
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';
}
$status = RULE_STATUS_SUCCESSFUL;
}
$owner = '';
@@ -177,13 +162,22 @@ class Create extends Action
'region' => $project->getAttribute('region')
]);
if ($rule->getAttribute('status', '') === RULE_STATUS_VERIFICATION_FAILED) {
try {
$this->verifyRule($rule, $log);
$rule->setAttribute('status', RULE_STATUS_GENERATING_CERTIFICATE);
} catch (Exception $err) {
$rule->setAttribute('logs', $err->getMessage());
}
}
try {
$rule = $dbForPlatform->createDocument('rules', $rule);
} catch (Duplicate $e) {
throw new Exception(Exception::RULE_ALREADY_EXISTS);
}
if ($rule->getAttribute('status', '') === 'verifying') {
if ($rule->getAttribute('status', '') === RULE_STATUS_GENERATING_CERTIFICATE) {
$queueForCertificates
->setDomain(new Document([
'domain' => $rule->getAttribute('domain'),
@@ -194,6 +188,22 @@ class Create extends Action
$queueForEvents->setParam('ruleId', $rule->getId());
$certificate = $dbForPlatform->getDocument('certificates', $rule->getAttribute('certificateId', ''));
// Merge logs: priority to certificate logs if both have values, otherwise use whichever is not empty
$ruleLogs = $rule->getAttribute('logs', '');
$certificateLogs = $certificate->getAttribute('logs', '');
$logs = '';
if (!empty($certificateLogs) && !empty($ruleLogs)) {
$logs = $certificateLogs; // Certificate logs have priority
} elseif (!empty($certificateLogs)) {
$logs = $certificateLogs;
} elseif (!empty($ruleLogs)) {
$logs = $ruleLogs;
}
$rule->setAttribute('logs', $logs);
$rule->setAttribute('renewAt', $certificate->getAttribute('renewDate', ''));
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($rule, Response::MODEL_PROXY_RULE);
@@ -5,6 +5,7 @@ namespace Appwrite\Platform\Modules\Proxy\Http\Rules;
use Appwrite\Event\Delete as DeleteEvent;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Proxy\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
@@ -13,7 +14,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 Delete extends Action
@@ -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\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
@@ -15,14 +15,11 @@ 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 +31,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')
@@ -72,11 +71,22 @@ class Create extends Action
->inject('queueForEvents')
->inject('dbForPlatform')
->inject('dbForProject')
->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)
{
public function action(
string $domain,
string $functionId,
string $branch,
Response $response,
Document $project,
Certificate $queueForCertificates,
Event $queueForEvents,
Database $dbForPlatform,
Database $dbForProject,
Log $log
) {
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
@@ -140,31 +150,9 @@ class Create extends Action
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
$ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain->get()) : ID::unique();
$status = 'created';
$status = RULE_STATUS_VERIFICATION_FAILED;
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';
}
$status = RULE_STATUS_SUCCESSFUL;
}
$owner = '';
@@ -195,13 +183,22 @@ class Create extends Action
'region' => $project->getAttribute('region')
]);
if ($rule->getAttribute('status', '') === RULE_STATUS_VERIFICATION_FAILED) {
try {
$this->verifyRule($rule, $log);
$rule->setAttribute('status', RULE_STATUS_GENERATING_CERTIFICATE);
} catch (Exception $err) {
$rule->setAttribute('logs', $err->getMessage());
}
}
try {
$rule = $dbForPlatform->createDocument('rules', $rule);
} catch (Duplicate $e) {
throw new Exception(Exception::RULE_ALREADY_EXISTS);
}
if ($rule->getAttribute('status', '') === 'verifying') {
if ($rule->getAttribute('status', '') === RULE_STATUS_GENERATING_CERTIFICATE) {
$queueForCertificates
->setDomain(new Document([
'domain' => $rule->getAttribute('domain'),
@@ -212,6 +209,22 @@ class Create extends Action
$queueForEvents->setParam('ruleId', $rule->getId());
$certificate = $dbForPlatform->getDocument('certificates', $rule->getAttribute('certificateId', ''));
// Merge logs: priority to certificate logs if both have values, otherwise use whichever is not empty
$ruleLogs = $rule->getAttribute('logs', '');
$certificateLogs = $certificate->getAttribute('logs', '');
$logs = '';
if (!empty($certificateLogs) && !empty($ruleLogs)) {
$logs = $certificateLogs; // Certificate logs have priority
} elseif (!empty($certificateLogs)) {
$logs = $certificateLogs;
} elseif (!empty($ruleLogs)) {
$logs = $ruleLogs;
}
$rule->setAttribute('logs', $logs);
$rule->setAttribute('renewAt', $certificate->getAttribute('renewDate', ''));
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($rule, Response::MODEL_PROXY_RULE);
@@ -3,6 +3,7 @@
namespace Appwrite\Platform\Modules\Proxy\Http\Rules;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Proxy\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
@@ -10,7 +11,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
@@ -64,8 +64,21 @@ class Get extends Action
throw new Exception(Exception::RULE_NOT_FOUND);
}
// Fill response model
$certificate = $dbForPlatform->getDocument('certificates', $rule->getAttribute('certificateId', ''));
$rule->setAttribute('logs', $certificate->getAttribute('logs', ''));
// Merge logs: priority to certificate logs if both have values, otherwise use whichever is not empty
$ruleLogs = $rule->getAttribute('logs', '');
$certificateLogs = $certificate->getAttribute('logs', '');
$logs = '';
if (!empty($certificateLogs) && !empty($ruleLogs)) {
$logs = $certificateLogs; // Certificate logs have priority
} elseif (!empty($certificateLogs)) {
$logs = $certificateLogs;
} elseif (!empty($ruleLogs)) {
$logs = $ruleLogs;
}
$rule->setAttribute('logs', $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\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
@@ -15,14 +15,11 @@ 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 +32,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')
@@ -75,11 +74,24 @@ class Create extends Action
->inject('queueForEvents')
->inject('dbForPlatform')
->inject('dbForProject')
->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)
{
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,
Log $log
) {
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
@@ -145,31 +157,9 @@ class Create extends Action
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
$ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain->get()) : ID::unique();
$status = 'created';
$status = RULE_STATUS_VERIFICATION_FAILED;
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';
}
$status = RULE_STATUS_SUCCESSFUL;
}
$owner = '';
@@ -199,13 +189,22 @@ class Create extends Action
'region' => $project->getAttribute('region')
]);
if ($rule->getAttribute('status', '') === RULE_STATUS_VERIFICATION_FAILED) {
try {
$this->verifyRule($rule, $log);
$rule->setAttribute('status', RULE_STATUS_GENERATING_CERTIFICATE);
} catch (Exception $err) {
$rule->setAttribute('logs', $err->getMessage());
}
}
try {
$rule = $dbForPlatform->createDocument('rules', $rule);
} catch (Duplicate $e) {
throw new Exception(Exception::RULE_ALREADY_EXISTS);
}
if ($rule->getAttribute('status', '') === 'verifying') {
if ($rule->getAttribute('status', '') === RULE_STATUS_GENERATING_CERTIFICATE) {
$queueForCertificates
->setDomain(new Document([
'domain' => $rule->getAttribute('domain'),
@@ -216,6 +215,22 @@ class Create extends Action
$queueForEvents->setParam('ruleId', $rule->getId());
$certificate = $dbForPlatform->getDocument('certificates', $rule->getAttribute('certificateId', ''));
// Merge logs: priority to certificate logs if both have values, otherwise use whichever is not empty
$ruleLogs = $rule->getAttribute('logs', '');
$certificateLogs = $certificate->getAttribute('logs', '');
$logs = '';
if (!empty($certificateLogs) && !empty($ruleLogs)) {
$logs = $certificateLogs; // Certificate logs have priority
} elseif (!empty($certificateLogs)) {
$logs = $certificateLogs;
} elseif (!empty($ruleLogs)) {
$logs = $ruleLogs;
}
$rule->setAttribute('logs', $logs);
$rule->setAttribute('renewAt', $certificate->getAttribute('renewDate', ''));
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($rule, Response::MODEL_PROXY_RULE);
@@ -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\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
@@ -15,14 +15,11 @@ 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 +31,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')
@@ -72,11 +71,22 @@ class Create extends Action
->inject('queueForEvents')
->inject('dbForPlatform')
->inject('dbForProject')
->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)
{
public function action(
string $domain,
string $siteId,
string $branch,
Response $response,
Document $project,
Certificate $queueForCertificates,
Event $queueForEvents,
Database $dbForPlatform,
Database $dbForProject,
Log $log
) {
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
@@ -140,31 +150,9 @@ class Create extends Action
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
$ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain->get()) : ID::unique();
$status = 'created';
$status = RULE_STATUS_VERIFICATION_FAILED;
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';
}
$status = RULE_STATUS_SUCCESSFUL;
}
$owner = '';
@@ -195,13 +183,22 @@ class Create extends Action
'region' => $project->getAttribute('region')
]);
if ($rule->getAttribute('status', '') === RULE_STATUS_VERIFICATION_FAILED) {
try {
$this->verifyRule($rule, $log);
$rule->setAttribute('status', RULE_STATUS_GENERATING_CERTIFICATE);
} catch (Exception $err) {
$rule->setAttribute('logs', $err->getMessage());
}
}
try {
$rule = $dbForPlatform->createDocument('rules', $rule);
} catch (Duplicate $e) {
throw new Exception(Exception::RULE_ALREADY_EXISTS);
}
if ($rule->getAttribute('status', '') === 'verifying') {
if ($rule->getAttribute('status', '') === RULE_STATUS_GENERATING_CERTIFICATE) {
$queueForCertificates
->setDomain(new Document([
'domain' => $rule->getAttribute('domain'),
@@ -212,6 +209,22 @@ class Create extends Action
$queueForEvents->setParam('ruleId', $rule->getId());
$certificate = $dbForPlatform->getDocument('certificates', $rule->getAttribute('certificateId', ''));
// Merge logs: priority to certificate logs if both have values, otherwise use whichever is not empty
$ruleLogs = $rule->getAttribute('logs', '');
$certificateLogs = $certificate->getAttribute('logs', '');
$logs = '';
if (!empty($certificateLogs) && !empty($ruleLogs)) {
$logs = $certificateLogs; // Certificate logs have priority
} elseif (!empty($certificateLogs)) {
$logs = $certificateLogs;
} elseif (!empty($ruleLogs)) {
$logs = $ruleLogs;
}
$rule->setAttribute('logs', $logs);
$rule->setAttribute('renewAt', $certificate->getAttribute('renewDate', ''));
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($rule, Response::MODEL_PROXY_RULE);
@@ -5,22 +5,18 @@ 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\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 +27,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')
@@ -82,81 +80,28 @@ 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->getAttribute('status', '') !== RULE_STATUS_VERIFICATION_FAILED) {
return $response->dynamic($rule, Response::MODEL_PROXY_RULE);
}
$validator = new AnyOf($validators, AnyOf::TYPE_STRING);
$domain = new Domain($rule->getAttribute('domain', ''));
$updates = new Document();
$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);
try {
$this->verifyRule($rule, $log);
$updates->setAttribute('logs', '');
} catch (Exception $err) {
$dbForPlatform->updateDocument('rules', $rule->getId(), new Document([
'$updatedAt' => DateTime::now(),
'logs' => $err->getMessage(),
]));
throw $err;
}
// 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.');
}
}
$updates->setAttribute('status', RULE_STATUS_GENERATING_CERTIFICATE);
$dbForPlatform->updateDocument('rules', $rule->getId(), $rule->setAttribute('status', 'verifying'));
$rule = $dbForPlatform->updateDocument('rules', $rule->getId(), $updates);
// Issue a TLS certificate when domain is verified
$queueForCertificates
@@ -166,10 +111,22 @@ class Update extends Action
]))
->trigger();
$queueForEvents->setParam('ruleId', $rule->getId());
// Fill response model
$certificate = $dbForPlatform->getDocument('certificates', $rule->getAttribute('certificateId', ''));
$rule->setAttribute('logs', $certificate->getAttribute('logs', ''));
// Merge logs: priority to certificate logs if both have values, otherwise use whichever is not empty
$ruleLogs = $rule->getAttribute('logs', '');
$certificateLogs = $certificate->getAttribute('logs', '');
$logs = '';
if (!empty($certificateLogs) && !empty($ruleLogs)) {
$logs = $certificateLogs;
} elseif (!empty($certificateLogs)) {
$logs = $certificateLogs;
} elseif (!empty($ruleLogs)) {
$logs = $ruleLogs;
}
$rule->setAttribute('logs', $logs);
$rule->setAttribute('renewAt', $certificate->getAttribute('renewDate', ''));
$response->dynamic($rule, Response::MODEL_PROXY_RULE);
}
@@ -3,6 +3,7 @@
namespace Appwrite\Platform\Modules\Proxy\Http\Rules;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Proxy\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
@@ -13,7 +14,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;
@@ -107,9 +107,23 @@ class XList extends Action
$filterQueries = Query::groupByType($queries)['filters'];
$rules = $dbForPlatform->find('rules', $queries);
// Fill response model
foreach ($rules as $rule) {
$certificate = $dbForPlatform->getDocument('certificates', $rule->getAttribute('certificateId', ''));
$rule->setAttribute('logs', $certificate->getAttribute('logs', ''));
// Merge logs: priority to certificate logs if both have values, otherwise use whichever is not empty
$ruleLogs = $rule->getAttribute('logs', '');
$certificateLogs = $certificate->getAttribute('logs', '');
$logs = '';
if (!empty($certificateLogs) && !empty($ruleLogs)) {
$logs = $certificateLogs; // Certificate logs have priority
} elseif (!empty($certificateLogs)) {
$logs = $certificateLogs;
} elseif (!empty($ruleLogs)) {
$logs = $ruleLogs;
}
$rule->setAttribute('logs', $logs);
$rule->setAttribute('renewAt', $certificate->getAttribute('renewDate', ''));
}
@@ -5,6 +5,7 @@ namespace Appwrite\Platform\Modules\Sites\Http\Deployments;
use Appwrite\Event\Build;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
@@ -19,7 +20,6 @@ use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Storage\Device;
use Utopia\Storage\Validator\File;
@@ -288,7 +288,7 @@ class Create extends Action
'deploymentResourceType' => 'site',
'deploymentResourceId' => $site->getId(),
'deploymentResourceInternalId' => $site->getSequence(),
'status' => 'verified',
'status' => RULE_STATUS_SUCCESSFUL,
'certificateId' => '',
'search' => implode(' ', [$ruleId, $domain]),
'owner' => 'Appwrite',
@@ -353,7 +353,7 @@ class Create extends Action
'deploymentResourceType' => 'site',
'deploymentResourceId' => $site->getId(),
'deploymentResourceInternalId' => $site->getSequence(),
'status' => 'verified',
'status' => RULE_STATUS_SUCCESSFUL,
'certificateId' => '',
'search' => implode(' ', [$ruleId, $domain]),
'owner' => 'Appwrite',
@@ -5,6 +5,7 @@ namespace Appwrite\Platform\Modules\Sites\Http\Deployments;
use Appwrite\Event\Delete as DeleteEvent;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
@@ -14,7 +15,6 @@ use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Storage\Device;
@@ -3,6 +3,7 @@
namespace Appwrite\Platform\Modules\Sites\Http\Deployments\Download;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
@@ -11,7 +12,6 @@ use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Storage\Device;
use Utopia\Swoole\Request;
@@ -5,6 +5,7 @@ namespace Appwrite\Platform\Modules\Sites\Http\Deployments\Duplicate;
use Appwrite\Event\Build;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
@@ -14,7 +15,6 @@ use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Storage\Device;
use Utopia\Swoole\Request;
@@ -159,7 +159,7 @@ class Create extends Action
'deploymentResourceType' => 'site',
'deploymentResourceId' => $site->getId(),
'deploymentResourceInternalId' => $site->getSequence(),
'status' => 'verified',
'status' => RULE_STATUS_SUCCESSFUL,
'certificateId' => '',
'owner' => 'Appwrite',
'region' => $project->getAttribute('region')
@@ -3,13 +3,13 @@
namespace Appwrite\Platform\Modules\Sites\Http\Deployments;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\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\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
class Get extends Action
@@ -4,6 +4,7 @@ namespace Appwrite\Platform\Modules\Sites\Http\Deployments\Status;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
@@ -13,7 +14,6 @@ use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
class Update extends Action
@@ -5,7 +5,7 @@ namespace Appwrite\Platform\Modules\Sites\Http\Deployments\Template;
use Appwrite\Event\Build;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
@@ -17,7 +17,6 @@ use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Swoole\Request;
use Utopia\System\System;
@@ -26,7 +25,7 @@ use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
use Utopia\VCS\Adapter\Git\GitHub;
class Create extends Base
class Create extends Action
{
use HTTP;
@@ -201,7 +200,7 @@ class Create extends Base
'deploymentResourceType' => 'site',
'deploymentResourceId' => $site->getId(),
'deploymentResourceInternalId' => $site->getSequence(),
'status' => 'verified',
'status' => RULE_STATUS_SUCCESSFUL,
'certificateId' => '',
'owner' => 'Appwrite',
'region' => $project->getAttribute('region')
@@ -5,7 +5,7 @@ namespace Appwrite\Platform\Modules\Sites\Http\Deployments\Vcs;
use Appwrite\Event\Build;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
@@ -13,7 +13,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;
use Utopia\Swoole\Request;
use Utopia\Validator\Boolean;
@@ -21,7 +20,7 @@ use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
use Utopia\VCS\Adapter\Git\GitHub;
class Create extends Base
class Create extends Action
{
use HTTP;
@@ -3,7 +3,7 @@
namespace Appwrite\Platform\Modules\Sites\Http\Deployments;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
@@ -17,12 +17,11 @@ use Utopia\Database\Exception\Query as QueryException;
use Utopia\Database\Query;
use Utopia\Database\Validator\Query\Cursor;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\Boolean;
use Utopia\Validator\Text;
class XList extends Base
class XList extends Action
{
use HTTP;
@@ -2,17 +2,16 @@
namespace Appwrite\Platform\Modules\Sites\Http\Frameworks;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Config\Config;
use Utopia\Database\Document;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
class XList extends Base
class XList extends Action
{
use HTTP;
@@ -4,17 +4,16 @@ namespace Appwrite\Platform\Modules\Sites\Http\Logs;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\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\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
class Delete extends Base
class Delete extends Action
{
use HTTP;
@@ -3,17 +3,16 @@
namespace Appwrite\Platform\Modules\Sites\Http\Logs;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\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\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
class Get extends Base
class Get extends Action
{
use HTTP;
@@ -3,7 +3,7 @@
namespace Appwrite\Platform\Modules\Sites\Http\Logs;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
@@ -17,11 +17,10 @@ use Utopia\Database\Exception\Query as QueryException;
use Utopia\Database\Query;
use Utopia\Database\Validator\Query\Cursor;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\Boolean;
class XList extends Base
class XList extends Action
{
use HTTP;
@@ -4,7 +4,7 @@ namespace Appwrite\Platform\Modules\Sites\Http\Sites;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\Platform\Modules\Compute\Validator\Specification;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
@@ -17,7 +17,6 @@ use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\System\System;
use Utopia\Validator\Boolean;
@@ -25,7 +24,7 @@ use Utopia\Validator\Range;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
class Create extends Base
class Create extends Action
{
use HTTP;
@@ -5,7 +5,7 @@ namespace Appwrite\Platform\Modules\Sites\Http\Sites;
use Appwrite\Event\Delete as DeleteEvent;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
@@ -13,10 +13,9 @@ use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
class Delete extends Base
class Delete extends Action
{
use HTTP;
@@ -4,7 +4,7 @@ namespace Appwrite\Platform\Modules\Sites\Http\Sites\Deployment;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
@@ -14,10 +14,9 @@ use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
class Update extends Base
class Update extends Action
{
use HTTP;
@@ -3,17 +3,16 @@
namespace Appwrite\Platform\Modules\Sites\Http\Sites;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\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\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
class Get extends Base
class Get extends Action
{
use HTTP;
@@ -5,7 +5,7 @@ namespace Appwrite\Platform\Modules\Sites\Http\Sites;
use Appwrite\Event\Build;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\Platform\Modules\Compute\Validator\Specification;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
@@ -20,7 +20,6 @@ use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Swoole\Request;
use Utopia\System\System;
@@ -30,7 +29,7 @@ use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
use Utopia\VCS\Adapter\Git\GitHub;
class Update extends Base
class Update extends Action
{
use HTTP;
@@ -3,7 +3,7 @@
namespace Appwrite\Platform\Modules\Sites\Http\Sites;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
@@ -15,12 +15,11 @@ use Utopia\Database\Exception\Order as OrderException;
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;
class XList extends Base
class XList extends Action
{
use HTTP;
@@ -2,18 +2,17 @@
namespace Appwrite\Platform\Modules\Sites\Http\Specifications;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Config\Config;
use Utopia\Database\Document;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\System\System;
class XList extends Base
class XList extends Action
{
use HTTP;
@@ -3,18 +3,17 @@
namespace Appwrite\Platform\Modules\Sites\Http\Templates;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Config\Config;
use Utopia\Database\Document;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\Text;
class Get extends Base
class Get extends Action
{
use HTTP;
@@ -2,20 +2,19 @@
namespace Appwrite\Platform\Modules\Sites\Http\Templates;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Config\Config;
use Utopia\Database\Document;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Range;
use Utopia\Validator\WhiteList;
class XList extends Base
class XList extends Action
{
use HTTP;
@@ -3,7 +3,7 @@
namespace Appwrite\Platform\Modules\Sites\Http\Usage;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
@@ -14,11 +14,10 @@ use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\WhiteList;
class Get extends Base
class Get extends Action
{
use HTTP;
@@ -2,7 +2,7 @@
namespace Appwrite\Platform\Modules\Sites\Http\Usage;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
@@ -12,11 +12,10 @@ use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\WhiteList;
class XList extends Base
class XList extends Action
{
use HTTP;
@@ -3,7 +3,7 @@
namespace Appwrite\Platform\Modules\Sites\Http\Variables;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
@@ -15,12 +15,11 @@ use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\Boolean;
use Utopia\Validator\Text;
class Create extends Base
class Create extends Action
{
use HTTP;
@@ -3,7 +3,7 @@
namespace Appwrite\Platform\Modules\Sites\Http\Variables;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
@@ -11,10 +11,9 @@ use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
class Delete extends Base
class Delete extends Action
{
use HTTP;
@@ -3,17 +3,16 @@
namespace Appwrite\Platform\Modules\Sites\Http\Variables;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\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\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
class Get extends Base
class Get extends Action
{
use HTTP;
@@ -3,7 +3,7 @@
namespace Appwrite\Platform\Modules\Sites\Http\Variables;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
@@ -11,13 +11,12 @@ use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Exception\Duplicate as DuplicateException;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
use Utopia\Validator\Text;
class Update extends Base
class Update extends Action
{
use HTTP;
@@ -3,7 +3,7 @@
namespace Appwrite\Platform\Modules\Sites\Http\Variables;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
@@ -11,10 +11,9 @@ 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 XList extends Base
class XList extends Action
{
use HTTP;
+2
View File
@@ -4,6 +4,7 @@ namespace Appwrite\Platform\Services;
use Appwrite\Platform\Tasks\Doctor;
use Appwrite\Platform\Tasks\Install;
use Appwrite\Platform\Tasks\Interval;
use Appwrite\Platform\Tasks\Maintenance;
use Appwrite\Platform\Tasks\Migrate;
use Appwrite\Platform\Tasks\QueueRetry;
@@ -28,6 +29,7 @@ class Tasks extends Service
$this
->addAction(Doctor::getName(), new Doctor())
->addAction(Install::getName(), new Install())
->addAction(Interval::getName(), new Interval())
->addAction(Maintenance::getName(), new Maintenance())
->addAction(Migrate::getName(), new Migrate())
->addAction(QueueRetry::getName(), new QueueRetry())
+124
View File
@@ -0,0 +1,124 @@
<?php
namespace Appwrite\Platform\Tasks;
use Appwrite\Event\Certificate;
use DateTime;
use Utopia\CLI\Console;
use Utopia\Database\Database;
use Utopia\Database\DateTime as DatabaseDateTime;
use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Platform\Action;
use Utopia\System\System;
class Interval extends Action
{
public static function getName(): string
{
return 'interval';
}
public function __construct()
{
$this
->desc('Schedules interval tasks for rules verification and certificate renewal')
->inject('dbForPlatform')
->inject('queueForCertificates')
->callback($this->action(...));
}
public function action(Database $dbForPlatform, Certificate $queueForCertificates): void
{
Console::title('Interval V1');
Console::success(APP_NAME . ' interval process v1 has started');
$intervalRuleVerification = (int) System::getEnv('_APP_MAINTENANCE_INTERVAL_RULE_VERIFICATION', '60'); // 1 minute
$intervalCertificateRenewal = (int) System::getEnv('_APP_MAINTENANCE_INTERVAL', '86400'); // 1 day
\go(function () use ($dbForPlatform, $queueForCertificates, $intervalRuleVerification) {
Console::loop(function () use ($dbForPlatform, $queueForCertificates) {
$this->checkRuleVerification($dbForPlatform, $queueForCertificates);
}, $intervalRuleVerification);
});
\go(function () use ($dbForPlatform, $queueForCertificates, $intervalCertificateRenewal) {
Console::loop(function () use ($dbForPlatform, $queueForCertificates) {
$this->renewCertificates($dbForPlatform, $queueForCertificates);
}, $intervalCertificateRenewal);
});
}
private function checkRuleVerification(Database $dbForPlatform, Certificate $queueForCertificate): void
{
$time = DatabaseDateTime::now();
$oldestToCheck = new DateTime('-3 days');
$rules = $dbForPlatform->find('rules', [
Query::createdAfter(DatabaseDateTime::format($oldestToCheck)), // max 3 days old
Query::equal('status', [RULE_STATUS_VERIFICATION_FAILED]), // not verified yet
Query::orderAsc('$updatedAt'), // Pick the ones waiting for another attempt for longest
Query::equal('region', [System::getEnv('_APP_REGION', 'default')]), // Only current region
Query::limit(30), // Reasonable pagination limit, processable within a minute
]);
if (\count($rules) > 0) {
Console::info("[{$time}] Found " . \count($rules) . " rules for verification, scheduling jobs.");
foreach ($rules as $rule) {
$queueForCertificate
->setDomain(new Document([
'domain' => $rule->getAttribute('domain'),
'domainType' => $rule->getAttribute('deploymentResourceType', $rule->getAttribute('type')),
]))
->setAction(Certificate::ACTION_VERIFICATION)
->trigger();
}
} else {
// Silenced because interval makes it too often
// Console::log("[{$time}] No rules for checking verification status.");
}
}
private function renewCertificates(Database $dbForPlatform, Certificate $queueForCertificate): void
{
$time = DatabaseDateTime::now();
$certificates = $dbForPlatform->find('certificates', [
Query::lessThan('attempts', 5), // Maximum 5 attempts
Query::isNotNull('renewDate'),
Query::lessThanEqual('renewDate', $time), // includes 60 days cooldown (we have 30 days to renew)
Query::limit(200), // Limit 200 comes from LetsEncrypt (300 orders per 3 hours, keeping some for new domains)
]);
if (\count($certificates) > 0) {
Console::info("[{$time}] Found " . \count($certificates) . " certificates for renewal, scheduling jobs.");
foreach ($certificates as $certificate) {
$domain = $certificate->getAttribute('domain');
if (System::getEnv('_APP_RULES_FORMAT') === 'md5') {
$rule = $dbForPlatform->getDocument('rules', md5($domain));
} else {
$rule = $dbForPlatform->findOne('rules', [
Query::equal('domain', [$domain]),
]);
}
if ($rule->isEmpty() || $rule->getAttribute('region') !== System::getEnv('_APP_REGION', 'default')) {
continue;
}
$queueForCertificate
->setDomain(new Document([
'domain' => $rule->getAttribute('domain'),
'domainType' => $rule->getAttribute('deploymentResourceType', $rule->getAttribute('type')),
]))
->setAction(Certificate::ACTION_GENERATION)
->trigger();
}
} else {
Console::info("[{$time}] No certificates for renewal.");
}
}
}
+36 -76
View File
@@ -2,7 +2,6 @@
namespace Appwrite\Platform\Tasks;
use Appwrite\Event\Certificate;
use Appwrite\Event\Delete;
use DateInterval;
use DateTime;
@@ -27,17 +26,17 @@ class Maintenance extends Action
->desc('Schedules maintenance tasks and publishes them to our queues')
->inject('dbForPlatform')
->inject('console')
->inject('queueForCertificates')
->inject('queueForDeletes')
->callback($this->action(...));
}
public function action(Database $dbForPlatform, Document $console, Certificate $queueForCertificates, Delete $queueForDeletes): void
public function action(Database $dbForPlatform, Document $console, Delete $queueForDeletes): void
{
Console::title('Maintenance V1');
Console::success(APP_NAME . ' maintenance process v1 has started');
$interval = (int) System::getEnv('_APP_MAINTENANCE_INTERVAL', '86400'); // 1 day
$usageStatsRetentionHourly = (int) System::getEnv('_APP_MAINTENANCE_RETENTION_USAGE_HOURLY', '8640000'); //100 days
$cacheRetention = (int) System::getEnv('_APP_MAINTENANCE_RETENTION_CACHE', '2592000'); // 30 days
$schedulesDeletionRetention = (int) System::getEnv('_APP_MAINTENANCE_RETENTION_SCHEDULES', '86400'); // 1 Day
@@ -59,44 +58,45 @@ class Maintenance extends Action
Console::info('Setting loop start time to ' . $next->format("Y-m-d H:i:s.v") . '. Delaying for ' . $delay . ' seconds.');
Console::loop(function () use ($interval, $cacheRetention, $schedulesDeletionRetention, $usageStatsRetentionHourly, $dbForPlatform, $console, $queueForDeletes, $queueForCertificates) {
$time = DatabaseDateTime::now();
\go(function () use ($interval, $cacheRetention, $schedulesDeletionRetention, $usageStatsRetentionHourly, $dbForPlatform, $console, $queueForDeletes, $delay) {
Console::loop(function () use ($interval, $cacheRetention, $schedulesDeletionRetention, $usageStatsRetentionHourly, $dbForPlatform, $console, $queueForDeletes) {
$time = DatabaseDateTime::now();
Console::info("[{$time}] Notifying workers with maintenance tasks every {$interval} seconds");
Console::info("[{$time}] Notifying workers with maintenance tasks every {$interval} seconds");
// Iterate through project only if it was accessed in last 30 days
$dateInterval = DateInterval::createFromDateString('30 days');
$before30days = (new DateTime())->sub($dateInterval);
// Iterate through project only if it was accessed in last 30 days
$dateInterval = DateInterval::createFromDateString('30 days');
$before30days = (new DateTime())->sub($dateInterval);
$dbForPlatform->foreach(
'projects',
function (Document $project) use ($queueForDeletes, $usageStatsRetentionHourly) {
$queueForDeletes
->setType(DELETE_TYPE_MAINTENANCE)
->setProject($project)
->setUsageRetentionHourlyDateTime(DatabaseDateTime::addSeconds(new \DateTime(), -1 * $usageStatsRetentionHourly))
->trigger();
},
[
Query::equal('region', [System::getEnv('_APP_REGION', 'default')]),
Query::limit(100),
Query::greaterThanEqual('accessedAt', DatabaseDateTime::format($before30days)),
Query::orderAsc('teamInternalId'),
]
);
$dbForPlatform->foreach(
'projects',
function (Document $project) use ($queueForDeletes, $usageStatsRetentionHourly) {
$queueForDeletes
->setType(DELETE_TYPE_MAINTENANCE)
->setProject($project)
->setUsageRetentionHourlyDateTime(DatabaseDateTime::addSeconds(new \DateTime(), -1 * $usageStatsRetentionHourly))
->trigger();
},
[
Query::equal('region', [System::getEnv('_APP_REGION', 'default')]),
Query::limit(100),
Query::greaterThanEqual('accessedAt', DatabaseDateTime::format($before30days)),
Query::orderAsc('teamInternalId'),
]
);
$queueForDeletes
->setType(DELETE_TYPE_MAINTENANCE)
->setProject($console)
->setUsageRetentionHourlyDateTime(DatabaseDateTime::addSeconds(new \DateTime(), -1 * $usageStatsRetentionHourly))
->trigger();
$queueForDeletes
->setType(DELETE_TYPE_MAINTENANCE)
->setProject($console)
->setUsageRetentionHourlyDateTime(DatabaseDateTime::addSeconds(new \DateTime(), -1 * $usageStatsRetentionHourly))
->trigger();
$this->notifyDeleteConnections($queueForDeletes);
$this->renewCertificates($dbForPlatform, $queueForCertificates);
$this->notifyDeleteCache($cacheRetention, $queueForDeletes);
$this->notifyDeleteSchedules($schedulesDeletionRetention, $queueForDeletes);
$this->notifyDeleteCSVExports($queueForDeletes);
}, $interval, $delay);
$this->notifyDeleteConnections($queueForDeletes);
$this->notifyDeleteCache($cacheRetention, $queueForDeletes);
$this->notifyDeleteSchedules($schedulesDeletionRetention, $queueForDeletes);
$this->notifyDeleteCSVExports($queueForDeletes);
}, $interval, $delay);
});
}
private function notifyDeleteConnections(Delete $queueForDeletes): void
@@ -114,46 +114,6 @@ class Maintenance extends Action
->trigger();
}
private function renewCertificates(Database $dbForPlatform, Certificate $queueForCertificate): void
{
$time = DatabaseDateTime::now();
$certificates = $dbForPlatform->find('certificates', [
Query::lessThan('attempts', 5), // Maximum 5 attempts
Query::isNotNull('renewDate'),
Query::lessThanEqual('renewDate', $time), // includes 60 days cooldown (we have 30 days to renew)
Query::limit(200), // Limit 200 comes from LetsEncrypt (300 orders per 3 hours, keeping some for new domains)
]);
if (\count($certificates) > 0) {
Console::info("[{$time}] Found " . \count($certificates) . " certificates for renewal, scheduling jobs.");
foreach ($certificates as $certificate) {
$domain = $certificate->getAttribute('domain');
if (System::getEnv('_APP_RULES_FORMAT') === 'md5') {
$rule = $dbForPlatform->getDocument('rules', md5($domain));
} else {
$rule = $dbForPlatform->findOne('rules', [
Query::equal('domain', [$domain]),
]);
}
if ($rule->isEmpty() || $rule->getAttribute('region') !== System::getEnv('_APP_REGION', 'default')) {
continue;
}
$queueForCertificate
->setDomain(new Document([
'domain' => $certificate->getAttribute('domain')
]))
->trigger();
}
} else {
Console::info("[{$time}] No certificates for renewal.");
}
}
private function notifyDeleteCache($interval, Delete $queueForDeletes): void
{
$queueForDeletes
+159 -90
View File
@@ -3,12 +3,14 @@
namespace Appwrite\Platform\Workers;
use Appwrite\Certificates\Adapter as CertificatesAdapter;
use Appwrite\Event\Certificate;
use Appwrite\Event\Event;
use Appwrite\Event\Func;
use Appwrite\Event\Mail;
use Appwrite\Event\Realtime;
use Appwrite\Event\Webhook;
use Appwrite\Network\Validator\DNS;
use Appwrite\Extend\Exception as ExtendException;
use Appwrite\Platform\Modules\Proxy\Action;
use Appwrite\Template\Template;
use Appwrite\Utopia\Response\Model\Rule;
use Exception;
@@ -22,15 +24,12 @@ use Utopia\Database\Exception\Conflict;
use Utopia\Database\Exception\Structure;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Query;
use Utopia\DNS\Message\Record;
use Utopia\Database\Validator\Authorization as ValidatorAuthorization;
use Utopia\Domains\Domain;
use Utopia\Locale\Locale;
use Utopia\Logger\Log;
use Utopia\Platform\Action;
use Utopia\Queue\Message;
use Utopia\System\System;
use Utopia\Validator\AnyOf;
use Utopia\Validator\IP;
class Certificates extends Action
{
@@ -42,8 +41,10 @@ class Certificates extends Action
/**
* @throws Exception
*/
public function __construct()
public function __construct(...$params)
{
parent::__construct(...$params);
$this
->desc('Certificates worker')
->inject('message')
@@ -53,6 +54,7 @@ class Certificates extends Action
->inject('queueForWebhooks')
->inject('queueForFunctions')
->inject('queueForRealtime')
->inject('queueForCertificates')
->inject('log')
->inject('certificates')
->inject('plan')
@@ -67,6 +69,7 @@ class Certificates extends Action
* @param Webhook $queueForWebhooks
* @param Func $queueForFunctions
* @param Realtime $queueForRealtime
* @param Certificate $queueForCertificates
* @param Log $log
* @param CertificatesAdapter $certificates
* @return void
@@ -81,26 +84,102 @@ class Certificates extends Action
Webhook $queueForWebhooks,
Func $queueForFunctions,
Realtime $queueForRealtime,
Certificate $queueForCertificates,
Log $log,
CertificatesAdapter $certificates,
array $plan
): void {
$payload = $message->getPayload() ?? [];
if (empty($payload)) {
throw new Exception('Missing payload');
}
$document = new Document($payload['domain'] ?? []);
$domain = new Domain($document->getAttribute('domain', ''));
$domain = new Domain($document->getAttribute('domain', ''));
$domainType = $document->getAttribute('domainType');
$skipRenewCheck = $payload['skipRenewCheck'] ?? false;
$validationDomain = $payload['validationDomain'] ?? null;
$action = $payload['action'] ?? Certificate::ACTION_GENERATION;
$verificationDomainFunction = $payload['verificationDomainFunction'] ?? null;
$verificationDomainAPI = $payload['verificationDomainAPI'] ?? null;
Console::log('Received ' . $action . ' action for ' . $domain->get() . ' domain');
$log->addTag('domain', $domain->get());
$domainType = $document->getAttribute('domainType');
switch ($action) {
case Certificate::ACTION_GENERATION:
$this->executeGeneration($domain, $domainType, $dbForPlatform, $queueForMails, $queueForEvents, $queueForWebhooks, $queueForFunctions, $queueForRealtime, $log, $certificates, $skipRenewCheck, $plan, $verificationDomainFunction, $verificationDomainAPI);
break;
case Certificate::ACTION_VERIFICATION:
$this->executeVerification($domain, $dbForPlatform, $log, $queueForCertificates, $verificationDomainFunction, $verificationDomainAPI);
break;
default:
throw new Exception('Invalid action: ' . $action);
}
}
$this->execute($domain, $domainType, $dbForPlatform, $queueForMails, $queueForEvents, $queueForWebhooks, $queueForFunctions, $queueForRealtime, $log, $certificates, $skipRenewCheck, $plan, $validationDomain);
private function executeVerification(
Domain $domain,
Database $dbForPlatform,
Log $log,
Certificate $queueForCertificates,
?string $verificationDomainFunction = null,
?string $verificationDomainAPI = null,
): void {
// Get rule
if (System::getEnv('_APP_RULES_FORMAT') === 'md5') {
$rule = ValidatorAuthorization::skip(fn () => $dbForPlatform->getDocument('rules', md5($domain->get())));
} else {
$rule = ValidatorAuthorization::skip(
fn () => $dbForPlatform->find('rules', [
Query::equal('domain', [$domain->get()]),
Query::limit(1)
])
)[0] ?? new Document();
}
// Skip if verification not needed
if ($rule->getAttribute('status', '') !== RULE_STATUS_VERIFICATION_FAILED) {
Console::warning('Verification for ' . $rule->getAttribute('domain', '') . ' is not needed.');
return;
}
Console::info('Verification for domain ' . $rule->getAttribute('domain', '') . ' started.');
// Prepare for verification
$mainDomain = $this->getMainDomain();
$isMainDomain = isset($mainDomain) && $domain->get() === $mainDomain;
// Verify DNS records
$updates = new Document();
$success = false;
try {
$this->validateDomain($rule, $isMainDomain, $log, $verificationDomainAPI, $verificationDomainFunction);
$updates
->setAttribute('logs', '')
->setAttribute('status', RULE_STATUS_GENERATING_CERTIFICATE);
Console::success('Verification succeeded.');
$success = true;
} catch (ExtendException $err) {
Console::warning('Verification failed: ' . $err->getMessage());
$updates->setAttribute('logs', $err->getMessage());
}
$rule = $dbForPlatform->updateDocument('rules', $rule->getId(), $updates);
// Issue a TLS certificate when domain is verified
if ($success) {
$queueForCertificates
->setDomain(new Document([
'domain' => $rule->getAttribute('domain'),
'domainType' => $rule->getAttribute('deploymentResourceType', $rule->getAttribute('type')),
]))
->trigger();
Console::success('Certificate generation triggered successfully.');
}
}
/**
@@ -114,12 +193,13 @@ class Certificates extends Action
* @param CertificatesAdapter $certificates
* @param bool $skipRenewCheck
* @param array $plan
* @param string|null $validationDomain
* @param ?string $verificationDomainFunction
* @param ?string $verificationDomainAPI
* @return void
* @throws Throwable
* @throws \Utopia\Database\Exception
*/
private function execute(
private function executeGeneration(
Domain $domain,
?string $domainType,
Database $dbForPlatform,
@@ -132,7 +212,8 @@ class Certificates extends Action
CertificatesAdapter $certificates,
bool $skipRenewCheck = false,
array $plan = [],
?string $validationDomain = null
?string $verificationDomainFunction = null,
?string $verificationDomainAPI = null
): void {
/**
* 1. Read arguments and validate domain
@@ -172,17 +253,44 @@ class Certificates extends Action
$certificate->setAttribute('domain', $domain->get());
}
$success = false;
$status = $certificate->getAttribute('status', RULE_STATUS_GENERATING_CERTIFICATE);
try {
// Clean-up logs from previous attempt
if (!empty($certificate->getAttribute('logs', ''))) {
$certificate->setAttribute('logs', '');
}
$date = \date('H:i:s');
$certificate->setAttribute('logs', "\033[90m[{$date}] \033[97mCertificate generation started. \033[0m\n");
$certificate = $this->upsertCertificate($domain->get(), $certificate, $dbForPlatform);
// Validate domain and DNS records. Skip if job is forced
if (!$skipRenewCheck) {
$mainDomain = $validationDomain ?? $this->getMainDomain();
$isMainDomain = !isset($mainDomain) || $domain->get() === $mainDomain;
$this->validateDomain($domain, $isMainDomain, $log);
$mainDomain = $this->getMainDomain();
$isMainDomain = isset($mainDomain) && $domain->get() === $mainDomain;
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
if (System::getEnv('_APP_RULES_FORMAT') === 'md5') {
$rule = ValidatorAuthorization::skip(fn () => $dbForPlatform->getDocument('rules', md5($domain->get())));
} else {
$rule = ValidatorAuthorization::skip(
fn () => $dbForPlatform->find('rules', [
Query::equal('domain', [$domain->get()]),
Query::limit(1)
])
)[0] ?? new Document();
}
if ($rule->isEmpty()) {
$rule = new Document([
'domain' => $domain->get(),
'type' => 'api'
]);
}
$this->validateDomain($rule, $isMainDomain, $log, $verificationDomainAPI, $verificationDomainFunction);
// If certificate exists already, double-check expiry date. Skip if job is forced
if (!$certificates->isRenewRequired($domain->get(), $domainType, $log)) {
@@ -195,15 +303,22 @@ class Certificates extends Action
$certName = ID::unique();
$renewDate = $certificates->issueCertificate($certName, $domain->get(), $domainType);
// Command succeeded, store all data into document
$certificate->setAttribute('logs', 'Certificate successfully generated.');
// This is useful when cert provider does extra work in background
// For example, verification, or example certificate distribution to all edges
if ($certificates->isIssueInstant($domain->get(), $domainType)) {
$status = RULE_STATUS_SUCCESSFUL;
// Command succeeded, store all data into document
$certificate->setAttribute('logs', 'Certificate successfully generated.');
}
// Update certificate info stored in database
$certificate->setAttribute('renewDate', $renewDate);
$certificate->setAttribute('attempts', 0);
$certificate->setAttribute('issueDate', DateTime::now());
$success = true;
} catch (Throwable $e) {
$status = RULE_STATUS_GENERATION_FAILED;
$logs = $e->getMessage();
$currentLogs = $certificate->getAttribute('logs', '');
$date = \date('H:i:s');
@@ -227,7 +342,10 @@ class Certificates extends Action
$certificate->setAttribute('updated', DateTime::now());
// Save all changes we made to certificate document into database
$this->saveCertificateDocument($domain->get(), $certificate, $success, $dbForPlatform, $queueForEvents, $queueForWebhooks, $queueForFunctions, $queueForRealtime);
$certificate = $this->upsertCertificate($domain->get(), $certificate, $dbForPlatform);
// Synchronize new status to all rules
$this->updateDomainDocuments($certificate->getId(), $domain->get(), $status, $dbForPlatform, $queueForEvents, $queueForWebhooks, $queueForFunctions, $queueForRealtime);
}
}
@@ -236,27 +354,18 @@ class Certificates extends Action
*
* @param string $domain Domain name that certificate is for
* @param Document $certificate Certificate document that we need to save
* @param bool $success
* @param Database $dbForPlatform Database connection for console
* @param Event $queueForEvents
* @param Func $queueForFunctions
* @param Realtime $queueForRealtime
* @return void
* @return Document
* @throws \Utopia\Database\Exception
* @throws Authorization
* @throws Conflict
* @throws Structure
*/
private function saveCertificateDocument(
private function upsertCertificate(
string $domain,
Document $certificate,
bool $success,
Database $dbForPlatform,
Event $queueForEvents,
Webhook $queueForWebhooks,
Func $queueForFunctions,
Realtime $queueForRealtime
): void {
): Document {
// Check if update or insert required
$certificateDocument = $dbForPlatform->findOne('certificates', [Query::equal('domain', [$domain])]);
if (!$certificateDocument->isEmpty()) {
@@ -268,8 +377,7 @@ class Certificates extends Action
$certificate = $dbForPlatform->createDocument('certificates', $certificate);
}
$certificateId = $certificate->getId();
$this->updateDomainDocuments($certificateId, $domain, $success, $dbForPlatform, $queueForEvents, $queueForWebhooks, $queueForFunctions, $queueForRealtime);
return $certificate;
}
/**
@@ -292,62 +400,18 @@ class Certificates extends Action
* - Domain needs to be public and valid (prevents NFT domains that are not supported)
* - Domain must have proper DNS record
*
* @param Domain $domain Domain which we validate
* @param Document $rule Rule which we validate
* @param bool $isMainDomain In case of master domain, we look for different DNS configurations
* @param string|null $verificationDomainAPI Function to verify domain
* @param string|null $verificationDomainFunction Function to verify domain
*
* @return void
* @throws Exception
*/
private function validateDomain(Domain $domain, bool $isMainDomain, Log $log): void
private function validateDomain(Document $rule, bool $isMainDomain, Log $log, ?string $verificationDomainAPI = null, ?string $verificationDomainFunction = null): void
{
if (empty($domain->get())) {
throw new Exception('Missing certificate domain.');
}
if (!$domain->isKnown() || $domain->isTest()) {
throw new Exception('Unknown public suffix for domain.');
}
if (!$isMainDomain) {
$validationStart = \microtime(true);
$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);
}
// Validate if domain target is properly configured
if (empty($validators)) {
throw new Exception('At least one of domain targets environment variable must be configured.');
}
// Verify domain with DNS records
$validator = new AnyOf($validators, AnyOf::TYPE_STRING);
if (!$validator->isValid($domain->get())) {
$log->addExtra('dnsTiming', \strval(\microtime(true) - $validationStart));
$log->addTag('dnsDomain', $domain->get());
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', ''), 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('Failed to verify domain DNS records. CAA records do not allow Appwrite\'s certificate issuer.');
}
}
$this->verifyRule($rule, $log, $verificationDomainAPI, $verificationDomainFunction);
} else {
// Main domain validation
// TODO: Would be awesome to check A/AAAA record here. Maybe dry run?
@@ -416,14 +480,14 @@ class Certificates extends Action
*
* @param string $certificateId ID of a new or updated certificate document
* @param string $domain Domain that is affected by new certificate
* @param bool $success Was certificate generation successful?
* @param string $status Status of the certificate generation, can be 'verifying', 'verified' 'unverified'
*
* @return void
*/
private function updateDomainDocuments(
string $certificateId,
string $domain,
bool $success,
string $status,
Database $dbForPlatform,
Event $queueForEvents,
Webhook $queueForWebhooks,
@@ -440,9 +504,14 @@ class Certificates extends Action
}
if (!$rule->isEmpty()) {
$rule->setAttribute('certificateId', $certificateId);
$rule->setAttribute('status', $success ? 'verified' : 'unverified');
$dbForPlatform->updateDocument('rules', $rule->getId(), $rule);
$updates = new Document();
$updates
->setAttribute('certificateId', $certificateId)
->setAttribute('status', $status)
->setAttribute('$updatedAt', DateTime::now());
$rule = $dbForPlatform->updateDocument('rules', $rule->getId(), $updates);
$projectId = $rule->getAttribute('projectId');
+6 -7
View File
@@ -84,17 +84,16 @@ class Rule extends Model
'example' => 'main',
])
->addRule('status', [
'type' => self::TYPE_ENUM,
'description' => 'Domain verification status. Possible values are "created", "verifying", "verified" and "unverified"',
'default' => 'created',
'example' => 'verified',
'enum' => ['created', 'verifying', 'verified', 'unverified'],
'type' => self::TYPE_STRING,
'description' => 'Domain verification status. Possible values are "' . RULE_STATUS_VERIFICATION_FAILED . '", "' . RULE_STATUS_GENERATING_CERTIFICATE . '", "' . RULE_STATUS_SUCCESSFUL . '" and "' . RULE_STATUS_GENERATION_FAILED . '"',
'default' => '',
'example' => RULE_STATUS_SUCCESSFUL,
])
->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. Priority: certificate logs if both have values, otherwise whichever is not empty.',
'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,
@@ -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_VERIFICATION_FAILED, $rule['body']['status']);
}
public function testCreateRuleVcs(): void
@@ -359,7 +389,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_SUCCESSFUL, $rule['body']['status']);
$this->cleanupRule($rule['body']['$id']);
@@ -368,7 +398,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_SUCCESSFUL, $rule['body']['status']);
$this->cleanupRule($rule['body']['$id']);
@@ -377,7 +407,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_VERIFICATION_FAILED, $rule['body']['status']);
$ruleId = $rule['body']['$id'];
@@ -540,4 +570,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_GENERATING_CERTIFICATE, $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_GENERATING_CERTIFICATE, $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_GENERATING_CERTIFICATE, $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_VERIFICATION_FAILED, $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_VERIFICATION_FAILED, $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_VERIFICATION_FAILED, $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_GENERATING_CERTIFICATE, $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_GENERATING_CERTIFICATE, $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_VERIFICATION_FAILED, $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_VERIFICATION_FAILED, $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_VERIFICATION_FAILED, $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_VERIFICATION_FAILED, $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_VERIFICATION_FAILED, $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_VERIFICATION_FAILED, $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_GENERATING_CERTIFICATE, $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_VERIFICATION_FAILED, $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_VERIFICATION_FAILED, $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);
}
}
+73
View File
@@ -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
}
}
+29 -76
View File
@@ -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);
}
}