Showing posts with label russian. Show all posts
Showing posts with label russian. Show all posts

Tuesday, September 1, 2020

Dangers of air-gapped networks

 Некоторые мысли по поводу безопасности изолированных сетей


Мне доводилось довольно много возиться с тем, что называют "air-gapped networks" - то есть с сетями, которых ни при каких обстоятельствах к Интернету подключать нельзя. Запрет этот диктуется, естественно, соображениями безопасности - "а вдруг нас хакнут". Идея предотвратить хаканье на корню отключением от Интернета, конечно, привлекательна своей простотой и радикальностью, но тем не менее, принимающие такое решение зачастую упускают из вида разные последствия, безопасность не улучшающие, а наоборот.


1. Самое очевидное - патчи и обновления антивирусов. Всякий раз при проектировании кто-нибудь говорит "ну, мы, конечно, будем их раз в месяц снимать, прогонять через систему санитизации и устанавливать", но с этим есть две проблемы:

1.1 Этого попросту не происходит. Всегда всем не до того.

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

В результате, если уж в нашу надёжно изолированную сеть каким-то образом проникла малварь, использующая недавно найденные дырки - ей будет раздолье. А проникнуть она может разными путями - либо процедура санитизации её попросту не обнаружит ввиду своих устаревших баз, либо юзер забьёт на инструкции и её обойдёт.


2. Отсутствие уведомлений - как об угрозах безопасности (антивирус нашёл малварь), так и всяких других (отказал хард-диск). К слову, смысл всякой high availability, будь то RAID или VRRP, лишь в том, чтобы позволить системе жить, пока мы, админы, устраняем неполадку. Но для этого нужно вовремя об этой неполадке узнать - а то за одним диском откажет другой, и прощай, RAID. 

Теоретически, можно поставить какой-нибудь SMS gateway для отсылки уведомлений админам на телефон - но на практике этого никто не делает.


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


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


5. Невозможность использовать существующие user identities - заводи отдельную Active Directory или иной каталог, раздавай юзерам отдельные пароли. Как легко догадаться, приводит это к паролям, записанным на пришпилиных к экранам бумажках, к десяти юзерам под одним аккаунтом и прочим прелестям.

Теоретически, можно использовать PKI со смарткартами и USB-токенами - и в обычных сетях, и в изолированных. Но:

5.1 Это, опять же, требует инвестиций,

5.2 Регуляции могут запрещать тыкать в "чистый" комп изолированной сети тот же девайс, что и в обычные,

5.3 Возникает вопрос отзыва сертификатов. Можно импортировать CRL, но опять же, кто будет это делать и как часто? А использовать OCSP вообще невозможно.


6. Отсоединённость от мировых часов. В принципе, существует множество железячных time servers, умеющих получать сигнал точного времени по GPS, GLONASS и даже по радио, и затем распространять его в сети по протоколам NTP & PTP, но это опять же инвестиция, которую никто совершать не желает.

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


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


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

Saturday, January 26, 2019

Why Azure supports only unicast traffic?

Заинтересовался таким вопросом: почему микрософтовское облачко, Azure, не позволяет групповой (multicast) и широковещательный (broadcast) трафик между виртуальными машинами? Не означает ли это, что они используют на уровне ниже IP не Ethernet, а какой-то свой протокол, специально под облако заточенный?

(Я не имел дела с Амазоном, но и там та же петрушка, насколько я понимаю.)

Ответ: в общем, да.

В Azure решают проблему создания миллионов виртуальных сетей поверх единой физической благодаря протоколу VXLAN - если Ethernet-фрейм, посылаемый виртуалкой, предназначается для виртуалки на другом хосте, то он инкапсулируется виртуальным свитчем (процессом, бегущим на гипервизоре) внутрь UDP-пакета, и этот пакет доставляется по физической сети этому другому хосту. Там виртуальный свитч извлекает оригинальный фрейм и передаёт его своей виртуалке. Для обоих виртуальных машин всё выглядит так, словно они сидят в одном сегменте Ethernet.

Как в такой архитектуре можно передавать multicast или broadcast трафик? Два способа:

1. Рассовывать его по пакетам-носителям типа multicast и посылать их всем хостам, на которых определёна та же виртуальная Ethernet-сеть (насколько я понимаю, ей в Azure соответствует объект Subnet).

2. Рассовывать его по пакетам-носителям типа unicast и размножать их: посылать отдельную копию для каждого хоста из того же списка.

Обе опции проблемны. Допустим, у нас есть 1000 хостов, но из них виртуалки в этой сети существуют лишь на 20-ти. Тогда:
  • В первом случае нам придётся доставлять пакет-носитель всей тысяче хостов. Раутеры и свитчи в физической сети между ними понятия не имеют, какие виртуальные сети существуют на каких хостах, и потому оптимизировать трафик, доставляя такой пакет лишь заинтересованным в нём хостам (подобно тому, как это делается функцией IGMP snooping в традиционных VLAN-ах) не могут. Стало быть, если у нас какая-то виртуалка сгенерирует multicast-поток в 100Mbps, то каждый хост в датацентре получит все эти 100Mbps, в большинстве случаев совершенно для него бесполезные.
  • Во втором же случае поток в 100Mbps приведёт к тому, что хосту-отправителю придётся высылать 1.9Gbps (и даже слегка больше, учитывая overhead), высылая на каждый пакет с виртуалки девятнадцать своих, по одному на каждый хост с той же сетью.
То есть оба варианта могут значительно добавить нагрузку на облако - на сеть и на процессорное время, потраченное на обработку бесполезного трафика. Именно поэтому инженеры Azure решили, что единственным разрешённым видов трафик будет unicast. На каждый пакет, посылаемый виртуалкой, будет приходиться максимум один пакет, посылаемый по физической сети меж хостами - либо вообще ни одного, если предназначен он виртуалке на том же хосте.

Тут внимательный читатель воскликнет - позвольте, а как же ARP? Как же виртуалки будут узнавать MAC-адреса друг друга или раутеров, и что вообще будет писаться в поле "destination MAC" исходящих пакетов?

А вот что: на любой ARP-запрос с виртуалки vSwitch, то есть процесс, бегущий на хосте, ответит одним и тем же фальшивым MAC-адресом: 12:34:56:78:9a:bc. То есть любой пакет, на любой IP-адрес, будет послан виртуалкой на этот destination MAC.

Ну хорошо, а как же тогда пакеты доставлять? На адрес A в той же сети пакет ушёл с этим MAC, и на адрес B в ней же, и на адрес C в другой сети, и на адрес D вообще где-то в Интернете - и у всех MAC одинаковый. Что же с ним делать?

Ответ: а ничего не делать. Облаку плевать на MAC, доставка происходит прямо по IP-адресу получателя. То есть - к примеру, vSwitch получает от виртуалки пакет, где dst IP = 1.2.3.4 и dst MAC = 12:34:56:78:9a:bc. Если адрес 1.2.3.4 принадлежит другой виртуалке на том же хосте и той же сети, то пакет будет передан ей, и конец делу. Если же на другом хосте - то пакет будет обёрнут в VXLAN-пакет на адрес того хоста, и послан туда. Там, соответственно, будет декапсулирован и передан по назначению.

