среда, 16 августа 2017 г.

Как работают веб-приложения


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

Ниже приведена схема, на которой я очень упрощенно описал типичный веб-сервис и его взаимодействие с веб-браузером. Однако, тут показаны все важные компоненты, которые участвуют в процессе.
Далее рассмотрим по шагам что происходит.

Поиск сервера

Процесс начинается, когда пользователь запускает любимый веб-браузер и вбивает в строку адреса текст вида https://www.vk.com/codeatcpp, а затем нажимает Enter. После этого веб-браузер посылает запрос DNS-серверу, чтобы узнать какой IP-адрес соответствует серверу www.vk.com. Тут нужно отметить, что большие сервисы используют не один сервер, чтобы справляться с нагрузкой. В таких случаях одному доменному имени будет соответствовать множество IP-адресов. DNS-клиенты обычно используют первый IP-адрес из ответа DNS-сервера, поэтому простейший способ балансировки нагрузки — выдавать разным клиентам различные ответы. Стандартно это делается по алгоритму Round-robin, что не всегда эффективно, так как не учитывает состояние отдельных серверов (решение этой проблемы — тема отдельной статьи). Вы можете легко проверить данный факт, запустив в командной строке следующую команду:
$ nslookup vk.com

Name: vk.com
Address: 87.240.165.85
Name: vk.com
Address: 87.240.165.86
Name: vk.com
Address: 87.240.165.83
Name: vk.com
Address: 87.240.165.84
Если у вас в системе разрешен протокол IPv6, то в ответе вы увидите еще и IPv6 адреса. Можно попробовать запустить команду несколько раз подряд и самостоятельно убедиться, что порядок адресов в ответе меняется.

Подключение к серверу

Определив IP-адрес сервера, можно попытаться к нему подключиться. Для этого веб-браузер открывает сокет с адресом 87.240.165.85 (первый из списка выше) и портом 443 для HTTPS или 80 для HTTP.

Если используется протокол HTTP, то веб-браузер просто посылает примерно такой текст в канал:
GET  HTTP/1.1
Host: vk.com
Если вы не используете прокси сервер, то можете попробовать самостоятельно послать такой текст, используя утилиту telnet и указав порт 80. Если используется HTTPS, то веб-браузер сначала договаривается с сервером о шифровании канала по протоколу TLS, а дальше все запросы и ответы передаются по зашифрованному каналу. Формат запросов и ответов остается таким же, как при обычном HTTP.

В ответ на ваш запрос сервер вышлет HTML код, который обычно лежит в файле index.html. Примитивная на первый взгляд страница www.google.ru присылает текст размером почти 70 килобайт и уже содержит в себе сжатые скрипты. Мы рассмотрим более простой пример, чтобы лучше разобраться что к чему. Наш сервер (пусть www.example.com) прислал веб-браузеру такой текст:
<html>
    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
    <body>
        <div id="content">Пожалуйста, разрешите выполнение JavaScript, чтобы увидеть сайт.</div>
    </body>
    <script type="text/javascript" src="scripts/app.js"></script>
</html>
Здесь видно, что в теле странице есть единственный тег с именем content. Также подключается некий внешний скрипт app.js — это и есть наше веб-приложение. Если выполнение скриптов не разрешено в веб-браузере, то ничего не произойдет и будет выведен текст из тега.

А вот если выполнение JavaScript разрешено, то веб-браузер получит с сервера файл app.js и будет выполнять код из него.

Веб-приложение

Код, который получается с сервера и представляет собой веб-приложение. Этот код на лету сгенерирует необходимое содержимое и вставит его внутрь тега content. Простейший вариант app.js приведен ниже:
function onLogin(form) {
    var xhttp = new XMLHttpRequest();
    xhttp.open("GET", "/rest_api/v1/me_page", false);
    xhttp.setRequestHeader("Content-type", "application/json");
    xhttp.setRequestHeader("Authorization", "Basic " + btoa(form.username.value + ":" + form.username.password));
    xhttp.send();
    var response = JSON.parse(xhttp.responseText);
}

(function(){
    document.getElementById("content").innerHTML = '\
        <form name="login">\
            Username: <input type="text" name="username"/></p>\
            Password: <input type="password" name="passwd"/></p>\
            <input type="button" onclick="onLogin(this.form)" value="Login"/>\
        </form>';
})()
Данный код найдет на странице элемент с именем content и заменит его содержимое на форму логина, которая состоит из двух полей ввода и кнопки Login. По кнопке будет запускаться функция onLogin, которая определена тут же.

