Chrome S3 Cloudfront: sin encabezado 'Access-Control-Allow-Origin' en la solicitud XHR inicial

30

Tengo una página web ( https://smartystreets.com/contact ) que usa jQuery para cargar algunos archivos SVG desde S3 a través de CloudFront CDN.

En Chrome abriré una ventana de incógnito, así como la consola. Luego cargaré la página. A medida que se carga la página, normalmente recibo de 6 a 8 mensajes en la consola que se parecen a esto:

XMLHttpRequest cannot load 
https://d79i1fxsrar4t.cloudfront.net/assets/img/feature-icons/documentation.08e71af6.svg.
No 'Access-Control-Allow-Origin' header is present on the requested resource.
Origin 'https://smartystreets.com' is therefore not allowed access.

Si realizo una recarga estándar de la página, incluso varias veces, sigo recibiendo los mismos errores. Si lo hago Command+Shift+R, la mayoría de las imágenes, y a veces todas, se cargarán sin el XMLHttpRequesterror.

A veces, incluso después de que las imágenes se hayan cargado, actualizaré y una o más de las imágenes no se cargarán y devolverán ese XMLHttpRequesterror nuevamente.

He verificado, cambiado y vuelto a comprobar la configuración en S3 y Cloudfront. En S3 mi configuración CORS se ve así:

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedOrigin>http://*</AllowedOrigin>
    <AllowedOrigin>https://*</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
    <AllowedHeader>Authorization</AllowedHeader>
</CORSRule>
</CORSConfiguration>

(Nota: inicialmente solo tenía el <AllowedOrigin>*</AllowedOrigin>mismo problema).

En CloudFront el comportamiento de distribución está configurado para permitir los métodos HTTP: GET, HEAD, OPTIONS. Los métodos en caché son los mismos. Reenviar encabezados se establece en "Lista blanca" y esa lista blanca incluye, "Acceso-Control-Solicitud-Encabezados, Acceso-Control-Solicitud-Método, Origen".

El hecho de que funcione después de una recarga del navegador sin caché parece indicar que todo está bien en el lado S3 / CloudFront, de lo contrario, ¿por qué se entregaría el contenido? Pero entonces, ¿por qué no se entregaría el contenido en la vista de página inicial?

Estoy trabajando en Google Chrome en macOS. Firefox no tiene problemas para obtener los archivos cada vez. Opera NUNCA obtiene los archivos. Safari recogerá las imágenes después de varias actualizaciones.

Usando curlno tengo ningún problema:

curl -I -H 'Origin: smartystreets.com' https://d79i1fxsrar4t.cloudfront.net/assets/img/phone-icon-outline.dc7e4079.svg

HTTP/1.1 200 OK
Content-Type: image/svg+xml
Content-Length: 508
Connection: keep-alive
Date: Tue, 20 Jun 2017 17:35:57 GMT
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET
Access-Control-Max-Age: 3000
Last-Modified: Thu, 15 Jun 2017 16:02:19 GMT
ETag: "dc7e4079f937e83291f2174853adb564"
Cache-Control: max-age=31536000
Expires: Wed, 01 Jan 2020 23:59:59 GMT
Accept-Ranges: bytes
Server: AmazonS3
Vary: Origin,Access-Control-Request-Headers,Access-Control-Request-Method
Age: 4373
X-Cache: Hit from cloudfront
Via: 1.1 09fc52f58485a5da8e63d1ea27596895.cloudfront.net (CloudFront)
X-Amz-Cf-Id: wxn_m9meR6yPoyyvj1R7x83pBDPJy1nT7kdMv1aMwXVtHCunT9OC9g==

Algunos han sugerido que elimine la distribución de CloudFront y la vuelva a crear. Parece una solución bastante dura e inconveniente.

que esta causando este problema?

Actualizar:

Agregar encabezados de respuesta de una imagen que no se pudo cargar.

age:1709
cache-control:max-age=31536000
content-encoding:gzip
content-type:image/svg+xml
date:Tue, 20 Jun 2017 17:27:17 GMT
expires:2020-01-01T23:59:59.999Z
last-modified:Tue, 11 Apr 2017 18:17:41 GMT
server:AmazonS3
status:200
vary:Accept-Encoding
via:1.1 022c901b294fedd7074704d46fce9819.cloudfront.net (CloudFront)
x-amz-cf-id:i0PfeopzJdwhPAKoHpbCTUj1JOMXv4TaBgo7wrQ3TW9Kq_4Bx0k_pQ==
x-cache:Hit from cloudfront
SunSparc
fuente
Tiene razón: eliminar y recrear es extremo y simplemente nunca debería ser necesario. ¿Puede mostrarnos los encabezados de solicitud y respuesta del navegador para una solicitud fallida? ¿Y tal vez por una solicitud exitosa del mismo objeto exacto?
Michael - sqlbot
@ Michael-sqlbot, esperaba que visitaras la URL ( smartystreets.com/contact ) y vieras si sucedía lo mismo en tu máquina. :) Lo interesante de los errores es que, aparte del error en la consola, el navegador informa un estado de 200, citando que está usando la imagen "(de la caché de disco)", que no debería ser posible con Incognito, I pensamiento. Incluso después de borrar el caché local.
SunSparc
1
Sí, las personas a menudo "inventan" nombres de dominio (que resultan ser sitios reales, pero no el sitio en cuestión) que inicialmente no me di cuenta de que había dado el enlace correcto y real a su sitio. Gracias por eso, puedes ignorar mi solicitud. Puedo duplicar el problema. Esto parece un problema del lado del cliente. Estoy persiguiendo una teoría.
Michael - sqlbot
1
Eso es exactamente lo que creo que es el caso. Chrome y S3 están interactuando de una manera que rompe una solicitud CORS que sigue a una solicitud que no es CORS para el mismo objeto. Podría decirse que ambos están equivocados ... pero podría decirse que ninguno de ellos está equivocado. No creo que pueda solucionar esto sin almacenar dos copias del objeto con claves diferentes ... o usar dos distribuciones diferentes de CloudFront (nombres de host diferentes) para que no realice una solicitud CORS y no CORS. Lo escribiré con detalles de cómo estoy llegando a esta conclusión, si lo desea.
Michael - sqlbot
1
Cuando juegas con encabezados, etc., se almacenan en caché, para volver a la configuración actual, simplemente invalidar aquellos en CloudFront
tarikakyol hace