То есть традиционный Ethernet-свитчинг  в Azure customer networks  реально не используется - Ethernet лишь эмулируется для виртуалок. Этим эти сети отличаются от customer networks в OpenStack или CloudStack, где виртуальные и физические свитчи по-прежнему имеют дело с MAC-адресами.

Отсюда следуют несколько важных следствий:

1. Любой протокол не на основе IP (скажем, IPX, если кто ещё помнит такой) работать в Azure не может по определению.

2. Нельзя просто раздать виртуалкам дополнительные адреса (secondary IPs, loopback adapters) или передавать через них трафик из других сетей (скажем, использовать их для VPN) и ожидать, что всё будет работать. Нет. Облако должно знать, какие адреса относятся к каким машинам. Если машина посылает пакет с source IP, который ей не принадлежит и о котором облако не знает, он будет выкинут нафиг.
Есть два способа это починить:

  • если этот source IP относится к тому же сабнету - то можно облаку указать его как secondary IP этой виртуалки.
  • или же можно указать, что виртуалка занимается передачей чужого трафика (включить IP forwarding) и настроить на неё user-defined route (UDR), чтобы ответы на эти адреса приходили к ней. Обычно это делают, если виртуалка работает как firewall / VPN gateway.
3. Допустим, у нас есть сеть 10.10.10.0/24, в ней есть две машины (10.10.10.11 и 10.10.10.12), и для обеих определён общий default gateway, 10.10.10.1.
И допустим, что вторая виртуалка работает VPN-гейтом, передавая трафик от удалённой сети 192.168.12.0/24.
Тогда чтобы позволить первой машинке общаться с этой удалённой сетью, в обычных сетях нам надо было бы определить на ней маршрут через вторую:
    route -p add 192.168.12.0/24 10.10.10.12

Но в Azure в этом нет необходимости. Поскольку в тот момент, что первая виртуалка пошлёт пакет на адрес, скажем, 192.168.12.222 - будет совершенно неважно, имела ли она в мыслях передать его напрямую, или на дефолтовый гейт 10.10.10.1, или на VPN-гейт 10.10.10.12, и чей именно MAC-адрес она запрашивала по ARP миллисекундой ранее. Пакет будет выглядеть совершенно одинаково:
src IP  = 10.10.10.11,     dst IP  = 192.168.12.222
src MAC = some_MAC_of_VM1, dst MAC = 12:34:56:78:9a:bc


После чего произойдёт следующее: vSwitch проверит свою таблицу маршрутов и узрит в ней user-defined route: на сеть 192.168.12.0/24 ступай через 10.10.10.12. После чего пакет передан второй виртуалке - либо напрямую, если она на том же хосте, что и первая, либо, если она на другом хосте - то через туннель VXLAN между этими хостами.

Вот такие пироги.

Thursday, December 27, 2018

Highly-available Fileserver in a IaaS cloud

Вот есть, скажем, IaaS-облачко, а в нём виртуалка с Windows, работает файловым сервером - публикует папки по обычному протоколу SMB. Хочется, чтобы если она упадёт, файлы были доступны с какой-нибудь другой. Как этого добиться минимальными средствами?

Казалось бы, для такого совершенно стандартного желания и решение должно быть совершенно стандартным, особенно уж для такой неновой и распространённой штуки, как общие папки Windows: поставь такую-то фичу, и щёлкни раз-два-три. Ага, щас!


Вариант 1 - shared storage

Удобнее всего, конечно, чтобы у двух виртуалок был общий диск. Тогда на них можно настроить Failover Cluster, в нём запустить fileserver role с такой прекрасной фичей, как Transparent Failover, которая позволяет клиентским приложениям вообще не чувствовать, что сервер сменился. Клиенты будут обращаться по некоему floating IP, который будет переходить с сервера на сервер вместе с ролью в случае фэйловера. Скажем, в данном примере этому IP соответствует DNS-имя Filer:

Но увы! наше облачко такое не предоставляет.


Вариант 2 - репликация

Пускай у каждой из виртуалок будет свой местный диск, а мы будем файлы между ними синхронизировать.
В набор Windows Server для таких дел входит DFS Replication (DFS-R).

Достоинства:
  • фича входит в стандартный набор в Windows Server, ей сто лет в обед.
На этом достоинства заканчиваются и начинаются недостатки:
  • главный - никаких гарантий по части целостности данных. Один клиент может изменять файл на одном сервере, а другой в то же - тот же файл на другом сервере, и пожалуйста: конфликт;
  • если файл залочен клиентом, то изменения в нём копироваться не будут, пока клиент замок не снимет; пример - логи. Стало быть, его чтение с другого сервера реальное содержание не покажет;
  • очень непрозрачный и недокументированный способ работы. Пример: если файлы синхронизуются более-менее сразу, то вот изменения в NTFS permissions или ownership - со здоровенной задержкой, могущей доходить минут до 20. В чём логика, догадываюсь - такое изменение может переписать ACLs сверху вних по здоровеннейшему дереву, так что лучше подождать, пока юзер закончит их вносить, и лишь затем реплицировать. Но вот не документировано это нигде. В случае накладок, десинхронизации, конфликтов понять, что именно пошло не так, очень тяжело.
Схожий по функциям софт от прочих производителей я не пробовал, но наверняка и у них те же ограничения.

Поскольку это не кластер, и никакого floating IP между серверами нет, как нам обеспечить переключение? Тут может помочь другая фича - DFS-Namespaces или DFS-N, она позволяет распределять клиентов между двумя или более серверами. К Replication она отношения прямого не имеет - она просто позволяет замаскировать UNC-пути к общим папкам на разных серверах за одним общим путём, и неважно, каким образом файлы на этих серверах оказались. К примеру, при обращении к контроллеру домена example.org по пути \\example.org\SHARED\Folder, SMB-клиент Windows получит ссылки на два пути:
  • \\server1.example.org\SharedFolder
  • \\server2.example.org\SharedFolder
Он выберет одну из них и будет работать соответственно либо с одним сервером, либо с другим - а если тот перестанет отвечать, переключится на другой.

Фича полезная, но failover достаточно медленный - если сервер вдруг пропадает с горизонта, то клиент сперва пытается посылать пакеты по существующему TCP-соединению, затем - открыть новые на тот же сервер, и лишь затем - пойти на следующий сервер по списку, полученному от DFS namespace. Уходит на это секунд 80.

Ещё возможность: просто определить DNS-имя, указывающее на IP-адреса обоих серверов -  к примеру:
  • filer.example.org = 192.168.10.100
  • filer.example.org = 192.168.20.100
Тогда клиенты распределятся между серверами - получив от DNS оба адреса, одни выберут первый, а другие - второй. Если же выбранный адрес перестанет отвечать, SMB-клиент достаточно умён, чтобы переключиться на второй - см. ниже про failover. Занимает процесс около 30-ти секунд.

Недостаток - не будет работать протокол аутентификации Kerberos, поскольку SMB-служба на каждом сервере привязана к его личному computer account в Active Directory, а аккаунт - к его личному хостнейму. Привязать же общий хостнейм к обоим аккаунтам нельзя. Можно ли запустить SMB-службу не под локальным аккаунтом (SYSTEM), а под доменным - обычным юзерским или gMSA? Не знаю, не пробовал.

Впрочем, всегда есть в запасе старый добрый NTLM.


