2 pontos por GN⁺ 2024-06-02 | 1 comentários | Compartilhar no WhatsApp
  • No risco de vazamento de dados na AWS, o acesso não autorizado a buckets S3 aparece repetidamente, e por causa de um design de API antigo e comportamentos excepcionais, não é fácil fazer um julgamento simples de “público/privado”
  • Muitas operações do S3 são chamadas não por um endpoint geral da AWS, mas pela própria URL do bucket, e em políticas de bucket mal configuradas é possível executar operações perigosas até com uma requisição curl sem autenticação
  • Não dá para considerar que bloquear apenas s3:ListBucket já é suficiente; caminhos como ListBucketVersions, ListMultipartUploads e fetch-owner podem revelar chaves de objeto e identificadores de conta
  • Quem faz upload pode influenciar atributos do objeto como classe de armazenamento, tags, Object Lock e alguns headers relacionados a redirecionamento, então são necessários controles adicionais como condições no IAM e políticas de ciclo de vida
  • Se você concluir que algo é privado olhando apenas ACL e bloqueio de acesso público, pode deixar passar outros caminhos; usuários da internet podem acessar objetos no S3 por meio de uma distribuição CloudFront ou de um Cognito identity pool

Design antigo da API do S3 e chamadas anônimas

  • O S3 é um dos serviços iniciais da AWS, então é robusto e bem testado, mas por carregar traços anteriores aos padrões de design padronizados, tem uma forma de API diferente de outros serviços da AWS
  • Algumas APIs do S3 usam endpoints gerais como s3.us-east-2.amazonaws.com, mas em muitas operações é preciso enviar a requisição diretamente para a URL do bucket de destino
    • Um exemplo de listagem do bucket é GET / com Host: [bucketname].s3.amazonaws.com
    • A consulta de tags do bucket também envia GET /?tagging para o host daquele bucket
  • Muitos serviços da AWS, como EC2 e DynamoDB, usam endpoints gerais, e é comum passar o recurso de destino por headers HTTP ou parâmetros
  • Como buckets S3 suportam tanto acesso público quanto acesso autenticado, nem sempre fica claro quais operações de API podem ser usadas sem autenticação
  • Se uma política de bucket de exemplo permitir Principal: "*" e Action: "s3:*" no recurso do bucket, será possível até apagar o bucket com requisições sem autenticação
  • Algumas operações não aceitam requisições anônimas, retornando erros como s3:GetBucketOwnershipControls does not support Anonymous requests!
  • Requisições anônimas à API ficam registradas no CloudTrail como conta anonymous
    • Se a requisição não for autenticada, não é possível identificar quem apagou o bucket ou consultou configurações de criptografia e estado de logging
  • Caminhos como /?logging, /?tagging e /?encryption também podem ser testados no navegador
  • Há operações como GetObjectTorrent que ainda aparecem na documentação, mas já não podem mais ser executadas

Bloquear apenas ListBucket não basta para impedir exposição de chaves de objeto

  • Para baixar objetos do S3, é preciso conhecer a chave de cada objeto
  • Essa chave funciona como um caminho de arquivo
  • Uma requisição GET ao bucket raiz pode, dependendo das condições, retornar o conteúdo do bucket, então negar s3:ListBucket costuma parecer uma defesa comum
  • Mesmo combinando ACL public-read com uma política que negue s3:ListBucket, ainda restam caminhos para obter chaves de objeto
    • GET /?versions, ou seja, s3:ListBucketVersions, retorna metadados das versões de objetos no bucket
    • GET /?uploads, isto é, s3:ListMultipartUploads, retorna a lista de uploads multipart em andamento
  • A documentação de HeadBucket fala em verificar existência do bucket e permissões de acesso, mas na prática verifica se há permissão para executar a operação ListBucket
  • Se você validar apenas se ListBucket está negado, pode deixar passar a possibilidade de exposição de chaves de objetos no S3

