2 puntos por GN⁺ 2024-06-02 | 1 comentarios | Compartir por WhatsApp
  • En los riesgos de filtración de datos en AWS, el acceso no autorizado a buckets de S3 aparece repetidamente, y por el diseño antiguo de la API y algunos comportamientos excepcionales no es fácil determinarlo solo como “público/privado”.
  • Muchas operaciones de S3 no se invocan mediante un endpoint general de AWS, sino con la URL del propio bucket; con una política de bucket mal configurada, incluso una solicitud curl sin autenticación puede ejecutar operaciones peligrosas.
  • No basta con bloquear s3:ListBucket para considerarlo seguro; rutas como ListBucketVersions, ListMultipartUploads y fetch-owner pueden exponer claves de objetos e identificadores de cuenta.
  • Quien sube archivos puede influir en propiedades del objeto como la clase de almacenamiento, etiquetas, Object Lock y algunos headers relacionados con redirecciones, por lo que se necesitan controles adicionales como condiciones de IAM y políticas de ciclo de vida.
  • Si se concluye que es privado solo por ver ACL y la configuración de bloqueo de acceso público, se pueden pasar por alto otras rutas; mediante una distribución de CloudFront o un identity pool de Cognito, usuarios de internet pueden acceder a objetos de S3.

Diseño antiguo de la API de S3 y llamadas anónimas

  • S3 es uno de los servicios iniciales de AWS, por lo que es sólido y está bien probado, pero por rastros previos a los patrones de diseño estandarizados tiene una forma de API distinta a la de otros servicios de AWS.
  • Algunas API de S3 usan endpoints generales como s3.us-east-2.amazonaws.com, pero muchas operaciones deben enviarse directamente a la URL del bucket de destino.
    • Un ejemplo para consultar la lista del bucket es GET / con la forma Host: [bucketname].s3.amazonaws.com.
    • Consultar las etiquetas del bucket también envía GET /?tagging al host de ese bucket.
  • Muchos servicios de AWS, como EC2 o DynamoDB, usan endpoints generales y suelen pasar el recurso de destino mediante headers HTTP o parámetros.
  • Como los buckets de S3 admiten tanto acceso público como autenticado, no siempre está claro qué operaciones de la API pueden realizarse sin autenticación.
  • Si una política de bucket de ejemplo permite Principal: "*" y Action: "s3:*" sobre el recurso del bucket, incluso una solicitud sin autenticación puede borrar el bucket.
  • Algunas operaciones no admiten solicitudes anónimas y devuelven errores como s3:GetBucketOwnershipControls does not support Anonymous requests!.
  • Las solicitudes anónimas a la API quedan registradas en CloudTrail como cuenta anonymous.
    • Si la solicitud no está autenticada, no se puede identificar quién borró el bucket ni quién consultó la configuración de cifrado o el estado del logging.
  • Rutas como /?logging, /?tagging y /?encryption pueden probarse incluso desde el navegador.
  • También hay operaciones como GetObjectTorrent que siguen documentadas, pero ya no pueden ejecutarse.

Bloquear ListBucket no basta para evitar la exposición de claves de objetos

  • Para descargar objetos de S3 se necesita la clave de cada objeto, que funciona como una ruta de archivo.
  • Una solicitud GET al bucket raíz puede devolver el contenido del bucket según las condiciones, por lo que negar s3:ListBucket suele parecer una defensa común.
  • Incluso usando un ACL public-read junto con una política que niega s3:ListBucket, siguen existiendo rutas para obtener claves de objetos.
    • GET /?versions, es decir s3:ListBucketVersions, devuelve metadatos de versiones de objetos dentro del bucket.
    • GET /?uploads, es decir s3:ListMultipartUploads, devuelve la lista de cargas multipart en curso.
  • La documentación de HeadBucket habla de verificar la existencia del bucket y los permisos de acceso, pero en la práctica comprueba si existe permiso para realizar la operación ListBucket.
  • Si solo se valida si ListBucket está denegado, puede pasarse por alto la posibilidad de exposición de claves de objetos en S3.

