Русский
Русский
English
Статистика
Реклама

Safari

Предотвращение cross-site tracking в Safari на MacOs

16.11.2020 16:04:47 | Автор: admin
image

Каждый раз, когда вы посещаете сайт, он собирает данные о вашем устройстве такие как системные настройки и использует эти данные для отображения страницы адаптированной под ваше устройство. Некоторые компании пытаются использовать эти данные для идентификации именно вашего устройства это называется снять отпечаток (fingerprinting). Во избежание этого Сафари предоставляет упрощенную версию конфигурации вашей системы. Ваш Мак будет похож на большинство других Маков, что серьезно снижает возможности следящих программ, трекеров, надежно идентифицировать ваше устройство.

Некоторые сайты используют сторонних поставщиков контента. Вы можете запретить сторонним контент провайдерам отслеживать ваши действия между сайтами для рекламы услуг и товаров.

Для этого у надо включить птичку Prevent cross-site tracking в Safari > Preferences > Privacy. По умолчанию она включена.

Сайты соцсетей ставят кнопки Share, Like, или Comment на сторонние сайты. Эти кнопки могут использоваться для отслеживания ваших действий, даже если вы ими не пользуетесь. Сафари блокирует такое отслеживание. Если вам надо ими пользоваться, потребуется ваше разрешение для сайта на просмотр вашей активности на других сайтах.

Все 4 абзаца сверху это вольный перевод с сайта Apple (user guide). А натолкнулся я на эту тему потому, что только что установил новую MacOs Big Sur. Как водится после обновления операционки приятно полазить по системе, посмотреть что изменилось и что появилось нового. Ну и вот, так я добрался до кнопки Show the privacy report for this site, раньше я ее видел, но не обращал внимания, теперь же, в режиме любопытства, нажал.



То, что я увидел, не удивило меня, я понимаю много всего висит на сайтах. Но посмотреть стало интересно. Нажмем на кнопочку в верхнем левом углу.

Видим:



в списке 25 трекеров (на рисунке видны первые) и 7 сайтов:



Хоть и написано, что отчет за 30 дней, на самом деле счетчик пошел с момента установки MacOs Big Sur. В списке видим только те сайты и трекеры, на которые я зашел, перед тем как решил об этом написать. Только одного сайта там не было, проницательный читатель догадается какого.

Сколько будет в этом списке через тридцать дней, думаю сотни сайтов и сотни трекеров. И судя по заявлениям Apple, сотни случаев, когда Сафари помешает сборщикам информации анализировать мой отпечаток системы. Если так, отлично, я согласен. В любом случае хорошо, что можно быстро взглянуть на то, насколько увешан конкретный сайт всякими трекерскими штучками.

какого сайта не было в списке?
в списке не было сайта Apple, который я открывал ради текста об этой фиче.


Подробнее..

JavaScript Стек вызовов и магия его размера

03.04.2021 18:14:06 | Автор: admin

Привет, Хабровчане!

Большинство разработчиков, которые использовали рекурсию для решения своих задач, видели такую ошибку:

 RangeError: Maximum call stack size exceeded. 

Но не каждый разработчик задумывался о том, а что означает "размер стэка вызовов" и каков же этот размер? А в чем его измерять?

Думаю, те, кто работают с языками, напрямую работающими с памятью, смогут легко ответить на этот вопрос, а вот типичный фронтэнд разработчик скорее всего задает себе подобные вопросы впервые. Что-ж, попробуем разобраться!

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

О чем ты вообще, автор?

Для статьи важно понимание таких понятий как Execution Stack, Execution Context. Если вы не знаете, что это такое, то советую об этом почитать. На данном ресурсе уже было достаточно хороших статей на эту тему. Пример -http://personeltest.ru/aways/habr.com/ru/company/ruvds/blog/422089/

Когда возникает эта ошибка?

Разберем на простом примере - функция, которая рекурсивно вызывает сама себя.

const func = () => {    func();}

Если попытаться вызвать такую функцию, то мы увидим в консоли/терминале ошибку, о которой я упомянул выше.

А что если подглядеть, сколько же раз выполнилась функция перед тем, как возникла ошибка?

На данном этапе код запускается в Chrome DevTools последней версии на март 2021. Результат будет различаться в разных браузерах. В дальнейшем в статье я упомяну об этом.

Для эксперимента будем использовать вот такой код:

let i = 0;const func = () => {  i++;  func();};try {  func();} catch (e) {  // Словили ошибку переполнения стэка и вывели значение счетчика в консоль  console.log(i);}

Результатом вывода в консоль стало число в 13914. Делаем вывод, что перед тем, как переполнить стэк, наша функция вызвалась почти 14 тысяч раз.

Магия начинается тогда, когда мы начинаем играться с этим кодом. Допустим, изменим его вот таким образом:

let i = 0;const func = () => {  let someVariable = i + 1;  i++;  func();};try {  func();} catch (e) {  console.log(i);}

Единственное, что мы добавили, это объявление переменной someVariableв теле функции. Казалось бы, ничего не поменялось, но число стало меньше. На этот раз функция выполнилась 12523 раз. Что более чем на тысячу меньше, чем в прошлом примере. Чтобы убедиться, что это не погрешность, пробуем выполнять такой код несколько раз, но видим одни и те же результаты в консоли.

Почему же так? Что изменилось? Как понять, посмотрев на функцию, сколько раз она может выполниться рекурсивно?!

Магия раскрыта

Отличие второго примера от первого - наличие дополнительной переменной внутри тела функции. На этом все. Соответственно, можно догадаться, именно из-за этого максимальное количество рекурсивных вызовов стало меньше. Что-ж, а что, если у нас будет не одна, а четыре переменных внутри функции? По этой логике, количество рекурсивных вызовов станет еще меньше? Проверим:

let i = 0;const func = () => {  let a = i + 1;  let b = a + 1;  let c = b + 1;  let d = c + 1;  let e = d + 1;  i++;  func();};try {  func();} catch (e) {  console.log(i);}

По этой логике, значение счетчика должно стать еще меньше. Выдыхая, видим вывод - 8945. Ага, оно стало еще меньше. Значит, мы на правильном пути. Хочу привести небольшую аналогию, чтобы даже самым маленьким стало понятно.

Execution Stack (Call Stack) - это емкость с водой. Изначально она пустая. Так получилось, что эта емкость с водой стоит прямо на электрической проводке. Как только емкость переполнится, вода проливается на провода, и мы видим ошибку в консоли. При каждом новом рекурсивном вызове функции в стэк падает капелька воды. Само собой, чем капелька воды крупнее, тем быстрее наполнится стэк.

Емкости бывают разные по размеру. И размер емкости в данном примере - эторазмер коллстэка. А точнее - количество байт, которое он максимально может в себе удержать. Как гласит статья про Call Stack (Execution Stack), которую я приложил в начале, на каждый вызов функции создается Execution Context - контекст вызова функции (не путать с this). И упрощая, в нем, помимо разных "подкапотных" штук, содержатся все переменные, которые мы объявили внутри функции. Как Execution Context, так и каждая переменная внутри него имеют определенный размер, который они занимают в памяти. Сложив эти два размера мы и получим "размер" капли, которая капает в кувшин при каждом рекурсивном вызове функции.

У нас уже достаточно много данных. Может быть, поэкспериментируем еще? А давайте попробуем вычислить, какой размер коллстэка в движке, который использует Chrome?

Математика все-таки пригодилась

Как мы выяснили, у нас есть две неизвестные, которые составляют размер функции (капельки, которая падает в емкость). Это размер самого Execution Stack, а так же сумма размеров всех переменных внутри функции. Назовем первую N, а вторую K. Сам же неизвестный размер коллстэка обозначим как X.

В итоге - количество байт, которое занимает функция (в упрощенном виде) будет:

FunctionSize = N + K * SizeOfVar

SizeOfVar в данном случае - количество байт, которые занимает переменная в памяти.

Учитывая, что мы знаем количество вызовов первой функции, в теле которой не объявляются переменные, размер коллстэка можно выразить как:

X = (N + 0 * SizeOfVar)* 13914 = N * 13914

И, для второго случая, возьмем функцию, внутри которой было объявлено пять переменных.

X = (N + 5 * SizeOfVar) * 8945

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

N * 13914 = (N + 5 * SizeOfVar) * 8945

Выглядит неплохо. У нас тут две неизвестные переменные - N и SizeOfVar. Если N мы не можем откуда-то узнать, то что насчет SizeOfVar? Заметим, что во всех функциях, которые фигурировали выше, переменные хранили значение с типом "number", а значит, нужно просто узнать, сколько же байт в памяти занимает одна такая переменная.

С помощью великого гугла получаем ответ - "Числа в JavaScript представлены 64-битными значениями с плавающей запятой. В байте 8 бит, в результате каждое число занимает 64/8 = 8 байт." Вот она - последняя неизвестная. 8 байт. Подставляем ее в наше уравнение и считаем, чем равно N.

N * 13914 = (N + 5 * 8) * 8945

Упрощаем:

N * 13914 = N * 8945 + 40 * 8945

Если выразить отсюда N, то получим ответ: N равно приблизительно 72. В данном случае 72 байтам.

Теперь, подставив N = 72 в самое первое уравнение, получим, что размер коллстэка в Chrome равен... 1002128 байтов. Это почти один мегабайт. Не так уж и много, согласитесь.

Мы получили какое-то число, но как убедиться, что наши расчеты верны и число правильное? А давайте попробуем с помощью этого числа спрогнозировать, сколько раз сможет выполниться функция, внутри которой будет объявлено 7 переменных типа 'number'.

Считаем: Ага, каждая функция будет занимать (72 + 7 * 8) байт, это 128. Разделим 1002128 на 128 и получим число... 7829! Согласно нашим расчетам, такая функция сможет рекурсивно вызваться именно 7829 раз!Идем проверять это в реальном бою...

Мы были очень даже близки. Реальное число отличается от теоретического всего на 3. Я считаю, что это успех. В наших расчетах было несколько округлений, поэтому результат мы посчитали не идеально, но, очень-очень близко. Небольшая погрешность в таком деле - нормально.

Получается, что мы посчитали все верно и можем утверждать, что размер пустого ExecutionStack в Chrome равен 72 байтам, а размер коллстэка - чуть меньше одного мегабайта.

Отличная работа!

Важное примечание

Размер стэка разнится от браузера к браузеру. Возьмем простейшую функцию из начала статьи.Выполнив ее в Сафари получим совершенно другую цифру. Целых 45606 вызовов. Функция с пятью переменными внутри выполнилась бы 39905 раз. В NodeJS числа очень близки к Chrome по понятным причинам. Любопытный читатель может проверить это самостоятельно на своем любимом движке JavaScript.

А что с непримитивами?

Если с числами все вроде бы понятно, то что насчет типа данных Object?

let i = 0;const func = () => {  const a = {    key: i + 1,  };  i++;  func();};try {  func();} catch (e) {  console.log(i);}

Простейший пример на ваших глазах. Такая функция сможет рекурсивно вызваться 12516. Это практически столько же, сколько и функция с одной переменной внутри. Тут в дело вступает механизм хранения и передачи объектов в JS'е - по ссылке. Думаю, большинство уже знают об этом.

А что с этим? А как поведет себя вот это? А что с *?

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

Итоги:

  • Количество рекурсивных вызовов функции до переполнения стэка зависит от самих функций.

  • Размер стэка измеряется в байтах.

  • Чем "тяжелее" функция, тем меньше раз она может быть вызвана рекурсивно.

  • Размер стэка в разных движках различается.

Вопрос особо любознательным: А сколько переменных типа "number" должно быть объявлено в функции, чтобы она могла выполниться рекурсивно всего два раза, после чего стэк переполнится?

Подробнее..

Как баг с потерянными днями рождения привёл нас в историю СССР

25.05.2021 18:15:50 | Автор: admin

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

Сначала команда проверила бэкенд и убедилась, что данные приходят корректно. Проанализировали логи взаимодействия пользовательских браузеров с сайтом выяснилось, что баг воспроизводится только в Safari на устройствах Apple. А когда пользователи говорят, что дата рождения отображается правильно, они заходят на портал с другого устройства и браузера, например, с Google Chrome на компьютере.

Далее, команда стала перебирать разные периоды, постепенно сокращая временные отрезки, чтобы понять, с какой датой связан наш баг. Выяснилось, что проблема крутится вокруг июня 1930 года именно там происходит сбой по времени, причем только в Safari. Стали разбираться дальше, что особенного произошло 21 июня 1930 года, и откопали, что в этот день в Советском Союзе перевели время на час вперёд. Google Chrome обрабатывает эту ситуацию корректно, а Safari нет.

Еще одна особенность в формате переменной, которая передает это время. Формат отсчитывает количество миллисекунд от нулевого времени отметка стоит на 1 января 1970 года.Google Chrome обрабатывает дату 21 июня следующим образом: с 23:59 до часа ночи стоит 1000 миллисекунд (что равно 1 секунде), то есть это время пропадает. Safari же считает, что это время там есть, так как не знает, что в СССР переводили часы.

Команда зарепортила этот баг в Apple Feedback Assistant и ожидает ответа. А портал получит собственную заплатку со следующим релизом.

P.S. В процессе расследования разработчики обратили внимание ещё на один интересный факт. У пользователей есть фича, которая позволяет отображать на портале только день и месяц рождения без года. И оказалось, что если пользователь выбирает такую настройку, то с бэкенда передаётся значение 4 год нашей эры. Установить, почему создатели портала выбрали эту дату, удалось только в самом финале.

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

Подробнее..

Из песочницы Обновленный браузер Safari как быть маркетологам

20.09.2020 02:20:07 | Автор: admin

Что произошло?


16 сентября вышла новая версия операционной системы iOS 14 для мобильных устройств экосистемы Apple. Обновился также и встроенный браузер Safari не только на смартфонах и планшетах, но и на ноутбуках и десктопах. В новой версии браузера появился встроенный функционал блокировки трекинговых пикселей на сайтах для лучшей защиты пользовательских данных. Блокируются популярные пиксели для веб-аналитики, например:


  • Google Tag Manager
  • Google Analytics
  • Facebook
  • Яндекс.Метрика
  • Mail.ru (myTarget)
  • Doubleclick (Floodlight)
  • VK

Вот так это выглядит:


image


Статистику по заблокированным пикселям можно также отследить в Safari:


image


И в iPhone и iPad:


image


Что происходит, если серфить интернет с помощью обновленного браузера Safari:


  • ни одна стандартная система веб-аналитики, а значит и бренды, не могут отследить действия пользователя;
  • пользователь не попадает в ремаркетинговые аудитории;
  • не работает Google Tag Manager.

Важно понимать

Соблюдение конфиденциальности пользователей и усиленная защита данных сейчас популярный рыночный тренд. Это понимают компании-разработчики браузеров, постепенно внедряя технологии повышенной приватности. Для справки: доля пользователей браузера Safari на рынке составляет 17%. И хотя ограничения в будущем только усилятся, мы еще можем успеть подготовиться и улучшить работу наших систем аналитики.


Как ограничение работает на самом деле?


Многие тематические ресурсы и обозреватели (такие, как Apple Insider и Search Engine Journal) не совсем корректно поняли сообщениями в браузере Safari и вышли с громким сообщением, что трекинг-системы перестали работать. На самом деле обновленная версия браузера не блокирует работу трекинговых систем, а повышает конфиденциальность даннх пользователей и показывает пользователю сообщение о том, какие трекинг-системы обнаружены на веб-сайте.


Как решить проблему?


Весной в Google Tag Manager появилась бета-версия функции настройки отслеживания на стороне сервера (Server-side Tagging).


image


Как работает Server-side Tagging


Если вкратце, то тегирование на стороне сервера позволяет запускать контейнеры Google Tag Manager на серверном окружении. К сожалению, на данный момент инструмент все еще находится в стадии бета-тестирования, поэтому пока что доступен только вариант установки на серверах Google Cloud Platform, однако будем надеяться, что поддержка других серверов появится в ближайшее время.


Отличия от стандартных контейнеров


Во многом тегирование на стороне сервера похоже на стандартные контейнеры Google Tag Manager:


  • теги запускаются по триггерам и оперируют переменными;
  • новые контейнеры можно запустить в режиме предпросмотра и затем опубликовать;
  • пользователи могут создавать свои собственные кастомные шаблоны.

Однако, есть и отличия:


  • контейнер серверного типа отличается от web-, app- и AMP-контейнеров;
  • триггеры срабатывают не при наступлении событий на сайте, а при получении входящих HTTP-запросов;
  • эти HTTP-запросы обрабатываются Клиентом это новая сущность GTM, которая является адаптером между устройством пользователя и серверным контейнером;
  • клиент обрабатывает запросы, генерирует объект данных события и выгружает его в виртуальный контейнер, с помощью которого теги могут обращаться к объектам событий, чтобы отправлять данные в сторонние системы.

Вот схема работы нового типа контейнеров:


image


А вот как выглядит интерфейс отличий от стандартного веб-контейнера совсем немного:


image


Более подробно про добавление тегов на стороне сервера можно прочитать в официальной документации.


Преимущества Server-side Tagging


С помощью тегирования на стороне сервера мы можем проводить полноценную веб-аналитику без необходимости установки какого-либо пикселя в код сайта. При правильной настройке, мы сможем забыть о блокировке трекинга как со стороны производителей браузеров, так и со стороны плагинов блокировки рекламы (например, AdBlock). Помимо этого, браузер перестает загружать скрипты пикселей с внешних серверов, соответственно, ощутимо возрастает скорость работы сайта для пользователей.


Скорость загрузки сайта


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


Все данные хранятся у вас


Вы полностью контролируете и владеете всеми данными на сервере Google Cloud:


  • сервера Google Cloud обрабатывают только те данные, которые вы хотите обрабатывать;
  • вы владеете всеми данными. Google не использует ваши данные для рекламных целей;
  • вы всегда знаете и контролируете в каком регионе хранятся ваши данные;
  • хранение данных производится со всеми современными стандартами безопасности.

Обходим блокировки трекеров отслеживания


Блокировка трекеров и пикселей обычно осуществляется двумя способами:


  • блокируется JS-код от трекеров (пиксели на сайте);
  • блокируется домен трекера, чтобы предотвратить отправку данных на него.

С помощью серверного отслеживания мы обходим обе эти проблемы:


  • мы переносим логику из пикселей на серверное окружение, не размещая на сайте JS-код, поэтому браузер не сможет его заблокировать;
  • мы можем привязать к серверному контейнеру свой домен, поэтому блокировка по домену не будет работать.

Недостатки серверного отслеживания


Дополнительные расходы на сервер в облаке


На данный момент GTM поддерживает размещение серверных контейнеров только на серверах Google Cloud. Стоимость аренды одного сервера начинается от 40 долларов в месяц.


Не все пиксели можно поставить через серверный контейнер


Серверный контейнер работает как замена JS-коду на стороне браузера, поэтому поставщик пикселя должен уметь принимать данные через HTTP-запросы.


Для простого отслеживания, как правило это не является проблемой. Но проблемы могут возникнуть, если в скрипте используется сложный JS-код, который нельзя перенести на сервер, как например в Вебвизоре от Яндекс.Метрики. Он точно не будет работать.


Как внедрить отслеживание на стороне сервера?


  1. Создать серверный контейнер в Google Tag Manager;
  2. Зарегистрироваться на Google Cloud Platform, создать новый проект и настроить способ оплаты;
  3. Развернуть сервер Google Cloud AppEngine;
  4. Привязать свой домен к созданному серверу;
  5. Перенести теги, триггеры и переменные из веб-контейнера в серверный контейнер GTM.

Подробная инструкция доступна в официальной документации по ссылке.

Подробнее..

Как мы в ZeroTech подружили Apple Safari и клиентские сертификаты с websocket-ами

03.07.2020 10:08:54 | Автор: admin
Статья будет полезна тем, кто:
знает, что такое Client Cert, и понимает для чего ему websocket-ы на мобильном Safari;
хотел бы публиковать web-сервисы ограниченному кругу лиц или только себе;
думает, что всё уже кем-то сделано, и хотел бы сделать мир немного удобнее и безопаснее.

История веб-сокетов началась примерно 8 лет назад. Ранее использовались методы вида долгих http-запросов (на самом деле ответов): браузер пользователя отправлял запрос на сервер и ждал, пока он ему что-то ответит, после ответа подключался вновь и ждал. Но потом появились веб-сокеты.



Несколько лет назад мы разработали собственную реализацию на чистом php, которая не умеет использовать запросы https, так как это канальный уровень. Не так давно практически все web-серверы научились проксировать запросы по https и поддерживать connection:upgrade.

Когда это случилось, веб-сокеты стали практически сервисом по умолчанию у SPA-приложений, ведь как удобно предоставлять пользователю контент по инициативе сервера (передать сообщение от другого пользователя или загрузить новую версию изображения, документа, презентации, которую сейчас кто-то ещё редактирует).

Хотя Сlient Сert появился уже довольно давно, он всё ещё остаётся мало поддерживаемым, так как создаёт массу проблем с попытками его обойти. И (возможно :slightly_smiling_face: ) поэтому IOS-браузеры (все, кроме Safari) не хотят его использовать и запрашивать у локального хранилища сертификатов. Сертификаты обладают массой преимуществ по сравнению с ключами login/pass или ssh или закрытием через firewall нужных портов. Но речь не об этом.

На IOS процедура установки сертификата довольно проста (не без специфики), но в общем делается по инструкциям, которых в сети очень много и которые доступны только для браузера Safari. К сожалению, Safari не умеет использовать Сlient Сert для веб-сокетов, но в интернете есть множество инструкций, как сделать такой сертификат, но на практике это недостижимо.



Чтобы разобраться в веб-сокетах, мы использовали следующий план: проблема/гипотеза/решение.

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

Гипотезы:
1. Возможно настроить такое исключение для использования сертификатов (зная, что их не будет) к веб-сокетам внутренних/внешних проксируемых ресурсов.
2. Для веб-сокетов можно сделать уникальное безопасное и защищаемое соединение с помощью временных сессий, которые генерируются при обычном (не веб-сокет) запросе браузера.
3. Временные сессии можно реализовать с помощью одного proxy web-сервера (только встроенные модули и функции).
4. Временные сессии-токены уже были реализованы в качестве готовых модулей apache.
5. Временные сессии-токены можно реализовать, логически спроектировав структуру взаимодействий.

Видимое состояние после внедрения.
Цель работы: управление сервисами и инфраструктурой должно быть доступно с мобильного телефона на IOS без дополнительных программ (таких как VPN), унифицировано и безопасно.

Дополнительная цель: экономия времени и ресурсов/трафика телефона (некоторые сервисы без веб-сокетов генерируют лишние запросы) с ускорением отдачи контента на мобильном интернете.

Как проверить?
1. Открытие страниц:
 например, https://teamcity.yourdomain.com в мобильном браузере Safari (доступен также в десктопной версии)  вызывает успешное подключение к веб-сокетам. например, https://teamcity.yourdomain.com/admin/admin.html?item=diagnostics&tab=webS показывает ping/pong. например, https://rancher.yourdomain.com/p/c-84bnv:p-vkszd/workload/deployment:danidb:ph-> viewlogs  показывает логи контейнера.


2. Или в консоли разработчика:


Проверка гипотез:
1. Возможно настроить такое исключение для использования сертификатов (зная, что их не будет) к веб-сокетам внутренних/внешних проксируемых ресурсов.

Тут было найдено 2 решения:
а) На уровне