Custo e exposição de uploads multipart não concluídos

  • Um upload multipart começa com create-multipart-upload e envia partes com upload-part
  • Uploads multipart não concluídos não são fáceis de visualizar no console web; é possível verificá-los com /?uploads ou aws s3api list-multipart-uploads --bucket [bucket-name]
  • Se a requisição de conclusão não for enviada com sucesso, o Amazon S3 não monta as partes nem cria o objeto
    • As partes enviadas permanecem na conta até que o multipart upload seja concluído ou abortado
    • As partes armazenadas geram custo de armazenamento no S3
  • O autor não encontrou um meio de baixar partes do objeto antes da conclusão, mas é possível apagá-las
  • A AWS recomenda aplicar uma regra de ciclo de vida para apagar uploads não concluídos após um determinado número de dias
  • Ao listar uploads multipart não concluídos com /?uploads, é retornado o ARN do principal que iniciou o upload
    • Se você considera que identificadores como ID de conta e ARN não são sensíveis, isso pode não parecer um problema
    • Se você evita expor identificadores úteis a atacantes, isso pode ser visto como exposição

ACL e verificação de conta por email

  • A documentação de ACL do S3 ainda carrega vestígios da época em que contas AWS eram identificadas pelo email do usuário root
  • A operação PutBucketACL permite definir o grantee por endereço de email
    • Ela usa Type: AmazonCustomerByEmail e EmailAddress
  • Se não houver uma conta AWS associada ao email informado, ocorre o erro UnresolvableGrantByEmailAddress
    • A mensagem de erro segue a linha de “o endereço de email fornecido não corresponde a nenhuma conta em nossos registros”
  • Por causa desse comportamento, é possível verificar se um determinado endereço de email possui uma conta AWS registrada

Classe de armazenamento e metadados do objeto podem ser escolhidos por quem faz upload

  • A classe de armazenamento do S3 é aplicada ao objeto, não ao bucket
  • Não existe uma configuração para fixar uma classe de armazenamento desejada no nível do bucket; quem faz o upload pode definir a classe de armazenamento do objeto
    • O exemplo é aws s3 cp "my.txt" "s3://mybucket/myobject.txt" --storage-class [CLASS]
  • Dentro de uma lista predefinida, quem faz upload pode influenciar os custos de armazenamento e acesso por GB pagos pelo dono do bucket
  • Em políticas IAM, a chave de condição s3:x-amz-storage-class permite restringir quais classes de armazenamento são aceitas
    • A política de exemplo permite apenas STANDARD para s3:PutObject
  • Se você configurar uma política de ciclo de vida, poderá mover todos os objetos para uma determinada classe de armazenamento após um certo período
  • Em uploads com pre-signed URL, o AWS Signature Version 4 exige assinatura de todos os headers que começam com X-Amz-
    • A classe de armazenamento é definida pelo header x-amz-storage-class
    • A menos que a aplicação esteja muito mal implementada, não há uma forma óbvia de manipular isso imediatamente

Tags, Object Lock e redirecionamento também ficam sob influência de quem faz upload

  • Muitos atributos associados a objetos do S3 são controlados por quem faz upload
  • Tags do objeto podem ser definidas no momento do upload
    • O exemplo é --tagging "AllYourTags=AreBelong&To=Us"
  • Sistemas que executam automações com base no valor de tags podem ser afetados por valores criados por quem fez o upload
  • Object Lock permite definir retenção e legal hold quando o object locking está habilitado no bucket
    • O comando de exemplo usa --object-lock-retain-until-date "2099-01-01T00:00:00+0000", --object-lock-legal-hold-status "ON" e --object-lock-mode "COMPLIANCE"
  • Em buckets com hospedagem de site estático ativada, é possível fazer open redirect usando configurações do arquivo enviado
  • Também é preciso prestar atenção à lista completa de headers suportados por PutObject
    • Há limitações em pre-signed URLs
    • Em configurações que dependem de identidade do Cognito e políticas IAM, é possível assinar a requisição com um contexto autenticado do Cognito

Exposição do dono do bucket e de identificadores de conta

  • Para verificar se um determinado ID de conta é o dono de um bucket acessível, você pode incluir o header x-amz-expected-bucket-owner em uma requisição ListBucket
    • Se o ID de conta estiver errado, será retornado AccessDenied
    • Se o ID de conta estiver correto e o chamador tiver permissão ListBucket, a resposta será retornada normalmente
  • Usando o parâmetro fetch-owner=true da API ListBucket, cada chave na resposta passa a incluir o elemento Owner
  • O ID dentro de Owner é uma string hexadecimal de 64 caracteres que a documentação da AWS chama de canonical user ID
    • É uma forma ofuscada do ID da conta AWS
  • Se você colocar o canonical user ID em Principal como CanonicalUser numa política IAM, salvar e atualizar, ele será interpretado como o ID da conta AWS
  • ListBucketVersions e ListMultipartUploads também se comportam de forma parecida mesmo sem fetch-owner