Costos y exposición de cargas multipart no completadas

  • Una carga multipart empieza con create-multipart-upload y luego se suben partes con upload-part.
  • Las cargas multipart no completadas no son fáciles de revisar desde la consola web; pueden consultarse con /?uploads o con aws s3api list-multipart-uploads --bucket [bucket-name].
  • Si la solicitud de finalización no se envía correctamente, Amazon S3 no ensambla las partes ni crea el objeto.
    • Las partes subidas permanecen en la cuenta hasta que la carga multipart se complete o se aborte.
    • Las partes almacenadas generan costos de almacenamiento de S3.
  • No se encontró una forma de descargar partes del objeto antes de completarlo, pero sí es posible borrarlas.
  • AWS recomienda aplicar una regla de ciclo de vida para eliminar cargas no completadas después de cierta cantidad de días.
  • Al listar cargas multipart no completadas con /?uploads, se devuelve el ARN del principal que inició la carga.
    • Si no se consideran sensibles identificadores como el ID de cuenta o el ARN, esto puede no parecer un problema.
    • Si se quiere evitar exponer identificadores útiles para un atacante, sí puede considerarse una filtración.

ACL y verificación de cuentas basada en email

  • La documentación de ACL de S3 todavía conserva rastros de la época en que las cuentas de AWS se identificaban por el email del usuario root.
  • La operación PutBucketACL permite especificar al grantee mediante dirección de email.
    • Usa Type: AmazonCustomerByEmail y EmailAddress.
  • Si no existe una cuenta de AWS asociada al email indicado, se produce el error UnresolvableGrantByEmailAddress.
    • El mensaje de error toma la forma de “la dirección de email proporcionada no coincide con ninguna cuenta registrada”.
  • Debido a este comportamiento, es posible verificar si una dirección de email específica tiene una cuenta de AWS registrada.

Clase de almacenamiento y metadatos del objeto que puede elegir quien sube archivos

  • La clase de almacenamiento de S3 se aplica al objeto, no al bucket.
  • No existe una configuración para fijar una clase de almacenamiento deseada a nivel de bucket; quien sube el archivo puede especificar la clase de almacenamiento del objeto.
    • El ejemplo es aws s3 cp "my.txt" "s3://mybucket/myobject.txt" --storage-class [CLASS]
  • Quien sube archivos puede influir, dentro de una lista predefinida, en los costos de almacenamiento y acceso por GB que asume el propietario del bucket.
  • En políticas de IAM, la clave de condición s3:x-amz-storage-class permite restringir las clases de almacenamiento permitidas.
    • La política de ejemplo permite solo STANDARD para s3:PutObject.
  • Si se configura una política de ciclo de vida, todos los objetos pueden trasladarse a una clase de almacenamiento específica después de cierto tiempo.
  • En cargas que usan URL prefirmadas, AWS Signature Version 4 exige firmar todos los headers que comiencen con X-Amz-.
    • La clase de almacenamiento se especifica con el header x-amz-storage-class.
    • A menos que la aplicación esté muy mal implementada, no parece haber una forma clara de manipularlo de inmediato.

Etiquetas, Object Lock y redirecciones también están bajo influencia del uploader

  • Muchas propiedades asociadas a objetos de S3 están controladas por quien sube el archivo.
  • Las etiquetas del objeto pueden definirse al momento de la carga.
    • El ejemplo es --tagging "AllYourTags=AreBelong&To=Us"
  • Los sistemas que ejecutan automatizaciones según el valor de las etiquetas pueden verse afectados por valores creados por quien sube el archivo.
  • Object Lock permite configurar retención del objeto y legal hold cuando el object locking está habilitado en el bucket.
    • El comando de ejemplo usa --object-lock-retain-until-date "2099-01-01T00:00:00+0000", --object-lock-legal-hold-status "ON" y --object-lock-mode "COMPLIANCE".
  • En buckets con hosting de sitio web estático habilitado, es posible un open redirect usando la configuración del archivo subido.
  • También hay que prestar atención a la lista completa de headers admitidos por PutObject.
    • Las URL prefirmadas tienen restricciones.
    • En configuraciones que dependen de Cognito identity y políticas de IAM, la solicitud puede firmarse con un contexto autenticado de Cognito.