<Location sock*> SSLVerifyClient optional </Location><Location /> SSLVerifyClient require </Location>

менять уровень доступа.

У данного метода возникли такие нюансы:
Проверка сертификата происходит после запроса к проксируемому ресурсу, то есть post request handshake. Это означает, что прокси сначала нагрузит, а потом отсечёт запрос к защищаемому сервису. Это плохо, но не критично;
В протоколе http2. Он ещё находится в draft-е, и производители браузеров не знают, как его реализовать #info about tls1.3 http2 post handshake (not working now) Implement RFC 8740 Using TLS 1.3 with HTTP/2;
Непонятно, как унифицировать эту обработку.

б) На базовом уровне разрешить ssl без сертификата.
SSLVerifyClient require => SSLVerifyClient optional, но это снижает уровень защиты proxy-сервера, так как такое соединение будет обработано без сертификата. Однако можно далее запретить доступ к проксируемым сервисам такой директивой:

RewriteEngine        onRewriteCond     %{SSL:SSL_CLIENT_VERIFY} !=SUCCESSRewriteRule     .? - [F]ErrorDocument 403 "You need a client side certificate issued by CAcert to access this site"


Более подробная информация в статье о ssl: Apache Server Client Certificate Authentication

Оба варианта были проверены, выбран вариант б за универсальность и совместимость с протоколом http2.

