Как обслуживать пользовательские домены, указывающие на поддомен в Saas App

Я хочу создать пример приложения SaaS, в котором пользователи могут регистрироваться, создавать веб-страницы, использовать шаблоны и / или настраивать их с помощью пользовательских CSS, обслуживать свои веб-страницы вне пользовательских доменов.

Я думал о сохранении шаблонов на S3/ других CDN вместе с файлами media/stylesheets/js. Хотя все это технически возможно (практическое? Это может быть спорно). В любом случае, мне было трудно понять, как в этом случае веб-сайты будут обслуживаться с пользовательских доменов? Например, когда они зарегистрируются, они могут получить subdomain.domain.com адрес. Тем не менее, как они указывают customerdomain.com так что когда customerdomain.com введен, он обслуживает тот же контент, что и customerdomain.domain.comи URL остается customerdomain.com

Кроме того, если я хочу иметь "feature"при этом пользовательские домены могут быть платной функцией. Как бы я ограничил это только для платных пользователей?

Обычно, когда мы настраиваем веб-сайты, мы указываем его в конфигурационном файле виртуального хоста (apache) и присваиваем ему псевдонимы, поэтому он ищет и обслуживает эти псевдонимы. В этом случае я не хочу иметь отдельный файл vhost для каждого подписавшегося. Есть ли альтернатива? Как я могу запрограммировать это? Есть ли какие-нибудь ошибки, о которых нужно знать?

Одно из решений, которое я видел, состоит в том, чтобы сервер обслуживал подстановочный домен, т.е. *.domain.comи отдельный vhost для каждого настраиваемого домена, однако я бы предпочел избежать, если смогу.

Благодарю.

2 ответа

Решение

Пользовательский домен обычно создается через DNS-запись CNAME (своего рода символическая ссылка для DNS-записей). Вы говорите своему клиенту (который обычно контролирует customerdomain.com) создать запись CNAME, в которой говорится, что customerdomain.com является псевдонимом для customerdomain.domain.com. Затем вам нужно настроить свой собственный сервер для интерпретации запросов к customerdomain.com так же, как он будет обрабатывать запросы к customerdomain.domain.com.

В зависимости от того, как вы обслуживаете свои субдомены, это можно сделать несколькими различными способами.

Если у вас есть файл vhost для каждого отдельного клиента, вам нужно добавить директиву "ServerAlias" для настраиваемого домена, предоставленного вашим клиентом.

Если вы кодируете точку входа через свой собственный сервер приложений (скажем, читая HTTP-заголовок "Host" из PHP, а затем устанавливая имя клиента из него), вам необходимо соответствующим образом настроить этот код для интерпретации запросов на внешние домены в соответствии с вашим собственная база пользовательских доменов. Вы даже можете использовать прямой DNS для этого!

Что-то на линии:

if http "host" header does not end in domain.com:
    cname = get_cname_record(http "host" header value)
    if cname does not end in domain.com:
        return error 404
    else:
        site = first part of cname
else:
    site = first part of http "host" header

Затем вы можете использовать DNS в качестве "базы данных пользовательских доменов". Убедитесь, что вы используете кеш DNS, так как эти запросы будут выполняться при каждом отдельном запросе.

Принятый ответ хорош, но он не показывает полной картины.

Если ваши клиенты просто CNAME для вашего домена или создают запись A для вашего IP-адреса, и вы не обрабатываете завершение TLS для этих пользовательских доменов, ваше приложение не будет поддерживать HTTPS, а без него ваше приложение не будет работать в современных браузерах на эти пользовательские домены.

Вам необходимо настроить обратный прокси-сервер завершения TLS перед вашим веб-сервером. Этот прокси можно запустить на отдельном компьютере, но вы можете запустить его на том же компьютере, что и веб-сервер.

CNAME против записи A

Если ваши клиенты хотят, чтобы ваше приложение было на их субдомене, например, они могут создать CNAMEapp.customer.comуказывая на ваш прокси.

Если они хотят, чтобы ваше приложение было в их корневом домене, например, им придется создать запись A наcustomer.comуказывая на IP-адрес вашего прокси. Убедитесь, что этот IP никогда не изменится!

Как справиться с завершением TLS?

Чтобы завершение TLS работало, вам необходимо выпустить сертификаты TLS для этих пользовательских доменов. Вы можете использовать Let's Encrypt для этого. Ваш прокси увидит заголовок входящего запроса, напримерapp.customer1.comилиcustomer2.comи т. д., а затем он решит, какой сертификат TLS использовать, проверив SNI.

Прокси-сервер можно настроить на автоматическую выдачу и обновление сертификатов для этих пользовательских доменов. При первом запросе из нового пользовательского домена прокси увидит, что у него нет соответствующего сертификата. Он запросит у Let’s Encrypt новый сертификат. Let's Encrypt сначала выдаст запрос, чтобы узнать, управляете ли вы доменом, и, поскольку клиент уже создал запись CNAME или A, указывающую на ваш прокси-сервер, это говорит Let's Encrypt, что вы действительно управляете доменом, и это позволит вам выдать сертификат для это.

Для автоматического выпуска и обновления сертификатов я бы рекомендовал использовать Caddyserver, greenlock.js, OpenResty (Nginx).

tl;dr о том, что здесь происходит;Caddyserver прослушивает 443 и 80, автоматически получает запросы, выдает и обновляет сертификаты, проксирует трафик на ваш сервер.

Как справиться с этим на моем бэкэнде

Ваш прокси завершает работу TLS и проксирует запросы на ваш сервер. Однако ваш сервер не знает, кто является первоначальным клиентом, стоящим за запросом. Вот почему вам нужно сообщить своему прокси-серверу, чтобы он включал дополнительные заголовки в проксируемые запросы для идентификации клиента. Просто добавьX-Serve-For: app.customer.comилиX-Serve-For: customer2.comили что тамHostзаголовок исходного запроса.

Теперь, когда вы получаете проксированный запрос на серверной части, вы можете прочитать этот настраиваемый заголовок и узнать, кто является клиентом, стоящим за запросом. Вы можете реализовать свою логику на основе этого, показать данные, принадлежащие этому клиенту и т. д.

Более

Поместите балансировщик нагрузки перед парком прокси-серверов для повышения доступности. Вам также придется использовать распределенное хранилище для сертификатов и вызовов Let's Encrypt. Используйте AWS ECS или EBS для автоматического восстановления в случае сбоя, в противном случае вы можете просыпаться среди ночи, перезапуская машины или прокси-сервер вручную.

Кроме того, в последнее время появилось несколько таких сервисов , которые позволяют вам добавлять пользовательские домены в ваше приложение, не запуская инфраструктуру самостоятельно.

Если вам нужна дополнительная информация, вы можете написать мне в Твиттере @dragocrnjac.

Другие вопросы по тегам