ruby-on-rails heroku fonts cdn amazon-cloudfront

ruby on rails - El problema de Cloudfront CORS que sirve fuentes en la aplicación Rails



ruby-on-rails heroku (4)

Sigo recibiendo este mensaje de error de la consola cuando visito mi sitio web:

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.

Lo he intentado todo:

  • He instalado la gema font_assets
  • Configuró el archivo application.rb

    config.font_assets.origin = ''http://example.com''

  • Los encabezados en lista blanca en Cloudfront como se explica en este artículo para

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

Pero nada, cero, nada.

Estoy usando Rails 4.1 en Heroku.


A partir de la versión 5.0, Rails permite configurar encabezados HTTP personalizados para los activos y no tienes que usar las gemas de rack-cors o font-asset. Para establecer Access-Control-Allow-Origin para activos (incluidas las fuentes), solo agregue el siguiente código a config / environment / production.rb:

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

El valor del encabezado también podría ser un dominio específico, como el siguiente:

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

Esto funcionó para mi aplicación y no tuve que cambiar ninguna configuración en Cloudfront.


Acabo de tener el mismo problema y logré resolverlo.

Ha dicho correctamente a Cloudfront que permita esos encabezados, pero no ha agregado esos encabezados a donde Cloudfront obtiene la fuente. Sí, tus encabezados de origen están permitidos, pero Heroku no está enviando esos encabezados con la fuente de todos modos.

Para solucionar este problema, deberá obtener los encabezados CORS adecuados agregados a la fuente en Heroku. Por suerte, esto es bastante fácil.

Primero, agregue la gema de rack/cors a su proyecto. https://github.com/cyu/rack-cors

A continuación, configure su servidor Rack para cargar y configurar CORS para cualquier activo que sirva. Agregue lo siguiente después de que su aplicación se cargue en 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

Esto establece cualquier recurso devuelto por Heroku para que se apliquen los encabezados CORS adecuados. Puede restringir la aplicación de encabezados según sus necesidades de archivos y seguridad.

Una vez implementado, vaya a Cloudfront y comience a invalidar cualquier cosa que previamente le estaba dando un error de permiso CORS. Ahora, cuando Cloudfront carga una copia nueva de Heroku, tendrá los encabezados correctos, y Cloudfront pasará esos encabezados al cliente como se configuró previamente con sus permisos de Origin .

Para asegurarse de que está sirviendo los encabezados correctos desde su servidor, puede usar el siguiente comando curl para validar sus encabezados: curl -I -s -X GET -H "Origin: www.yoursite.com" http://www.yoursite.dev:5000/assets/fonts/myfont.svg

Debería ver los siguientes encabezados devueltos:

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


Este fue un tema increíblemente difícil de tratar, por dos razones:

  1. El hecho de que CloudFront esté reflejando los encabezados de respuesta de nuestra aplicación Rails requiere que te vuelvas loco. El protocolo CORS es lo suficientemente difícil de entender como es, pero ahora tiene que seguirlo en dos niveles: entre el navegador y CloudFront (cuando nuestra aplicación Rails lo usa como CDN), y entre el navegador y nuestra aplicación Rails (cuando algún sitio malicioso quiere abusar de nosotros).

    CORS se trata realmente de un diálogo entre el navegador y los recursos de terceros a los que una página web quiere acceder. (En nuestro caso de uso, ese es el CDN de CloudFront, que sirve activos para nuestra aplicación). Pero como CloudFront obtiene sus encabezados de respuesta de Control de acceso desde nuestra aplicación, nuestra aplicación debe servir esos encabezados como si fuera CloudFront hablando, y al mismo tiempo no conceda permisos que se expondrían al tipo de abuso que llevó a que se desarrollara, en primer lugar, la Política del mismo origen / CORS. En particular, no debemos otorgar * acceso a * recursos en nuestro sitio.

  2. Encontré mucha información desactualizada, una interminable línea de publicaciones de blog y SO hilos. CloudFront ha mejorado significativamente su soporte de CORS desde muchas de esas publicaciones, aunque todavía no es perfecto. (CORS realmente debería manejarse de forma inmediata). Y las gemas en sí han evolucionado.

Mi configuración: Rails 4.1.15 se ejecuta en Heroku, con activos servidos desde CloudFront. Mi aplicación responde a http y https, tanto en "www". y el vértice de la zona, sin hacer ninguna redirección.