Для завершения проверки этой гипотезы потребовалось немало экспериментов с конфигурацией, были проверены конструкции:
if = require = rewrite
Apache Core Features
Expressions in Apache HTTP Server

Получилась следующая базовая конструкция:
SSLVerifyClient optionalRewriteEngine onRewriteCond %{SSL:SSL_CLIENT_VERIFY} !=SUCCESSRewriteCond %{HTTP:Upgrade} !=websocket [NC]RewriteRule     .? - [F]#ErrorDocument 403 "You need a client side certificate issued by CAcert to access this site"#websocket for safari without cert auth<If "%{SSL:SSL_CLIENT_VERIFY} != 'SUCCESS'"><If "%{HTTP:Upgrade} = 'websocket'">...    #замещаем авторизацию по владельцу сертификата на авторизацию по номеру протокола    SSLUserName SSl_PROTOCOL</If></If>


С учётом существующей авторизации по владельцу сертификата, но при отсутствующем сертификате пришлось добавить несуществующего владельца сертификата в виде одной из доступных переменных SSl_PROTOCOL (вместо SSL_CLIENT_S_DN_CN), подробнее в документации:
Apache Module mod_ssl



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

Исходя из предыдущего опыта нужно добавить дополнительную секцию в конфигурацию, чтобы при обычном (не веб-сокет) запросе готовить временные токены для веб-сокет соединений.