Тут начинается самое интересное. Функция onLogin посылает серверу запрос GET. В виде текста это может выглядеть следующим образом:
GET /rest_api/v1/me_page HTTP/1.1
Host: www.example.com
Authorization:Basic YWRtaW46dW5kZWZpbmVk
Легко заметить, что это практически тоже самое, если бы вы адресной строке браузера написали текст вида http://www.example.com/rest_api/v1/me_page. Тут и начинается взаимодействие с приложением-сервером (см. схему в начале статьи).

Приложение-сервер

На схеме видно, что приложение-сервер не взаимодействует с Интернетом напрямую. Ему не нужно заботиться о передачи статического контента (в нашем случае, файлов index.html и app.js), который не меняется от запроса к запросу. И не нужно думать про организацию защищенного HTTPS канала до веб-браузера. Для этих целей лучше использовать проверенные средства, например, проксирующий веб-сервер nginx, который умеет эффективно отдавать статический контент и проксировать запросы к нашему приложению-серверу. В реальном применении на nginx также удобно возложить вопросы отказоустойчивости и распределения нагрузки, а также многие другие.

Какие запросы обрабатывает nginx, а какие передаются приложению-серверу, определяется настройками в файле nginx.conf.

Продолжение следует...

пятница, 21 апреля 2017 г.

Небольшие заметки про отладку Bash скриптов


Если вам когда-либо приходилось писать сложные скрипты на Bash, то вы наверняка думали о двух вещах:
  1. Никогда больше не писать сложные скрипты на Bash.
  2. Как отлаживать то, что уже написано?
Если с первым пунктом все понятно, то ответ на второй вопрос не так просто найти. Интегрированный среды для пошаговой отладки я не нашел (если не брать в расчет странный BASH Debugger). Но способ получить трейс есть, также как и выполнить скрипт по шагам.

Получить трейс выполнения проще простого. Надо всего лишь запустить bash с параметром -x. Либо в самом коде выполнить команду set +x. Первый вариант выглядит предпочтительней, потому что вы извне контролируете — нужен вам подробный вывод или нет. Конечному пользователю такой вывод обычно не только не нужен, но и откровенно мешает.

Посмотрим, как это работает на практике. Например, следующий простейший скрипт имеет ветвление:
#!/bin/bash

if [[ "$1" == "hello" ]]; then
 echo "hello world!!!"
fi
Запускаем его с трассировкой выполнения и сразу видим, что происходит внутри:
$ bash -x ./my_mega_script.sh hello

+ [[ hello == \h\e\l\l\o ]]
+ echo 'hello world!!!'
hello world!!!
Для запуска своего скрипта с сохранением трассировки удобно использовать вспомогательный скрипт примерно такого содержания:
#/bin/bash

/bin/bash -x ./my_mega_script.sh 2>my_mega_script.log
В таком режиме отладочный вывод не будет мешать смотреть результаты выполнения скрипта. А если выполняется много действий в течении длительного времени, то для просмотра трассировки удобно в отдельном окне запустить следующую команду:
$ tail -f ./my_mega_script.log
Утилита tail с параметром -f (follow) выводит текст на экран по мере обновления файла.

Ну и напоследок, режим пошагового выполнения:
#!/bin/bash
echo "Press CTRL+C to proceed."
trap "pkill -f 'sleep 1h'" INT
trap "set +x ; sleep 1h ; set -x" DEBUG

###
# ДАЛЬШЕ ИДЕТ ВАШ КОД
После каждого нажатия Ctrl+C выполняется один шаг скрипта и потом "отладчик" засыпает на 1 час либо до следующего нажатия Ctrl+C.

Книги по теме:

четверг, 2 марта 2017 г.

Google: кто-то завладел вашим паролем



Ночью мне пришло письмо на резервную почту о том, что кто-то использовал мой пароль для доступа к почте GMail, и что Google отважно заблокировал негодяев. Ну и мне, конечно, необходимо срочно принять меры. Я немного удивился, что почта, которая заведена специально для PayPal оказалась кем-то взломана. Учитывая, что она почти нигде не засвечена и имеет сгенерированный высокоэнтропийный пароль, это казалось невозможным. Однако, я сменил пароль, внутренне уже приготовившись к СМС с информацией о снятии денег с карты. А после смены пароля я начал разбираться в произошедшем и вот что выяснилось...

Пароль взломать почти нереально, поэтому сначала я подумал, что это мошенническое письмо о взломе, чтобы добыть мои логин/пароль. Тем более, что Google предупреждает об этом:
К сожалению, иногда хакеры пытаются получить данные аккаунтов, копируя сообщения Google о подозрительной попытке входа. Не доверяйте сообщениям, в которых вас просят предоставить имя пользователя, пароль или другие персональные данные. Если сообщение содержит ссылку на сторонний сайт, не переходите по ней и не вводите на этом сайте никаких данных.

