# Про HTTPS { #about-https }

Легко припустити, що HTTPS - це щось, що просто «увімкнено» або ні.

Але все значно складніше.

/// tip | Порада

Якщо ви поспішаєте або це неважливо для вас, переходьте до наступних розділів для покрокових інструкцій із налаштування всього різними техніками.

///

Щоб **вивчити основи HTTPS** з точки зору споживача, перегляньте [https://howhttps.works/](https://howhttps.works/).

Тепер, з **точки зору розробника**, ось кілька речей, які варто пам'ятати, розмірковуючи про HTTPS:

* Для HTTPS **сервер** має **мати «сертифікати»**, видані **третьою стороною**.
    * Насправді ці сертифікати **«отримуються»** у третьої сторони, а не **«генеруються»**.
* Сертифікати мають **строк дії**.
    * Їхній строк дії **спливає**.
    * І тоді їх потрібно **поновити**, **знову отримавши** у третьої сторони.
* Шифрування з'єднання відбувається на **рівні TCP**.
    * Це один шар **нижче від HTTP**.
    * Тож **обробка сертифіката та шифрування** виконується **до HTTP**.
* **TCP не знає про «домени»**. Лише про IP-адреси.
    * Інформація про **конкретний домен**, який запитується, міститься в **даних HTTP**.
* **Сертифікати HTTPS** «засвідчують» **певний домен**, але протокол і шифрування працюють на рівні TCP, **до того як відомо**, з яким доменом маємо справу.
* **Типово**, це означало б, що на одну IP-адресу можна мати **лише один сертифікат HTTPS**.
    * Неважливо, наскільки великий ваш сервер або наскільки малий кожен застосунок на ньому.
    * Однак для цього є **рішення**.
* Є **розширення** протоколу **TLS** (який обробляє шифрування на рівні TCP, до HTTP), що називається **[<abbr title="Server Name Indication - Ідентифікація імені сервера">SNI</abbr>](https://en.wikipedia.org/wiki/Server_Name_Indication)**.
    * Це розширення SNI дозволяє одному серверу (з **однією IP-адресою**) мати **кілька сертифікатів HTTPS** і обслуговувати **кілька доменів/застосунків через HTTPS**.
    * Щоб це працювало, один-єдиний компонент (програма), що працює на сервері та слухає **публічну IP-адресу**, має мати **всі сертифікати HTTPS** на сервері.
* **Після** отримання захищеного з'єднання протокол обміну **залишається HTTP**.
    * Вміст **зашифровано**, хоча він надсилається з використанням **протоколу HTTP**.

Поширена практика мати **одну програму/HTTP-сервер**, що працює на сервері (машині, хості тощо) і **керує всіма частинами HTTPS**: приймає **зашифровані HTTPS-запити**, надсилає **розшифровані HTTP-запити** до фактичного HTTP-застосунку, що працює на тому ж сервері (у нашому випадку застосунок **FastAPI**), отримує **HTTP-відповідь** від застосунку, **шифрує її** за допомогою відповідного **сертифіката HTTPS** і надсилає її назад клієнту через **HTTPS**. Такий сервер часто називають **[TLS Termination Proxy](https://en.wikipedia.org/wiki/TLS_termination_proxy)**.

Деякі варіанти, які ви можете використати як TLS Termination Proxy:

* Traefik (також може виконувати поновлення сертифікатів)
* Caddy (також може виконувати поновлення сертифікатів)
* Nginx
* HAProxy

## Let's Encrypt { #lets-encrypt }

До Let's Encrypt ці **сертифікати HTTPS** продавалися довіреними третіми сторонами.

Процес отримання одного з таких сертифікатів був громіздким, вимагав чимало паперової роботи, а самі сертифікати були доволі дорогими.

Але потім з'явився проєкт **[Let's Encrypt](https://letsencrypt.org/)**.

Це проєкт Linux Foundation. Він надає **сертифікати HTTPS безкоштовно**, в автоматизований спосіб. Ці сертифікати використовують усі стандартні криптографічні механізми безпеки і є короткостроковими (близько 3 місяців), тож **безпека насправді краща** завдяки зменшеній тривалості життя.

Домени безпечно перевіряються, а сертифікати генеруються автоматично. Це також дозволяє автоматизувати поновлення цих сертифікатів.

Ідея полягає в автоматизації отримання та поновлення цих сертифікатів, щоб ви могли мати **безпечний HTTPS, безкоштовно і назавжди**.

## HTTPS для розробників { #https-for-developers }

Ось приклад того, як може виглядати HTTPS API, крок за кроком, зосереджуючись переважно на ідеях, важливих для розробників.

### Доменне ім'я { #domain-name }

Ймовірно, все почнеться з того, що ви **придбаєте** якесь **доменне ім'я**. Потім ви налаштуєте його на сервері DNS (можливо, у вашого ж хмарного провайдера).

Ви, скоріш за все, отримаєте хмарний сервер (віртуальну машину) або щось подібне, і він матиме <dfn title="З часом не змінюється. Не динамічний.">фіксовану</dfn> **публічну IP-адресу**.

На сервері(ах) DNS ви налаштуєте запис («`A record`»), щоб спрямувати **ваш домен** на публічну **IP-адресу вашого сервера**.

Ймовірно, ви зробите це лише один раз, уперше, коли все налаштовуватимете.

/// tip | Порада

Частина про доменне ім'я значно раніше за HTTPS, але оскільки все залежить від домену та IP-адреси, варто згадати це тут.

///

### DNS { #dns }

Тепер зосередьмося на всіх власне частинах HTTPS.

Спочатку браузер звернеться до **DNS-серверів**, щоб дізнатися, яка **IP-адреса для домену**, у цьому випадку `someapp.example.com`.

Сервери DNS повідомлять браузеру використати конкретну **IP-адресу**. Це буде публічна IP-адреса, яку використовує ваш сервер і яку ви налаштували на серверах DNS.

<img src="/img/deployment/https/https01.drawio.svg">

### Початок TLS рукостискання { #tls-handshake-start }

Потім браузер зв'яжеться з цією IP-адресою на **порту 443** (порт HTTPS).

Перша частина комунікації - це просто встановлення з'єднання між клієнтом і сервером та узгодження криптографічних ключів тощо.

<img src="/img/deployment/https/https02.drawio.svg">

Ця взаємодія між клієнтом і сервером для встановлення з'єднання TLS називається **TLS рукостисканням**.

### TLS із розширенням SNI { #tls-with-sni-extension }

**Лише один процес** на сервері може слухати конкретний **порт** на конкретній **IP-адресі**. Інші процеси можуть слухати інші порти на тій самій IP-адресі, але лише один для кожної комбінації IP-адреси та порту.

TLS (HTTPS) за замовчуванням використовує конкретний порт `443`. Отже, це порт, який нам потрібен.

Оскільки лише один процес може слухати цей порт, процесом, що робитиме це, буде **TLS Termination Proxy**.

TLS Termination Proxy матиме доступ до одного або кількох **сертифікатів TLS** (сертифікатів HTTPS).

Використовуючи **розширення SNI**, обговорене вище, TLS Termination Proxy перевірить, який із наявних сертифікатів TLS (HTTPS) слід використати для цього з'єднання, обравши той, що відповідає домену, очікуваному клієнтом.

У цьому випадку він використає сертифікат для `someapp.example.com`.

<img src="/img/deployment/https/https03.drawio.svg">

Клієнт уже **довіряє** сутності, яка видала цей сертифікат TLS (у цьому випадку Let's Encrypt, але про це згодом), тож він може **перевірити**, що сертифікат дійсний.

Потім, використовуючи сертифікат, клієнт і TLS Termination Proxy **вирішать, як шифрувати** решту **TCP-комунікації**. На цьому частина **TLS рукостискання** завершується.

Після цього клієнт і сервер мають **зашифроване TCP-з'єднання** - саме це надає TLS. І тоді вони можуть використати це з'єднання, щоб почати власне **HTTP-комунікацію**.

І це і є **HTTPS**: це звичайний **HTTP** усередині **захищеного TLS-з'єднання** замість чистого (незашифрованого) TCP-з'єднання.

/// tip | Порада

Зверніть увагу, що шифрування комунікації відбувається на **рівні TCP**, а не на рівні HTTP.

///

### HTTPS-запит { #https-request }

Тепер, коли клієнт і сервер (конкретно браузер і TLS Termination Proxy) мають **зашифроване TCP-з'єднання**, вони можуть почати **HTTP-комунікацію**.

Отже, клієнт надсилає **HTTPS-запит**. Це просто HTTP-запит через зашифроване TLS-з'єднання.

<img src="/img/deployment/https/https04.drawio.svg">

### Розшифрування запиту { #decrypt-the-request }

TLS Termination Proxy використає узгоджене шифрування, щоб **розшифрувати запит**, і передасть **звичайний (розшифрований) HTTP-запит** процесу, що запускає застосунок (наприклад, процесу з Uvicorn, який запускає застосунок FastAPI).

<img src="/img/deployment/https/https05.drawio.svg">

### HTTP-відповідь { #http-response }

Застосунок обробить запит і надішле **звичайну (незашифровану) HTTP-відповідь** TLS Termination Proxy.

<img src="/img/deployment/https/https06.drawio.svg">

### HTTPS-відповідь { #https-response }

Потім TLS Termination Proxy **зашифрує відповідь**, використовуючи попередньо узгоджену криптографію (що почалася із сертифіката для `someapp.example.com`), і надішле її назад у браузер.

Далі браузер перевірить, що відповідь дійсна й зашифрована правильним криптографічним ключем тощо. Потім він **розшифрує відповідь** і обробить її.

<img src="/img/deployment/https/https07.drawio.svg">

Клієнт (браузер) знатиме, що відповідь надходить від правильного сервера, тому що використовується узгоджена раніше криптографія з використанням **сертифіката HTTPS**.

### Кілька застосунків { #multiple-applications }

На тому самому сервері (або серверах) може бути **кілька застосунків**, наприклад інші програми API або база даних.

Лише один процес може обробляти конкретну IP-адресу і порт (TLS Termination Proxy у нашому прикладі), але інші застосунки/процеси також можуть працювати на сервері(ах), доки вони не намагаються використати ту саму **комбінацію публічної IP-адреси й порту**.

<img src="/img/deployment/https/https08.drawio.svg">

Таким чином, TLS Termination Proxy може обробляти HTTPS і сертифікати для **кількох доменів**, для кількох застосунків, а потім передавати запити до відповідного застосунку в кожному випадку.

### Поновлення сертифіката { #certificate-renewal }

У певний момент у майбутньому строк дії кожного сертифіката **спливе** (приблизно через 3 місяці після його отримання).

Потім інша програма (в деяких випадках це інша програма, а в деяких - той самий TLS Termination Proxy) зв'яжеться з Let's Encrypt і поновить сертифікат(и).

<img src="/img/deployment/https/https.drawio.svg">

**Сертифікати TLS** пов'язані **з доменним іменем**, а не з IP-адресою.

Тому, щоб поновити сертифікати, програма поновлення має **довести** авторитету (Let's Encrypt), що вона справді **«володіє» і контролює цей домен**.

Щоб зробити це й задовольнити різні потреби застосунків, є кілька способів. Деякі популярні:

* **Змінити деякі записи DNS**.
    * Для цього програма поновлення має підтримувати API провайдера DNS, тож залежно від того, якого провайдера DNS ви використовуєте, це може бути або не бути варіантом.
* **Запуститися як сервер** (принаймні під час процесу отримання сертифіката) на публічній IP-адресі, пов'язаній із доменом.
    * Як ми казали вище, лише один процес може слухати конкретну IP-адресу та порт.
    * Це одна з причин, чому дуже зручно, коли той самий TLS Termination Proxy також займається процесом поновлення сертифікатів.
    * Інакше вам, можливо, доведеться на мить зупинити TLS Termination Proxy, запустити програму поновлення, щоб отримати сертифікати, потім налаштувати їх у TLS Termination Proxy і перезапустити TLS Termination Proxy. Це неідеально, оскільки ваші застосунки будуть недоступні під час вимкнення TLS Termination Proxy.

Увесь цей процес поновлення, паралельно з обслуговуванням застосунку, - одна з головних причин, чому ви можете захотіти мати **окрему систему для обробки HTTPS** за допомогою TLS Termination Proxy замість того, щоб просто використовувати сертифікати TLS безпосередньо з сервером застосунку (наприклад, Uvicorn).

## Направлені заголовки проксі { #proxy-forwarded-headers }

Коли ви використовуєте проксі для обробки HTTPS, ваш **сервер застосунку** (наприклад, Uvicorn через FastAPI CLI) нічого не знає про процес HTTPS, він спілкується звичайним HTTP із **TLS Termination Proxy**.

Цей **проксі** зазвичай динамічно встановлює деякі HTTP-заголовки перед передачею запиту **серверу застосунку**, щоб дати йому знати, що запит **направляється** проксі.

/// note | Технічні деталі

Заголовки проксі:

* [X-Forwarded-For](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-For)
* [X-Forwarded-Proto](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-Proto)
* [X-Forwarded-Host](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-Host)

///

Втім, оскільки **сервер застосунку** не знає, що він стоїть за довіреним **проксі**, за замовчуванням він не довірятиме цим заголовкам.

Але ви можете налаштувати **сервер застосунку**, щоб довіряти направленим заголовкам, надісланим **проксі**. Якщо ви використовуєте FastAPI CLI, ви можете скористатися курсивною *опцією CLI* `--forwarded-allow-ips`, щоб повідомити, з яких IP-адрес слід довіряти цим направленим заголовкам.

Наприклад, якщо **сервер застосунку** отримує комунікацію лише від довіреного **проксі**, ви можете встановити `--forwarded-allow-ips="*"`, щоб довіряти всім вхідним IP-адресам, оскільки він отримуватиме запити лише з тієї IP-адреси, яку використовує **проксі**.

Так застосунок зможе знати свою публічну URL-адресу, чи використовує він HTTPS, домен тощо.

Це буде корисно, наприклад, для коректної обробки перенаправлень.

/// tip | Порада

Ви можете дізнатися більше про це в документації [За проксі - Увімкнути направлені заголовки проксі](../advanced/behind-a-proxy.md#enable-proxy-forwarded-headers)

///

## Підсумок { #recap }

Наявність **HTTPS** дуже важлива і в більшості випадків **критична**. Більшість зусиль, які вам як розробнику доведеться докласти навколо HTTPS, полягають лише в **розумінні цих концепцій** і того, як вони працюють.

Але як тільки ви знаєте базову інформацію про **HTTPS для розробників**, ви можете легко комбінувати й налаштовувати різні інструменти, щоб керувати всім просто.

У деяких наступних розділах я покажу кілька конкретних прикладів налаштування **HTTPS** для застосунків **FastAPI**. 🔒