Chaves de objeto do S3 parecem nomes de arquivo, mas se comportam diferente

  • As chaves de objeto do S3 diferenciam maiúsculas e minúsculas
  • É possível enviar vários objetos com nomes aparentemente iguais, desde que a capitalização seja diferente
  • Isso pode causar problemas se a aplicação tratar chaves de objeto do S3 como nomes de arquivo que não diferenciam maiúsculas e minúsculas
    • No exemplo, a aplicação guarda senhas de usuários em arquivos no S3 e usa o nome de usuário como nome do arquivo
    • No cadastro, ela apenas verifica se o arquivo já existe; na troca de senha, converte o nome de usuário para minúsculas e grava no arquivo
    • Mesmo que jeff já exista, ainda seria possível cadastrar JEFF, e o usuário JEFF poderia sobrescrever o arquivo de jeff ao trocar a senha
  • Chaves de objeto do S3 podem usar qualquer caractere UTF-8
    • Certos caracteres podem causar problemas em algumas aplicações e protocolos
    • Espaços, barras, caractere de porcentagem e outros também são válidos como chave de objeto

Mesmo parecendo “bucket privado”, ainda pode haver caminho de acesso

  • Mesmo com ACL desativada, política de recurso restrita e block public access habilitado, um bucket pode continuar acessível publicamente
  • O caminho mais comum é uma distribuição Amazon CloudFront
    • Quando há um CDN na frente do bucket S3, em geral existe a intenção de entregar conteúdo pela internet
    • Ferramentas de segurança podem concluir que não é público se a política de recurso do bucket estiver restrita ao CloudFront
  • No exemplo, get-bucket-policy-status retorna IsPublic: false
    • Ao enviar a requisição diretamente ao bucket, o retorno é AccessDenied
    • Ao enviar a mesma requisição ao domínio da distribuição CloudFront, o conteúdo do objeto é retornado
  • Um Cognito identity pool também pode expor buckets com política de recurso restrita
    • Após login bem-sucedido, o Cognito fornece credenciais temporárias da AWS do papel previamente configurado
    • Se esse papel tiver permissões s3:ListBucket e s3:GetObject, o usuário poderá chamar a API do S3
  • Há duas configurações do Cognito que podem equivaler a acesso público
    • Self-registration: se usuários da internet puderem se cadastrar e fazer login no app, na prática isso vira acesso público
    • Guest access: fornece identificador único e credenciais AWS a usuários não autenticados
  • O exemplo de acesso de convidado segue o fluxo de obter IdentityId com get-id, depois credenciais temporárias com get-credentials-for-identity, e então executar aws s3 ls com aquele perfil
  • CloudFront e Cognito identity pool são caminhos de acesso público realmente usados com frequência na internet, mas raramente aparecem como tal nas ferramentas de segurança