Respuestas:

57

Estás haciendo dos solicitudes para el mismo objeto, una de HTML, otra de XHR. El segundo falla, porque Chrome usa la respuesta en caché de la primera solicitud, que no tiene Access-Control-Allow-Originencabezado de respuesta.

¿Por qué?

El error de Chromium 409090 La solicitud de origen cruzado del error de caché después de que la solicitud regular se almacena en caché describe este problema, y ​​es un "no se solucionará": creen que su comportamiento es correcto. Chrome considera que la respuesta almacenada en caché es utilizable, aparentemente porque la respuesta no incluyó un Vary: Originencabezado.

Pero S3 no regresa Vary: Origincuando se solicita un objeto sin un Origin:encabezado de solicitud, incluso cuando CORS está configurado en el depósito. Vary: Originsolo se envía cuando hay un Originencabezado presente en la solicitud.

Y CloudFront no agrega Vary: Originincluso cuando Originestá en la lista blanca para el reenvío, lo que por definición debería significar que variar el encabezado podría modificar la respuesta; esa es la razón por la que reenvía y almacena en caché los encabezados de solicitud.

CloudFront obtiene un pase, porque su respuesta sería correcta si los S3 fueran más correctos, ya que CloudFront lo devuelve cuando lo proporciona S3.

S3, un poco más borroso. No es incorrecto regresar Vary: Some-Headercuando no había Some-Headeren la solicitud.

