Аутентификация и разрешения
Если Web-cepвep требует аутентификации пользователя, можно создать удостоверение личности пользователя и передать его Web-запросу. При этом полезны следующие интерфейсы и классы: ICredentials, NetworkCredential и CredentialCache.
Для аутентификации пользователя создадим объект типа NetworkCredential. Этот класс обеспечивает информацию с целью удостоверения личности пользователя для базовой аутентификации, аутентификации на основе дайджестов, NTLM и Kerberos.
Конструктору класса NetworkCredential можно передать имя пользователя, пароль и дополнительно домен, разрешающий доступ пользователя:
Для авторизации пользователя эту информацию удостоверения личности можно установить в свойстве Credentials класса WebRequest:
Если нужна разная информация удостоверений личности для разных URI, можно использовать класс CredentialCache, как показано в следующем коде. С таким кэшем также определяется тип аутентификации для конкретного соединения. Здесь используется базовая аутентификация для Web-сайта www.unsecure.com и аутентификация на основе дайджестов для Web-сайта www.moresecure.com, для которого через сеть посылается не пароль, а хеш-код:
Разрешения
Всякий раз когда используются сетевые классы, требуются разрешения. Для вопросов сетевого обмена рассмотрим три типа разрешения.
Разрешение DnsPermission требуется для поиска имени DNS с помощью класса Dns. WebPermission используется классами из пространства имен System.Net, которые отправляют данные в Интернет и получают данные с помощью URI. SocketРеrmission используется для приема данных на локальном сокете или соединения с хостом через транспортный протокол.
DnsPermission
Когда используется класс Dns для поиска IP-адреса, требуется разрешение DnsPermission. Для него различаем только значения «признать» и «отвергнуть». Запросы DNS или абсолютно не ограничиваются, или не разрешаются совсем.
WebPermission
WebPermission требуется для таких классов, как WebRequest и WebResponse, чтобы отправлять данные в Интернет и получать их из Интернета.
В этом случае различаются разрешения «согласиться» (Accept) и «соединиться» (Connect). Разрешение Accept нужно для URI, используемых внутри классов и методов. Клиентским приложениям, использующим URI для соединения с сервером, требуется полномочие Connect. У класса WebPermission также есть список URI, с которыми можно соединиться, и список URI, с которыми можно согласиться.
SocketPermission
Разрешения SocketРеrmission нужны для классов сокетов из пространства имен System.Net.Sockets. Это разрешение — самое гибкое из трех классов сетевых разрешений.
Для серверных приложений, ожидающих запросы на соединения от клиентов, в конструктор передается значение перечисления NetworkAccess.Accept; клиентские приложения, соединяющиеся с серверами, используют значение NetworkAccess.Connect. Можем ограничить соединение конкретными хостами и номерами портов и определить используемый транспортный протокол.
Использование атрибутов разрешения
Если требуемое разрешение недоступно, программа завершается с исключением SecurityException, как только вызван привилегированный метод. До порождения исключения пользователь мог какое-то время работать с приложением, и может потерять данные, если не обработать исключение аккуратно. Хороший способ, позволяющий этого избежать, состоит в том, чтобы отметить сборку необходимыми разрешениями.
Если для получения данных из Интернета используется класс WebRequest, требуется разрешение WebPermission. Можно отметить классы и методы, требующие разрешения, атрибутом WebPermission (реализованным в классе WebPermissionAttribute) следующим образом:
В данном случае исключение SecurityException возникает, как только создается экземпляр класса PermissionDemo. Если хотим, чтобы эта проверка выполнялась при старте программы, можно применить атрибут WebPermission ко всей сборке:
Если этот атрибут применен к сборке, при старте программы среда выполнения проверяет, имеет ли программа необходимое разрешение. Если она не обладает требуемым разрешением, выполнение прекращается немедленно, прежде чем пользователь ввел (или потерял) информацию.
Со всеми атрибутами разрешения конструктору передается значение перечисления SecurityAction. Здесь рассмотрим только самые важные значения перечисления:
| Значения перечисления | Описание |
|---|---|
| Demand и Deny | Значения перечисления SecurityAction.Demand и SecurityAction.Deny можно использовать с классами и методами. Значением Demand указывается, что классу или методу требуется разрешение, значение Deny определяет, что это разрешение не нужно. |
| RequestMinimum, RequestOptional и RequestRefuse | Значением перечисления Requestxxx можно пользоваться только в контексте сборки, его нельзя указывать с классами и методами. RequestMinimum определяет, что это разрешение обязательно для использования программы. Значением RequestOptional сообщается, что программа может выполнить некоторую полезную работу без этого разрешения. В этом случае надо постепенно обработать исключение SecurityException. Значение RequestRefuse определяет, что это разрешение не нужно. Это значение используется в тех случаях, когда возможно неправильное применение разрешения, например вызов сборок, для которых у нас нет исходных текстов и которым мы не доверяем полностью. |
С атрибутом WebPermission можно в дополнение к SecurttyAction установить следующие свойства:
Accept и AcceptPattern
В свойстве Accept определяется URI ресурса для использования с классом, методом или сборкой, к которым применен этот атрибут. В свойстве AcceptPattern указывается регулярное выражение, чтобы разрешить или запретить доступ к URI.
Connect и ConnectPattern
Два свойства ConnectXX аналогичны свойствам AcceptXX, но отличаются тем, что применяются для строки соединения с URI.
В классе SocketPermissionAttribute определены следующие дополнительные свойства:
Access
В этом свойстве определяется допустимый метод доступа к сети. Допустимы только два: Accept и Connect. Значение Accept используется для серверного приложения, слушающего и принимающего соединения клиентов, а значение Connect предназначено для клиента, соединяющегося с сервером.
Host
В свойстве Host с использованием синтаксиса DNS или IP-адреса устанавливается имя хоста, к которому относится разрешение.
Port
Это строковое свойство, задающее номер порта, для которого требуется разрешение. Свойство может использоваться для ограничения приложений-клиентов некоторыми конкретными серверами. Свойство имеет тип string, поскольку разные протоколы необязательно определяют номер порта как целое число.
Transport
С помощью свойства Transport ограничиваются сетевые соединения конкретным транспортным протоколом. Возможные значения: All, ConnectionLess, CormectionOriented, Tcp и Udp. Значение ConnectionLess позволяет использовать все протоколы, не требующие устанавливать соединения, например UDP; значение ConnectionOriented позволяет использовать протоколы, ориентированные на установление соединения, например TCP.
Как перейти с secrets на credentials (Ruby on Rails)
Почему стоит использовать сredentials вместо secrets?
В цикле разработки, по мере развития в проект интегрируется всё больше различных сервисов. Для каждого внешнего сервиса существует собственный API-ключ. Обычно проходит совсем немного времени до момента, когда коллеги начнут охоту за за последним API-ключом. Это очень раздражает!
Или, просто представьте, API-ключ обновляется. Каждый разработчик в команде отдельно должен обновить ключ в локальных файлах dotenv. Кажется, всё это не соответствует концепции автоматизации и идеям программирования, верно?
Забудьте об обмене API-ключами в чатах в Slack или по электронной почте. Больше не придется нарушать политику безопасности.
Credentials в Rails решают эту проблему просто и эффективно: загрузка ключей на Github.
Загрузка на Github? Да, загрузка на Github! Небольшое примечание: API-ключи полностью зашифрованы.
Большое преимущество такого подхода в том, что существует один единственный ключ, которым можно поделиться с командой. И он никогда не меняется!
Новые API-ключи, добавленные вашими коллегами как credentials, подтягиваются из Github каждый раз, когда вы вытаскиваете последнюю версию ветки main (ранее носила название «ветка master»).
Ключ можно найти в папке config/master.key.
Как это работает?
Запуск bin/rails credentials:edit в rails создает два файла, которые требуются в папке config:
Переход на credentials
Откройте файл credentials, выполнив в терминале следующее:
В зависимости от используемого вами редактора, замените code (VS Code). Например:
vim or vi = Vim
atom = Atom
subl or stt = Sublime
Примечание: Ключ Cloudinary API разделен на несколько частей, согласно документации.
Все готово! Для вывода на экран, credentials можно запустить в терминале.
Как использовать Credentials в различных форматах, таких как Ruby, YML и JavaScript
Объявление
[Терминология ИБ] Что такое «Credentials»?
Задался целью сделать терминологию ИБ – человекопонятной. Чтобы она была не «для стандартов», а «для людей». В связи с этим хочу позадавать серию «глупых» вопросов, чтобы в ходе их обсуждения – вместе «родить» терминологию, с которой будет удобно работать. Чтобы она однозначно отражала то, что в неё вкладывается (и по возможности, чтобы термины из одного слова состояли). Плюс, чтобы она была дружественной – даже для тех людей, которые далеки от ИБ.
В итоге планирую подготовить статью для журнала «Системный администратор», где будут собраны актуальные термины. Что-то вроде «ГОСТ Р 50922-2006» и «Р 50.1.053-2005», но только – «для людей», а не «для стандартов». Чтобы этими терминами реально удобно было пользоваться при обсуждении и создании киберзащиты.
И начать предлагаю с поиска подходящего перевода к термину «Credentials». Исходя из того, что сюда относятся не только пароли, но также разного вида токены, хэши, теги, жетоны, – которые подтверждают, что системой всё ещё пользуется уполномоченное лицо, а не мошенник.
Учётные данные, персональные данные, – на мой взгляд, не очень отражают то содержание, которое в «Credentials» должно быть вложено. На мой взгляд, сюда по смыслу больше всего подходит «верительная грамота». Но хотелось бы этот смысл в одно слово уложить. И по возможности, чтобы это из этого слова было видно, что оно к ИТ относится. Коллеги, есть идеи? Как это сделать? Или может быть, альтернативный перевод кто придумает?
Тонкости авторизации: обзор технологии OAuth 2.0
Информационная система Dodo IS состоит из 44 различных сервисов, таких как Трекер, Кассы ресторана или Базы знаний и многих других. Чтобы не отвлекаться на несколько аккаунтов, 3 года назад мы написали сервис Auth для реализации сквозной аутентификации, а сейчас пишем уже вторую версию, в основе которого лежит стандарт авторизации OAuth 2.0. Этот стандарт довольно сложный, но если у вас сложная архитектура с множеством сервисов, то OAuth 2.0 вам пригодится при разработке своего сервиса аутентификации. В этой статье я постарался рассказать о стандарте максимально просто и понятно, чтобы вы сэкономили время на его изучение.
Задача Auth
Проблема авторизации в десятках сервисов встречалась ещё несколько лет назад — в начале «эпохи распила монолита». Эту проблему решили новым сервисом, который назвали – Auth. Он помог реализовать бесшовную аутентификацию в различных сервисах и перенести данные о пользователях в отдельные базы данных.
У сервиса Auth есть три основные задачи:
Проблемы
Первая версия Auth — часть монолита. Он использует свой собственный протокол общения с сервисами. Такая «схема» была необходима в тот момент, но за несколько лет работы проявились проблемы.
Auth — часть монолита. Следовательно, сервис привязан к релизному циклу, что лишает возможности независимой разработки и деплоя. Кроме того, придется разворачивать весь монолит, если захотелось развернуть Auth, например, при масштабировании сервиса.
Dodo IS зависит от Auth. В старой реализации внешние сервисы обращаются к Auth при каждом действии пользователя, чтобы валидировать данные о нём. Настолько сильная привязка может привести к остановке работы всей Dodo IS, если Auth «приляжет» по какой-то причине.
Auth зависит от Redis. Притом достаточно сильно — неисправность работы Redis’а приведёт к падению Auth’а. Мы используем Azure Redis, для которого заявленный SLA 99,9%. Это значит, что сервис может быть недоступен до 44 минут в месяц. Такие простои не позволительны.
Текущая реализация Auth использует свой протокол аутентификации, не опираясь на стандарты. В большинстве своих сервисов мы используем C# (если говорим о backend) и у нас нет проблем с поддержкой библиотеки для нашего протокола. Но если вдруг появятся сервисы на Python, Go или Rust, разработка и поддержка библиотек под эти языки потребует дополнительных затрат времени и принесет дополнительные сложности.
Текущий Auth использует схему Roles Based Access Control, которая базируется на ролях. Обычно с ролью выдаётся полный доступ к определённому сервису, вместо привязки к конкретному функционалу. Например, в пиццериях есть заместители управляющего, которые могут вести определенные проекты: составлять графики или учитывать сырьё. Но у нас нет выдачи прав на конкретные компоненты системы. Приходится выдавать полный доступ к сервису, чтобы сотрудники могли получить доступ к составлению графиков или настройкам какого-либо компонента учёта.
Проблемы подтолкнули к тому, чтобы спроектировать и написать новую версию Auth. На старте проекта мы потратили 3 недели только на изучение стандартов авторизации и аутентификации OAuth 2.0 и OpenID Connect 1.0.
Примечание. Утрированно, статья — это пересказ RFC, который приходилось перечитывать несколько раз, чтобы понять, что происходит вокруг. Здесь я постарался уйти от этой сложности и рассказать всё максимально просто, структурировано, кратко и без описания сложных вещей, например, какие символы может содержать в себе ответ сервиса. В отличии от RFC, прочитав это один раз, можно во всём разобраться. Надеюсь, статья будет полезна и сэкономит время при выборе решения для реализации сервиса аутентификации, а может, кого-то заставит задуматься о его необходимости.
Что такое ОAuth2.0?
Разработку нового Auth мы решили начать с изучения доступных протоколов и технологий. Самый распространённый стандарт авторизации — фреймворк авторизации OAuth2.0.
Стандарт был принят в 2012 году, и за 8 лет протокол меняли и дополняли. RFC стало настолько много, что авторы оригинального протокола решили написать OAuth 2.1, который объединит все текущие изменения по OAuth 2.0 в одном документе. Пока он на стадии черновика.
Актуальная версия OAuth описанна в RFC 6749. Именно его мы и разберем.
OAuth 2.0 — это фреймворк авторизации.
Он описывает, как должно реализовываться взаимодействие между сервисами для обеспечения безопасной авторизации. Многие нюансы описаны достаточно подробно, например, flow взаимодействия узлов между собой, но некоторые отдаются на откуп конкретной реализации.
В OAuth 2.0 определены четыре роли:
Важно: клиент должен быть заранее зарегистрирован в сервисе. Как это сделать?
Регистрация клиента
Способ регистрации клиента, например, ручной или service discovery, вы выбираете сами, в зависимости от фантазии конкретной реализации. Но при любом способе при регистрации, кроме ID клиента, должны быть обязательно указаны 2 параметра: redirection URI и client type.
Redirection URI — адрес, на который отправится владелец ресурса после успешной авторизации. Кроме авторизации, адрес используется для подтверждения, что сервис, который обратился за авторизацией, тот, за кого себя выдаёт.
Client type — тип клиента, от которого зависит способ взаимодействия с ним. Тип клиента определяется его возможностью безопасно хранить свои учётные данные для авторизации — токен. Поэтому существует всего 2 типа клиентов:
Токены
Токен в OAuth 2.0 — это строка, непрозрачная для клиента. Обычно строка выглядит как случайно сгенерированная — её формат не имеет значения для клиента. Токен — это ключ доступа к чему-либо, например, к защищённому ресурсу (access token) или к новому токену (refresh Token).
У каждого токена своё время жизни. Но у refresh token оно должно быть больше, т.к. он используется для получения access token. Например, если срок жизни access token около часа, то refresh token можно оставить жить на целую неделю.
Refresh token опционален и доступен только для confedential клиентов. Пользуясь опциональностью токена, в некоторых реализациях время жизни access token сделано очень большим, а refresh token вообще не используется, чтобы не заморачиваться с обновлением. Но это не безопасно. Если access token был скомпрометирован, его можно обнулить, а сервис получит новый Access token с помощью refresh token. В случае, если refresh token нет, то потребуется проходить процесс авторизации заново.
За access token закреплён определённый набор прав доступа, который выдаётся клиенту во время авторизации. Давайте разберёмся, как выглядят права доступа в OAuth 2.0.
Права доступа
Права доступа выдаются клиенту в виде scope. Scope – это параметр, который состоит из разделённых пробелами строк — scope-token.
На этапе регистрации клиента, в настройках сервиса авторизации клиенту выдаётся стандартный scope по умолчанию. Но клиент может запросить у сервера авторизации scope, отличный от стандартного. В зависимости от политик на сервере авторизации и выбора владельца ресурса, итоговый набор scope может выглядеть совсем иначе. В дальнейшем, после авторизации клиента, владелец ресурсов может отобрать часть прав без повторной авторизации сервиса, но, чтобы выдать дополнительные разрешения, потребуется повторная авторизация клиента.
Абстрактный OAuth 2.0. Flow c применением Access token
Мы рассмотрели роли, рассмотрели виды токенов, а также как выглядят scope. Посмотрим на flow предоставления доступа к сервису.
Ниже представлена абстрактная схема (или flow) взаимодействия между участниками. Все шаги на данной схеме выполняются строго сверху вниз. Разберём детальнее.
Абстрактный OAuth 2.0. Flow c применением Refresh token
Первый и второй шаги опущены из данной схемы — они ничем не отличаются от схемы абстрактного flow выше.
Что такое grant?
Grant — это данные, которые представляют из себя успешную авторизацию клиента владельцем ресурса, используемые клиентом для получения access token.
Например, когда мы где-либо аутентифицируемся с помощью Google, перед глазами всплывает уведомление. В нём говорится, что такой-то сервис хочет получить доступ к данным о вас или к вашим ресурсам (выводятся запрашиваемые scope-token). Это уведомление называется «Consent Screen».
В момент, когда нажимаем «ОК», в базу данных попадает тот самый grant: записываются данные о том, что такой-то пользователь дал такие-то доступы такому-то сервису. Клиент получает какой-то идентификатор успешной аутентификации, например строку, которая ассоциируется с данными в базе данных.
Существует 4 + 1 способа получения grant — grant type:
Client credentials grant flow
Имеет самый простой flow, напоминающий обычную авторизацию на любом сервисе. Она выполняется с помощью учётных данных клиента, которые представляют собой client id и client secret — аналог логина и пароля для пользователя. Так как для аутентификации требуется client secret, который должен соответствующе храниться, данный flow могут использовать только confedential клиенты.
Схема проста: клиент аутентифицируется на сервере авторизации передавая client id и client secret. В ответ получает access token, с которым уже может получить доступ к нужному сервису.
Этот flow требуется, когда клиент пытается получить доступ к своим ресурсам или ресурсам, заранее согласованным с сервером авторизации. Например, сервису А нужно время от времени ходить в сервис Б и актуализировать там данные о количестве пиццерий в сети.
Resource owner password credentials flow
По текущим рекомендациям безопасности описанных в данном RFC, данный flow не рекомендуется использовать вовсе из-за явных проблем с безопасностью.
Resource owner передаёт свой логин и пароль клиенту, например, через формы на клиенте. Клиент, в свою очередь, с помощью него получает access token (и, опционально, refresh token).
Здесь есть проблема. Resource owner просто берёт и отдаёт в открытом виде свой логин и пароль клиенту, что не безопасно. Изначально он был сделан только для клиентов, которым вы доверяете или тех, что являются частью операционной системы. Позже он был разрешён только для миграции с аутентификации по логину и паролю на OAuth 2.0. Текущие рекомендации по безопасности запрещают его использование.
Authorization code
Самый распространённый flow на данный момент. В основном используется для confidential клиентов, но с появлением дополнительной проверки с помощью PKCE, может применяться и для public-клиентов.
В данном flow взаимодействие client с resource owner проходит через user-agent (браузер). К user-agent есть одно требование: он должен уметь работать с HTTP-редиректами. Без этого resource owner не сможет попасть к серверу авторизации и вернуться обратно с grant.
Данный flow сложнее, чем предыдущие, поэтому будем разбирать по шагам. Для начала представим, что мы — resource owner и перешли на страницу сервиса онлайн-обучения, который хочет сохранять результаты обучения к нам в облако. Ему требуется получить доступ к нашему ресурсу, например, определённой директории в облаке. Мы нажимаем на «Авторизоваться» и начинается путешествие по Authorization code grant flow:
Следующий flow построен на основе этого.
Implicit grant
Это оптимизация Authorization code grant flow для public-клиентов, которые умеют работать с redirection URI. Например, для браузерных приложений на JavaScript, или мобильных приложений. Требование к user-agent, с помощью которого взаимодействуют клиент и resource owner, сохраняется: он должен уметь работать с HTTP-редиректами.
Между authorization code и implicit есть основное отличие: вместо получения authorization code и access token по нему, мы сразу получаем access token после успешной авторизации resource owner. Кроме того, здесь не используется client secret из соображений безопасности — приложение можно дизассемблировать и получить его. Подлинность проверяется только по redirection URI.
Многие шаги из данной схемы похожи на шаги из authorization code, но предлагаю их разобрать также подробно. Представим, что некое браузерное приложение хочет сохранять свои настройки в нашем Git-репозитории. Мы нажимаете «Войти в GitHub» и на этом этапе начинается работа Implicit flow:
Device authorization (RFC 8628)
С 2012 до 2019 появилось много умных устройств, на которых неудобно авторизоваться. Например, неудобно вводить сложный логин и пароль на телевизоре каждый раз при открытии ресурса. На некоторых устройствах это невозможно, например на серверных ОС без графического интерфейса. В августе 2019 этот flow появился как раз для таких сценариев.
Есть, как минимум, 3 требования к устройствам, чтобы работа с помощью Device authoraztion grant flow была возможна:
Возможно, схема кажется сложной из-за обилия стрелок. Разберём её также пошагово, как и разбирали сложные flow до него.
Представим, что мы пытаемся авторизоваться на web-сервисе с помощью телевизора. Мы видим кнопку «Авторизоваться как устройство» и нажимаем. В этот момент начинается наш Device flow:
Вместо вывода
В этой статье я опустил много подробностей, чтобы максимально просто и доступно рассказать о самом важном. Например, типы запросов, как и в каком виде передавать параметры, какие символы допустимы в качестве значений для того.
Если хотите погрузиться в тематику детальнее, то рекомендую в RFC 6749 (для OAuth 2.0) и RFC 8628 (для Device Flow). Кроме того, следить за актуальными версиями RFC можно на ресурсе, посвящённому OAuth.
Если статья была полезна и захотите подробностей — пишите в комментариях, и в следующих статьях расскажу о PKCE, о протоколе аутентификации OpenID Connect 1.0, о нашей реализации сервера аутентификации и многом другом.