Но ссылки в письме вели на https://accounts.google.com и браузер говорил, что сайт не поддельный. Вот что я там увидел в списке устройств:


Ага, американские хакеры пытаются взломать мою почту. Немного необычно, что указан только IPv6 адрес. В голову начали приходить мысли об огромном ботнете из IoT устройств, который забрутфорсил мой пароль.

Из любопытства я решил проверить — не к Пентагону ли ведет трейс до этого адреса. Результат все расставил по местам:
traceroute to 2a00:1450:400c:c0c:0:0:0:211 (2a00:1450:400c:c0c::211), 30 hops max, 80 byte packets 
1 2600:3c01::8678:acff:fe0d:79c1 (2600:3c01::8678:acff:fe0d:79c1) 3.296 ms 3.057 ms 3.024 ms (United States) 
2 2600:3c01:3333:1::1 (2600:3c01:3333:1::1) 2.886 ms 2.970 ms 2.939 ms                     (United States) 
3 eqixsjc-v6.google.com (2001:504:0:1:0:1:5169:1) 2.866 ms as15169.sfmix.org (2001:504:30::ba01:5169:1) 3.986 ms 4.011 ms (United States) 
4 2001:4860:0:1004::2 (2001:4860:0:1004::2) 3.178 ms 2001:4860:0:1005::2 (2001:4860:0:1005::2) 2.576 ms 2.540 ms (United States) 
5 2001:4860::8:0:6117 (2001:4860::8:0:6117) 145.173 ms 141.854 ms 142.622 ms               (United States) 
6 2001:4860::c:4000:d20a (2001:4860::c:4000:d20a) 46.065 ms 40.774 ms 41.735 ms            (United States) 
7 2001:4860::8:4000:cbc2 (2001:4860::8:4000:cbc2) 49.985 ms 2001:4860::8:0:b0e2 (2001:4860::8:0:b0e2) 154.440 ms 154.322 ms (United States) 
8 2001:4860::c:4000:d2a0 (2001:4860::c:4000:d2a0) 59.087 ms 2001:4860::c:4000:d64b (2001:4860::c:4000:d64b) 154.324 ms 2001:4860::c:4000:d29f (2001:4860::c:4000:d29f) 90.399 ms (United States) 
9 2001:4860::8:0:bafa (2001:4860::8:0:bafa) 153.360 ms 2001:4860::8:4000:cd7f (2001:4860::8:4000:cd7f) 63.042 ms 2001:4860::8:0:bafa (2001:4860::8:0:bafa) 143.081 ms (United States) 
10 2001:4860::c:4000:d9af (2001:4860::c:4000:d9af) 170.679 ms 2001:4860::c:4000:d9ab (2001:4860::c:4000:d9ab) 139.273 ms 140.791 ms (United States) 
11 2001:4860::8:0:cc3f (2001:4860::8:0:cc3f) 145.014 ms 145.056 ms 2001:4860::8:4000:d324 (2001:4860::8:4000:d324) 144.381 ms (United States) 
12 2001:4860::2:0:76e7 (2001:4860::2:0:76e7) 144.918 ms 2001:4860::2:0:76e8 (2001:4860::2:0:76e8) 144.521 ms 2001:4860::2:0:76e7 (2001:4860::2:0:76e7) 145.444 ms (United States) 
13 * * *                                                                                   (?) 
14 * * *                                                                                   (?) 
15 * * *                                                                                   (?) 
16 * * *                                                                                   (?) 
17 * * *                                                                                   (?) 
18 * * *                                                                                   (?) 
19 * * *                                                                                   (?) 
20 * * *                                                                                   (?) 
21 mail-wr0-x211.google.com (2a00:1450:400c:c0c::211) 142.782 ms 143.318 ms 144.535 ms     (Ireland)
В конце указан mail-wr0-x211.google.com. Вот тут я и вспомнил, что пароль к этой почте знает сам же Google в другом аккаунте GMail, который аккумулирует почту с различных моих адресов. И в этот раз Google, видимо, стал проверять почту из другого датацентра, а не как обычно. И, подозреваю, что в этот момент не я один получил подобные письма о взломе. Тут я делаю вывод, что чехарда датацентров и оказалась причиной детектирования подозрительной активности. А может Google просто так форсирует людей включать дополнительные шаги аутентификации. А вы что скажете?