#подготовка передача себе Сookie через пользовательский браузер<If "%{SSL:SSL_CLIENT_VERIFY} = 'SUCCESS'"><If "%{HTTP:Upgrade} != 'websocket'">Header set Set-Cookie "websocket-allowed=true; path=/; Max-Age=100"</If></If>#проверка Cookie для установления веб-сокет соединения<source lang="javascript"><If "%{SSL:SSL_CLIENT_VERIFY} != 'SUCCESS'"><If "%{HTTP:Upgrade} = 'websocket'">#check for exists cookie#get and checkSetEnvIf Cookie "websocket-allowed=(.*)" env-var-name=$1#or rewrite ruleRewriteCond %{HTTP_COOKIE} !^.*mycookie.*$#or if<If "%{HTTP_COOKIE} =~ /(^|; )cookie-name\s*=\s*some-val(;|$)/ ></If</If></If>


Проверка показала, что это работает. Возможно через пользовательский браузер передавать cебе Cookie.

3. Временные сессии можно реализовать с помощью одного proxy web-сервера (только встроенные модули и функции).
Как мы выяснили ранее, у Apache довольно много core-функциональности, которая позволяет создавать условные конструкции. Однако нам нужны средства защиты нашей информации, пока она находится в пользовательском браузере, поэтому устанавливаем, что и для чего хранить, и какие встроенные функции будем задействовать:
Нужен такой токен, который не поддаётся простому декодированию.
Нужен такой токен, в котором зашито устаревание и возможность проверки устаревания на сервере.
Нужен такой токен, который будет связан с владельцем сертификата.

