На днях мне пришлось настроить серверную часть для веб-приложения, которое будет обмениваться данными с клиентами по протоколу HTTPS, и мне не удалось найти никаких надежных ресурсов, чтобы сделать это с нуля. Я подумал: «Эй, было бы здорово, если бы где-то была страница, которая бы провела меня через это». Это будет то.

Рабочую версию этой демонстрации можно найти в моем GitHub Repo. Не стесняйтесь разветвлять, клонировать и экспериментировать с ним или даже использовать его в качестве шаблона для собственного приложения!

Если вы читаете это, вы, вероятно, уже имеете представление о том, что такое HTTP и как его использовать. HTTPS — это, по сути, HTTP, но зашифрованный (ура!). HTTPS оборачивает исходный HTTP в SSL, который является одним из наиболее распространенных протоколов для шифрования сетевого трафика. Есть много полезной информации, которую нужно изучить с самими протоколами, но она не очень поможет вам в создании защищенной серверной части узла. Важно знать, что для HTTPS требуется несколько вещей: а именно ключ шифрования и сертификат. В идеале этот сертификат должен быть подписан доверенным лицом, чего мы и добиваемся.

Для начала вам нужно подготовить несколько вещей. Во-первых, в этом пошаговом руководстве предполагается, что у вас есть доступ к оболочке сервера Linux (например, доступный через Digital Ocean, AWS, Linode и многие другие), а также sudo привилегии⁰. Во-вторых, вам нужно убедиться, что порт 443 открыт и доступен на вашем сервере. То, как вы это сделаете, зависит от вашей среды развертывания, но обычно существует некоторый уровень веб-интерфейса, который позволяет вам управлять открытыми портами. Наконец, вам понадобится не эфемерное доменное имя, указывающее на ваш сервер¹. Это означает, что вам понадобится доменное имя, зарегистрированное на постоянной основе на лицо, которое вы знаете. Например, домен по умолчанию, предоставленный AWS, не будет служить этой цели, поскольку центр сертификации не может гарантировать, что этот домен не будет принадлежать кому-то другому в будущем².

Хорошо, теперь, когда мы разобрались с этой настройкой, давайте немного повеселимся в терминале. Мы собираемся использовать центр сертификации под названием Let’s Encrypt, который является бесплатным, простым и совершенно восхитительным³. Let’s Encryptпредлагает чрезвычайно удобный инструмент командной строки под названием certbot, который упрощает получение сертификата и ключей, поэтому мы начнем с установки certbot:

$ sudo add-apt-repository ppa:certbot/certbot
$ sudo apt-get update
$ sudo apt-get install certbot

Эти строки по порядку: добавят пакет certbot к вашим доступным пакетам, обновят ваш менеджер пакетов (apt), чтобы убедиться, что он видит новый, и установит certbot на ваш компьютер. Довольно легко, нет? Теперь давайте получим несколько ключей:

$ sudo certbot certonly
Saving debug log to /var/log/letsencrypt/letsencrypt.log
How would you like to authenticate with the ACME CA?
--------------------------------------------------------------------
1: Place files in webroot directory (webroot)
2: Spin up a temporary webserver (standalone)
--------------------------------------------------------------------
Select the appropriate number [1-2] then [enter] (press 'c' to cancel):

Ввод команды вверху должен дать показанный результат. В противном случае пакет certbot, вероятно, не удалось правильно установить. Вернитесь к предыдущим шагам. Однако, если предположить, что это сработало, у вас есть два варианта. Первый включает в себя много ручной настройки и содержит ряд предположений о вашем существующем веб-сервере. По сути, это раздражает, и я рекомендую избегать этого, если только ваши конкретные потребности не диктуют этого. Если это так, вы, вероятно, уже знаете об этом. Второй вариант — это то, почему мы позаботились о том, чтобы порт 443 был открыт в начале. Это потому, что он автоматически создаст простой веб-сервер, подключится к Let’s Encrypt, получит ваши сертификаты, сохранит их и закроет все сразу. Это замечательно, просто и подходит для большинства приложений. Ввод числа 2 и нажатие Enter должны привести вас к этому биту:

Starting new HTTPS connection (1): acme-v01.api.letsencrypt.org Please enter in your domain name(s) (comma and/or space separated)  (Enter 'c' to cancel):

Вот тут-то и появляется это неэфемерное доменное имя. Эти типы сертификатов предоставляются доменам, а не IP-адресам, приложениям или чему-либо еще. Вы должны предоставить домен (что-то вроде example.com), чтобы Let’s Encrypt мог собрать ваш сертификат и подписать его, что позволит вам быстро создавать безопасные веб-приложения. Когда вы вводите свой домен и нажимаете Enter, он должен выдать вам сообщение о том, что ваши сертификаты и ключи были созданы, а также сообщить вам, где они сохранены. По умолчанию они будут сохранены по путям, которые выглядят следующим образом:

/etc/letsencrypt/live/<<your domain>>/privkey.pem
/etc/letsencrypt/live/<<your domain>>/fullchain.pem

В этом каталоге, который назван в честь вашего домена, есть еще файлы, но это должны быть единственные два, которые вам нужны для запуска серверной части Node/Express. Чтобы запустить это скелетное приложение, необходимо выполнить несколько ключевых шагов. Прежде всего, принесите пакеты express, https и fs, а затем получите экземпляр express⁴. Этот код выглядит примерно так:

const express = require('express');
const fs = require('fs');
const https = require('https');
const app = express();

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

const domainName = ''; // Your Domain
const key = fs.readFileSync('/etc/letsencrypt/live/' + domainName + '/privkey.pem');
const cert = fs.readFileSync('/etc/letsencrypt/live/' + domainName + '/fullchain.pem');

И, наконец, создайте и запустите свой экземпляр сервера, используя эти файлы .pem и только что созданный экземпляр express:

const server = https.createServer({
  key: key,
  cert: cert
}, app);
const port = 443; // 443 is default, but you can use anything
server.listen(port, () => {
  console.log('Listening securely on port ' + port);
});

Это создаст HTTPS-сервер и начнет слушать порт 443. Обратите внимание, что 443 — это порт по умолчанию для HTTPS, поэтому клиент может подключиться только с https://example.com в качестве примера, но любой порт будет работать с чем-то вроде https://example.com:8080. На данный момент у вас есть работающий HTTPS-сервер! Давайте добавим несколько битов, чтобы сделать его немного менее пустым. Для начала мы можем добавить общий ответ GET, чтобы убедиться, что он работает:

app.get('*', (req, res) => {
  res.send('Hello from my secure server!');
});

И в качестве последнего штриха давайте перенаправим пользователей, которые подключаются с порта 80 (порт по умолчанию для обычного HTTP), на наш новый безопасный сервер:

const httpRedirect = express();
httpRedirect.get('*', (req, res) => {
  if (port === 443) {
    res.redirect('https://' + domainName + req.url);
  } else {
    res.redirect('https://' + domainName + ':' + port + req.url);
  }
  console.log('HTTP request made, redirected to HTTPS');
});
httpRedirect.listen(80);

Теперь, если какой-либо клиент подключается, он будет автоматически перенаправлен на HTTPS, если он еще не использует его, и получит приветственное сообщение. Если ваше приложение работает на порту 443, оно будет опущено. В противном случае этот код добавит вам правильный порт. Тем не менее, он по-прежнему будет перенаправлять только клиентов, которые подключаются к вашему домену через HTTP-порт 80 по умолчанию. Это все, что нужно, чтобы зашифровать базовую связь вашего приложения и начать делать Интернет более безопасным!

Сноски:

⁰ Это руководство основано на моей работе на нескольких разных серверах, работающих под управлением Ubuntu 16.04. Это, вероятно, будет работать без изменений для многих других выпусков Ubuntu, а также для различных дистрибутивов Linux, но имейте в виду, что вам, возможно, придется настроить некоторые шаги, если ваша среда сильно отличается от тех, которые я использовал.

¹ Для привязки домена к вашему серверу потребуется изменить настройки DNS у вашего регистратора домена, если только домен не предоставлен вам используемой вами средой. Однако имейте в виду, что во многих средах развертывания используются эфемерные домены, которые не смогут выполнить запрос сертификата. В этом случае зарегистрируйте домен по вашему выбору и используйте его.

² Я не проверял это в очень многих средах развертывания. Я использовал этот процесс на своем личном сервере, к которому у меня есть физический доступ, а также на AWS. Не стесняйтесь поэкспериментировать с этими шагами и оставить комментарий здесь или проблему на GitHub, если у вас есть какая-то информация об этом процессе на Digital Ocean, Linode или что-то еще!

³ Эта сноска будет посвящена Let’s Encrypt. Подробнее о них можно узнать в Википедии или на их сайте. По сути, это организация, которая решила, что людям не нужно платить или нырять в пылающие обручи, чтобы сделать свои приложения безопасными, поэтому они создали систему, позволяющую легко и бесплатно защищать ваши веб-приложения. Довольно здорово, правда?

⁴ В этих фрагментах кода используется синтаксис ES6. Большинство серверов не позволят вам использовать синтаксис ES6 за пределами строгого режима. Для этого просто поместите строку 'use strict' вверху ваших файлов. В репозитории это уже есть.