Подтвердить что ты не робот

Cloudfront CORS выпускает шрифты в приложении Rails

Я продолжаю получать это сообщение об ошибке с консоли при посещении моего веб-сайта:

font from origin 'https://xxx.cloudfront.net' has been blocked from loading by Cross-Origin Resource Sharing policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'https://www.example.com' is therefore not allowed access.

Я пробовал все:

  • Я установил font_assets gem
  • настроил файл application.rb

    config.font_assets.origin = 'http://example.com'
    
  • "Белые заголовки на Cloudfront", как описано в этой статье, чтобы

    Access-Control-Allow-Origin
    Access-Control-Allow-Methods
    Access-Control-Allow-Headers
    Access-Control-Max-Age
    

Но ничего, ноль, нада.

Я использую Rails 4.1 на Heroku.

4b9b3361

Ответ 1

Это была невероятно трудная проблема для решения по двум причинам:

  • Тот факт, что CloudFront зеркалирует, наши заголовки ответов приложений Rails требуют, чтобы вы крутили свой разум. Протокол CORS достаточно прост, чтобы понять его, но теперь вы должны следовать ему на двух уровнях: между браузером и CloudFront (когда наше приложение Rails использует его как CDN), так и между браузером и нашим Rails-приложением (когда какой-то вредоносный сайт хочет нас оскорбить).

    CORS действительно представляет собой диалог между браузером и сторонними ресурсами, к которым хочет получить доступ веб-страница. (В нашем прецеденте, это CloudFront CDN, обслуживающий активы для нашего приложения.) Но поскольку CloudFront получает заголовки ответов Access-Control из нашего приложения, наше приложение должно обслуживать эти заголовки, как если бы речь шла о CloudFront, и одновременно не предоставить разрешения на выдачу разрешений, которые будут подвергать себя жестокому обращению, которое привело бы к тому, что в первую очередь разрабатывалась политика одинакового происхождения /CORS. В частности, мы не должны предоставлять * доступ к ресурсам * на нашем сайте.

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

Моя настройка: Rails 4.1.15 работает на Heroku, с активами, обслуживаемыми CloudFront. Мое приложение отвечает как на http, так и на https, как на "www". и вершину зоны, без какого-либо перенаправления.

