Merge pull request #6155 from appwrite/fix-unknown-domains
Fix: Untrusted custom domains + auto-ssl
This commit is contained in:
commit
67698cce88
5 changed files with 78 additions and 35 deletions
|
@ -3,7 +3,6 @@
|
|||
use Appwrite\Auth\Auth;
|
||||
|
||||
$member = [
|
||||
'global',
|
||||
'public',
|
||||
'home',
|
||||
'console',
|
||||
|
@ -25,7 +24,6 @@ $member = [
|
|||
];
|
||||
|
||||
$admins = [
|
||||
'global',
|
||||
'graphql',
|
||||
'teams.read',
|
||||
'teams.write',
|
||||
|
|
|
@ -47,8 +47,6 @@ Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE);
|
|||
|
||||
function router(App $utopia, Database $dbForConsole, SwooleRequest $swooleRequest, Request $request, Response $response)
|
||||
{
|
||||
$utopia->getRoute()?->label('error', __DIR__ . '/../views/general/error.phtml');
|
||||
|
||||
$host = $request->getHostname() ?? '';
|
||||
|
||||
$route = Authorization::skip(
|
||||
|
@ -59,14 +57,11 @@ function router(App $utopia, Database $dbForConsole, SwooleRequest $swooleReques
|
|||
)[0] ?? null;
|
||||
|
||||
if ($route === null) {
|
||||
$mainDomain = App::getEnv('_APP_DOMAIN', '');
|
||||
// Act as API - no Proxy logic
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($mainDomain === 'localhost') {
|
||||
throw new AppwriteException(AppwriteException::ROUTER_DOMAIN_NOT_CONFIGURED);
|
||||
} else {
|
||||
throw new AppwriteException(AppwriteException::ROUTER_HOST_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
$utopia->getRoute()?->label('error', __DIR__ . '/../views/general/error.phtml');
|
||||
|
||||
$projectId = $route->getAttribute('projectId');
|
||||
$project = Authorization::skip(
|
||||
|
@ -163,6 +158,7 @@ function router(App $utopia, Database $dbForConsole, SwooleRequest $swooleReques
|
|||
|
||||
return true;
|
||||
} elseif ($type === 'api') {
|
||||
$utopia->getRoute()?->label('error', '');
|
||||
return false;
|
||||
} else {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_SERVER_ERROR, 'Unknown resource type ' . $type);
|
||||
|
@ -193,7 +189,7 @@ App::init()
|
|||
$host = $request->getHostname() ?? '';
|
||||
$mainDomain = App::getEnv('_APP_DOMAIN', '');
|
||||
// Only run Router when external domain
|
||||
if ($host !== $mainDomain && $host !== 'localhost' && $host !== APP_HOSTNAME_INTERNAL) {
|
||||
if ($host !== $mainDomain) {
|
||||
if (router($utopia, $dbForConsole, $swooleRequest, $request, $response)) {
|
||||
return;
|
||||
}
|
||||
|
@ -234,6 +230,61 @@ App::init()
|
|||
Request::setFilter(null);
|
||||
}
|
||||
|
||||
$domain = $request->getHostname();
|
||||
$domains = Config::getParam('domains', []);
|
||||
if (!array_key_exists($domain, $domains)) {
|
||||
$domain = new Domain(!empty($domain) ? $domain : '');
|
||||
|
||||
if (empty($domain->get()) || !$domain->isKnown() || $domain->isTest()) {
|
||||
$domains[$domain->get()] = false;
|
||||
Console::warning($domain->get() . ' is not a publicly accessible domain. Skipping SSL certificate generation.');
|
||||
} elseif (str_starts_with($request->getURI(), '/.well-known/acme-challenge')) {
|
||||
Console::warning('Skipping SSL certificates generation on ACME challenge.');
|
||||
} else {
|
||||
Authorization::disable();
|
||||
|
||||
$envDomain = App::getEnv('_APP_DOMAIN', '');
|
||||
$mainDomain = null;
|
||||
if (!empty($envDomain) && $envDomain !== 'localhost') {
|
||||
$mainDomain = $envDomain;
|
||||
} else {
|
||||
$domainDocument = $dbForConsole->findOne('rules', [Query::orderAsc('$id')]);
|
||||
$mainDomain = $domainDocument ? $domainDocument->getAttribute('domain') : $domain->get();
|
||||
}
|
||||
|
||||
if ($mainDomain !== $domain->get()) {
|
||||
Console::warning($domain->get() . ' is not a main domain. Skipping SSL certificate generation.');
|
||||
} else {
|
||||
$domainDocument = $dbForConsole->findOne('rules', [
|
||||
Query::equal('domain', [$domain->get()])
|
||||
]);
|
||||
|
||||
if (!$domainDocument) {
|
||||
$domainDocument = new Document([
|
||||
'domain' => $domain->get(),
|
||||
'resourceType' => 'api',
|
||||
'status' => 'verifying',
|
||||
'projectId' => 'console',
|
||||
'projectInternalId' => 'console'
|
||||
]);
|
||||
|
||||
$domainDocument = $dbForConsole->createDocument('rules', $domainDocument);
|
||||
|
||||
Console::info('Issuing a TLS certificate for the main domain (' . $domain->get() . ') in a few seconds...');
|
||||
|
||||
(new Certificate())
|
||||
->setDomain($domainDocument)
|
||||
->setSkipRenewCheck(true)
|
||||
->trigger();
|
||||
}
|
||||
}
|
||||
$domains[$domain->get()] = true;
|
||||
|
||||
Authorization::reset(); // ensure authorization is re-enabled
|
||||
}
|
||||
Config::setParam('domains', $domains);
|
||||
}
|
||||
|
||||
$localeParam = (string) $request->getParam('locale', $request->getHeader('x-appwrite-locale', ''));
|
||||
if (\in_array($localeParam, $localeCodes)) {
|
||||
$locale->setDefault($localeParam);
|
||||
|
@ -327,7 +378,7 @@ App::init()
|
|||
* @see https://www.owasp.org/index.php/List_of_useful_HTTP_headers
|
||||
*/
|
||||
if (App::getEnv('_APP_OPTIONS_FORCE_HTTPS', 'disabled') === 'enabled') { // Force HTTPS
|
||||
if ($request->getProtocol() !== 'https' && ($swooleRequest->header['host'] ?? '') !== 'localhost' && ($swooleRequest->header['host'] ?? '') !== APP_HOSTNAME_INTERNAL) { // Localhost allowed for proxy, APP_HOSTNAME_INTERNAL allowed for migrations
|
||||
if ($request->getProtocol() !== 'https' && ($swooleRequest->header['host'] ?? '') !== APP_HOSTNAME_INTERNAL) { // APP_HOSTNAME_INTERNAL allowed for migrations
|
||||
if ($request->getMethod() !== Request::METHOD_GET) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_PROTOCOL_UNSUPPORTED, 'Method unsupported over HTTP.');
|
||||
}
|
||||
|
@ -497,7 +548,7 @@ App::options()
|
|||
$host = $request->getHostname() ?? '';
|
||||
$mainDomain = App::getEnv('_APP_DOMAIN', '');
|
||||
// Only run Router when external domain
|
||||
if ($host !== $mainDomain && $host !== 'localhost' && $host !== APP_HOSTNAME_INTERNAL) {
|
||||
if ($host !== $mainDomain) {
|
||||
if (router($utopia, $dbForConsole, $swooleRequest, $request, $response)) {
|
||||
return;
|
||||
}
|
||||
|
@ -763,7 +814,7 @@ include_once __DIR__ . '/shared/api/auth.php';
|
|||
|
||||
App::wildcard()
|
||||
->groups(['api'])
|
||||
->label('scope', 'global')
|
||||
->label('scope', 'public')
|
||||
->action(function () {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_ROUTE_NOT_FOUND);
|
||||
});
|
||||
|
|
|
@ -31,13 +31,6 @@ App::get('/console/*')
|
|||
->inject('request')
|
||||
->inject('response')
|
||||
->action(function (Request $request, Response $response) {
|
||||
// Serve static files (console) only for main domain
|
||||
$host = $request->getHostname() ?? '';
|
||||
$mainDomain = App::getEnv('_APP_DOMAIN', '');
|
||||
if ($host !== $mainDomain && $host !== 'localhost' && $host !== APP_HOSTNAME_INTERNAL) {
|
||||
throw new Exception(Exception::GENERAL_ROUTE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$fallback = file_get_contents(__DIR__ . '/../../../console/index.html');
|
||||
|
||||
// Card SSR
|
||||
|
|
|
@ -229,10 +229,6 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo
|
|||
$request = new Request($swooleRequest);
|
||||
$response = new Response($swooleResponse);
|
||||
|
||||
// Serve static files (console) only for main domain
|
||||
$host = $request->getHostname() ?? '';
|
||||
$mainDomain = App::getEnv('_APP_DOMAIN', '');
|
||||
if ($host === $mainDomain || $host === 'localhost') {
|
||||
if (Files::isFileLoaded($request->getURI())) {
|
||||
$time = (60 * 60 * 24 * 365 * 2); // 45 days cache
|
||||
|
||||
|
@ -244,7 +240,6 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo
|
|||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$app = new App('UTC');
|
||||
|
||||
|
|
|
@ -438,6 +438,12 @@ class CertificatesV1 extends Worker
|
|||
$this->dbForConsole->updateDocument('rules', $rule->getId(), $rule);
|
||||
|
||||
$projectId = $rule->getAttribute('projectId');
|
||||
|
||||
// Skip events for console project (triggered by auto-ssl generation for 1 click setups)
|
||||
if ($projectId === 'console') {
|
||||
return;
|
||||
}
|
||||
|
||||
$project = $this->dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
/** Trigger Webhook */
|
||||
|
|
Loading…
Reference in a new issue