Miré brevemente la fuente de información que se menciona en la pregunta, pero rápidamente la solté en favor de rack-cors, que parecía más acertada. No quería simplemente abrir todos los orígenes y todos los caminos, ya que eso anularía el punto de CORS y la seguridad de la Política del mismo origen, por lo que necesitaba poder especificar los pocos orígenes que permitiría. Finalmente, personalmente prefiero configurar Rails a través de los archivos config/initializers/*.rb config.ru individuales en lugar de editar los archivos de configuración estándar (como config.ru o config/application.rb ) Poniendo todo eso en conjunto, aquí está mi solución, que creo que es El mejor disponible, a partir de 2016-04-16:

  1. Gemfile

    gem "rack-cors"

    La gema rack-cors implementa el protocolo CORS en un middleware de rack. Además de configurar Access-Control-Allow-Origin y los encabezados relacionados en los orígenes aprobados, agrega un encabezado de respuesta Vary: Origin , que indica a CloudFront que almacene en caché las respuestas (incluidos los encabezados de respuesta) para cada origen por separado. Esto es crucial cuando se puede acceder a nuestro sitio a través de múltiples orígenes (por ejemplo, a través de http y https, y a través de "www." Y el dominio simple)

  2. config / initializers / rack-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

    Esto le dice al navegador que puede acceder a los recursos en nuestra aplicación Rails (y por extensión, en CloudFront, ya que nos está reflejando) solo en nombre de nuestra aplicación Rails (y no en nombre de malware-site.com) y solo para /assets/ urls (y no para nuestros controladores). En otras palabras, permita que CloudFront sirva activos pero no abra la puerta más de lo necesario.

    Notas:

    • Intenté insertar esto después del tiempo de espera de rack en lugar de en la cabeza de la cadena de middleware. Funcionó en dev pero no estaba pateando en Heroku, a pesar de tener el mismo middleware (que no sea Honeybadger).
    • La lista de orígenes también se podría hacer como Regexps. Tenga cuidado de anclar los patrones al final de la cadena.

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

      pero creo que es más fácil leer cadenas literales.

  3. Configure CloudFront para pasar el encabezado de solicitud de Origen del navegador a nuestra aplicación Rails.

    Extrañamente, parece que CloudFront reenvía el encabezado de Origin desde el navegador a nuestra aplicación Rails independientemente de si lo agregamos aquí, pero que CloudFront respeta la directiva de almacenamiento en caché Vary: Origin nuestra aplicación solo si Origin se agrega explícitamente a la lista blanca de encabezados (a partir de abril de 2016). ).

    La lista blanca de encabezado de solicitud está un poco enterrada.

    Si la distribución ya existe, puedes encontrarla en:

    • https://console.aws.amazon.com/cloudfront/home#distributions
    • seleccione la distribución
    • haga clic en Configuración de distribución
    • ir a la pestaña Comportamientos
    • Selecciona el comportamiento (probablemente solo habrá uno)
    • Haga clic en Editar
    • Encabezado Delantero: Lista Blanca
    • Encabezados de lista blanca: seleccione Origen y haga clic en Agregar >>


    Si aún no ha creado la distribución, créela en:

    • https://console.aws.amazon.com/cloudfront/home#distributions
    • Haga clic en Crear distribución.

      (En aras de la integridad y la reproducibilidad, enumero todas las configuraciones que cambié de los valores predeterminados, sin embargo, las configuraciones de la lista blanca son las únicas que son relevantes para esta discusión)

    • Método de entrega: Web (no RTMP)

    • Configuraciones de origen

      • Nombre de dominio de origen: example.com
      • Protocolos SSL de origen: SOLO TLSv1.2
      • Política de protocolo de origen: solo HTTPS
    • Configuración predeterminada de comportamiento de caché

      • Política de protocolo del visor: redirecciona HTTP a HTTPS
      • Encabezado Delantero: Lista Blanca
      • Encabezados de lista blanca: seleccione Origen y haga clic en Agregar >>
      • Comprimir objetos automáticamente: sí

Después de cambiar todas estas cosas, recuerde que puede llevar algún tiempo que los valores antiguos y almacenados en caché caduquen desde CloudFront. Puede invalidar explícitamente los activos almacenados en caché yendo a la pestaña Invalidaciones de la distribución de CloudFront y creando una invalidación para * .


Si ejecuta Rails on Passenger y Heroku: (si no es así, vaya directo a la respuesta de Noach Magedman)

La respuesta de Noach Magedman me resultó muy útil para configurar CloudFront correctamente.

También instalé rack-cors exactamente como se describe y, aunque funcionó bien en el desarrollo, los comandos CURL en producción nunca devolvieron ninguna de las configuraciones de 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

Tenga en cuenta que hago ping al servidor directamente sin pasar por el CDN, el CDN y luego, después de invalidar todo el contenido, debe reenviar lo que sea que el servidor responda. La línea importante aquí es Server: nginx/1.10.0 , que indica que los activos son atendidos por nginx y no por Rails. Como consecuencia, las configuraciones de rack-cors no se aplican.

La solución que funcionó para nosotros está aquí: http://monksealsoftware.com/ruby-on-rails-cors-heroku-passenger-5-0-28/

Básicamente, se trata de clonar y modificar el archivo de configuración nginx para el Pasajero, lo cual no es ideal, ya que esta copia debe mantenerse cada vez que el Pasajero se actualiza y la plantilla cambia.

===

Aquí hay un resumen:

Vaya a la carpeta raíz de su proyecto de Rails y haga una copia de la plantilla de configuración nginx

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

Abra config/passenger_config.erb y comente esta línea

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

Agregue estas configuraciones debajo de la línea mencionada arriba

### 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 ###

Cambie el Procfile para incluir este archivo de configuración personalizado

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

Luego desplegar ...

===

Si sabe de una mejor solución, por favor ponga en los comentarios.

Después de la implementación, el comando CURL produjo la siguiente respuesta:

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