WebSocket PHP SSL (Поддержка HTTPS)

Настал 2020-й год и я перевел свой сайт на https и на странице одной из самых популярных записей в блоге перестал работать пример анонимного чата на технологии WebSocket. К моему удивлению, задача перехода на https весьма ощутимо затронула WebSocket: от небольших правок скрипта JavaScript, до значительной доработки PHP кода, в том числе и с погружением в некоторые детали стандарта RFC-6455.

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

Для новичков попавших на эту страницу миновав изучение базовых принципов работы технологии WebScoket настоятельно рекомендую ознакомиться со статьями в моём блоге на тему WebScoket начиная с простой веб-сокет на PHP или веб-сокеты с абсолютного 0.

История массового перехода на https

С развитием сети Интернет со временем компании стали задумываться о безопасности данных пользователей передаваемых по сети. Времена HTTP/0.9 и HTTP/1.0 когда данные передавались в незашифрованном виде давно прошли. В середине нулевых с появлением большого количества веб-сайтов требующих авторизации и содержащих большое количество данных пользователей начало набирать популярность расширение протокола HTTP HTTPS (HTTP Sercure), которое обеспечивало ассиметричное шифрование тела HTTP запроса передаваемого по сети. А в середине десятых, в эру Facebook, Google и регуляторов Интернета из Евросоюза, запрос общества на полную защищенность передаваемых данных в сети Интернет сформировался в требование поддержки HTTPS для большинства сайтов. И это требование не то, что можно игнорировать – ведь сайты без поддержки HTTPS стали понижаться в поисковой выдаче, браузеры стали предупреждать о нежелательности посещения сайтов без поддержки HTTPS, а также отключать возможность смешанного использования HTTP и HTTPS в рамках одного веб-сайта. Нужно понимать, что это не прихоть, а к такому стремлению использовать везде HTTPS подтолкнуло развитие технологий анализа трафика (снифферов) позволяющих перехватывать все данные пользователей вплоть до паролей передаваемых по незашифрованному HTTP протоколу.

В каких случаях нужна поддержка веб-сокетами https?

Очевидно, что во всех приложениях в которых передаются мало-мальски чувствительные данные или пароли настоятельно рекомендуется использовать secure версию протокола веб-сокетов wss://. Казалось бы, как это может относиться к скрипту моего чата, который не передаёт никаких конфиденциальных данных? А всё дело в том, что современные браузеры открывая страницу по HTTPS автоматически запрещают использовать внутри неё незащищенное соединение по протоколу WS. Поэтому задача реализации и поддержки шифрованного протокола веб-сокетов WSS становится как никогда актуальной для всех сайтовладельцев использующих протокол HTTPS.

Переход на wss

В качестве примера я обновлю версию своего анонимного чата на веб-сокетах выпустив новую версию wss server admin panel v.0.5.1 & chat v.0.2..

