1
0
Fork 0
mirror of synced 2024-06-29 19:50:26 +12:00

Merge pull request #6155 from appwrite/fix-unknown-domains

Fix: Untrusted custom domains + auto-ssl
This commit is contained in:
Christy Jacob 2023-09-06 14:09:17 -04:00 committed by GitHub
commit 67698cce88
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 78 additions and 35 deletions

View file

@ -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',

View file

@ -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,15 +57,12 @@ function router(App $utopia, Database $dbForConsole, SwooleRequest $swooleReques
)[0] ?? null;
if ($route === null) {
$mainDomain = App::getEnv('_APP_DOMAIN', '');
if ($mainDomain === 'localhost') {
throw new AppwriteException(AppwriteException::ROUTER_DOMAIN_NOT_CONFIGURED);
} else {
throw new AppwriteException(AppwriteException::ROUTER_HOST_NOT_FOUND);
}
// Act as API - no Proxy logic
return false;
}
$utopia->getRoute()?->label('error', __DIR__ . '/../views/general/error.phtml');
$projectId = $route->getAttribute('projectId');
$project = Authorization::skip(
fn () => $dbForConsole->getDocument('projects', $projectId)
@ -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);
});

View file

@ -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

View file

@ -229,21 +229,16 @@ $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
if (Files::isFileLoaded($request->getURI())) {
$time = (60 * 60 * 24 * 365 * 2); // 45 days cache
$response
->setContentType(Files::getFileMimeType($request->getURI()))
->addHeader('Cache-Control', 'public, max-age=' . $time)
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $time) . ' GMT') // 45 days cache
->send(Files::getFileContents($request->getURI()));
$response
->setContentType(Files::getFileMimeType($request->getURI()))
->addHeader('Cache-Control', 'public, max-age=' . $time)
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $time) . ' GMT') // 45 days cache
->send(Files::getFileContents($request->getURI()));
return;
}
return;
}
$app = new App('UTC');

View file

@ -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 */