Вариант 3 - виртуальный shared storage

Если облако не предоставляет shared storage, давайте построим его сами!
Тут, конечно, напрашивается воткнуть ещё одну виртуалку, которая будет предоставлять диски для наших файлсерверов по протоколу iSCSI - и тогда мы можем построить красивенький Failover Cluster и радоваться. Но это будет, естественно, самообман, потому как тогда наш iSCSI-сервер станет единой точкой отказа - если он рухнет, то всему хана.

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


Можно попытаться функции совместить: а пускай наши файлсервера заодно и работают как virtual SAN - синхронизируют между собой некий виртуальный диск и к нему же и подключаются как iSCSI-клиенты (то, что называется модным словом hyper-converged).
Но:
  • это повышает риск - надо полагаться на то, что при поломке, падении, перезагрузке не только Failover Cluster отработает как надо, но и virtual SAN.
  • вариантов virtual SAN очень много, но большинство из них под Линукс. Стало быть, на наши виртуалочки их не воткнёшь.
  • вероятно, что количество виртуалок всё равно придётся повышать до трёх, чтобы избежать возможности split brain.
На практике я опробовал два подхода к этому снаряду:

1. Windows S2D - первый кандидат, поскольку готовая фича в Windows Server 2016. Очень хотелочь бы опробовать, но увы. У неё суровые требования к локальным дискам, которые без хаканья на уровне VMware обойти нельзя. А облачко подобных игр не позволяет.

2. StarWind vSAN - привлёк внимание тем, что написан для Windows и интересной бизнес-моделью: разница между платной и бесплатной версиями лишь в отсутствии в бесплатном варианте техподдержки и графической консоли - управление идёт через PowerShell. Освоить ихний PowerShell совсем несложно, но:
  • требует он скрупулёзной внимательности и аккуратности, особенно если создаёшь много дисков; цена опечатки может быть слишком высока.
  • увы, багов слишком много. Диски могут рассинхронизироваться навсегда от простой перезагрузки; одни и те же команды могут выполняться раз выполняться, а раз - выдавать совершенно невразумительные ошибки; траблшутить всё это одному - не легче, чем DFS, а то и тяжелее. И привести эти баги могут к очень тяжёлым последствиям.
Основательно намучившись, решил, что в продакшене у меня никакого Старвинда не будет. Возможно, что при работе через платный GUI вдруг та же система проявляет чудеса надёжности, но мне это что-то сомнительно.


Конечный вариант - слоноптица

В конце остановился на совмещении вариантов 1 и 2: дублировать файлы с помощью DFS Replication, но при этом использовать плавающий адрес Failover Cluster для доступа к ним.


Иными словами, если у нас есть \\server1.example.org и \\server2.example.org, то для доступа к файлам мы будем использовать  некое третье имя - скажем, \\filer.example.org - и оно будет привязано к кластерному IP-адресу, переходящему от одного сервера к другому в случае падения. Или же IP-адресам, в случае если наши сервера сидят в разных сетях.

Строится это следующим образом:
  1. Определяем DFS-R между двумя машинами.
  2. Добавляем обе в Failover Cluster. Поскольку shared storage у нас нету, используем какой-нибудь удалённый shared folder как quorum witness - или Azure как cloud witness.
  3. Создаём в кластере empty role, даём этой роли некое имя, привязываем к IP-адресу, прописываем в DNS. Computer account в Active Directory для этой роли не создаётся - незачем.
  4. Определяем на каждой машине те же shared folders на основе дерева, синхронизируемого посредством DFS-R.
Это позволяет, с одной стороны, использовать локальные диски. а с другой, избежать недостатков DFS:
  • все клиенты юзают тот же сервер, который будет предоставлять замок на запись в конкретный файл лишь одному из них. Чтение изменений в файле доступны всем сразу.
  • соответственно, нет конфликтов между серверами, все изменения идут в одном направлении.
  • очень быстрый graceful failover, если оба сервера в той же сети (не graceful - не очень). См. ниже.
Но стоит учитывать:
  • мы лишаемся возможности распределять нагрузку между серверами (нам это особенно и не нужно - она и так мала).
  • если файл был открыт на запись, были внесены изменения, записаны на диск, но сервер рухнул перед тем, как замок был снят - значит, реплицированы эти изменения не были. Если рухнувший сервер подымется перед тем, как тот же файл был изменён на втором - репликация произойдёт. Если после - то реплицирована будет версия файла со второго сервера, поскольку у неё будет метка времени посвежее.

Дополнения и уточнения
Свидетель из Фрязино

Тонкий момент - если мы просто подымем два сервера в рамках Failover Cluster и будем ждать, что они будут друг друга взаимно подстраховывать, нас ждёт облом. В тот момент, что один из них отключится, роль файлсервера остановится так же и на втором, прекратит работать наш кластер.

Почему? Для избежания неприятной ситуации под названием "split brain", когда оба сервера считают себя главными и могут, в отсутствии координации друг с другом, искорёжить данные.

Как этого избежать? Есть два варианта:
  • Добавить третий сервер в кластер - и заодно в replication group DFS-R, чтобы копия файлов была на всех трёх.
  • Добавить так называемого "свидетеля" (witness).
Свидетель этот является не процессом, как сервер, а просто-напросто неким "журналом", куда оба сервера могут писать, отмечаясь, что они живы и указывая, кто из них в активной роли. 
  • Если сервер обнаруживает, что не слышит второй, но "свидетель" доступен - он продолжает работать.
  • Если он обнаруживает, что не может связаться ни со вторым, ни со "свидетелем" - он останавливается.
  • Если оба сервера общаются нормально, но "свидетель" отвалился - они продолжают работать как обычно.

В Windows 2016 есть три варианта для witness:
  1. Либо это shared disk, общий для всех узлов кластера - но мы начали с того, что наше облако таких не предоставляет. Если предоставляет - нам не нужна эта морока с DFS-R, стоит выстроить просто обычный Failover Cluster по рецептам Microsoft.
  2. Либо эта shared folder на каком-то третьем SMB-сервере - но тогда надо иметь этот сервер. Его не надо подключать в кластер, достаточно, чтобы он был доступен с обоих кластерных узлов. Это может быть контроллер домена, к примеру.
  3. Либо (новая фишка Win2016) это может быть blob в микрософтовском облаке - в Azure storage account. Но тогда надо располагать подпиской на Azure.

Немного технических потрохов про failover:

Скорость переключения очень зависит от того, происходит ли оно в результате того, что бывший активный сервер сам сдаёт полномочия (graceful failover - по команде админа или из-за "мягкой" перезагрузки) - или же в результате резкого падения сервера или полной потери связи с ним. Если graceful может занять до 5 секунд, то hard failover - до 20-ти. Разница происходит от того, что оставшемуся серверу надо во втором случае установить, что ранее активный признаков жизни не подаёт - ни напрямую, не через witness (см. ниже) - а в первом тот может сообщить и сам.

После того, как второй сервер решает тянуть одеяло на себя, события могут развиваться по следующим сценариям:

Внутри одной сети (VLAN, subnet):

