Um upload direto para o servidor de armazenamento (S3, armazenamento GCP, etc) pode ser interessante ao trabalhar com arquivos grandes, uma vez que o upload através do servidor de aplicação consumiria recursos de tempo e processamento com o tratamento dessas requests. Uma possível abordagem para implementar uploads diretos é mostrada abaixo. Nela, um cliente carrega um arquivo diretamente no S3 usando as credenciais que ele já possui (geralmente que permitem qualquer upload) e quando o upload é concluído com sucesso, o cliente faz uma solicitação POST ao servidor indicando que um novo arquivo foi carregado. No entanto, essa abordagem expõe os secrets do S3 e permitiria que um agente inválido carregasse qualquer arquivo (e possivelmente sobrescrevesse os arquivos existentes).
Fluxo de inseguro de upload cliente-S3
A adição de uma requisição extra ao servidor no processo aumenta a segurança do upload, não expondo os secrets e permitindo que o servidor valide a solicitação (verifique a autorização etc.). Este novo fluxo é mostrado abaixo.
Fluxo seguro: etapa adicional de geração de URL pré-assinada
Pré-requisitos
Você precisa ter a gem oficial aws-sdk-s3 instalada com suas credenciais e região devidamente configuradas.
Passo 1: Método para gerar a URL assinada
No seu model (ex: models/attachment.rb), adicione um método de classe para gerar uma URL assinada para upload S3.
1 2 3 4 5 6 7 8 9 |
def self.generate_upload_url(file_name) Aws::S3::Presigner.new.presigner.presigned_url( :put_object, bucket: ENV['MY_S3_BUCKET_NAME'], key: file_name, use_accelerate_endpoint: true, expires_in: 300 # Number of seconds the URL is valid for ) end |
Passo 2: Action no Controller para gerar a URL assinada
Nós criaremos uma action POST em nosso controller que receberá um parâmetro chamado file_name e retornará uma URL assinada que permite o upload desse arquivo específico. Nota: Em vez de usar o file_name diretamente, o controller pode retornar um nome de arquivo de upload a ser usado, como um hash ou UUID.
1 2 3 4 5 6 |
# POST api/attachments/generate_upload_url def generate_upload_url ensure_user_is_authorized_to_perform_this_action @signed_url = Attachment.generate_upload_url(params[:file_name]) render json: { url: @signed_url } end |
Passo 3: Action no Controller para receber a confirmação do upload
Essa é a etapa com a qual geralmente se começaria na abordagem insegura. Nesta action POST, o controller receberá as informações relevantes sobre o anexo carregado e persistirá no model. Nota: Uma etapa adicional pode ser implementada para verificar se o arquivo carregado realmente existe no S3.
1 2 3 4 5 6 7 8 9 |
# POST api/attachments def create ensure_user_is_authorized_to_perform_this_action @attachment = Attachment.new(safe_params) if @attachment.save render json: @attachment else render json: @attachment.errors, status: :unprocessable_entity end end |
Considerações finais
A etapa adicional de geração de um URL de upload pré-assinado elimina a necessidade de que as credenciais da AWS estejam disponíveis publicamente no cliente para uploads diretos, eliminando os problemas de segurança apresentados. Neste post, existem muitas etapas intermediárias implícitas na comunicação entre o cliente e o servidor, que se espera que o desenvolvedor seja capaz de inferir pelo fluxo descrito. De qualquer forma, no caso de maiores dúvidas ou sugestões, entre em contato ou utilize a área de comentários.
Fonte: https://thepaulo.medium.com/secure-uploads-to-aws-s3-with-ruby-on-rails-1325e59827d
Nenhum Comentário