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:
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.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:
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)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.
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