Перенос кластерного IP - очень быстрая операция, прозрачная для клиента: сервер, забирающий его к себе, отсылает ряд пакетов Gratuitous ARP (GARP), прописывающих новый MAC-адрес для этого IP в ARP-таблице подключённых к той же сети раутеров или клиентов. Заодно и свитчи, физические или виртуальные, прописывают у себя, на каком порту сидит этот MAC. Всё, трафик продолжает течь как тёк.
Естественно, TCP-подключения при этом слетят - поскольку новый сервер вдруг получит какие-то TCP-пакеты без SYN-флага. Но поскольку при этом он отошлёт RST, клиент возобновит их сразу же, без попыток повторных передач.

Между разными сетями:

Естественно, перекинуть floating IP из сети в сеть нельзя, да и незачем. Решается эта задача вот как. Наш Failover Cluster в курсе о существовании двух сетей - по каждой на сервер. Мы можем привязать нашу кластерную "роль" к двум IP-адресам - по одному на сеть - и прописать между ними "OR dependancy". Иными словами, для того, чтобы "роль" запустилась на хосте, достаточно, чтобы один из этих адресов был доступен.

Затем мы прописываем оба адреса в DNS на одно и то же имя - к примеру:
  • filer.example.org = 192.168.10.100
  • filer.example.org = 192.168.20.100
SMB-клиенты при запросе получают из DNS оба адреса и пытаются параллельно подключиться на оба. Лишь один из них ответит (в этом разница со случаем, если бы мы просто приписали к этому имени частные IP-адреса server1 и server2, о чём см. выше).

Если посреди работы с одним из адресов он перестаёт отвечать - Windows пытается в течении 15 секунд повторять (retransmit) запрос на уровне того же TCP-соединения - а затем посылает RST и снова пытается подключиться на оба одновременно. И если наш Failover Cluster отработал как положено - то соединение на второй адрес будет установлено благополучно.

В обоих вариантах, ещё пара секунд уйдёт на аутентификацию клиента против нового сервера - по Kerberos или NTLM.

Какой вариант быстрее? Это, как сказано выше, зависит от того, насколько быстро передадут друг другу роль наши сервера.
В случае graceful failover - переключить IP-адреса между двумя MAC-адресами в той же сети берёт намного меньше времени, чем дождаться, пока SMB-клиент начнёт стучаться по запасному адресу.
В случае же hard failover - наибольшую задержку обеспечит сам кластер.

Но и даже в наихудшем случае оба варианта всё же куда быстрее, чем переключение между двумя UNC-путями, полученными от DFS-N.


Ещё свидетель. Но совсем другой. И сугубо опциональный.

Есть некий способ сократить время, которое SMB-клиент тратит на переподключение. Идея в следующем: подключившись к кластеру по floating IP, клиент получает адреса всех узлов кластера. Затем он устанавливает соединение на один из неактивных узлов, и просит тот сообщить ему по дружески, если активный вдруг отвалится  - предполагается, что узлы кластера узнают об этом событии раньше, чем клиенты. Если клиент получит такую подсказку - то начнёт переподключение немедленно, не ожидая, пока существующее TCP-соединение вылетит по таймауту.

Называется этот узел-подсказчик опять таки witness - и он, хотя имеет прямое отношение к Failover Cluster и к shared folders, не имеет ничего общего со "свидетелем" для координации кластера, о котором рассказано выше. Спасибо Микрософту за этот гнусный бардак в терминологии.

Несмотря на то, что звучит эта идея интересно, у себя я это включать большого смысла не вижу, и вот почему:
  • в случае переключения между узлами кластера в той же сети IP-адрес остаётся тем же, и клиент будет пытаться переподключиться на него же. Если новый активный узел этот адрес ещё не подобрал - неважно, продолжает ли клиент стучаться по тому же TCP-соединению, или пытается установить новое, один чёрт он в ответ ничего не получит. Если же подобрал - то на попытку retransmission по старому соединению он моментально ответит сегментом RST, после чего клиент тут же откроет новое.
  • В случае hard failover между узлами в разных сетях, новому активному узлу возьмёт даже больше времени принять на себя роль, чем клиенту - переподключиться по запасному адресу.
Таким образом, единственный случай, где есть смысл в этом протоколе - это graceful failover в кластере, разнесённом по двум разным сетям. Но дело ещё и в том, что он не очень-то дружелюбен к файерволлам. Для того, чтобы он работал, нам надо дополнительно к SMB на floating IP позволить также RPC на него же, а так же RPC на все индивидуальные адреса кластерных узлов. Поскольку открывать при этом клиентам доступ на личные SMB-службы этих узлов не хочется, эта бодяга требует двух правил вместо одного:
  1. SMB на адрес fileserver role
  2. RPС на адрес fileserver role + адреса серверов
Во-первых, это усложняет конфигурацию, а во-вторых, открытие доступа к RPC само по себе является дополнительным риском. Поэтому овчинка, на мой взгляд, выделки не стоит.

Впрочем, насколько я понимаю, придуман witness protocol не столько для кластерного файлсервера общего назначения, о котором этот пост, сколько для Scale-Out File Server. А в рамках Scale-Out не существует floating IP - соединения устанавливаются на адреса самих серверов. В этом случае заранее знать, что такой-то адрес отвалился и стучаться к нему незачем, действительно имеет большой смысл. Впрочем, Scale-Out я тут не рассматриваю.

Saturday, August 5, 2017

Publishing Windows domain DNS to outer world

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

Но поскольку сеточка тестовая, то секретность ейной внутренней структуры мне пофигу, а вот выигрыш от открытости есть: какие бы DNS-сервера не были прописаны у юзеров, каким бы методом они не подключались - по личному VPN, по site-to-site VPN, да хоть напрямую - записи будут им доступны. Поскольку вечные приколы VPN с сетевыми настройками задрали изрядно, универсальности хочется.

Что до домен-контроллеров, то открывать их во всеобщий доступ и мне не хотелось, по двум соображениям:

  • Чтобы не подставлять их под DoS или ещё какую пакость,
  • Чтобы не отключать на них рекурсию. Я хочу, чтобы они предоставляли внешнему миру информацию лишь о моём домене, а не о любом в мире. Можно, конечно, отключить, но тогда для машин во внутренней сети надо дополнительно что-то городить - лишнее усложнение.
Поэтому пошёл искать службу-посредник, и нашёл: BuddyNS.

Процесс простой:

  1. На файерволле разрешить входящий DNS-трафик только от их IP-адресов (и исходящий на них же - для уведомлений).  Разрешить по обоим протоколам - и TCP, и UDP.
  2. В настройках DNS-сервера разрешить этим адресам zone transfer, и их же внести в список, кому посылать уведомления об изменениях в зоне).
  3. Указать несколько их серверов как Name Servers для конкретного домена. Этот список не имеет отношения к списку IP-адресов, вытягивающих записи из наших DC, хотя пересечения есть.
  4. Указать те же сервера как ответственные за наш домен на родительском домене (или у DNS-регистратора).
  5. Зарегистрироваться у BuddyNS, указать домен и внешний IP-адрес нашего DC, откуда те попытаются вытащить записи этого домена.
Пара примечаний:
  • Эта контора только публикуют slave zones во внешний мир - форвардером они не работают. Если нет желания давать DC запрашивать DNS-сервера по всему миру, можно в качестве форвардеров указать адреса Гугла или OpenDNS. Понятно, не забыть про файерволл.
  • Понятно также, что с доменами типа .local делать нечего. Не надо такие заводить изначально.

Israeli electronic ID bowels

Недавно мимо меня пробегало новенькое израильское удостоверение личности (теудат зеут) - уже не бумажка, а смарткарта - и я, конечно, тут же его зацапал глянуть, что на ентой смарткарте есть. Мда. После потрошения ейного сертификата могу честно сказать - построено ужасно, просто ужасно.
Collapse )


Технические потроха:

1. Поле AIA - линк на службу проверки статуса сертификатов, OCSP:
Такой хост DNS-у просто неизвестен, и служба, соответственно, недоступна.

Время жизни CRL - три дня. Что означает, если моё удостоверение покрано, то даже если МВД отзовёт сертификат немедленно - то может пройти полных три дня, пока он перестанет приниматься сторонами, проверяющими этот CRL.

2. Поле AIA - линк на вышестоящий сертификат выглядит так:
Этим идиотам никто не объяснил, что использовать HTTPS для линков в AIA и CDP нельзя? Потому что может возникнуть логическая петля?
Да к тому же и линк битый.

То есть если у меня есть корневой сертификат, и мне надо проверить сертификат со смарткарты, а промежуточного у меня нет - то увы мне. Цепочку не выстроить.

Аналогичный линк с промежуточного на корневой:
Опять HTTPS, но на этот раз хотя б линк не битый. Зато сервер отдаёт сертификат с неправильным MIME-type: text/html вместо application/x-x509-ca-cert.

3. Линки на CRL - их два:
В конечном:
В промежуточном:
Второй в паре - битый, хост crl2.igcas.gov.il на подключения не отвечает.

4. Линки на документы с policy:
В промежуточном:
В корневом:
Как ни странно, рабочие, хотя один из них с HTTPS, причём сайт оборудован неизвестным никому сертификатом, поэтому браузер выбрасывает ошибку. Впрочем, кто её читает, эту галиматью?

5. В поле SAN значится такое:
  • Principal Name=номер_ID@res.igcas.gov.il
Такой хост DNS-у неизвестен. Не то, чтобы это проблема, но жалко: можно было бы каждому сделать официальный мейлбокс или алиас.

6. Поле "Enhanced Key Usage":
  • Client Authentication
  • Smart Card Logon
А почему не Document Signing, к примеру? Да, шифровать ключом, у которого нет бэкапа, и впрямь не стоит - но почему не дать людям возможность документы подписывать, например?

7. Subject корневого выглядит вот так: 
    CN = Residents eID Root CA 12-01
    OU = Population and Immigration Authority
    O = Government of Israel
    C = IL

А почему бы не произвезти его от главного государственного сертификата, тем более, что такой, похоже, есть: Government of Israel Root CA G2 (правда, эти кретины отдают его мало того, что в бинарном формате, так ещё и с кривым MIME-type)? Чтобы цепочку доверия можно было легче выстроить?


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

Если кто хочет повтыкать в детали сам - припадайте. "Leaf" сертификат со смарткарты не выкладываю из соображений приватности владельца, а правительственные - на здоровье.

Authentication by client certificates as anti-interception measure

У аутентификации по клиентским сертификатам есть множество ограничений, из-за которых она редко используется:
- общая сложность для понимания и настройки,
- сложность с управлением ключами, особенно когда юзеру надо ходить с нескольких устройств, с их обновлением,
- сложность с отзывом сертификатов, необходимость держать доступными точки CRL и OCSP, что может быть довольно геморройно, если у нас не общий Интернет, а изолированые сети,
- нельзя защитить часть сайта - скажем, чтобы https://hostname/public был доступен всем, а https://hostname/private требовал сертификата для опознания юзера. Точнее, этого результата можно добиться, но лишь при применении определённых хитростей.
- отсутствует нормальный logout - для того, чтобы сайт перестал тебя опознавать, надо полностью закрывать браузер (или изначально заходить в incognito-окне).

Но есть одно крутое преимущество - по сравнению с другими методами логина поверх SSL, она сильно усложняет проведение атаки man-in-the-middle, она же SSL interception. Вплоть до "вообще никак".

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

Способов добавить сертификат в список доверяемых есть масса, начиная с домейновых group policies и windows update, и заканчивая малварью и банальной социальной инженерией. Более того, тот же Avast antivirus делает это просто по дефолту - добавляет сертификат своего типа-CA в "Trusted Root Certification Authorities" и расшифровывает трафик.

Но когда требуется сертификат ещё и клиентский, то всё намного усложняется, посколько тогда система-перехватчик должна подменить и его тоже. А сервера куда недоверчивее в этих вопросах, чем браузеры. Им, как правило, просто указывают - сертификаты от этого CA и от этого принимать, а остальных лесом. К примеру, в FortiGate при создании PKI-юзера надо в явном виде задать и CA, которым его сертификат должен быть подписан, и что у этого сертификата должно быть в Subject. К тому же, тот может быть и вовсе самоподписанным. Тут не разгуляешься.

А подменить серверный сертификат, не подменяя клиентский, посылая его как есть - не выйдет. У сервера банально циферки не сойдутся, на чём подключению и конец.

Так что стоящая вещь, товарищи, пользуйтесь. :-)

(Это я тут с админом одной сеточки пообщался - негодовал, что не может инспектировать наш SSL-VPN. Ну, мои тебе соболезнования, приятель. :-))

Monday, April 24, 2017

Google Apps as SAML IdP

Ознакомился с возможностями Гугла в качестве Identity Provider по протоколу SAML.
Вкратце: не впечатлён. Понимаю теперь, почему ЖЖ, StackOverflow и прочие, тысячи их, используют oAuth.

Недостатки такие:

  • Identity сайта / приложения, желающего опознавать гугл-юзеров по SAML, криптографически никак не проверяется, запросы не подписываются, да и возможности залить соответствующий ключ Гуглу для проверки нету.
    Единственное, что проверяется - что строка "Entity ID", указанная Гуглу при регистрации приложения, соответствуюет полю <saml:Issuer> в запросе. Есть совпадение - и ладушки.

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

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

  • В отличие от сценария с oAuth, Гугл никак не предупреждает юзера, что собирается передавать информацию о нём третьей стороне, и уж тем более не разъясняет, какую именно.
    Просто получаешь страничку аутентификации, проходишь её и возвращаешься на исходный сайт, без каких-либо комментариев. Нехорошо, товарищи гугловцы.

  • Недостаток, несколько смягчающий два предыдущих: очень убогие возможности по части юзерских атрибутов, которые можно прописать в ответе запрашивающему приложению. Можно указать имя, фамилию, e-mail, номер телефона или адрес (а вот это для фишеров уже интересно!), а из корпоративной информации - название должности, департамент и cost center (что бы это не значило). Фсё. Группы, к которым юзер принадлежит в Google Apps, указать никак нельзя, к глубокому моему удивлению.
    ADFS по этому критерию кроет Гугл, как бык овцу.

  • А да, и касается вся эта бодяга исключительно юзеров G-Suite (Google Apps). Обычные юзеры, с @gmail.com в названии, могут не волноваться.

Недостаток у oAuth про сравнению с SAML, правда, тоже есть: он требует от приложения знать конкретный API, от которого оно будет получать информацию после получения юзерского согласия - гугловский ли, фейсбучный или твиттеровский. SAML более стандартизирован (и оттого сложен, как моя жизнь). Хотя при "феодальной" модели, при которой identity provider'ов для 90% сетевого населения можно пересчитать по пальцам, этот недостаток не очень-то заметный.

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

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


А вот чем очень приятно впечатлён, это опен-сорсной библиотекой Keycloak, которую использовал, чтобы встроить SAML  в свою маленькую Java-апликацию. Вставляешь servlet filter - и готово, можешь считать юзерский аккаунт и, положим, департамент, через привычные, милые сердцу вызовы getUserPrincipal() и isUserInRole()Причём, поскольку это стандартный servlet filter, можешь гонять свою апликашку на любом Java-сервере - хошь Tomcat, хошь Glassfish, хошь JBoss.

Saturday, November 26, 2016

Adventures with FortiGate LB

Столкнулся на днях с довольно загадочной ситуацией.

Преамбула
Дано: есть сайт об осьми веб-серверах. Есть перед ними load balancer в виде файерволла FortiGate v5.4.1 - распределяет между ними нагрузку, а заодно переводит HTTPS-трафик в HTTP и инспектирует его. А перед ним находится сеть CDN, которая в свою очередь также шерстит трафик на предмет всяких там SQL-иньекций, блокирует DDoS-атаки, кэширует картинки и прочую статику - в общем, полезная вещь.

Когда юзер открывает сайт, DNS направляет его браузер на прокси CDN, браузер устанавливает с прокси SSL-соединение и посылает ему HTTP-запрос. Если запрос прокси нравится - пересылает его по другому SSL-соединению на LB. Тот прогоняет свои проверки - IPS, антивирус, - и перекидывает запрос на один из восьми серверов. Ответ сервера пересылается обратным путём.

Как видим, всё несложно, но есть дополнительное требование: sessions persistence, так же известное как sticky sessions. Сиречь, начав работать с каким-то сервером, юзер так и должен продолжать с ним работать - все запросы от браузера должны приходить на тот же сервер, разве что тот загнётся.
За это у нас отвечает FortiGate - он этого может добиваться одним из двух способов:

  • HTTP cookie - когда приходит запрос от нового клиента, FortiGate добавляет в ответ новый cookie с названием FGTServer и длинным псевдорандомальным значением. Браузер запоминает этот cookie и добавляет в каждый последующий запрос. По его значению Фортигейт знает, на какой из серверов эти запросы перекидывать.
  • SSL Session ID - когда клиент с сервером устанавливает SSL-соединение, сервер сочиняет некий session identifier и посылает его клиенту. Если TCP-connection, на котором SSL-соединение основано, сломалось, клиент может быстро восстановить общение с сервером, открыв новую TCP-трубу и выслав этот identifier - избежав таким образом необходимости в полном обмене ключами. Это ускоряет SSL и расцепляет его с TCP - одно SSL-соединение может использовать множество TCP-соединений. Подробности можно узнать здесь.
    В данном случае "сервер" - это не веб-сервер, а сам FortiGate. Он запоминает, на какой настоящий сервер он перебросил запрос, пришедший по соединению с новым session ID, и последующие запросы направляет соответственно.
Мы используем первый способ.

А теперь амбула
Выплыла проблема: юзерская сессия ломается. Вот залогинился он на сайт, и всё вроде отлично, но вдруг сайт ему и говорит: а ты кто такой, мил человек? Знать тебя не знаю.

Стало быть, не фурычит наша sessions persistence - попадают запросы от юзера вдруг на новый сервер, а не обслуживаются всё тем же. Открываем браузер, жмём F12, смотрим, что происходит - и впрямь: браузер получает в начале печеньку FGTServer, честно отсылает её - зато Фортигейт в какой-то момент от неё отмахивается, будто её и нету, перекидывает запрос на другой сервак и пришивает к ответу новое значение FGTServer.

Казалось бы, дело ясное - FortiGate виновен, на помойку. Но вот в чём закавыка: происходит это только при обращении через CDN - если браузер общается с Фортигейтом напрямую, то всё прекрасно, cookie при всех запросах сохраняется тот же, сервер - тот же, работает наш persistence!

Что за лабуда, думаю - может, CDN наши cookies срезает каким-то образом? Запускаю сниффер на Фортигейте, смотрю - ничего подобного! Вот он наш FGTServer в запросе, и значение правильное - да Фортигейт запрос на другой сервак перекидывает!

От отсутвия идей перешёл на второй способ, на SSL Session ID - не помогло ровно ничем. Напрямую - работает чудесно, через CDN - ломается. Что за притча?

Может, сервер health check проваливает и LB его помечает как дохлый? Может, мы максимум соединений на сервер перешли? Нет, вроде ничем не подтверждается - да и как объяснить тогда, что напрямую всё летает?


Развязка
А ларчик открылся вот как: оказывается, чёртов CDN запросы от множества разных клиентов мультиплексирует на уровне TCP. То есть приходят к ихнему прокси запросы от сотни юзеров - а тот поддерживает с нашим LB какой-то пул TCP-соединений - скажем, пять - и в каждое пихает запросы от разных юзеров.

Это performance, конечно, улучшает - не нужно для каждого нового юзера новый TCP-handshake устанавливать и отдельный сокет держать: бери да пихай его запросы в свободную TCP-трубу. Да только наш load balancer это с ума сводит - видать, не подумали программисты Fortinet, что такая ситуация возможна. При прямом-то обращении такого не бывает - по каждому TCP-коннекшену запросы только от одного клиента идут, с тем же самым FGTServer cookie - а тут он вдруг раз и меняется.

И уж понятно, почему SSL Session ID не помогает - соответствует-то он не клиенту, а CDN-прокси. Решил прокси пихнуть один запрос от клиента в один сокет, а следующий - в другой, и привет: session IDs разные, LB их отсылает на разные сервера.

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

P.S.
Была, впрочем, и альтернатива - почётную функцию load balancing на сам CDN возложить. Но его жадные хозяева хотели за это дополнительную денежку, а нас жаба задавила. Нам грубияны не нужны, мы сами грубияны.

Sunday, June 26, 2016

Публикация CRL на внешнем хостинге

(Унылый апдейт от апреля 2017: ну всё, увы. SmartFile вслед за Дропбоксом ввели обязательный редирект на HTTPS, который нам противопоказан - см. внизу, почему. Придётся искать что-то другое.)

Частенько сталкиваюсь с типичной сисадминской задачкой: надо настроить CA и опубликовать в Интернете подписанный им список отозванных сертификатов (CRL). Это просто небольшой файлик, который надо сделать доступным по HTTP-линку, чтобы затем прописать этот линк в выдаваемых этим CA сертификатах. Любая сторона, желающая проверить, действителен ли ещё сертификат, найдёт в нём поле CDP, содержащее линк, скачает по нему файл, проверит на нём цифровую подпись и затем глянет, содержит ли CRL серийный номер сертификата in question, или нет.

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