Для этого нужна функция хеширования, соль и дата для устаревания токена. Исходя из документации Expressions in Apache HTTP Server у нас есть всё это из коробки sha1 и %{TIME}.

Получилась такая конструкция:
#нет сертификата, и обращение к websocket<If "%{SSL:SSL_CLIENT_VERIFY} != 'SUCCESS'"><If "%{HTTP:Upgrade} = 'websocket'">    SetEnvIf Cookie "zt-cert-sha1=([^;]+)" zt-cert-sha1=$1    SetEnvIf Cookie "zt-cert-uid=([^;]+)" zt-cert-uid=$1    SetEnvIf Cookie "zt-cert-date=([^;]+)" zt-cert-date=$1#только так можно работать с переменными, полученными в env-ах в этот момент времени, более они нигде не доступны для функции хеширования (по отдельности можно, но не вместе, да и ещё с хешированием)    <RequireAll>        Require expr %{sha1:salt1%{env:zt-cert-date}salt3%{env:zt-cert-uid}salt2} == %{env:zt-cert-sha1}        Require expr %{env:zt-cert-sha1} =~ /^.{40}$/    </RequireAll></If></If>#есть сертификат, запрашивается не websocket<If "%{SSL:SSL_CLIENT_VERIFY} = 'SUCCESS'"><If "%{HTTP:Upgrade} != 'websocket'">    SetEnvIf Cookie "zt-cert-sha1=([^;]+)" HAVE_zt-cert-sha1=$1    SetEnv zt_cert "path=/; HttpOnly;Secure;SameSite=Strict"#Новые куки ставятся, если старых нет    Header add Set-Cookie "expr=zt-cert-sha1=%{sha1:salt1%{TIME}salt3%{SSL_CLIENT_S_DN_CN}salt2};%{env:zt_cert}" env=!HAVE_zt-cert-sha1    Header add Set-Cookie "expr=zt-cert-uid=%{SSL_CLIENT_S_DN_CN};%{env:zt_cert}" env=!HAVE_zt-cert-sha1    Header add Set-Cookie "expr=zt-cert-date=%{TIME};%{env:zt_cert}" env=!HAVE_zt-cert-sha1</If></If>


