Compare commits

...
Author SHA1 Message Date
Hemachandar 74517c6fea managed-dns verification 2026-01-27 21:32:01 +05:30
Hemachandar 4582994e3b rule response 2026-01-27 19:58:41 +05:30
Hemachandar d1c0af0908 default value 2026-01-27 19:39:44 +05:30
Hemachandar b1a6dd02fb nameservers param 2026-01-27 19:05:49 +05:30
Hemachandar 92c81ba429 actions column 2026-01-27 18:25:51 +05:30
Hemachandar abdf89ff89 Add nameservers param 2026-01-27 17:51:11 +05:30
Hemachandar 5a5279a10f Action required status 2026-01-27 14:56:06 +05:30
Hemachandar 7f99bd2dd2 Change getCertificateStatus return type 2026-01-27 14:48:19 +05:30
8 changed files with 89 additions and 27 deletions
+11
View File
@@ -1379,6 +1379,17 @@ $platformCollections = [
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('actions'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 65535,
'signed' => true,
'required' => false,
'default' => '',
'array' => false,
'filters' => ['json'],
],
],
'indexes' => [
[
+5
View File
@@ -215,9 +215,14 @@ const DELETE_TYPE_SESSION_TARGETS = 'session_targets';
const DELETE_TYPE_CSV_EXPORTS = 'csv_exports';
const DELETE_TYPE_MAINTENANCE = 'maintenance';
// Rule verification types
const RULE_VERIFICATION_TYPE_DNS = 'dns'; // Basic verification of DNS records
const RULE_VERIFICATION_TYPE_MANAGED_DNS = 'managed_dns'; // ACME-DNS challenge verification
// Rule statuses
const RULE_STATUS_CREATED = 'created'; // This is also the status when domain DNS verification fails.
const RULE_STATUS_CERTIFICATE_GENERATING = 'verifying';
const RULE_STATUS_ACTION_REQUIRED = 'action_required';
const RULE_STATUS_CERTIFICATE_GENERATION_FAILED = 'unverified';
const RULE_STATUS_VERIFIED = 'verified';
+1 -1
View File
@@ -10,7 +10,7 @@ interface Adapter
public function isInstantGeneration(string $domain, ?string $domainType): bool;
public function getCertificateStatus(string $domain, ?string $domainType): string;
public function getCertificateStatus(string $domain, ?string $domainType): array;
public function isRenewRequired(string $domain, ?string $domainType, Log $log): bool;
+1 -1
View File
@@ -90,7 +90,7 @@ class LetsEncrypt implements Adapter
return true;
}
public function getCertificateStatus(string $domain, ?string $domainType): string
public function getCertificateStatus(string $domain, ?string $domainType): array
{
throw new CertificateStatusException('Certificate status retrieval is not supported for LetsEncrypt.');
}
+20 -1
View File
@@ -86,7 +86,7 @@ class Action extends PlatformAction
* @param Log|null $log Log instance to add timings to
* @return void
*/
protected function verifyRule(Document $rule, ?Log $log = null): void
protected function verifyRule(Document $rule, ?Log $log = null, string $verificationType = RULE_VERIFICATION_TYPE_DNS): void
{
$dnsValidatorClass = $this->dnsValidatorClass;
$dnsEnv = System::getEnv('_APP_DNS', '8.8.8.8');
@@ -103,6 +103,25 @@ class Action extends PlatformAction
throw new Exception(Exception::RULE_VERIFICATION_FAILED, 'DNS verification failed as domain ' . $domain->get() . ' does not resolve to a known public apex domain.');
}
// Verify managed-dns challenge
if ($verificationType === RULE_VERIFICATION_TYPE_MANAGED_DNS) {
$managedDnsAction = current(array_filter($rule->getAttribute('actions', []), fn ($action) => $action['challengeType'] === RULE_VERIFICATION_TYPE_MANAGED_DNS));
if (!$managedDnsAction) {
throw new Exception(Exception::RULE_VERIFICATION_FAILED, 'DNS verification failed as no managed-dns challenge is available in the rule.');
}
$validationStart = \microtime(true);
$validator = new $dnsValidatorClass($managedDnsAction['recordValue'], Record::typeNameToCode($managedDnsAction['recordType']), $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 CAA won't block certificate issuance
$caaTarget = System::getEnv('_APP_DOMAIN_TARGET_CAA', '');
if (!empty($caaTarget)) {
@@ -16,6 +16,7 @@ use Utopia\Database\Document;
use Utopia\Database\Validator\UID;
use Utopia\Logger\Log;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\WhiteList;
class Update extends Action
{
@@ -55,6 +56,7 @@ class Update extends Action
]
))
->param('ruleId', '', new UID(), 'Rule ID.')
->param('type', '', new WhiteList([RULE_VERIFICATION_TYPE_DNS, RULE_VERIFICATION_TYPE_MANAGED_DNS]), 'Type of verification to perform.')
->inject('response')
->inject('queueForCertificates')
->inject('queueForEvents')
@@ -66,6 +68,7 @@ class Update extends Action
public function action(
string $ruleId,
string $type,
Response $response,
Certificate $queueForCertificates,
Event $queueForEvents,
@@ -87,20 +90,42 @@ class Update extends Action
}
try {
$this->verifyRule($rule, $log);
// Reset logs and status for the rule
$rule = $dbForPlatform->updateDocument('rules', $rule->getId(), new Document([
'logs' => '',
'status' => RULE_STATUS_CERTIFICATE_GENERATING,
]));
$this->verifyRule($rule, $log, $type);
$certificateId = $rule->getAttribute('certificateId', '');
// Reset logs for the associated certificate.
if (!empty($certificateId)) {
$certificate = $dbForPlatform->updateDocument('certificates', $certificateId, new Document([
'logs' => '',
if ($type === RULE_VERIFICATION_TYPE_MANAGED_DNS) {
// Set status to "generating" - This will ensure background sync will pick it up
$rule = $dbForPlatform->updateDocument('rules', $rule->getId(), new Document([
'status' => RULE_STATUS_CERTIFICATE_GENERATING,
]));
} else {
// Reset logs and status for the rule
$rule = $dbForPlatform->updateDocument('rules', $rule->getId(), new Document([
'logs' => '',
'status' => RULE_STATUS_CERTIFICATE_GENERATING,
]));
$certificateId = $rule->getAttribute('certificateId', '');
// Reset logs for the associated certificate.
if (!empty($certificateId)) {
$certificate = $dbForPlatform->updateDocument('certificates', $certificateId, new Document([
'logs' => '',
]));
}
// Issue a TLS certificate when DNS verification is successful
$queueForCertificates
->setDomain(new Document([
'domain' => $rule->getAttribute('domain'),
'domainType' => $rule->getAttribute('deploymentResourceType', $rule->getAttribute('type')),
]))
->trigger();
if (!empty($certificate)) {
$rule->setAttribute('renewAt', $certificate->getAttribute('renewDate', ''));
}
}
$response->dynamic($rule, Response::MODEL_PROXY_RULE);
} catch (Exception $err) {
$dbForPlatform->updateDocument('rules', $rule->getId(), new Document([
'$updatedAt' => DateTime::now(),
@@ -108,18 +133,5 @@ class Update extends Action
throw $err;
}
// Issue a TLS certificate when DNS verification is successful
$queueForCertificates
->setDomain(new Document([
'domain' => $rule->getAttribute('domain'),
'domainType' => $rule->getAttribute('deploymentResourceType', $rule->getAttribute('type')),
]))
->trigger();
if (!empty($certificate)) {
$rule->setAttribute('renewAt', $certificate->getAttribute('renewDate', ''));
}
$response->dynamic($rule, Response::MODEL_PROXY_RULE);
}
}
@@ -76,6 +76,7 @@ class Certificates extends Action
* @param CertificatesAdapter $certificates
* @param array $plan
* @param ValidatorAuthorization $authorization
* @param array $nameservers
* @return void
* @throws Throwable
* @throws \Utopia\Database\Exception
@@ -93,6 +94,7 @@ class Certificates extends Action
CertificatesAdapter $certificates,
array $plan,
ValidatorAuthorization $authorization,
array $nameservers = [],
): void {
$payload = $message->getPayload() ?? [];
@@ -103,6 +103,19 @@ class Rule extends Model
'example' => APP_DATABASE_ATTRIBUTE_DATETIME,
'array' => false,
])
->addRule('actions', [
'type' => self::TYPE_ARRAY,
'description' => 'Additional actions required from the user when rule status is "action_required"',
'default' => [],
'example' => [
[
'challengeType' => 'managed-dns',
'recordType' => 'CNAME',
'recordName' => '_acme-challenge',
'recordValue' => 'test.fastly-validations.com',
],
],
])
;
}