1 comentários

 
GN⁺ 2024-06-02
Opiniões do Hacker News
  • Há muitos pontos interessantes, mas é difícil concordar em tratar como problema o fato de o sistema de arquivos ser case-sensitive
    Acho que é assim que deveria ser, e o fato de o macOS não ser assim é que é irritante

    • Não sei por que “é assim que deveria ser”. O Windows também não diferencia maiúsculas de minúsculas, então o S3 também não está contrariando uma convenção quase universal
      A diferenciação entre maiúsculas e minúsculas em nomes de arquivos pode parecer surpreendente até para usuários não técnicos. Se alguém disser que enviou “Book Draft 1.docx”, mas na caixa de e-mail estiver “Book draft 1.docx”, normalmente a pessoa não diria “acho que você enviou outro arquivo”
      Na escrita, maiúsculas e minúsculas geralmente também não mudam o significado. “Hi, how are you?” e “hi, how are you?” significam a mesma coisa, e quando uma letra maiúscula muda o sentido é mais para distinguir nomes próprios de substantivos comuns, algo que raramente é uma preocupação em nomes de arquivos
    • Do ponto de vista da implementação técnica, o fato de 'A' e 'a' serem caracteres diferentes é algo bem estabelecido em ASCII, Unicode etc.
      Independentemente de preferência pessoal, é difícil entender um desenvolvedor ou administrador de sistemas ficar surpreso ou irritado com um sistema de arquivos case-sensitive. Se necessário, o desenvolvedor pode abstrair isso para o usuário final, como em resultados de busca
    • Sou o autor. Não é uma reclamação, está mais para uma observação. Não é uma questão absoluta de bom/ruim, mas um fator a considerar ao projetar uma aplicação
    • Não sei exatamente qual é o benefício de nomes de arquivos serem case-sensitive. O contrário evita muitos erros comuns que nem poderiam surgir
      Também não é como em código, em que impor um estilo ajuda na legibilidade. Mesmo em programação, até as IDEs ficarem inteligentes o bastante para detectar erros de digitação em nomes de variáveis, isso costumava causar bugs. Uma das coisas boas do Pascal era que, ao contrário do C, você não precisava se preocupar com maiúsculas e minúsculas
    • O macOS preserva maiúsculas e minúsculas. Pessoalmente, acho que é uma boa mistura dos dois lados
      Você pode escrever o nome do arquivo no estilo que quiser e essa grafia é preservada, mas, ao procurar ou processar, não precisa lembrar exatamente esse estilo. Isso porque a busca não diferencia maiúsculas de minúsculas
  • Diferenciar maiúsculas de minúsculas é a parte fácil; o menos intuitivo é que os caminhos do S3 são falsos
    O S3 aceita o upload de “/builds/1/installer.exe” e também mostra a lista dentro de /builds, mas, na realidade, você subiu uma única chave chamada '/builds/1/installer.exe', com '/' no nome
    Por isso, “/builds/1//installer.exe” e “/builds//1/installer.exe” também podem ser enviados e são arquivos completamente diferentes. São apenas nomes de chave; não há diretórios reais

    • Exato. Mas há uma exceção quando se usa os novos Directory buckets do S3 [1], o que, na verdade, deixa o todo ainda mais confuso
      [1] https://docs.aws.amazon.com/AmazonS3/latest/userguide/direct...
    • Também não se pode esquecer que "/" é apenas o caractere separador de caminho padrão. Se você precisa de "/" no nome do arquivo, pode usar qualquer outro caractere como delimitador: https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObje...
    • Além de interpretar caminhos em alguma forma canônica, por exemplo juntando barras / duplicadas, não sei em que um diretório real difere essencialmente de um prefixo
    • O modelo de prefixos gera muitos bugs. Entendo por que a AWS fez assim e, na prática, é uma abordagem inteligente, mas ainda pega muitos desenvolvedores
      Este ano, nosso sistema de produção também teve um bug estranho, e só conseguimos encontrá-lo com 5 pessoas investigando. A causa era um objeto cujo nome era literalmente “/”, e o software tentou tratá-lo como um caminho, não como um arquivo
  • É difícil confiar em S3 ou em outros serviços da AWS. Nada é intuitivo, há partes demais em movimento e documentação demais para ler
    Mesmo assim, você ainda pode acabar, por engano, expondo tudo ao mundo como no texto original. Eu preferiria usar serviços que são realmente simples, como Hetzner Storage Boxes ou DigitalOcean Spaces

    • Gosto do DigitalOcean Spaces, mas ele também tem suas peculiaridades irritantes
      Recentemente descobri que, ao enviar por pipe um arquivo de vídeo maior que alguns MB, o Location retornado vem sem https://. Então, a cada upload de arquivo, é preciso verificar se o Location começa com https e, se não começar, acrescentar
      Naturalmente, nas issues do cliente S3 para Node no GitHub dizem “parece bug da DigitalOcean”, e nos fóruns da DigitalOcean dizem “parece bug do cliente S3 para Node”
    • A forma como a DigitalOcean lida com segredos é de assustar qualquer um. Ao usar o Container Registry e configurá-lo para que o K8S acesse automaticamente, você sabia que esse serviço cria um secret com acesso total ao Spaces?
    • Passei alguns anos afastado de desenvolvimento em nuvem, trabalhando principalmente no lado cliente, e voltei recentemente; fiquei surpreso com a complexidade acumulada nesse período e com a carga cognitiva necessária para construir uma solução à prova de falhas na nuvem pública
      Inúmeros recursos e peculiaridades originalmente projetados para ajudar em algumas situações especiais agora viraram parte do protocolo comum. Parece que isso aconteceu porque, por motivos de negócio, ninguém queria deixar ninguém de fora
  • Também é preciso ter cuidado ao apagar dezenas de bilhões de objetos. Chamar diretamente a API de exclusão pode sair caro
    Em vez disso, é possível configurar gratuitamente uma regra de ciclo de vida que define o tempo de expiração como now para um curinga ou para o bucket inteiro. Assim, a cobrança de armazenamento para imediatamente e a AWS cuida da exclusão

    • Estritamente falando, as chamadas de exclusão são gratuitas; o que custa são as chamadas de listagem para obter os objetos. Em tese, se você souber por outra fonte quais objetos existem, sai de graça
    • O efeito de uma regra de ciclo de vida não é imediato. Ela é aplicada por um job em lote que roda uma vez por dia, então a remoção não acontece na hora
    • Isso porque a AWS pode escolher o momento real da exclusão. Nos metadados, ela marca que o objeto foi excluído, e a AWS pode processar a exclusão em horários de baixa utilização
      Também evita que os servidores da API do S3 sejam martelados por requisições por segundo
  • É bem ruim que uploads multipart com falha fiquem invisíveis e, se você não configurar explicitamente o ciclo de vida, ainda gerem custo de armazenamento
    Eu achava que o S de “Simple” significava simplicidade

    • Sim, isso é ruim. Pode culpar o ahenry@, que era GM do S3 na época
      Minha proposta era que as partes de uploads incompletos permanecessem por apenas 24 horas após a última atividade e que, nesse período, o armazenamento não fosse cobrado. O ahenry@ rejeitou isso
    • Perdemos milhares de dólares por causa desse problema
      Em um servidor muito antigo, havia quase 10 anos um script cron rodando todas as noites e iniciando um upload multipart. Ele servia para empurrar backups para um bucket, mas esse bucket também armazenava conteúdo enviado por usuários, então parecia normal que ele crescesse um pouco a cada dia
      O script “não funcionava”, então não dependíamos dos dados de backup; os arquivos não apareciam no S3; e o tamanho do bucket crescia de forma constante, mas não exagerada. Só que, nesta primavera, vimos que ele estava armazenando quase 3 TB de uploads multipart incompletos
      Claro que sei que essa história está cheia de más práticas
    • O S é de forma simples de fazer os custos dispararem
    • O nome “Simple” foi dado numa época em que a alternativa era gerenciar você mesmo um monte de servidores com discos. O tempo muda tudo
    • Eu também já pisei numa mina terrestre de custo de armazenamento. Felizmente foram poucos centavos, mas o jeito como o console mostra as informações relacionadas é tão péssimo que dá bastante raiva
  • A discussão sobre sensibilidade/insensibilidade a maiúsculas e minúsculas geralmente me parece centrada demais no inglês
    Em outras palavras, especialmente em TI, discussões relacionadas a idioma com frequência são excessivamente centradas no inglês

    • Acho que ser centrado no inglês acaba sendo até uma sorte. ASCII era muito mais fácil de lidar do que Unicode
      Falando como alguém cuja língua materna não é o inglês, programação já tem conceitos e elementos demais. É melhor não aumentar a complexidade tendo que considerar mais 101 idiomas diferentes
      Unicode e fusos horários são exemplos clássicos de elementos que tentam considerar mais línguas e culturas na programação, mas acabam criando o maior sofrimento para todos, inclusive programadores de países não anglófonos
      Não quero escrever programas na minha língua materna. Menos ainda se o preço for ter que considerar todos os principais idiomas ao programar. Tudo bem que as discussões de TI sejam centradas no inglês. Diversidade é complexidade, e inglês não é uma língua que pertence a alguém, é apenas uma ferramenta que as pessoas usam para se comunicar
      Graças a essa língua comum, consigo expressar minhas ideias para muita gente na Índia, China, Japão, América do Sul etc. No momento em que elas decidem falar em inglês, elas também passam a possuir o inglês. Não há necessidade de trazer política de diversidade para TI; é melhor manter a questão no plano técnico
    • Já que falamos de culturas não anglófonas: em japonês, um sistema que não diferencia maiúsculas de minúsculas diferencia hiragana e katakana?
      De certa forma, esses dois silabários parecem alfabetos em maiúsculas/minúsculas
  • Há mais algumas coisas
    Uploads multipart não podem ser feitos a partir de várias máquinas com credenciais de instância. Isso porque os principals são diferentes e não conseguem acessar os uploads multipart uns dos outros. Para montar um único upload multipart a partir de várias máquinas, é necessário um usuário IAM real
    Requisições LIST não são apenas lentas; em grande volume, ficam muito caras. Existem contornos como “bucket inventory”, mas eles não são nem convenientes nem baratos
    A criação de buckets usa DNS internamente, então não há consistência read-after-write. Por isso, às vezes não é possível acessar um bucket logo depois de criá-lo, ou excluir um bucket recém-criado antes de esperar tempo suficiente pela propagação das mudanças. Veja https://github.com/julik/talks/blob/master/euruko-2019-no-su...
    Você pode criar ao mesmo tempo um objeto chamado “foo” e outro chamado “foo/bar”. Isso cria uma estrutura em que um arquivo sobrescreve um diretório, tornando impossível portar os dados do bucket para uma estrutura de sistema de arquivos
    Como o S3 diferencia maiúsculas de minúsculas, é possível criar objetos que não são portáveis para uma estrutura de sistema de arquivos. O armazenamento de arquivos do Rails quebrou feio no macOS por assumir um armazenamento case-sensitive, e foi corrigido para sempre usar identificadores em minúsculas
    A maioria das configurações do S3 permite GET, mas não HEAD. Parece ser uma forma de impedir a sondagem da existência de objetos, mas não tenho certeza. De todo modo, o fluxo amigável a cache de verificar o tamanho de um objeto com uma requisição HEAD não funciona, especialmente com URLs pre-signed. Em vez disso, é preciso contornar com um GET usando um Range muito pequeno, por exemplo buscando apenas o primeiro byte
    Se você gera muitas URLs pre-signed, talvez consiga aumentar a velocidade de geração em 10 a 40 vezes: https://github.com/WeTransfer/wt_s3_signer
    Você ainda paga pelo armazenamento de uploads multipart incompletos. É preciso ter cuidado especial se sua arquitetura permite que usuários iniciem esses uploads. Existe uma configuração para excluir automaticamente uploads multipart incompletos após certo tempo; se não quiser dor de cabeça, ative-a
    Paradoxalmente, o S3 foi revolucionário e ainda é um ótimo produto em vários níveis. Só que, como tem muitos recursos, também tem muitas armadilhas

    • O que me pegou algumas semanas atrás foi o limite de tamanho mínimo do chunk inicial de 5 MiB em uploads multipart: https://docs.aws.amazon.com/AmazonS3/latest/userguide/qfacts...
      Criei em Elixir um pipeline de pós-processamento de CSV em streaming que modifica e injeta colunas usando Stream.transform(https://hexdocs.pm/elixir/Stream.html#transform/3). Os módulos de AWS e CSV do Elixir processam dados de entrada em streaming, mas, se o total do stream de saída for menor que 5 MiB, o módulo da AWS usa upload multipart e o S3 dá erro, o que foi triste
  • Há outro problema interessante que diagnostiquei com um colega depois de vários dias de análise. O S3, depois que uma única conexão TCP envia 100 requisições HTTP, passa a descartar silenciosamente as requisições seguintes
    https://github.com/aws/aws-sdk-go/issues/2825

    • Não é exatamente silencioso: ele envia um header informando que fechou a conexão TCP
      É um padrão comum quando você quer keep-alive por motivos de desempenho, mas também quer impedir que o cliente fique conectado por tempo demais e crie hotspots no balanceador de carga
  • Também há o fato de que o S3, na classe de armazenamento Standard, tem latência alta e não é adequado para servir conteúdo web
    Muita gente acha que pode hospedar recursos de sites, como imagens ou fontes, diretamente no S3, mas isso pode piorar a experiência do usuário
    “applications can achieve consistent small object latencies (and first-byte-out latencies for larger objects) of roughly 100–200 milliseconds.”
    Fonte: https://docs.aws.amazon.com/AmazonS3/latest/userguide/optimi...

    • A maioria usa o S3 como origem do AWS CloudFront para entrega de conteúdo
      Com CloudFront signed cookies, também dá para conceder a usuários específicos acesso via CDN apenas ao conteúdo do S3 que pertence a eles. É bem legal
    • Para servir assets web, normalmente se usa S3 junto com CloudFront
      Dá para cachear assets acessados com frequência, reduzindo a latência e também cortando bastante os custos
    • O S3 não é otimizado para servir sites diretamente; ele é otimizado para armazenar e recuperar, com durabilidade, uma quantidade quase ilimitada de dados
  • O fato de o uploader decidir as regras é bem bruto. Em um site com configuração frouxa, isso significa que um usuário suficientemente motivado poderia fazer conteúdo de usuário ser enviado para o Amazon Glacier e depois ser servido a partir de lá?