Цель достигнута, но есть проблемы с серверным устареванием (можно использовать Cookie годичной давности), а значит токены, хоть и безопасны для внутреннего использования, но небезопасны для промышленного (массового).



4. Временные сессии-токены уже были реализованы в качестве готовых модулей Аpache.

С предыдущей итерации осталась одна существенная проблема невозможность контролировать устаревание токена.

Ищем готовый модуль, который это делает, по словам: apache token json two factor auth
Client authentication using tokens based on JSON Web Tokens
Apache Two-Factor (2FA) Authentication
How to Add Two-Factor Authentication to Apache
Bring two-factor authentication to your Apache instance with a simple module install

Да, готовые модули есть, но все привязаны к конкретным действиями и обладают артефактами в виде старта сессии и дополнительных Cookie. То есть не на время.
У нас ушло пять часов на поиск, который не дал конкретного результата.

5. Временные сессии-токены можно реализовать, логически спроектировав структуру взаимодействий.

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

(%{env:zt-cert-date} + 30) > %{DATE}

Можно сравнивать только два числа.

При поиске обхода проблемы Safari нашлась интересная статья: Securing HomeAssistant with client certificates (works with Safari/iOS)
В ней описан пример кода на Lua для Nginx, и который, как оказалось, очень повторяет логику той части конфигурации, которую мы уже ранее реализовали, за исключением использования hmac-метода расстановки соли для хеширования (такого в Apache не нашлось).

Стало понятно, что Lua это язык, с понятной логикой, возможно сделать что-то простенькое и для Apache:
LuaHookAccessChecker Directive
UnsetEnv Directive

Изучив разницу с Nginx и Apache:
modules_lua
lua_load_resty_core