На самом деле все модификации я делал последовательно и вы можете проделать их самостоятельно в своих работающих проектах:

  1. Первое с чего я начал, получив уведомление о невозможности подключиться в браузере на странице чата – изменил протокол в строке адреса подключения к серверу с ws:// на wss://. На этом все изменения в клиентском JavaScript.
  2. На сервере это выглядело несколько сложнее. Существует два способа решения задачи:
    1. Настройка SSL-шифрования через проксирование трафика с порта на веб-сервере. Этот вариант отлично подходит, если нужно осуществить миграцию с WS на WSS в большом количестве сложных скриптов, так как абсолютно не требует изменений в php скриптах. Однако этот метод требует понимания настроек веб-сервера и соответствующих прав. К сожалению, для запуска на моём виртуальном хостинге он не подошел, по причине того, что у меня оказалось недостаточно прав для использования VirtualHost в файле .htaccess. При попытке включения в файл .htaccess я получил ошибку в error log:
      [Sun Jul 05 19:47:08.011946 2020] [core:alert] [pid 16242] [client 31.173.84.75:35056] 
      /var/www/petukhovsky/data/www/petukhovsky.com/.htaccess: <VirtualHost not allowed here, referer: https://petukhovsky.com/index.php
      	

      Полностью конфигурация выглядит примерно так, и может отличаться в зависимости от веб-сервера и его настроек. Я написал слово примерно, поскольку мне не удалось полноценно протестировать этот способ.

      <VirtualHost *:8889>
      # Common SSL Config
      ServerName my-site.test
      SSLEngine on
      SSLCertificateFile "/usr/local/etc/httpd/server.crt"
      SSLCertificateKeyFile "/usr/local/etc/httpd/server.key"
      DocumentRoot "/var/www/my/site"
      <Directory  "/var/www/my/site">
        Options +Indexes +FollowSymLinks +MultiViews
        AllowOverride All
        Require local
      </Directory>
      # Websocket proxy
      # wss redirects to working ws protocol
      ProxyPass /wss ws://127.0.0.1:8889 retry=0 keepalive=On 
      ProxyPassReverse /wss ws://127.0.0.1:8889 retry=0 
      </VirtualHost>
      	
    2. SSL-шифрование в PHP. Обычно в качестве сертификатов в PHP используется *.pem файл содержащий SSL сертификат из двух файлов *.crt и *.key. Формат содержимого *.pem файла приведен ниже. Cамо-собой, вместо многоточия должно быть тело ключа.
      -----BEGIN CERTIFICATE-----
      MIIFVjCCBD6gAwIBAgISBCQb....5V+jN81Y=
      -----END CERTIFICATE-----
      -----BEGIN RSA PRIVATE KEY-----
      MIIEpQIBAAKCAQEAyslsWOVc....46wjM2GI=
      -----END RSA PRIVATE KEY-----
      	

      Сервер WSS поднялся и прекрасно заработал на базе моего SSL сертификата используемого в шифровании данных передаваемых по HTTPS на мой домен. Но для меня это решение казалось не очень удачным, потому как не хотелось хранить в директории веб-сайта *.crt и *.key файлы содержащие ключи HTTPS домена, ко всему также не хотелось обновлять их вручную каждый раз при обновлении сертификата домена. Но, к сожалению, использовать их из директории сертификатов тоже оказалось проблематичным из-за ограничения доступа накладываемых на уровне файловой системы. Не помогло и точечное присвоение прав, по причине того что пользовательская директория с содержимым веб-сайта и скриптов PHP оказалась сильно изолированной от директории хранения файлов сертификатов и для доступа к ней пришлось бы давать PHP скриптам доступ ко многим критическим каталогам сервера.

      В итоге я попробовал генерировать самоподписываемый сертификат и сохранять его в *.pem файле в моменте запуска WSS, но, как выяснилось, некоторые браузеры запрещают устанавливать WSS соединение с серверами использующими самоподписанные сертификаты и передо мной встала задача выбора наименее плохого из предложенных выше способов.

      Upd 2021.04.13: Утомившись ручным копированием *.crt и *.key, я написал простой скрипт копирующий данные сертификатов в директорию проекта.

      #!/bin/bash
      cp -f /var/www/httpd-cert/petukhovsky/petukhovsky.com_le2.crt /var/www/petukhovsky/data/www/petukhovsky.com/f/simple-web-socket-on-php-chat/chat/pem/petukhovsky.com_le2.crt
      chown petukhovsky /var/www/petukhovsky/data/www/petukhovsky.com/f/simple-web-socket-on-php-chat/chat/pem/petukhovsky.com_le2.crt
      chgrp petukhovsky /var/www/petukhovsky/data/www/petukhovsky.com/f/simple-web-socket-on-php-chat/chat/pem/petukhovsky.com_le2.crt
      chmod 644 /var/www/petukhovsky/data/www/petukhovsky.com/f/simple-web-socket-on-php-chat/chat/pem/petukhovsky.com_le2.crt
      
      cp -f /var/www/httpd-cert/petukhovsky/petukhovsky.com_le2.key /var/www/petukhovsky/data/www/petukhovsky.com/f/simple-web-socket-on-php-chat/chat/pem/petukhovsky.com_le2.key
      chown petukhovsky /var/www/petukhovsky/data/www/petukhovsky.com/f/simple-web-socket-on-php-chat/chat/pem/petukhovsky.com_le2.key
      chgrp petukhovsky /var/www/petukhovsky/data/www/petukhovsky.com/f/simple-web-socket-on-php-chat/chat/pem/petukhovsky.com_le2.key
      chmod 644 /var/www/petukhovsky/data/www/petukhovsky.com/f/simple-web-socket-on-php-chat/chat/pem/petukhovsky.com_le2.key 
      	

      Поместил этот скрипт в крон через 5 минут после проверки обновления letsencrypt выпускающего сертификаты для сайта. Добавление в крон можно сделать командой crontab -e выполненной из под root. Обратите внимание что может быть вызван редактор VI, который не всегда привычен пользователю графических оболочек.
      cron

    Я остановился на втором способе как наиболее подходящем для себя.

По итогам, архив wss server admin panel v.0.5.1 & chat v.0.2. содержит обновленную версию чата на веб-сокетах, способную работать с протоколами ws и wss, соответствующие установки вынесены в конфиг, также добавлена инструкция по установке и настройке в файле !manual.txt.

2020.06.07: Что нового в wss server admin panel v.0.5.0

  • Добавлена возможность использования WSS (на сайтах использующих HTTPS);
  • В панели администратора wss server admin panel добавлена функция очистки лог-файлов;
  • Их архива исключен эхо-клиент;
  • Добавлен файл инструкций по настройке и запуску скрипта !manual.txt

2021.04.13: Что нового в wss server admin panel v.0.5.1

  • Добавлен скрипт для автоматического обновления сертификата;
  • Добавлены инструкции по настройке скрипта обновления сертификата

Мои статьи про PHP демонов и веб-сокеты