Netlify Identity защищает приложения Ably от взлома

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

Клиент ответил на сообщение 703762824
Название тикета: xxxxxxxxxxxxxxxx – Нарушение безопасности ключа API
Приоритет: Тикет
Описание: Тикет поднят в результате отчета о безопасности, как указано … связаться с клиентом, чтобы сообщить им и принять меры, поскольку их API-ключ был раскрыт в Wayback Machine и все еще жив.

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

Как сделать так, чтобы ваше приложение Ably взломали

Войдите в Ably, скопируйте API-ключ вашего приложения и вставьте его в код, как показано ниже:

const ably = new
Ably.Realtime('aBCdeFg.ABcDEfG:abc123def456....789xyz');
Войти в полноэкранный режим Выйти из полноэкранного режима

Затем зафиксируйте эту строку кода в git-репозитории, разверните его на производстве, и машина обратного хода заморозит его в янтаре. Это так просто. Там она будет ждать, пока нетерпеливый маленький хоббит не найдет ее в темноте. Ваш драгоценный API-ключ окажется в чужих руках.

К счастью, в Ably мы отслеживаем утечки ключей аутентификации и связываемся с клиентом.

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

Использование функций Netlify для защиты вашего приложения

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

const authUrl = ".netlify/functions/ably-jwt?id={user-id}";
const ably = new Ably.Realtime({ authUrl });
Вход в полноэкранный режим Выход из полноэкранного режима

Все выглядит отлично, нигде нет API, но что мешает кому-то в интернете взять URL аутентификации и использовать его в другом месте? Ничего не мешает. Токен-аутентификация позволяет скрыть ключ API, но одного этого недостаточно.

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

TL;DR

Если вы просто хотите разобраться в настройке собственного приложения Netlify без объяснений, то перейдите на Github-репозиторий ably-labs, где в README находится сжатая версия этой статьи.

Напоминание: вам понадобятся учетные записи на этих платформах:
Ably, Github и Netlify.

Если вы заинтересованы только в прохождении шагов по внедрению, то перейдите вниз к разделу Окей, окей, достаточно… давайте отправим это!

Что такое JWT-токен “Ably”?

JSON Web Token (JWT) – это открытый стандарт (RFC 7519), который определяет компактный и самодостаточный способ безопасной передачи информации между сторонами в виде объекта JSON.

(Взято с сайта https://jwt.io/introduction/ )

Обычно закодированный токен JWT выглядит следующим образом:

Попробуйте здесь https://jwt.io/ – создано ребятами из auth0.

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

Ably JWT – это не совсем конструкция Ably, скорее это JWT, который был создан для совместимости с Ably. Если вы хотите узнать в деталях, как это работает, наше руководство по лучшей практике расскажет обо всем.

Netlify serverless functions & identity workflow

В нашем примере приложения новый пользователь должен зарегистрироваться и подтвердить свой адрес электронной почты, чтобы активировать себя, прежде чем он сможет войти в систему. При входе в систему мы проверяем их личность с помощью Netlify Identity и убеждаемся, что они не были помечены как запрещенные. Плохим участникам не выдается токен JWT. Действительным пользователям выдается токен для аутентификации в Ably, а URL-адрес авторизации содержит их уникальный идентификатор.

(1) Пользователь входит в систему (2) Проверка личности пользователя – действительные пользователи получают JWT-токен и продолжают работу с приложением, а недействительные пользователи отклоняются (3) Используют токен, ждут, пока он истечет, затем повторяют.

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

Токен JWT как бессерверная функция

Ключевой частью нашей конечной точки JWT является этот фрагмент кода. Независимо от того, какую платформу вы используете, эта часть будет одинаковой. Это анатомия JWT-токена Ably, который отличается от стандартного JWT тем, что нам требуются дополнительные ключи/значения в полезной нагрузке данных.

function generateAblyJWT(props) {
  const { apiKey, clientId, capability, ttlSeconds } = props;

  const [appId, keyId, keySecret] = apiKey.split(/[.:]/g);
  const keyName = `${appId}.${keyId}`;

  const typ = "JWT"; // type
  const alg = "HS256"; // algorithm sha256
  const kid = keyName; // appId + keyId

  const currentTime = Math.floor(Date.now() / 1000);
  const iat = currentTime; // initiated at (seconds)
  const exp = currentTime + ttlSeconds; // expire after

  const header = { typ, alg, kid };
  const claims = {
    iat,
    exp,
    "x-ably-capability": capability,
    "x-ably-clientId": clientId,
  };

  const base64Header = encryptObject(header);
  const base64Claims = encryptObject(claims);

  const token = `${base64Header}.${base64Claims}`;
  const signature = b64(SHA256(token, keySecret));

  const jwt = `${token}.${signature}`;

  console.log({ header, claims, signature, jwt });
  return jwt;
}
Вход в полноэкранный режим Выход из полноэкранного режима

Функция generateAblyJWT почти идентична той, которую вы найдете, прочитав документацию Ably по методам аутентификации. В качестве платформы для этой статьи мы выбрали Netlify, но ту же идею можно применить к любой платформе, которая предоставляет бессерверную конечную точку. Например, вы можете сделать то же самое с функцией Cloudflare, конечной точкой Runkit или дино Heroku … и т. д. По сути, вы можете использовать любой сервис, который “просыпается”, выполняет код и затихает после доставки полезной нагрузки ответа.

Использование Netlify Identity для проверки наших пользователей

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

Я хотел бы обратить ваше внимание на строку кода, выделенную ниже. Это конечная точка Netlify, которую мы будем использовать для получения пользователя с данным идентификатором. Значение id берется из строки запроса, которую мы используем для “запекания” в наш Ably JWT, как свойство clientId. Это означает, что и Ably, и Netlify будут иметь один и тот же id в своих соответствующих журналах аудита.

Самое замечательное в Netlify Identity то, что после активации второй аргумент в обработчике конечной точки имеет заполненный пользовательский контекст Object, и именно так мы соединяем внешний клиент с базовым бэкендом.

const axios = require("axios");
const generateAblyJWT = require("./generate-ably-jwt.js");

exports.handler = async function (event, context) {
  const { queryStringParameters } = event;
  const { id } = queryStringParameters || {};
  const { clientContext } = context || {};

  console.log({ queryStringParameters });
  console.log({ clientContext });

  const { identity } = context.clientContext || {};
  const { token, url } = identity || {};

  const userUrl = `${url}/admin/users/${id}`;  <<<<<
  const Authorization = `Bearer ${token}`;

  console.log({ identity, id, token, url, userUrl, Authorization });

  let response;

  /*

    We Use client context and querystring ID value
    to check the user exists by retrieving their
    User Identity object.

    Then we inspect that accounts metadata for role flags,
    which are added via the Identity dashboard,
    if it contains "Banned" we do not reissue the token

  */

  await axios
    .get(userUrl, { headers: { Authorization } })
    .then(({ data }) => {
      console.log("Success! User identity", data);

      const banned = /^banned/i;
      const { roles = [] } = data.app_metadata;
      const reject = roles.some((item) => banned.test(item));

      // delegate the error message to the catch clause.
      if (reject) throw new Error(`User with id [${id}] has been banned`);

      const settings = {
        clientId: id,
        apiKey: process.env.ABLY_APIKEY,
        capability: process.env.ABLY_CAPABILITY,
        ttlSeconds: Number(process.env.ABLY_TTLSECONDS),
      };

      response = {
        statusCode: 200,
        body: generateAblyJWT(settings),
        headers: { "Content-Type": "application/jwt" },
      };
    })
    .catch((error) => {
      console.log("Failed to get user!");
      response = {
        statusCode: 500,
        body: `Internal Error: ${error}`,
        headers: { "Content-Type": "text/plain" },
      };
    });

  console.log("response payload", response);
  return response;
};
Вход в полноэкранный режим Выход из полноэкранного режима

Экспортируемая функция-обработчик представляет собой модифицированную версию туториала Netlify “hello world”. Единственные существенные изменения: она интегрирована с Identity, возврат (он же ответ) имеет заголовок Content-Type: application/jwt, тело примера изначально было (объект JSON), теперь это String.

Кроме этих двух основных функций есть дополнительный код, который загружает библиотеку шифрования CryptoJS. О, и убедитесь, что время истечения срока действия вычисляется как Number(). Если что-то не так, сеть реального времени Ably выдаст диагностическое сообщение об ошибке.

Кратко о настройке внешнего интерфейса

Модальное всплывающее окно, выполняющее действия регистрации, входа в систему и напоминания пароля, управляется одним div и JavaScript от Netlify: Виджет Identity.

<main>
  <div>
    <a href="https://ably.com/" target="_blank">
      <img src="images/motif-red.svg?netlify-identity-jwt"
        alt="Ably logo" /></a>
    <h1>
      Ably JWT Authentication <br />
      using Netlify Identity and <br />
      serverless functions
    </h1>

    <div data-netlify-identity-menu></div> <<<<<<<<<<<

    <span class="container">
      <button onclick="go(this)">authenticate</button>
      <strong class="message"></strong>
    </span>
  </div>
</main>
Вход в полноэкранный режим Выход из полноэкранного режима

А кнопка AUTHENTICATE выполняет эту функцию …

function go(el) {
  /*
    get the user id from localstorage.
    This is only created once the user logs in
  */
  const user = localStorage.getItem("gotrue.user") || null;

  if (!user) {
    showMessage("Can't access user ID, please log in first.");
    return null;
  }

  /*
     Add the User's identity ID to the authURL
     to ensure the Ably token refresh has that credential
     we are binding the authURL and the user
  */

  const { id } = JSON.parse(user);
  const { origin } = window.location;
  const authUrl = `${origin}/.netlify/functions/ably-jwt?id=${id}`;

  console.log({ authUrl });

  window.ably = new Ably.Realtime({ authUrl });
  window.ably.connection.on(handleConnection(el));
}
Войти в полноэкранный режим Выход из полноэкранного режима

Снова высвечивается URL. Это значение плюс ID пользователя используется для подключения клиента к Ably realtime. В тот момент, когда это происходит, начинается таймер обратного отсчета, согласно значению TTL, установленному в переменных нашего окружения. Когда он истечет, URL (теперь хранящийся в Ably realtime) будет использован для запроса обновленного токена.

Хорошо, хорошо, достаточно… давайте отправим его!

Подготовка

Первое, что нам нужно сделать, это подготовить соответствующие платформы. Здесь мы заходим в наш аккаунт Ably и создаем новое приложение, для которого нам позже понадобится API-ключ. Затем мы форкнем репозиторий из Ably Labs в ваш собственный репозиторий. Это необходимо для того, чтобы Netlify мог импортировать наш проект на следующем этапе.

Развертывание и настройка служб Netlify

Теперь мы можем импортировать исходный код из Github в Netlify и развернуть приложение. Когда приложение будет запущено, мы столкнемся с некоторыми ошибками, которые нам нужно будет исправить. По мере их исправления вы узнаете немного больше о том, как работает Netlify.

Чтобы контролировать, кому разрешено использовать наше приложение, нам нужен процесс регистрации пользователей. Netlify предоставляет очень хороший виджет для создания пользователей, их регистрации и даже отправки напоминаний о пароле. Кроме того, вы можете использовать сервисы SSO для входа в систему, такие как Google, Github и т.д. Для этого проекта я выбрал базовую регистрацию по электронной почте. Прежде чем начать регистрацию, вам необходимо активировать услуги Netlify Identity в вашей приборной панели.

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

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

Добавьте переменные окружения

Мы завершаем процесс добавлением переменных окружения, повторным развертыванием приложения и проверкой достоверности учетной записи пользователя. На этом этапе вы должны увидеть, что кнопка аутентификации стала зеленой, что означает, что этому пользователю был выдан токен JWT и он успешно подключился к сети Ably realtime.

Ключ Тип Описание
ABLY_APIKEY Строка API-ключ вашего приложения Ably App
ABLY_CAPABILITY Строка или Null JSON-строка разрешений, например, {"channel-name":["subscribe"]}.
ABLY_TTLSECONDS Число Частота обновления в секундах, например, 3600 (60 секунд).

Возможность связана с разрешениями Ably APP.

Возможно, вы захотите ограничить операции только подпиской. Для уточнения разрешений обратитесь к нашей документации: capabilities explained. В этом примере {"channel-name": ["subscribe", "publish"]} означает, что канал статуса имеет разрешение на публикацию и подписку.

Это демонстрационное приложение не использует канал, его цель – проверить аутентификацию, однако Ably JWT должен иметь это значение при генерации JWT.

Модерация пользователей

В этом последнем разделе мы сделаем вид, что наш главный пользователь больше не приветствуется, отредактируем его метаданные в разделе Identity и добавим “Banned” к свойству роли. С этого момента наше приложение будет отказываться повторно выдавать JWT этому пользователю.

Заключение

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

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

С Netlify весь процесс работы разработчика прошел гладко, начиная с регистрации, подключения к Github, импорта проекта и развертывания на производстве. Единственное, что меня замедлило, это перемещение настроек приборной панели (значения пользовательского интерфейса) в файл netlify.toml, чтобы гарантировать, что все, кто использует это репозиторий, получат такой же простой опыт.

TOML загружает мои настройки по умолчанию, которые определяют, где находятся функции и откуда подаются файлы сайта. Была еще одна заминка: выяснить, как сделать тип содержимого application/jwt для заголовка ответа. Изначально я использовал обычный текст (это и есть строка JWT), но в данном случае заголовок должен определять тип как “JWT”.

И в заключение несколько слов о разработке этого демо. Большая часть работы была выполнена с помощью Netlify CLI, который является превосходным инструментом для разработчиков. Он отлично имитирует бессерверную среду, но использование Identify требует, чтобы вы завершили разработку онлайн. Я столкнулся с ошибкой, касающейся объекта контекста, передаваемого обработчику конечной точки, в котором всегда отсутствовал контекст пользователя. Это указано в документации Netlify по Identity, и там четко сказано, что Identity требует HTTPS, но я упустил это из виду и большую часть дня был с яйцами на лице. (Краснеет) Читайте документацию!

Очистка и удаление

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

В этом последнем GIF мы рассмотрим три способа: отзыв ключа API (в случае, если вы были скомпрометированы) – это сохраняет приложение, но уничтожает ключ. Полное удаление приложения, которое навсегда уничтожает все, связанное с этим приложением, и, наконец, как удалить экземпляр Netlify.


Ably в двух словах

Ably предоставляет API для реализации обмена сообщениями pub/sub для функций реального времени в ваших приложениях. Вы также получаете глобально распределенную масштабируемую инфраструктуру в комплекте с набором услуг.

Они включают такие функции, как присутствие 窶, которое показывает статус онлайн/оффлайн различных участников, автоматическое переподключение и возобновление сообщений в случае неполадок в сети, заказ сообщений и гарантированная доставка, а также простые способы интеграции с API сторонних разработчиков.Ably позволяет обмениваться сообщениями pub/sub, в основном через WebSockets.

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

Узнайте больше о платформе Ably


Инструменты для ограничения доступа

gitguardian хранит секреты в вашем исходном коде. Он сканирует ваш исходный код для обнаружения ключей API, паролей, сертификатов, ключей шифрования и других конфиденциальных данных в режиме реального времени.

GitHub Actions – отлично подходит для правил защиты среды и секретов среды (бета-версия)

GitHub Secret Scanning Partners – список поддерживаемых секретов и партнеров, с которыми работает GitHub, чтобы предотвратить мошенническое использование секретов, которые были совершены случайно.

Global .gitignore – хорошо известны файлы .gitignore проекта, менее известный и более важный файл .gitignore может быть установлен на вашей машине и применен к каждому git-репозиторию, который вы отслеживаете. git config --global core.excludesfile ~/.gitignore_global

Веб-токен JSON (JWT) и утилита для тестирования.

Оцените статью
Procodings.ru
Добавить комментарий