DevGang
Авторизоваться

Организация и защита сторонних ресурсов CDN на Yelp

В Yelp мы используем сервис-ориентированную архитектуру для обслуживания наших веб-страниц. Он состоит из множества внешних сервисов, каждый из которых отвечает за обслуживание разных страниц (например, страницы поиска или страницы с перечнем компаний).

В этих веб-службах мы используем несколько сторонних ресурсов JavaScript / CSS ( React , Babel Polyfill и т. д.) для отображения наших веб-страниц. Мы решили обслуживать такие ресурсы, используя стороннюю сеть доставки контента (CDN) для повышения производительности.

В прошлом, если службе внешнего интерфейса требовалось использовать сторонние ресурсы JavaScript / CSS, инженерам приходилось жестко кодировать URL-адрес CDN. Например:

< script 
 src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.8.3/jquery.min.js" 
>< /script>

С сотнями инженеров, работающих в Yelp, было трудно обеспечить следующее (для каждого стороннего ресурса):

  • script или link теги имели контрольную сумму целостности подресурса через атрибут integrity (см. раздел о контрольных суммах целостности подресурса ниже)
  • URL использовали протокол HTTPS
  • Использовались только общедоступные поставщики CDN (одобренные нашей службой безопасности)
  • Инженеры могут легко обновиться до последних версий
  • Организация наших сторонних ресурсов

 

Здесь, в Yelp, мы создали наши веб-сервисы, используя стек сервисов Python, с Pyramid в качестве нашей веб-платформы и uWSGI в качестве нашего веб-сервера.

Мы создали общий пакет Python cdn_assets для хранения URL-адресов и контрольных сумм целостности подресурсов наших сторонних ресурсов JavaScript / CSS.

Для каждого ресурса мы просто использовали словарь Python с семантической версией ресурса в качестве ключа. Например:

# React (facebook.github.io/react)
CDN_SCRIPT_REACT = {
    '16.8.6': CDNAsset.construct_asset(
        cdn=CDNDomain.CDNJS,
        library='react',
        version='16.8.6',
        filename='umd/react.production.min',
        filename_unminified='umd/react.development',
        extension='js',
        integrity='sha384-qn+ML/QkkJxqn4LLs1zjaKxlTg2Bl/6yU/xBTJAgxkmNGc6kMZyeskAG0a7eJBR1',
        integrity_unminified='sha384-u6DTDagyAFm2JKvgGBO8jWd9YzrDzg6FuBPKWkKIg0/GVA6HM9UkSxH2rzxEJ5GF',
    ),
    '16.8.5': CDNAsset.construct_asset(
        # … similar properties for this version
    ),
    # … more versions…
}

# Babel Polyfill (babeljs.io/docs/usage/polyfill)
CDN_SCRIPT_BABEL_POLYFILL = {
    '6.23.0': CDNAsset.construct_asset(
        cdn=CDNDomain.CDNJS,
        library='babel-polyfill',
        version='6.23.0',
        filename='polyfill.min',
        filename_unminified='polyfill',
        extension='js',
        integrity='sha384-FbHUaR69a828hqWjPw4PFllFj1bvveKOTWORGkyosCw720HXy/56+2hSuQDaogMb',
        integrity_unminified='sha384-4L0QKU4TUZXBNNRtCIbt9G73L2fXYHnzgCjL65qwFxsXPvuAf1aB6D3X+LIflqu3',
    ),
    # … more versions…
}

# … more assets…

Использование

Вот фрагмент кода Python, который показывает, как ресурс включен в наши шаблоны Yelp-Cheetah :

CDN_SCRIPT_REACT['16.8.6'].generate_script_tag(minified=True)
# returns < script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js" integrity="sha384-qn+ML/QkkJxqn4LLs1zjaKxlTg2Bl/6yU/xBTJAgxkmNGc6kMZyeskAG0a7eJBR1" crossorigin="anonymous">< /script>

Чтобы упростить использование и обслуживание, мы разработали инфраструктуру строительных лесов, чтобы:

  • Определите общедоступных провайдеров CDN (например, Cloudflare CDNJS , Google CDN и т. Д.)
  • Рендеринг минимизированных скриптов и стилей в производственной среде, а также унифицированных скриптов и стилей в среде разработки
  • Создайте полезный generate_script_tagметод, который позволит пользователям этого пакета легко сгенерировать scriptтег HTML с правильной целостностью подресурса SHA (см. Раздел Сравнение криптографических хеш-функций ниже)

Мы упростили для инженеров добавление новой версии, создав make цель для вычисления контрольной суммы целостности, например:

# Usage: make sri-hash --urls="URL1[ URL2 ... URLn]
$ make sri-hash --urls="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"
sha384-qn+ML/QkkJxqn4LLs1zjaKxlTg2Bl/6yU/xBTJAgxkmNGc6kMZyeskAG0a7eJBR1

Тестирование

Мы написали тесты, которые повторяют все версии всех ресурсов, чтобы гарантировать, что:

  • URL-адреса указывают на действительный ресурс в CDN
  • Целостные контрольные суммы SHA верны
  • URL начинаются с https:// и заканчиваются .js или .css