И доступные функции от производителя языка Lua:
22.1 Date and Time

Найден способ задания переменных env в небольшом Lua-файле для того, чтобы установить дату из будущего для сверки с текущей.

Вот так выглядит простенький Lua-скрипт:
require 'apache2'function handler(r)    local fmt = '%Y%m%d%H%M%S'    local timeout = 3600 -- 1 hour    r.notes['zt-cert-timeout'] = timeout    r.notes['zt-cert-date-next'] = os.date(fmt,os.time()+timeout)    r.notes['zt-cert-date-halfnext'] = os.date(fmt,os.time()+ (timeout/2))    r.notes['zt-cert-date-now'] = os.date(fmt,os.time())    return apache2.OKend


И вот так это всё работает в сумме, c оптимизацией числа Cookie и заменой токена при наступлении половинного времени до истечения старых Cookie (токена):
SSLVerifyClient optional#LuaScope thread#generate event variables zt-cert-date-nextLuaHookAccessChecker /usr/local/etc/apache24/sslincludes/websocket_token.lua handler early#запрещаем без сертификата что-то ещё, кроме webscoketRewriteEngine onRewriteCond %{SSL:SSL_CLIENT_VERIFY} !=SUCCESSRewriteCond %{HTTP:Upgrade} !=websocket [NC]RewriteRule     .? - [F]#ErrorDocument 403 "You need a client side certificate issued by CAcert to access this site"#websocket for safari without certauth<If "%{SSL:SSL_CLIENT_VERIFY} != 'SUCCESS'"><If "%{HTTP:Upgrade} = 'websocket'">    SetEnvIf Cookie "zt-cert=([^,;]+),([^,;]+),[^,;]+,([^,;]+)" zt-cert-sha1=$1 zt-cert-date=$2 zt-cert-uid=$3    <RequireAll>        Require expr %{sha1:salt1%{env:zt-cert-date}salt3%{env:zt-cert-uid}salt2} == %{env:zt-cert-sha1}        Require expr %{env:zt-cert-sha1} =~ /^.{40}$/        Require expr %{env:zt-cert-date} -ge %{env:zt-cert-date-now}    </RequireAll>       #замещаем авторизацию по владельцу сертификата на авторизацию по номеру протокола    SSLUserName SSl_PROTOCOL    SSLOptions -FakeBasicAuth</If></If><If "%{SSL:SSL_CLIENT_VERIFY} = 'SUCCESS'"><If "%{HTTP:Upgrade} != 'websocket'">    SetEnvIf Cookie "zt-cert=([^,;]+),[^,;]+,([^,;]+)" HAVE_zt-cert-sha1=$1 HAVE_zt-cert-date-halfnow=$2    SetEnvIfExpr "env('HAVE_zt-cert-date-halfnow') -ge %{TIME} && env('HAVE_zt-cert-sha1')=~/.{40}/" HAVE_zt-cert-sha1-found=1    Define zt-cert "path=/;Max-Age=%{env:zt-cert-timeout};HttpOnly;Secure;SameSite=Strict"    Define dates_user "%{env:zt-cert-date-next},%{env:zt-cert-date-halfnext},%{SSL_CLIENT_S_DN_CN}"    Header set Set-Cookie "expr=zt-cert=%{sha1:salt1%{env:zt-cert-date-next}sal3%{SSL_CLIENT_S_DN_CN}salt2},${dates_user};${zt-cert}" env=!HAVE_zt-cert-sha1-found</If></If>SetEnvIfExpr "env('HAVE_zt-cert-date-halfnow') -ge %{TIME} && env('HAVE_zt-cert-sha1')=~/.{40}/" HAVE_zt-cert-sha1-found=1работает,а так работать не будетSetEnvIfExpr "env('HAVE_zt-cert-date-halfnow') -ge  env('zt-cert-date-now') && env('HAVE_zt-cert-sha1')=~/.{40}/" HAVE_zt-cert-sha1-found=1 


Потому что LuaHookAccessChecker будет активирован только после проверок доступа исходя из этой информации от Nginx.


Cсылка на источник изображения.

Ещё один момент.
В целом неважно, в какой последовательности в конфигурации Аpache (вероятно и Nginx) написаны директивы, так как в итоге всё будет отсортировано исходя из очерёдности прохождения запроса от пользователя, который соответствует схеме для отработки Lua-скриптов.

Завершение:
Видимое состояние после внедрения (цель):
Управление сервисами и инфраструктурой доступно с мобильного телефона на IOS без дополнительных программ (VPN), унифицировано и безопасно.

Цель достигнута, веб-сокеты работают и обладают не меньшим уровнем безопасности, чем сертификат.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru