Как включить несколько HTTPS-сайтов на одном IP в Debian Etch с помощью расширений TLS
Введение
Данный how‑to ориентирован на Debian Etch, но идея применима и к другим дистрибутивам: для поддержки нескольких HTTPS-хостов на одном IP требуется поддержка расширений TLS (SNI) в OpenSSL и модуле Apache. Для этого нужно пересобрать OpenSSL с конфигурацией enable-tlsext и применить патч к apache2. Если браузер клиента не поддерживает SNI, он увидит только дефолтный SSL‑виртуальный хост.
Определение: SNI (Server Name Indication) — расширение TLS, позволяющее клиенту указывать имя хоста в ClientHello для выбора соответствующего сертификата на сервере.
Важно: предполагается, что вы уже выполнили perfect_setup_debian_etch.
1. Подготовка pbuilder
Установите pbuilder и вспомогательные пакеты:
apt-get update
apt-get install pbuilder fakeroot sudo devscripts apt-utilsОтредактируйте /etc/pbuilder/pbuilderrc и укажите ближайший к вам зеркальный сайт, например:
MIRRORSITE=http://http.us.debian.org/debianУстановите DISTRIBUTION в etch (в том же файле, строка ~20):
DISTRIBUTION=etchСоздайте образ pbuilder:
pbuilder create --distribution etch2. Патч и перекомпиляция apache2
Создайте директорию для исходников и загрузите пакет apache2:
mkdir /usr/src/apache2
cd /usr/src/apache2
apt-get source apache2Сохраните патч в /usr/src/apache2/apache2-2.2.3/httpd-2.2.3-sni.patch. Для этого можно выполнить:
cat > /usr/src/apache2/apache2-2.2.3/httpd-2.2.3-sni.patch <<'PATCH'
httpd-2.2.3-sni.patch - server name indication support for Apache 2.2
(see RFC 4366, "Transport Layer Security (TLS) Extensions")
based on a patch from the EdelKey project
(http://www.edelweb.fr/EdelKey/files/apache-2.2.0+0.9.9+servername.patch)
Needs openssl-SNAP-20060330 / openssl-0.9.8-stable-SNAP-20070813 or later
to work properly (ftp://ftp.openssl.org/snapshot/). The 0.9.8 branch
must be configured explicitly for TLS extension support at compile time
("./config enable-tlsext").
Index: httpd-2.2.x/modules/ssl/ssl_engine_init.c
===================================================================
--- httpd-2.2.x/modules/ssl/ssl_engine_init.c (revision 423224)
+++ httpd-2.2.x/modules/ssl/ssl_engine_init.c (working copy)
@@ -156,6 +156,87 @@
return OK;
}
+#ifndef OPENSSL_NO_TLSEXT
+static int set_ssl_vhost(void *servername, conn_rec *c, server_rec *s)
+{
+ SSLSrvConfigRec *sc;
+ SSL *ssl;
+ BOOL found = FALSE;
+ apr_array_header_t *names;
+ int i;
+
+ /* check ServerName */
+ if (!strcasecmp(servername, s->server_hostname))
+ found = TRUE;
+
+ /* if not matched yet, check ServerAlias entries */
+ if (!found) {
+ names = s->names;
+ if (names) {
+ char name = (char ) names->elts;
+ for (i = 0; i < names->nelts; ++i) {
+ if(!name[i]) continue;
+ if (!strcasecmp(servername, name[i])) {
+ found = TRUE;
+ break;
+ }
+ }
+ }
+ }
+
+ /* if still no match, check ServerAlias entries with wildcards */
+ if (!found) {
+ names = s->wild_names;
+ if (names) {
+ char name = (char ) names->elts;
+ for (i = 0; i < names->nelts; ++i) {
+ if(!name[i]) continue;
+ if (!ap_strcasecmp_match(servername, name[i])) {
+ found = TRUE;
+ break;
+ }
+ }
+ }
+ }
+
+ /* set SSL_CTX (if matched) */
+ if (found) {
+ if ((ssl = ((SSLConnRec *)myConnConfig(c))->ssl) == NULL)
+ return 0;
+ if (!(sc = mySrvConfig(s)))
+ return 0;
+ SSL_set_SSL_CTX(ssl,sc->server->ssl_ctx);
+ return 1;
+ }
+ return 0;
+}
+
+int ssl_set_vhost_ctx(SSL *ssl, const char *servername)
+{
+ conn_rec *c;
+
+ if (servername == NULL) /* should not occur. */
+ return 0;
+
+ SSL_set_SSL_CTX(ssl,NULL);
+
+ if (!(c = (conn_rec *)SSL_get_app_data(ssl)))
+ return 0;
+
+ return ap_vhost_iterate_given_conn(c,set_ssl_vhost,servername);
+}
+
+int ssl_servername_cb(SSL *s, int *al, modssl_ctx_t *mctx)
+{
+ const char *servername = SSL_get_servername(s,TLSEXT_NAMETYPE_host_name);
+
+ if (servername) {
+ return ssl_set_vhost_ctx(s,servername)?SSL_TLSEXT_ERR_OK:SSL_TLSEXT_ERR_ALERT_FATAL;
+ }
+ return SSL_TLSEXT_ERR_NOACK;
+}
+#endif
+
/*
* Per-module initialization
*/
@@ -376,6 +457,29 @@
}
}
+static void ssl_init_server_extensions(server_rec *s,
+ apr_pool_t *p,
+ apr_pool_t *ptemp,
+ modssl_ctx_t *mctx)
+{
+ /*
+ * Configure TLS extensions support
+ */
+
+#ifndef OPENSSL_NO_TLSEXT
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+ "Configuring TLS extensions facility");
+
+ if (!SSL_CTX_set_tlsext_servername_callback(mctx->ssl_ctx, ssl_servername_cb) ||
+ !SSL_CTX_set_tlsext_servername_arg(mctx->ssl_ctx, mctx)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
+ "Unable to initialize servername callback, bad openssl version.");
+ ssl_log_ssl_error(APLOG_MARK, APLOG_ERR, s);
+ ssl_die();
+ }
+#endif
+}
+
static void ssl_init_ctx_protocol(server_rec *s,
apr_pool_t *p,
apr_pool_t *ptemp,
@@ -709,6 +810,8 @@
/* XXX: proxy support? */
ssl_init_ctx_cert_chain(s, p, ptemp, mctx);
}
+
+ ssl_init_server_extensions(s, p, ptemp, mctx);
}
static int ssl_server_import_cert(server_rec *s,
@@ -1035,6 +1138,7 @@
}
}
+#ifdef OPENSSL_NO_TLSEXT
/*
* Give out warnings when more than one SSL-aware virtual server uses the
* same IP:port. This doesn't work because mod_ssl then will always use
@@ -1079,6 +1183,7 @@
"Init: You should not use name-based "
"virtual hosts in conjunction with SSL!!");
}
+#endif
}
#ifdef SSLC_VERSION_NUMBER
Index: httpd-2.2.x/modules/ssl/ssl_engine_kernel.c
===================================================================
--- httpd-2.2.x/modules/ssl/ssl_engine_kernel.c (revision 423224)
+++ httpd-2.2.x/modules/ssl/ssl_engine_kernel.c (working copy)
@@ -231,7 +231,20 @@
* the currently active one.
*/
+#ifndef OPENSSL_NO_TLSEXT
/*
+ * We will switch to another virtualhost and to its ssl_ctx
+ * if changed, we will force a renegotiation.
+ */
+ if (r->hostname && !SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name)) {
+ SSL_CTX *ctx = SSL_get_SSL_CTX(ssl);
+ if (ssl_set_vhost_ctx(ssl,(char *)r->hostname) &&
+ ctx != SSL_get_SSL_CTX(ssl))
+ renegotiate = TRUE;
+ }
+#endif
+
+ /*
* Override of SSLCipherSuite
*
* We provide two options here:
@@ -997,6 +1010,9 @@
SSLDirConfigRec *dc = myDirConfig(r);
apr_table_t *env = r->subprocess_env;
char *var, *val = "";
+#ifndef OPENSSL_NO_TLSEXT
+ const char* servername;
+#endif
STACK_OF(X509) *peer_certs;
SSL *ssl;
int i;
@@ -1018,6 +1034,12 @@
/* the always present HTTPS (=HTTP over SSL) flag! */
apr_table_setn(env, "HTTPS", "on");
+#ifndef OPENSSL_NO_TLSEXT
+ /* add content of SNI TLS extension (if supplied with ClientHello) */
+ if (servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name))
+ apr_table_set(env, "TLS_SNI", servername);
+#endif
+
/* standard SSL environment variables */
if (dc->nOptions & SSL_OPT_STDENVVARS) {
for (i = 0; ssl_hook_Fixup_vars[i]; i++) {
Index: httpd-2.2.x/modules/ssl/ssl_toolkit_compat.h
===================================================================
--- httpd-2.2.x/modules/ssl/ssl_toolkit_compat.h (revision 423224)
+++ httpd-2.2.x/modules/ssl/ssl_toolkit_compat.h (working copy)
@@ -258,6 +258,12 @@
#define SSL_SESS_CACHE_NO_INTERNAL SSL_SESS_CACHE_NO_INTERNAL_LOOKUP
#endif
+#ifndef OPENSSL_NO_TLSEXT
+#ifndef SSL_CTRL_SET_TLSEXT_HOSTNAME
+#define OPENSSL_NO_TLSEXT
+#endif
+#endif
+
#endif /* SSL_TOOLKIT_COMPAT_H */
/** @} */
PATCHПримените патч:
cd apache2-2.2.3/
patch -p1 < httpd-2.2.3-sni.patchОбновите версию пакета перед сборкой:
cd debian/
dch -iИзмените заголовок в changelog (строки 1–5) на что-то подобное:
apache2 (2.2.3-4a+etch) stable; urgency=low
* Enabled TLS Extensions
-- John Doe Tue, 5 Nov 2007 06:29:54 -0600 Подготовьте исходный tar и соберите пакет через pbuilder:
cd ../..
dpkg-source -b apache2-2.2.3/ apache2_2.2.3.orig.tar.gz
pbuilder build apache2_2.2.3-4a+etch.dsc3. Сборка OpenSSL 0.9.8g с enable-tlsext
Добавьте source-репозиторий sid в /etc/apt/sources.list, чтобы взять исходники:
deb-src http://ftp.debian.org/debian/ sid mainОбновите индекс и скачайте исходники OpenSSL:
apt-get update
mkdir /usr/src/openssl
cd /usr/src/openssl/
apt-get source opensslОтредактируйте /usr/src/openssl/openssl-0.9.8g/debian/rules и добавьте enable-tlsext в CONFARGS, например:
CONFARGS = --prefix=/usr --openssldir=/usr/lib/ssl no-idea no-mdc2 no-rc5 zlib enable-tlsextОбновите changelog и измените версию в записях (dch -i), например:
openssl (0.9.8g-1) unstable; urgency=low
* Enabled TLS Extensions
-- John Doe Mon, 5 Nov 2007 22:40:05 -0600 Подготовьте исходный tar и соберите пакет:
cd ../..
dpkg-source -b openssl-0.9.8g/ openssl_0.9.8g.orig.tar.gz
pbuilder build openssl_0.9.8g-1.dsc4. Установка собранных пакетов
Установите пакеты из результата pbuilder:
cd /var/cache/pbuilder/result
dpkg -i apache2_2.2.3-4a+etch_all.deb
dpkg -i apache2.2-common_2.2.3-4a+etch_i386.deb
dpkg -i apache2-mpm-prefork_2.2.3-4a+etch_i386.deb
dpkg -i libssl0.9.8_0.9.8g-1_i386.deb
dpkg -i openssl_0.9.8g-1_i386.debИсправьте зависимости, если потребуется:
apt-get install -f5. Настройка ISPConfig для общей SSL-IP
Отредактируйте файл /home/admispconfig/ispconfig/lib/classes/ispconfig_isp_web.lib.php и найдите блок, который проверяет, существует ли уже SSL-сайт на IP. Закомментируйте соответствующий код (в примере — SQL‑проверка и ветвление), чтобы ISPConfig не запрещал создавать несколько SSL‑сайтов на одном IP.
Пример (закомментируйте эти строки):
//////////////////////////////////////////////////////
// Check ob bereits ein SSL Cert auf der IP Existiert
//////////////////////////////////////////////////////
// $ssl_count = $go_api->db->queryOneRecord("SELECT count(doc_id) as ssl_co
// if($ssl_count["ssl_count"] > 1) {
// // Es existiert bereits ein SSL Web mit dieser IP
// $status = "NOTIFY";
// $errorMessage .= $go_api->lng("error_web_ssl_exist");
// $go_api->db->query("UPDATE isp_isp_web set web_ssl = 0 where doc_id =
// }Создайте дефолтный защищённый сайт, который будет показываться для клиентов без SNI (несовместимых браузеров):
mkdir /var/www/sharedip/ssl
cd /var/www/sharedip/ssl
openssl genrsa -des3 -passout pass:yourpassword -out 192.168.1.2.key2 1024
openssl req -new -passin pass:yourpassword -passout pass:yourpassword -key 192.168.1.2.key2 -out 192.168.1.2.csr -days 365
openssl req -x509 -passin pass:yourpassword -passout pass:yourpassword -key 192.168.1.2.key2 -in 192.168.1.2.csr -out 192.168.1.2.crt -days 365
openssl rsa -passin pass:yourpassword -in 192.168.1.2.key2 -out 192.168.1.2.key
chmod 400 192.168.1.2.keyПодсказка: используйте звёздочку (*) в Common Name, если нужно покрыть несколько поддоменов.
Добавьте в /etc/apache2/apache2.conf блок для дефолтного SSL‑виртуального хоста выше Include /etc/apache2/vhosts/Vhosts_ispconfig.conf:
NameVirtualHost 192.168.1.2:443
ServerName localhost
ServerAdmin root@localhost
DocumentRoot /var/www/sharedip
SSLEngine on
SSLCertificateFile /var/www/sharedip/ssl/192.168.1.2.crt
SSLCertificateKeyFile /var/www/sharedip/ssl/192.168.1.2.key
После этого создавайте несколько сайтов в ISPConfig с включённым SSL. Клиенты с поддержкой RFC 4366 (SNI) увидят каждый свой сайт; остальные — дефолтный.
Проверка браузера на SNI
Чтобы проверить, поддерживает ли ваш браузер SNI, перейдите по адресу:
Если сайт отображается корректно для вашего домена, браузер поддерживает SNI.
Альтернативные подходы и когда это не работает
- Если у вас много сайтов и вы не хотите собирать и поддерживать кастомные пакеты, используйте отдельные IP‑адреса или IPv6 для каждого SSL‑виртуального хоста. Это самый прямой и совместимый способ.
- В старых браузерах и некоторых встроенных клиентах SNI отсутствует — они всегда увидят дефолтный сертификат. Для важной аудитории (например, корпоративные клиенты со старым ПО) используйте отдельный IP.
- Современные дистрибутивы уже включают SNI в стандартных пакетах; обновление системы чаще решает задачу без перекомпиляции.
Чек‑лист администратора
- Создан pbuilder образ для etch
- Загружены исходники apache2 и применён патч SNI
- Изменён debian/changelog и собран пакет apache2 через pbuilder
- Загружены исходники OpenSSL и добавлен enable-tlsext
- Собран и установлен libssl0.9.8 и openssl из собранных пакетов
- Обновлён ISPConfig (закомментирован запрет на общую SSL-IP)
- Создан дефолтный SSL‑виртуальный хост для клиентов без SNI
- Протестировано в браузерах с поддержкой и без поддержки SNI
Критерии приёмки
- Apache и OpenSSL собраны и установлены как .deb пакеты без ошибок.
- Для нескольких SSL‑сайтов на одном IP каждый клиент с SNI получает корректный сертификат.
- Клиенты без SNI получают содержимое дефолтного SSL‑виртуального хоста.
Риски и рекомендации по безопасности
- Пересборка OpenSSL и apache меняет уровень поддержки безопасности — после сборки проверьте конфигурацию шифров и протоколов (SSLCipherSuite, SSLProtocol), чтобы отключить устаревшие алгоритмы.
- Храните приватные ключи с корректными правами (например, 400) и защищайте резервные копии.
Советы по миграции и совместимости
- На новых системах (после Debian Etch) поддержка SNI присутствует в стандартных пакетах — предпочтительнее обновиться.
- Если требуется масштабирование, рассмотрите использование обратного прокси (например, nginx с SNI/stream) или балансировщика, который умеет работать с SSL и SNI.
Полезные ссылки
- http://edseek.com/~jasonb/articles/pbuilder_backports/index.html
- https://dave.sni.velox.ch/
- http://www.edelweb.fr/EdelKey/
Краткое резюме
- SNI позволяет иметь несколько HTTPS‑сайтов на одном IP при условии, что OpenSSL и Apache собраны с поддержкой TLS Extensions.
- В Debian Etch нужно собрать openssl с enable-tlsext и применить патч к apache2, затем установить пакеты и настроить ISPConfig.
- Альтернативы: отдельные IP, обновление дистрибутива или использование прокси/балансировщика.
Важно: если вы не уверены в безопасности перекомпилированных пакетов, тестируйте изменение в изолированной среде перед продакшеном.
Похожие материалы
Несколько аккаунтов Skype: Multi Skype Launcher
Журнал для работы: повысить продуктивность
Персональные звуки уведомлений на Android
Скачивание шоу Hulu для офлайн‑просмотра
Microsoft Start: персонализированная новостная лента