Exposición del propietario del bucket e identificadores de cuenta

  • Para comprobar si cierto ID de cuenta es el propietario de un bucket al que se puede acceder, se puede incluir el header x-amz-expected-bucket-owner en una solicitud ListBucket.
    • Si se envía un ID de cuenta incorrecto, se devuelve AccessDenied.
    • Si se envía el ID de cuenta correcto y quien llama tiene permiso ListBucket, la respuesta se devuelve normalmente.
  • Si se usa el parámetro fetch-owner=true de la API ListBucket, cada clave de la respuesta incluye un elemento Owner.
  • El ID dentro de Owner es una cadena hexadecimal de 64 caracteres que la documentación de AWS llama canonical user ID.
    • Es una forma ofuscada del ID de cuenta de AWS.
  • Si se guarda ese canonical user ID en Principal como CanonicalUser dentro de una política de IAM y luego se refresca, AWS lo interpreta como un ID de cuenta de AWS.
  • ListBucketVersions y ListMultipartUploads funcionan de manera similar incluso sin fetch-owner.

Las claves de objetos de S3 parecen nombres de archivo, pero funcionan distinto

  • Las claves de objetos de S3 distinguen entre mayúsculas y minúsculas.
  • Aunque parezcan el mismo nombre, si cambia el uso de mayúsculas se pueden subir varios objetos.
  • Si una aplicación trata las claves de objetos de S3 como nombres de archivo sin distinción entre mayúsculas y minúsculas, pueden surgir problemas.
    • En el ejemplo, la aplicación guarda contraseñas de usuarios en archivos de S3 y usa el nombre de archivo como nombre de usuario.
    • Al registrarse, solo verifica si existe el archivo; al cambiar la contraseña, convierte el nombre de usuario a minúsculas y escribe en el archivo.
    • Aunque ya exista jeff, es posible registrar JEFF, y el usuario JEFF podría sobrescribir el archivo de jeff al cambiar su contraseña.
  • En las claves de objetos de S3 puede usarse cualquier carácter UTF-8.
    • Ciertos caracteres pueden causar problemas en algunas aplicaciones y protocolos.
    • Espacios, barras diagonales y el carácter de porcentaje también son válidos en claves de objetos.

Aunque parezca un “bucket privado”, pueden quedar rutas de acceso

  • Incluso si los ACL están desactivados, la política del recurso es restrictiva y block public access está habilitado, el bucket puede seguir siendo accesible públicamente.
  • La ruta más común es una distribución de Amazon CloudFront.
    • Cuando se coloca un CDN delante de un bucket de S3, normalmente existe la intención de entregar contenido a internet.
    • Las herramientas de seguridad pueden concluir que no es público si la política del recurso del bucket está restringida a CloudFront.
  • En el ejemplo, get-bucket-policy-status devuelve IsPublic: false.
    • Si se hace una solicitud directa al bucket, devuelve AccessDenied.
    • Si se envía la misma solicitud al dominio de la distribución de CloudFront, devuelve el contenido del objeto.
  • Un identity pool de Cognito también puede exponer buckets con políticas de recurso restringidas.
    • Cognito entrega credenciales temporales de AWS del rol preconfigurado después de un inicio de sesión exitoso.
    • Si ese rol tiene permisos s3:ListBucket y s3:GetObject, el usuario puede invocar la API de S3.
  • Hay dos configuraciones de Cognito que pueden equivaler a acceso público.
    • Self-registration: si usuarios de internet pueden registrarse e iniciar sesión en la app, en la práctica se trata de acceso público.
    • Guest access: entrega un identificador único y credenciales de AWS incluso a usuarios no autenticados.
  • El ejemplo de acceso de invitado sigue el flujo de obtener IdentityId con get-id, luego credenciales temporales con get-credentials-for-identity, y después ejecutar aws s3 ls con ese perfil.
  • CloudFront y los identity pools de Cognito se usan con frecuencia real en internet, pero son rutas de acceso público que rara vez aparecen marcadas en herramientas de seguridad.