Я коротко взглянул на драгоценный камень font_assets, упомянутый в вопросе, но быстро отбросил его в пользу стойки-корса, что казалось более точным. Я не хотел просто открывать все происхождение и все пути, так как это победит точку CORS и безопасность политики одинакового происхождения, поэтому мне нужно было указать несколько истоков, которые я бы позволил. Наконец, я лично предпочитаю настраивать Rails через отдельные файлы config/initializers/*.rb, а не редактировать стандартные файлы конфигурации (например, config.ru или config/application.rb). Объединяя все это вместе, вот мое решение, которое, по моему мнению, является лучшим из доступных 2016-04-16:

  • Gemfile

    gem "rack-cors"
    

    Драгоценный камень стойки-корса реализует протокол CORS в промежуточном ПО Rack. В дополнение к настройке Access-Control-Allow-Origin и связанных заголовков на одобренных источниках, он добавляет заголовок ответа Vary: Origin, направляя CloudFront кэшировать ответы (включая заголовки ответов) для каждого источника отдельно. Это важно, когда наш сайт доступен через несколько источников (например, через HTTP и HTTPS, а также через "www." И "голый домен" )

  • Config/инициализаторы/стойку cors.rb

    ## Configure Rack CORS Middleware, so that CloudFront can serve our assets.
    ## See https://github.com/cyu/rack-cors
    
    if defined? Rack::Cors
        Rails.configuration.middleware.insert_before 0, Rack::Cors do
            allow do
                origins %w[
                    https://example.com
                     http://example.com
                    https://www.example.com
                     http://www.example.com
                    https://example-staging.herokuapp.com
                     http://example-staging.herokuapp.com
                ]
                resource '/assets/*'
            end
        end
    end
    

    Это говорит браузеру, что он может получить доступ к ресурсам в нашем Rails-приложении (и, в добавлении, на CloudFront, поскольку он отражает нас) только от имени нашего Rails-приложения (а не от имени malicious-site.com) и только для /assets/ URL (а не для наших контроллеров). Другими словами, позволить CloudFront обслуживать активы, но не открывать дверь больше, чем мы должны.

    Примечания:

    • Я попытался вставить это после тайм-аута стойки, а не во главе цепи промежуточного программного обеспечения. Он работал над разработчиком, но не стал пинать на Heroku, несмотря на с тем же промежуточным программным обеспечением (кроме Honeybadger).
    • Список истоков также может быть выполнен как Regexps. Будьте осторожны, чтобы привязать шаблоны к концу строки.

      origins [
          /\Ahttps?:\/\/(www\.)?example\.com\z/,
          /\Ahttps?:\/\/example-staging\.herokuapp\.com\z/
      ]
      

      но я думаю, что его проще просто читать литералы.

  • Конфигурировать CloudFront для передачи заголовка запроса заголовка браузера в наше приложение Rails.

    Странно, похоже, что CloudFront пересылает заголовок Origin из браузера в наше приложение Rails, независимо от того, добавляем ли мы его здесь, но CloudFront чтит наши приложения Vary: Origin директива кэширования только в том случае, если Origin явно добавлен в белый список заголовков (как от апреля 2016 года).

    Белый список заголовка запроса похож на захороненный.

    Если распределение уже существует, вы можете найти его по адресу:

    • https://console.aws.amazon.com/cloudfront/home#distributions
    • выберите дистрибутив
    • нажмите Настройки распространения
    • перейдите на вкладку "Поведение"
    • выберите поведение (возможно, будет только один)
    • Нажмите "Изменить"
    • Передовые заголовки: белый список
    • Заголовки белых списков: Выберите Происхождение и нажмите Добавить →


    Если вы еще не создали дистрибутив, создайте его по адресу:

    • https://console.aws.amazon.com/cloudfront/home#distributions
    • Нажмите "Создать дистрибутив"

      (Для полноты и воспроизводимости я перечисляю все настройки, которые я изменил с значений по умолчанию, однако настройки Whitelist являются единственными, которые имеют отношение к этому обсуждению)

    • Способ доставки: Web (не RTMP)

    • Настройки источника

      • Происхождение Имя домена: example.com
      • Протоколы SSL происхождения: ТОЛЬКО TLSv1.2
      • Политика протокола происхождения: только HTTPS
    • Настройки поведения кэша по умолчанию

      • Политика протокола просмотра: перенаправление HTTP на HTTPS
      • Передовые заголовки: белый список
      • Заголовки белых списков: Выберите Происхождение и нажмите Добавить →
      • Автоматическое сжатие объектов: Да

Изменив все эти вещи, помните, что может потребоваться некоторое время для истечения срока действия старых, кешированных значений из CloudFront. Вы можете явно аннулировать кешированные активы, перейдя на вкладку Invalidations на вкладке CloudFront и создав недействительность для *.

Ответ 2

Если вы запустите Rails на Пассажире и Heroku: (если нет, перейдите прямо к ответу Noach Magedman)

Ответ Noach Magedman был очень полезен для меня, чтобы правильно настроить CloudFront.

Я также установил rack-cors точно так, как описано, и хотя он отлично работал в разработке, команды CURL в производстве никогда не возвращали ни одну из конфигураций CORS:

curl -H "Origin: https://tidyme-staging.com.au" -I http://tidyme-staging.com.au/assets/31907B_4_0-588bd4e720d4008295dcfb85ef36b233ee0817d7fe23c76a3a543ebba8e7c85a.ttf

HTTP/1.1 200 OK
Connection: keep-alive
Server: nginx/1.10.0
Date: Wed, 03 Aug 2016 00:29:37 GMT
Content-Type: application/x-font-ttf
Content-Length: 316664
Last-Modified: Fri, 22 Jul 2016 03:31:57 GMT
Expires: Thu, 31 Dec 2037 23:55:55 GMT
Cache-Control: max-age=315360000
Cache-Control: public
Accept-Ranges: bytes
Via: 1.1 vegur

Обратите внимание, что я пинговаю сервер напрямую, не пропуская CDN, CDN после того, как недействительный весь контент должен просто перенаправлять все, что отвечает сервер. Важная строка здесь Server: nginx/1.10.0, что указывает на то, что активы обслуживаются nginx, а не Rails. Как следствие, конфигурации rack-cors не применяются.

Решение, которое сработало для нас, находится здесь: http://monksealsoftware.com/ruby-on-rails-cors-heroku-passenger-5-0-28/

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

===

Здесь резюме:

Перейдите в корневую папку проекта Rails и создайте копию шаблона конфигурации nginx

cp $(passenger-config about resourcesdir)/templates/standalone/config.erb config/passenger_config.erb

Откройте config/passenger_config.erb и прокомментируйте эту строку

<%# include_passenger_internal_template('rails_asset_pipeline.erb', 8, false) %>

Добавьте эти конфигурации ниже указанной выше строки

### BEGIN your own configuration options ###
# This is a good place to put your own config
# options. Note that your options must not
# conflict with the ones Passenger already sets.
# Learn more at:
# https://www.phusionpassenger.com/library/config/standalone/intro.html#nginx-configuration-template

location ~ "^/assets/.+\.(woff|eot|svg|ttf|otf).*" {
    error_page 490 = @static_asset_fonts;
    error_page 491 = @dynamic_request;
    recursive_error_pages on;

    if (-f $request_filename) {
        return 490;
    }
    if (!-f $request_filename) {
        return 491;
    }
}

# Rails asset pipeline support.
location ~ "^/assets/.+-([0-9a-f]{32}|[0-9a-f]{64})\..+" {
    error_page 490 = @static_asset;
    error_page 491 = @dynamic_request;
    recursive_error_pages on;

    if (-f $request_filename) {
        return 490;
    }
    if (!-f $request_filename) {
        return 491;
    }
}

location @static_asset {
    gzip_static on;
    expires max;
    add_header Cache-Control public;
    add_header ETag "";
}

location @static_asset_fonts {
    gzip_static on;
    expires max;
    add_header Cache-Control public;
    add_header ETag "";
    add_header 'Access-Control-Allow-Origin' '*';
    add_header 'Access-Control-Allow-Methods' 'GET, HEAD, OPTIONS';
    add_header 'Access-Control-Allow-Headers' '*';
    add_header 'Access-Control-Max-Age' 3628800;
}

location @dynamic_request {
    passenger_enabled on;
}

### END your own configuration options ###

Измените Procfile, чтобы включить этот настраиваемый файл конфигурации

web: bundle exec passenger start -p $PORT --max-pool-size 2 --nginx-config-template ./config/passenger_config.erb

Затем разверните...

===

Если вы знаете лучшее решение, добавьте комментарии.

После реализации команда CURL дала следующий ответ:

curl -H "Origin: https://tidyme-staging.com.au" -I http://tidyme-staging.com.au/assets/31907B_4_0-588bd4e720d4008295dcfb85ef36b233ee0817d7fe23c76a3a543ebba8e7c85a.ttf

HTTP/1.1 200 OK
Connection: keep-alive
Server: nginx/1.10.0
Date: Wed, 03 Aug 2016 01:43:48 GMT
Content-Type: application/x-font-ttf
Content-Length: 316664
Last-Modified: Fri, 22 Jul 2016 03:31:57 GMT
Expires: Thu, 31 Dec 2037 23:55:55 GMT
Cache-Control: max-age=315360000
Cache-Control: public
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, HEAD, OPTIONS
Access-Control-Allow-Headers: *
Access-Control-Max-Age: 3628800
Accept-Ranges: bytes
Via: 1.1 vegur

Ответ 3

У меня была одна и та же проблема, и мне удалось ее решить.

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

Чтобы исправить это, вам нужно будет добавить правильные заголовки CORS к шрифту на Heroku. К счастью, это довольно легко.

Сначала добавьте драгоценный камень rack/cors в свой проект. https://github.com/cyu/rack-cors

Затем настройте сервер Rack для загрузки и настройки CORS для любых активов, которые он обслуживает. Добавьте следующее после предварительной загрузки приложения в config.ru

require 'rack/cors'
use Rack::Cors do
  allow do
    origins '*'

    resource '/cors',
      :headers => :any,
      :methods => [:post],
      :credentials => true,
      :max_age => 0

    resource '*',
      :headers => :any,
      :methods => [:get, :post, :delete, :put, :patch, :options, :head],
      :max_age => 0
    end
  end

Это устанавливает любые ресурсы, возвращенные из Heroku, чтобы иметь соответствующие заголовки CORS. Вы можете ограничить применение заголовков в зависимости от вашего файла и требований безопасности.

После развертывания перейдите в Cloudfront и начните аннулирование на все, что ранее давало вам ошибку разрешения CORS. Теперь, когда Cloudfront загружает новую копию из Heroku, у нее будут правильные заголовки, а Cloudfront передаст эти заголовки клиенту, как ранее настроенные с вашими разрешениями Origin.

Чтобы убедиться, что вы обслуживаете соответствующие заголовки с вашего сервера, вы можете использовать следующую команду curl для проверки своих заголовков: curl -I -s -X GET -H "Origin: www.yoursite.com" http://www.yoursite.dev:5000/assets/fonts/myfont.svg

Вы должны увидеть следующие возвращенные заголовки:

Access-Control-Allow-Origin: www.yoursite.com
Access-Control-Allow-Methods: GET, POST, DELETE, PUT, PATCH, OPTIONS, HEAD
Access-Control-Max-Age: 0
Access-Control-Allow-Credentials: true

Ответ 4

Начиная с версии 5.0, Rails позволяет устанавливать настраиваемые заголовки HTTP для ресурсов, и вам не нужно использовать гем-стойку или шрифты-ресурсы. Чтобы установить Access-Control-Allow-Origin для ресурсов (включая шрифты), просто добавьте следующий код в config/environment/production.rb:

config.public_file_server.headers = {
  'Access-Control-Allow-Origin' => '*'
}

Значением заголовка также может быть конкретный домен, например:

config.public_file_server.headers = {
  'Access-Control-Allow-Origin' => 'https://www.example.org'
}

Это работало для моего приложения, и мне не нужно было менять какие-либо настройки в Cloudfront.

Ответ 5

Вот репозиторий, демонстрирующий обслуживание пользовательского шрифта в Rails 5.2, который работает на Heroku. Это идет дальше и оптимизирует обслуживание шрифтов, чтобы быть максимально быстрым согласно https://www.webpagetest.org/

https://github.com/nzoschke/edgecors

Asset Pipeline и SCSS

  • Поместите шрифты в app/assets/fonts
  • Поместите объявление @font-face в файл scss и используйте помощник font-url

Из app/assets/stylesheets/welcome.scss:

@font-face {
  font-family: 'Inconsolata';
  src: font-url('Inconsolata-Regular.ttf') format('truetype');
  font-weight: normal;
  font-style: normal;
}

body {
  font-family: "Inconsolata";
  font-weight: bold;
}

Подавать из CDN с CORS

Я использую CloudFront, добавленный с помощью дополнения Heroku Edge.

Если вы используете свой собственный CloudFront, не забудьте настроить его так, чтобы он перенаправлял заголовок браузера Origin к исходному источнику.

Сначала настройте префикс CDN и заголовки Cache-Control умолчанию в production.rb:

Rails.application.configure do
  # e.g. https://d1unsc88mkka3m.cloudfront.net
  config.action_controller.asset_host = ENV["EDGE_URL"]

  config.public_file_server.headers = {
    'Cache-Control' => 'public, max-age=31536000'
  }
end

Если вы попытаетесь получить доступ к шрифту с URL-адреса herokuapp.com по URL-адресу CDN, вы получите сообщение об ошибке CORS в вашем браузере:

Доступ к шрифту по адресу https://d1unsc88mkka3m.cloudfront.net/assets/Inconsolata-Regular.ttf из источника " https://edgecors.herokuapp.com " был заблокирован политикой CORS: нет "Access-Control-Allow" Заголовок -Origin 'присутствует на запрашиваемом ресурсе. edgecors.herokuapp.com/ПОЛУЧИТЕ https://d1unsc88mkka3m.cloudfront.net/assets/Inconsolata-Regular.ttf net :: ERR_FAILED

Поэтому настройте CORS, чтобы разрешить доступ к шрифту из Heroku по URL CDN:

module EdgeCors
  class Application < Rails::Application
    # Initialize configuration defaults for originally generated Rails version.
    config.load_defaults 5.2

    config.middleware.insert_after ActionDispatch::Static, Rack::Deflater

    config.middleware.insert_before 0, Rack::Cors do
      allow do
        origins %w[
          http://edgecors.herokuapp.com
          https://edgecors.herokuapp.com
        ]
        resource "*", headers: :any, methods: [:get, :post, :options]
      end
    end
  end
end

Служить gzip Font Asset

Конвейер ресурсов создает файл .ttf.gz но не обслуживает его. Этот патч обезьяны изменяет белый список gzip конвейера ресурсов на черный список:

require 'action_dispatch/middleware/static'

ActionDispatch::FileHandler.class_eval do
  private

    def gzip_file_path(path)
      return false if ['image/png', 'image/jpeg', 'image/gif'].include? content_type(path)
      gzip_path = "#{path}.gz"
      if File.exist?(File.join(@root, ::Rack::Utils.unescape_path(gzip_path)))
        gzip_path
      else
        false
      end
    end
end

Конечным результатом является файл пользовательских шрифтов в app/assets/fonts подается из долгоживущего кеша CloudFront.