Взамен хочется такого:
  • Чтобы можно было выкладывать CRL на каком-то внешнем хостинге, автоматически заливать туда изменения - желательно через какой-нибудь простенький REST-сервис.
  • Чтобы был постоянный линк на опубликованный файл, чтобы он не требовал никакой авторизации или сложных редиректов.
  • Чтобы этот линк содержал наш собственный домен - и для понтов, и для гибкости: чтобы не переделывать массу сертификатов только потому, что сменился хостинг.
  • Чтобы линк был доступен по plain HTTP - не по HTTPS! Что сегодня уже, кстати, нетривиально. Необходимо это потому, что иначе может возникнуть логическая петля: некто хочет проверить сертификат, поэтому идёт скачивать CRL, но для этого нужно проверить сертификат HTTPS-сервера, но для этого нужно скачать CRL, и так далее. Чтобы не допускать этой дурной бесконечности, стандарт требует публиковать CRL по обычному HTTP - что безопасно, поскольку сам CRL защищён цифровой подписью и имеет ограниченное время жизни. Поэтому подсунуть фальшивый не выйдет.
  • Ну и наконец, чтобы всё это удовольствие было на халяву.
Я малость поныкался и нашёл хостинг, всем пунктам удолетворяющий - SmartFile.
Они предоставляют бесплатный девелоперовский аккаунт, позволяющий держать какие-то бешенные гиги - для нашей задачки это более чем излишне, нам-то всего несколько килобайтиков залить. В отличие от платного, этот аккаунт не даёт использовать удобный веб-интерфейс - но зато есть прекрасный REST API. Им и воспользуемся.

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

Процесс таков:

1. Регистрируемся и заходим в качестве девелопера.

2. Жмём на кнопку "Get API Key" и получаем две длинные строки, которые будут использоваться для аутентификации наших REST-запросов:
ACCESS_KEY = SeKyFDgmdA1ikWdnAuvrWxLX1do4q4
PASSWORD   = eSDBw3WcZskDSzs2Wu3wy4pixe8uIt

SmartFile использует Basic Authentication - схему, которая сама по себе не предоставляет шифрования пароля, но поскольку сами запросы идут по HTTPS, то проблемы в этом нет.

3. Итак, первый запрос - создаём папочку на хостинге. Назовём её, к примеру, PKI:
curl --user %ACCESS_KEY%:%PASSWORD% -F "path=PKI" -X PUT https://app.smartfile.com/api/2/path/oper/mkdir

4. Заливаем в неё наш CRL-файл:
curl --user %ACCESS_KEY%:%PASSWORD% -X POST -F path="@MyOwnCoolCA.crl" https://app.smartfile.com/api/2/path/data/PKI/

5. Заодно там же разместим и сертификат самого CA - его можно прописать в поле AIA выпекаемых им сертификатах, чтобы проверяющие могли подгрузить всю цепочку:
curl --user %ACCESS_KEY%:%PASSWORD% -X POST -F path="@MyOwnCoolCA.crt" https://app.smartfile.com/api/2/path/data/PKI/

6. Файлы залиты, но просто так их ещё не скачаешь - потребуется авторизация. Чтобы сделать их доступными urbi et orbi, надобно создать линк на содержащую их папку PKI:
curl --user %ACCESS_KEY%:%PASSWORD% -X POST -d "name=PKI&path=/PKI&usage_limit=1000000000&read=on&list=on" https://app.smartfile.com/api/2/link/

Параметр "usage_limit" задаёт, сколько раз файл может быть скачан по линку. К сожалению, мне не удалось выяснить, можно ли этот параметр выставить в бесконечность. Но для нашего маленького скромного CRL миллиарда закачек вполне хватит.

В ответ мы получаем линк вида http://file.ac/g743sRnJfgS. На этом работа с API закончена.

7. Осталось лишь прописать в DNS хостнейм, который мы хотим видеть в наших линках. Пускай это будет crl.mydomain.com. Можно создать его как CNAME-алиас для file.ac, или как A-запись для IP-адреса 209.43.40.101 - это не особо существенно.

Всё. На этом этапе у нас есть два живых линка:
  • http://crl.mydomain.com/g743sRnJfgS/MyOwnCoolCA.crl - собственно CRL,
  • http://crl.mydomain.com/g743sRnJfgS/MyOwnCoolCA.crt - сертификат подписавшего его CA.
Их уже можно взять и прописать в конфигурации CA, чтобы они фигурировали в полях CDP и AIA всех сертификатов, этим CA выданных.


Осталось лишь наладить обновления. Можно, конечно, просто регулярно заливать CRL каждые полчаса-час, но это путь какой-то тупой. Хочется, чтобы он заливался лишь тогда, когда CA подписывает новый выпуск - допустим, раз в неделю.

Дальше идёт уже конфигурация под определённую платформу - поскольку я юзаю Microsoft ADCS, то и дальнейшие штучки заточены под Windows. На Линуксе можно придумать что-то своё.

Итак, я хочу, чтобы всякий раз, как файл MyOwnCoolCA.crl меняется, автоматом исполнялся REST-запрос, отправляющий его содержание на хостинг. К счастью, нашёлся скрипт PowerShell, позволяющий добиться этого минимальным расходом ресурсов. Скрипт подписывается на события, происходящие с выбранными файлами в определённой папке, и запускает внешнюю програмку, когда они происходят:

UploadToSmartFile.ps1:
$ACCESS_KEY = "SeKyFDgmdA1ikWdnAuvrWxLX1do4q4"
$PASSWORD   = "eSDBw3WcZskDSzs2Wu3wy4pixe8uIt"

### SET FOLDER TO WATCH + FILES TO WATCH + SUBFOLDERS YES/NO
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = "C:\crl.mydomain.com"
$watcher.Filter = "MyOwnCoolCA.crl"
$watcher.EnableRaisingEvents = $true  

### DEFINE ACTIONS AFTER A EVENT IS DETECTED
$action = 

curl.exe --user ${ACCESS_KEY}:${PASSWORD} -X POST -F path="@$Event.SourceEventArgs.FullPath" https://app.smartfile.com/api/2/path/data/PKI/
}
  
### DECIDE WHICH EVENTS SHOULD BE WATCHED + SET CHECK FREQUENCY  
$created = Register-ObjectEvent $watcher "Created" -Action $action
$changed = Register-ObjectEvent $watcher "Changed" -Action $action
$renamed = Register-ObjectEvent $watcher "Renamed" -Action $action
while ($true) {sleep 5}

(Update: "curl.exe" - это важно; как неожиданно выяснилось, добрые люди из Микрософта определили в новых версиях Пауэршелла команду "curl" как alias на Invoke-WebRequest, поломав мне нафиг скрипт.)

Итак, то что делает скрипт: висит и ждёт, пока в директории C:\crl.mydomain.com будет либо создан файл MyOwnCoolCA.crl, либо изменён, либо какой-нибудь другой файл получит это название. В этом случае скрипт отправляет его содержимое на хостинг.

(N.B.: сама директория, как и скрипт, совершенно не обязана находиться непосредственно на CA - это может быть любой комп. Я обычно предпочитаю располагать её в папке SYSVOL - это гарантирует очень быструю репликацию на все контроллеры в домене. Дело вкуса).

Осталось отконфигурачить автоматическое исполнение скрипта при загрузке компьютера. Можно запускать его как сервис при помощи srvany.exe, но я воспользовался Task Scheduler.