Вот фрагмент из одного из наших тестовых файлов:

# `all_cdn_scripts` is a Pytest fixture; it’s not shown in this snippet.

@pytest.mark.parametrize('script', all_cdn_scripts)
def test_integrity_hashes_match(script):
    # Test that the unminified URL doesn’t error and has the right integrity hash.
    resp = requests.get(script.url_unminified)
    resp.raise_for_status()
    assert (
        'sha384-{}'.format(base64.b64encode(hashlib.sha384(resp.content).digest()).decode('utf8')) ==
        script.integrity_unminified
    )

    # Test that the minified URL doesn’t error and has the right integrity hash.
    resp = requests.get(script.url)
    resp.raise_for_status()
    assert (
        'sha384-{}'.format(base64.b64encode(hashlib.sha384(resp.content).digest()).decode('utf8')) ==
        script.integrity
    )


def test_sha384_for_all_checksums(all_cdn_scripts):
    SHA384_CHECKSUM_LENGTH = 64

    for cdn_script in all_cdn_scripts:
        assert cdn_script.integrity.startswith('sha384-')
        assert cdn_script.integrity_unminified.startswith('sha384-')

        checksum = cdn_script.integrity.replace('sha384-', '')
        assert len(checksum) == SHA384_CHECKSUM_LENGTH

        checksum = cdn_script.integrity_unminified.replace('sha384-', '')
        assert len(checksum) == SHA384_CHECKSUM_LENGTH


def test_valid_https_urls(all_cdn_scripts):
    https_url_validator = URLValidator(schemes=['https'], message='HTTPS URL validation failed')

    for cdn_script in all_cdn_scripts:
        https_url_validator(cdn_script.url)


def test_valid_script_files(all_cdn_scripts):
    for cdn_script in all_cdn_scripts:
        assert cdn_script.url.endswith('.js')


def test_minified_and_unminified_urls(all_cdn_scripts):
    for cdn_script in all_cdn_scripts:
        assert cdn_script.url.endswith('.min.js')
        assert not cdn_script.url_unminified.endswith('.min.js')

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

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

Контрольные суммы целостности подресурса

В веб - документы на Mozilla Developer Network определить Subresource Integrity как:

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

Поддержка проверки контрольной суммы целостности подресурса достигается путем добавления атрибута integrity в теги script или link. Например:

< script
  src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"
  integrity="sha384-qn+ML/QkkJxqn4LLs1zjaKxlTg2Bl/6yU/xBTJAgxkmNGc6kMZyeskAG0a7eJBR1"
>< /script>

Веб-браузер вычислит хеш из содержимого тега script или link. Затем он сравнит этот хеш со значением атрибута integrity. Если они не совпадают, браузер остановит выполнение тега script или link.

Сравнение криптографических хеш-функций

Согласно спецификации целостности подресурсов (SRI) :

Совместимые пользовательские агенты должны поддерживать криптографические хеш-функции SHA-256, SHA-384 и SHA-512 для использования в качестве части метаданных целостности запроса и могут поддерживать дополнительные хеш-функции.

Хотя поддерживаются как SHA-256, так и SHA-512, мы рекомендуем использовать криптографическую хеш-функцию SHA-384 для атрибута целостности. Это в основном обьясняется тем, что SHA-384  менее восприимчив к атакам расширения длины . (См. Github.com/w3c/webappsec - SRI: обновить примеры до sha384? И github.com/mozilla/srihash.org - почему SHA384? Для получения дополнительной информации.)

Всегда использовать HTTPS для загрузки ресурсов CDN

В Yelp мы перенесли веб-трафик на обслуживание, исключительно с использованием HTTPS и HSTS . Если вы заинтересованы в получении дополнительной информации, ознакомьтесь с этими отличными сообщениями в блоге моих коллег: Великая миграция HTTPS и Дорога к HSTS .

Протокол относительных ссылок

Рекомендуется использовать HTTPS при обслуживании ресурсов CDN вместо URL-адресов, относящихся к протоколу. Цитируя статью «Протокол-относительный URL» по Paul Irish :

Теперь, когда SSL поощряется для всех и не имеет проблем с производительностью , этот метод теперь является анти-паттерном. Если нужный вам ресурс доступен по SSL, всегда используйте https: // asset. Разрешение запроса фрагмента по протоколу HTTP открывает двери для атак, таких как недавняя атака Github Man-on-the-side . Всегда безопасно запрашивать HTTPS-ресурсы, даже если ваш сайт работает по протоколу HTTP, однако обратное неверно. Дополнительные указания и подробности см. В руководстве Эрика Миллса по CDNs & HTTPS и в статье digitalgov.gov о безопасном хостинге аналитики .

Подтверждения

Работа, описанная в этом сообщении в блоге, была выполнена и поддержана многочисленными членами команды инженеров здесь, в Yelp. Особая заслуга в этом принадлежит инженерам нашей команды Core Web Infrastructure (Webcore).

Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

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

Попробовать

Напиши статью и выиграй годовую подписку на Яндекс плюс или лицензию от Jet Brains

Участвовать