Por ejemplo, una respuesta que contiene

Vary: accept-encoding, accept-language

indica que el servidor de origen podría haber utilizado las solicitudes Accept-Encodingy los Accept-Languagecampos (o la falta de ellos) como factores determinantes al elegir el contenido para esta respuesta. (énfasis añadido)

https://tools.ietf.org/html/rfc7231#section-7.1.4

Claramente, Vary: Some-Absent-Headeres válido, por lo que S3 sería correcto si se agrega Vary: Origina su respuesta si CORS está configurado, ya que eso podría variar la respuesta.

Y, aparentemente, esto haría que Chrome hiciera lo correcto. O, si no hace lo correcto en este caso, estaría violando a MUST NOT. De la misma sección:

Un servidor de origen puede enviar Varyuna lista de campos para dos propósitos:

  1. Para informar a los destinatarios de caché que MUST NOTusan esta respuesta para satisfacer una solicitud posterior, a menos que la solicitud posterior tenga los mismos valores para los campos enumerados que la solicitud original (Sección 4.1 de [RFC7234]). En otras palabras, Vary expande la clave de caché requerida para hacer coincidir una nueva solicitud con la entrada de caché almacenada.

...

Entonces, S3 realmente SHOULDregresará Vary: Origincuando CORS esté configurado en el bucket, si Originestá ausente de la solicitud, pero no lo hace.

Aún así, S3 no está estrictamente equivocado para no devolver el encabezado, porque es solo un SHOULD, no un MUST. De nuevo, de la misma sección de RFC-7231:

Un servidor de origen SHOULDenvía un campo de encabezado Vary cuando su algoritmo para seleccionar una representación varía en función de aspectos del mensaje de solicitud que no sean el método y el destino de la solicitud, ...

Por otro lado, se podría argumentar que Chrome debería saber implícitamente que variar el Originencabezado debería ser una clave de caché porque podría cambiar la respuesta de la misma manera que Authorizationpodría cambiar la respuesta.

... a menos que la variación no se pueda cruzar o el servidor de origen se haya configurado deliberadamente para evitar la transparencia de la memoria caché. Por ejemplo, no es necesario enviar el Authorizationnombre del campo Varyporque la reutilización entre usuarios está limitada por la definición de campo [...]

Del mismo modo, la reutilización en todos los orígenes está posiblemente limitada por la naturaleza de, Originpero este argumento no es fuerte.


tl; dr: aparentemente no puede recuperar un objeto de HTML con éxito y luego recuperarlo nuevamente con una solicitud CORS con Chrome y S3 (con o sin CloudFront), debido a peculiaridades en las implementaciones.


Solución alternativa:

Este comportamiento se puede solucionar con CloudFront y Lambda @ Edge, utilizando el siguiente código como desencadenador de respuesta de origen.

Esto se suma Vary: Access-Control-Request-Headers, Access-Control-Request-Method, Origina cualquier respuesta de S3 que no tenga Varyencabezado. De lo contrario, el Varyencabezado en la respuesta no se modifica.

'use strict';

// If the response lacks a Vary: header, fix it in a CloudFront Origin Response trigger.

exports.handler = (event, context, callback) => {
    const response = event.Records[0].cf.response;
    const headers = response.headers;

    if (!headers['vary'])
    {
        headers['vary'] = [
            { key: 'Vary', value: 'Access-Control-Request-Headers' },
            { key: 'Vary', value: 'Access-Control-Request-Method' },
            { key: 'Vary', value: 'Origin' },
        ];
    }
    callback(null, response);
};

Atribución: también soy el autor de la publicación original en los foros de soporte de AWS donde este código se compartió inicialmente.


La solución Lambda @ Edge anterior da como resultado un comportamiento completamente correcto, pero aquí hay dos alternativas que puede encontrar útiles, según sus necesidades específicas:

Alternativa / Hackaround # 1: falsifique los encabezados CORS en CloudFront.

CloudFront admite encabezados personalizados que se agregan a cada solicitud. Si configura Origin:cada solicitud, incluso aquellas que no son de origen cruzado, esto permitirá un comportamiento correcto en S3. La opción de configuración se llama Encabezados de origen personalizados, con la palabra "Origen" que significa algo completamente diferente de lo que significa en CORS. La configuración de un encabezado personalizado como este en CloudFront sobrescribe lo que se envía en la solicitud con el valor especificado, o lo agrega si está ausente. Si tiene exactamente un origen que accede a su contenido a través de XHR, por ejemplo https://example.com, puede agregarlo. Usar *es dudoso, pero podría funcionar para otros escenarios. Considere las implicaciones cuidadosamente.

Alternativa / Hackaround # 2: Use un parámetro de cadena de consulta "ficticio" que difiera para HTML y XHR o esté ausente de uno u otro. Estos parámetros generalmente se nombran x-*pero no deberían x-amz-*.

Digamos que inventas el nombre x-request. Por lo tanto <img src="https://dzczcexample.cloudfront.net/image.png?x-request=html">. Al acceder al objeto desde JS, no agregue el parámetro de consulta. CloudFront ya está haciendo lo correcto, al almacenar en caché diferentes versiones de los objetos utilizando el Originencabezado o la ausencia de este como parte de la clave de caché, porque reenvió ese encabezado en su comportamiento de caché. El problema es que su navegador no lo sabe. Esto convence al navegador de que este es en realidad un objeto separado que debe solicitarse nuevamente, en un contexto CORS.

Si usa estas sugerencias alternativas, use una u otra, no ambas.

Michael - sqlbot
fuente
55
Su respuesta es un salvavidas, una gran respuesta. Me salvaste un tiempo serio.
mtyurt
Hola, no uso Cloudfront para mi s3, por lo que esta solución no está ayudando, ¿hay algo más que pueda hacer?
Jeffin
1
@Jeffin, la alternativa # 2 anterior funcionará solo para S3, sin CloudFront. Agregar un ?x-some-key=some-valueparámetro de cadena de consulta arbitraria convencerá al navegador de que la solicitud es diferente.
Michael - sqlbot
1
@ Michael-sqlbot: Sí, funcionó de
maravilla
1
@ Lionel sí, eso parece correcto.
Michael - sqlbot el
1

No sé por qué obtendría resultados tan diferentes de varios navegadores, pero:

X-Amz-Cf-Id: wxn_m9meR6yPoyyvj1R7x83pBDPJy1nT7kdMv1aMwXVtHCunT9OC9g ==

Esa línea justo allí es lo que (si puede llamar su atención) usará un ingeniero de CloudFront o de Soporte para seguir una de sus solicitudes fallidas. Si la solicitud llega a un servidor CloudFront, debe tener este encabezado en la respuesta. Si ese encabezado no está allí, es probable que la solicitud falle en algún lugar antes de llegar a CloudFront.

unixguy
fuente
Gracias, veré si puedo obtener alguna respuesta en los foros de AWS.
SunSparc
1
Es posible que deba pagar los $ 29 por el soporte para desarrolladores. Esa es una cantidad trivial de dinero para cualquier negocio, dado el costo de tiempo de una persona.
Tim
1
@Tim, tenga en cuenta que el soporte para desarrolladores no es simplemente $ 29. Ese es el precio base. Si el 3% de su factura mensual de AWS es> = $ 29, usted paga el 3% en lugar de la base.
Michael - sqlbot
Gracias @ Michael-sqlbot, no me di cuenta de eso. Sé que el precio de soporte puede aumentar rápidamente cuando tienes cosas como instancias reservadas, pero nunca he visto los precios de desarrollador cuando tienes muchos recursos.
Tim