Итак, создаём новый task со следующими параметрами:
  • Triggers: at startup
  • User account: SYSTEM
  • Action: powershell -ExecutionPolicy Bypass -File C:\CA\Script\UploadToSmartFile.ps1
и отменяем остановку скрипта через определённый период времени.
(Bypass требуется, поскольку по дефолту исполнение скриптов PowerShell запрещено).

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

Всё. Mischief managed.

Tuesday, May 10, 2016

Немного о трёхглавых собаках

Нашёл недавно довольно интересную штуку:

Положим, у нас есть сайт, использующий "Windows Integrated Authentication" - то есть Kerberos или NTLM. И допустим, что юзер, пытающийся на него зайти - относится к другому, недоверяемому домену, или же он локальный юзер на доменном компе, а то и вовсе на стэнд-элоне. Так или иначе, попытка автоматического, прозрачного для юзера логина проваливается. Что произойдёт?

Ответ: зависит от браузера. Firefox просто выдаст сообщение об ошибке. А вот Chrome или IE поступят по другому - выкинут стандартное окошко username & password. А самое интересное, что если username указать в полной форме - к примеру, me@mydomain.com, - то они пошлют запросы DNS-серверу на две записи SRV, стремясь обнаружить керберосовский центр раздачи ключей (KDC) для указанного домена:
_kerberos._tcp.Default-First-Site-Name._sites.dc._msdcs.mydomain.com
_kerberos._tcp.dc._msdcs.mydomain.com

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

Кстати, и клиент Remote Desktop в Win7 тоже так умеет.

Почему это так интересно? А потому, что означает, вопреки популярному убеждению, что Kerberos можно использовать в Интернете - а не только в корпоративных сеточках.
(Апдейт: не так всё здорово, см. внизу *).

Допустим, у нас есть сайт где-то в облаке, без всякой связи с сетью домена. Допустим, он вовсе не на Windows - например, это бегущий под линуксом Tomcat. Тогда всё, что нам надо сделать:
1. Создать какой-нибудь аккаунт в домене и сгенерить для него файл keytab - аналог пароля.
2. Связать URL сайта с этим аккаунтом, определив SPN.
3. Залить файл keytab на сервер и прописать его в конфигурации - сервер будет им юзерские билетики расшифровывать.
4. Открыть 88-й порт (TCP и UDP) на доменном контроллере в Интернет - наиболее спорный пункт. На мой взгляд, достаточно безопасный вариант - построить read-only domain controller, разместить его в DMZ, запретить ему держать у себя пароли и разрешить разговаривать с обычным DC. Тогда он превращается в прокси, обвал которого домену никак не вредит.
Если же это никак неприемлемо, остаётся вариант с VPN - что, конечно, странно, но может и кому-то и подойти, учитывая, что вся система предназначена всё же для людей из определённой организации.
5. Прописать публичный адрес этого RODC в DNS - под SRV-record _kerberos._tcp.dc._msdcs.mydomain.com.

Всё. Любой юзер домена, вооружённый IE или Хромом, может логиниться на сайт. Возможно, также и юзеры других доменов, у которых есть отношения доверия с доменом сайта - не проверял.

Достоинства:

  • Пароли не проходят через сайт ни на каком этапе.
  • Взаимная аутентификация - без нужды в SSL. Ответ сервера на предъявленный билетик доказывает, что он был способен его расшифровать - что сам сервер легитимен.
  • Сайт работает полностью автономно от доменной сети.
  • URL сайта может быть любым, никак с доменом не связаным.
  • В случае взлома сайта выписка любых новых билетов для него прекращается моментально, отключением ассоциированного с ним аккаунта, а уже выписанные билеты протухают по дефолту за 10 часов - но этот срок можно уменьшить и до 10-ти минут. 
  • В случае взлома юзера - тем более, баним его аккаунт и конец делу. Время жизни выданного ему TGT с дефолтовых 10-ти часов также можно урезать. Сравните с процессом отмены сертификата!
  • Проще в настройке, чем SAML, оAuth, OpenID и прочее с того же куста, и намного прозрачнее для серверной аппликации.
  • При этом позволяет передавать не только username, но и информацию о группах - микрософтовская имплементация Кербероса присобачивает к билетику поле под названием PAC - оно содержит список групп в Active Directory, к которым относится юзер (причём, что особенно прелестно, не обязательно напрямую - nesting groups там сидят также). Неприятное ограничение в том, что имена групп в PAC не значатся - только их длинные номера SID. Но построив на сервере mapping из SID в имена, можно справиться и с этим, после чего разным группам пораздавать разные права.
    Существует библиотека на Java, умеющая извлекать SID-ы групп из PAC - может быть полезна для всевозможных серверов JavaEE.


Недостатки:

  • Необходимость доступности контроллера домена в Интернете, пусть даже и read-only и лишь парой керберосовских портов. Я большой проблемы с этим не вижу, но возможно, что люди поумнее таки да. (Апдейт: таки есть проблема, см. внизу *).
  • Сами билетики шифрованные, но некоторые поля в пакете Kerberos передаются открытым текстом - например, юзернейм. С трафиком между сидящим вне доменной сети юзером и KDC особо делать нечего (разве что VPN), между юзером и сайтом - включить HTTPS. Лишний слой шифрования не помешает.
  • Отсутствует logout - нет способа сказать сайту, что юзерская сессия закончилась и дальнейшие запросы с тем же билетом приниматься не должны. Неидеальный способ для юзера состоит в том, чтобы закрыть полностью браузер - лишь в заново запущенном браузере процесс логина повторится сначала. Впрочем, с клиентскими сертификатами та же проблема.
  • Как уже сказано, не самый удобный способ передавать информацию о группах - тут ADFS куда круче, конечно, к тому же позволяет передавать массу других атрибутов. Тут же, если дополнительная информация из AD необходима - воленс-ноленс приходится либо запрашивать по LDAP, либо переходить на ADFS или аналог.
  • Какой-либо provisioning юзеров исключается - создавать или убивать их можно лишь через AD. Не через сайт.
  • Использовать таким образом иные методы аутентификации, кроме пароля - проблемно, несмотря на том, что в Windows в целом поддерживается интеграция сертификатов и Кербероса (см. PKINIT).
  • Привязка к конкретным браузерам.


Вот такие пироги с собачками. Для полноценного сайта, предназначенного для общей публики, это, конечно, не решение - но если задача в том, чтобы корпоративный сайтик из серверной комнаты куда-нибудь на Амазон перенести, то Kerberos тут вполне в тему, на мой взгляд.


* Апдейт: не так всё просто, оказывается. Между получением адресов контроллеров от DNS и запросом билетиков Kerberos есть ещё один этап - "LDAP ping". Это запрос по Connectionless LDAP (CLDAP) к домейн-контроллеру, адрес которого получен от DNS, по UDP-порту 389. Его цель - обнаружить ближайший контроллер для дальнейших запросов. Контроллер, получивший "ping", смотрит, с какого адреса он пришёл, и смотрит к какому сабнету можно его отнести (а сабнеты в AD относятся к тому или иному "сайту", в каждом "сайте" как минимум один свой контроллер). Соответственно, он сообщает клиенту, от какого контроллера тому требовать билетики.
Ответ кэшируется, так что при перезапуске браузера "LDAP ping" будет послан необязательно.

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