1 comentarios

 
GN⁺ 2024-06-02
Opiniones de Hacker News
  • Hay muchos puntos interesantes, pero me cuesta estar de acuerdo con ver como una queja que el sistema de archivos distinga entre mayúsculas y minúsculas.
    Me parece que así debería ser, y más bien me molesta que macOS no lo haga.

    • No sé por qué “así debería ser”. Windows tampoco distingue entre mayúsculas y minúsculas, así que S3 no es que esté rompiendo una convención casi universal.
      Que los nombres de archivo distingan mayúsculas y minúsculas puede resultar inesperado incluso para usuarios no técnicos. Si alguien dice que envió “Book Draft 1.docx”, pero en el buzón aparece “Book draft 1.docx”, normalmente uno no diría: “Creo que enviaste otro archivo”.
      En el texto, las mayúsculas y minúsculas por lo general tampoco cambian el significado. “Hi, how are you?” y “hi, how are you?” significan lo mismo, y las mayúsculas solo cambian el sentido en casos como distinguir nombres propios de sustantivos comunes, algo que rara vez importa en nombres de archivos.
    • Desde el punto de vista de implementación técnica, está bien establecido en ASCII, Unicode, etc., que 'A' y 'a' son caracteres distintos.
      Más allá de preferencias personales, me cuesta entender que a un desarrollador o administrador de sistemas le sorprenda o le moleste que un sistema de archivos distinga mayúsculas y minúsculas. Si hace falta, el desarrollador puede abstraer eso para el usuario final, como ocurre con los resultados de búsqueda.
    • Soy el autor. No era una queja, sino más bien una observación. No es una cuestión de absolutamente bueno o malo, sino un factor a considerar al diseñar aplicaciones.
    • No sé exactamente qué beneficio aporta que los nombres de archivo distingan entre mayúsculas y minúsculas. En cambio, abre la puerta a muchos errores comunes que directamente no podrían ocurrir si no fuera así.
      Tampoco es como en la programación, donde imponer un estilo de código ayuda a la legibilidad. Incluso en programación solía ser fuente de bugs hasta que los IDE se volvieron lo bastante inteligentes como para detectar errores tipográficos en nombres de variables. Una de las cosas buenas de Pascal, a diferencia de C, era que no había que preocuparse por las mayúsculas y minúsculas.
    • macOS sí preserva mayúsculas y minúsculas. Personalmente, me parece una buena mezcla de lo mejor de ambos mundos.
      Puedes escribir el nombre del archivo con el estilo que quieras y esa forma se conserva, pero al buscarlo o procesarlo no tienes que recordar exactamente ese estilo, porque la búsqueda no distingue mayúsculas y minúsculas.
  • La distinción entre mayúsculas y minúsculas es de lo más fácil; lo menos intuitivo es que las rutas de S3 son falsas.
    S3 acepta subir “/builds/1/installer.exe” y también muestra el listado dentro de /builds, pero en realidad lo que subiste es una sola clave llamada '/builds/1/installer.exe', con '/' incluido en el nombre.
    Por eso también puedes subir “/builds/1//installer.exe” y “/builds//1/installer.exe”, y son archivos completamente distintos. Son solo nombres de clave; no hay directorios reales.

    • Correcto. Aunque hay una excepción si usas los nuevos Directory buckets de S3 [1], lo que en realidad vuelve todo aún más confuso.
      [1] https://docs.aws.amazon.com/AmazonS3/latest/userguide/direct...
    • Tampoco hay que perder de vista que "/" es solo el carácter delimitador de ruta predeterminado. Si necesitas "/" en el nombre de archivo, puedes usar cualquier otro carácter como delimitador: https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObje...
    • Más allá de interpretar una ruta en alguna forma estándar, por ejemplo colapsar barras / duplicadas, no veo en qué se diferencia esencialmente un directorio real de un prefijo.
    • El enfoque basado en prefijos genera muchísimos bugs. Entiendo por qué AWS lo hizo así y, de hecho, es un enfoque inteligente, pero aun así muchos desarrolladores caen en la trampa.
      Este año, nuestro sistema de producción tuvo un bug raro y recién lo encontramos con 5 personas investigando. La causa era un objeto cuyo nombre era literalmente “/”, y el software intentaba tratarlo como una ruta, no como un archivo.
  • Me cuesta confiar en S3 o en otros servicios de AWS. Nada es intuitivo, hay demasiadas piezas móviles y demasiada documentación que leer.
    Aun así, como en el texto original, puedes terminar publicando todo al mundo por error. Preferiría usar servicios realmente simples como Hetzner Storage Boxes o DigitalOcean Spaces.

    • Me gusta DigitalOcean Spaces, pero también tiene sus rarezas molestas.
      Hace poco descubrí que, si subes por pipe un archivo de video de más de unos pocos MB, en el Location devuelto omite https://. Así que en cada carga de archivo hay que verificar si Location empieza con https y, si no, agregárselo.
      Como era de esperarse, en los issues de GitHub del cliente Node de S3 dicen “parece un bug de DigitalOcean”, y en el foro de DigitalOcean dicen “parece un bug del cliente Node de S3”.
    • La forma en que DigitalOcean maneja los secretos alcanza para asustar a cualquiera. ¿Sabías que, si usas Container Registry y configuras K8S para que acceda automáticamente, ese servicio crea un secret con acceso completo a Spaces?
    • Después de dejar el desarrollo cloud por varios años y dedicarme principalmente al lado cliente, volví hace poco y me sorprendieron la complejidad acumulada y la carga cognitiva necesaria para construir una solución blindada en la nube pública.
      Muchísimas funciones y rarezas que originalmente se diseñaron para ayudar en algunos casos especiales ahora forman parte del protocolo común. Parece que terminó así por intentar que, por razones de negocio, nadie se quedara afuera.
  • También hay que tener cuidado al borrar decenas de miles de millones de objetos. Llamar directamente a la API de eliminación puede salir caro.
    En cambio, puedes configurar gratis reglas de ciclo de vida que fijen el vencimiento en now para un comodín o para todo el bucket. Entonces el cobro por almacenamiento se detiene de inmediato y AWS se encarga de procesar la eliminación.

    • Estrictamente hablando, las llamadas de eliminación son gratis; lo que cuesta son las llamadas de listado para obtener los objetos. En teoría, si sabes por otra fuente qué objetos existen, es gratis.
    • El efecto de las reglas de ciclo de vida no es inmediato. Se aplican como un trabajo por lotes que corre una vez al día, así que la eliminación no ocurre al instante.
    • Porque AWS puede elegir el momento real de la eliminación. En los metadatos marca el objeto como eliminado, y AWS puede procesar el borrado en horarios de baja utilización.
      También se evita que los servidores de la API de S3 sean golpeados por la cantidad de solicitudes por segundo.
  • Es realmente malo que las subidas multipart fallidas queden ahí de forma invisible y, si no configuras explícitamente el ciclo de vida, incluso generen costos de almacenamiento.
    Pensé que la S de “Simple” significaba simplicidad.

    • Sí, eso es malo. Puedes culpar a ahenry@, que en ese entonces era GM de S3.
      Mi propuesta era que las partes de subidas incompletas solo permanecieran 24 horas después de la última actividad y que durante ese tiempo tampoco se cobrara almacenamiento. ahenry@ la rechazó.
    • Perdimos miles de dólares por este problema.
      En un servidor muy antiguo, durante casi 10 años se estuvo ejecutando todas las noches un script de cron que iniciaba una subida multipart. Servía para empujar backups a un bucket, pero ese bucket también almacenaba contenido subido por usuarios, así que parecía normal que creciera un poco cada día.
      El script estaba “sin funcionar”, así que no dependíamos de esos datos de backup; los archivos no se veían en S3, y el tamaño del bucket aumentaba de forma constante pero no exagerada. Pero esta primavera vimos que estaba almacenando casi 3 TB de subidas multipart incompletas.
      Por supuesto, sé que esta anécdota está llena de malas prácticas.
    • Esa S es de forma Simple de disparar los costos.
    • El nombre “Simple” viene de una época en la que la alternativa era administrar uno mismo una flota de servidores con discos. El tiempo lo cambia todo.
    • Yo también pisé una mina de costos de almacenamiento. Por suerte fueron unos centavos, pero la forma en que la consola muestra la información relacionada es tan pésima que me da bastante rabia.
  • Siento que las discusiones sobre sensibilidad/no sensibilidad a mayúsculas y minúsculas suelen ser demasiado anglocéntricas.
    Dicho de otra forma, especialmente en TI, demasiadas discusiones relacionadas con el lenguaje son excesivamente anglocéntricas.

    • Me parece más bien una suerte que sean anglocéntricas. ASCII era mucho más fácil de manejar que Unicode.
      Lo digo como alguien cuya lengua materna no es el inglés: la programación ya tiene demasiados conceptos y elementos. Es mejor no aumentar la complejidad teniendo que considerar además 101 idiomas distintos.
      Unicode y las zonas horarias son ejemplos representativos de elementos que intentan considerar más lenguas y culturas en programación, y al final generan el mayor sufrimiento para todos, incluidos los programadores que no hablan inglés.
      No quiero escribir programas en mi lengua materna. Mucho menos si el costo de hacerlo es tener que considerar todos los idiomas principales al programar. Está bien que las discusiones de TI sean anglocéntricas. La diversidad es complejidad, y el inglés no es un idioma que posea alguien, sino una herramienta que la gente usa para comunicarse.
      Gracias a esa lengua común puedo expresar mis ideas a mucha gente de India, China, Japón, Sudamérica, etc. En el momento en que ellos deciden hablar en inglés, también son dueños del inglés. No hace falta meter la política de la diversidad en TI; es mejor mantenerlo en el plano técnico.
    • Ya que salió el tema de las culturas no angloparlantes: en japonés, ¿los sistemas que no distinguen mayúsculas y minúsculas distinguen entre hiragana y katakana?
      En cierto sentido, esos dos silabarios se sienten como un alfabeto en mayúsculas/minúsculas.
  • Hay algunas cosas más
    Las cargas multipart no se pueden hacer desde varias máquinas con credenciales de instancia. Como el principal es distinto, no pueden acceder a las cargas multipart de los demás. Para ensamblar una sola carga multipart desde varias máquinas, hace falta un usuario de IAM real
    Las solicitudes LIST no solo son lentas; en grandes volúmenes también son muy caras. Hay alternativas como “bucket inventory”, pero no son cómodas ni baratas
    La creación de buckets usa DNS internamente, por lo que no tiene consistencia de lectura tras escritura. Por eso a veces no puedes acceder a un bucket justo después de crearlo, o no puedes borrar un bucket recién creado antes de esperar lo suficiente a que se propaguen los cambios. Consulta https://github.com/julik/talks/blob/master/euruko-2019-no-su...
    Puedes crear al mismo tiempo un objeto llamado “foo” y otro llamado “foo/bar”. Eso termina siendo una estructura donde un archivo sobrescribe un directorio, lo que impide portar los datos del bucket a una estructura de sistema de archivos
    Como S3 distingue mayúsculas de minúsculas, puedes crear objetos que no se pueden portar a una estructura de sistema de archivos. El almacenamiento de archivos de Rails se rompió bastante en macOS porque asumía un almacenamiento sensible a mayúsculas y minúsculas, y se corrigió para usar identificadores siempre en minúsculas
    La mayoría de las configuraciones de S3 permiten GET, pero no HEAD. Parece ser una forma de impedir la exploración de la existencia de objetos, aunque no estoy seguro. En cualquier caso, un flujo amigable con caché que verifica el tamaño de un objeto con una solicitud HEAD no funciona, especialmente con URL prefirmadas. En su lugar, hay que sortearlo con un GET usando un Range muy pequeño, por ejemplo trayendo solo el primer byte
    Si generas muchas URL prefirmadas, es posible aumentar la velocidad de generación entre 10 y 40 veces: https://github.com/WeTransfer/wt_s3_signer
    También sigues pagando el costo de almacenamiento de las cargas multipart incompletas. Hay que tener especial cuidado si tu arquitectura permite que los usuarios inicien este tipo de cargas. Existe una configuración para eliminar automáticamente las cargas multipart incompletas después de cierto tiempo; si no quieres sufrir, actívala
    Paradójicamente, S3 fue revolucionario y sigue siendo un producto excelente en muchos niveles. Pero así como tiene muchas funciones, también tiene muchas trampas

    • Algo que me atrapó hace unas semanas fue la restricción de tamaño mínimo de 5 MiB para el chunk inicial en las cargas multipart: https://docs.aws.amazon.com/AmazonS3/latest/userguide/qfacts...
      En Elixir, usando Stream.transform(https://hexdocs.pm/elixir/Stream.html#transform/3), armé un pipeline de posprocesamiento de CSV en streaming para modificar e inyectar columnas. Los módulos de AWS y CSV de Elixir procesan datos entrantes en streaming, pero me dio tristeza que, si el total del stream saliente era menor que 5 MiB, S3 arrojaba un error porque el módulo de AWS usa cargas multipart
  • Hay otro problema interesante que diagnosticamos tras varios días de análisis con un colega. S3 descarta silenciosamente las solicitudes posteriores cuando una única conexión TCP ha enviado 100 solicitudes HTTP
    https://github.com/aws/aws-sdk-go/issues/2825

    • No las descarta silenciosamente: envía un encabezado indicando que cerró la conexión TCP
      Es un patrón común cuando quieres keep-alive por rendimiento, pero también quieres evitar que los clientes permanezcan conectados demasiado tiempo y creen hotspots en el balanceador de carga
  • También está el hecho de que S3, en la clase de almacenamiento estándar, tiene alta latencia y no es adecuado para servir contenido web
    Mucha gente piensa que puede alojar directamente en S3 recursos de un sitio web como imágenes o fuentes, pero eso puede empeorar la experiencia de usuario
    “applications can achieve consistent small object latencies (and first-byte-out latencies for larger objects) of roughly 100–200 milliseconds.”
    Fuente: https://docs.aws.amazon.com/AmazonS3/latest/userguide/optimi...

    • La mayoría usa S3 como origen de AWS CloudFront para entregar contenido
      Con CloudFront signed cookies, también puedes dar acceso por CDN a usuarios específicos solo al contenido de S3 que les pertenece. Es bastante genial
    • Para servir assets web, normalmente se usan juntos S3 y CloudFront
      Puedes cachear assets a los que se accede con frecuencia para reducir la latencia y también bajar bastante los costos
    • S3 no está optimizado para servir sitios web directamente, sino para almacenar y recuperar de forma duradera cantidades prácticamente ilimitadas de datos
  • Que el uploader decida las reglas es bastante fuerte. Si un sitio web está configurado de forma descuidada, ¿eso significa que un usuario con suficiente motivación podría hacer que el contenido de usuario se suba a Amazon Glacier y luego se sirva desde ahí?