Criando uma Presigned URL no S3: Acesso Temporário a Arquivos Privados

Você tem um arquivo privado no S3 e precisa dar acesso temporário a um usuário sem expor suas credenciais AWS nem tornar o bucket público — esse é exatamente o cenário para o qual as Presigned URLs foram criadas. Com uma URL assinada, você delega acesso a um objeto específico por um período determinado, sem alterar as permissões do bucket.

TL;DR — Resumo Rápido

PontoDetalhe
O que éURL temporária que carrega credenciais assinadas na própria query string
Expiração padrão usada aqui3600 segundos (1 hora)
Quem assinaA identidade IAM que gera a URL (usuário, role ou instância)
Bucket precisa ser público?Não — o objeto pode permanecer completamente privado
SDK usadoAWS SDK for Python (Boto3) e AWS SDK for JavaScript v3
Alternativa via CLIaws s3 presign

Como a Presigned URL do S3 Funciona

Uma Presigned URL não é um token separado — ela é a própria requisição HTTP pré-assinada. O SDK pega as credenciais da identidade atual, calcula a assinatura SigV4 para a operação GetObject, e embute tudo isso nos parâmetros da URL. Quando o usuário acessa a URL, o S3 verifica a assinatura e o tempo de expiração antes de servir o objeto.

O ponto crítico: a verificação acontece no momento do acesso, não no momento da geração. Se a identidade que gerou a URL perder a permissão s3:GetObject antes da expiração, a URL para de funcionar imediatamente. O mesmo vale para políticas de bucket que bloqueiem o acesso.

sequenceDiagram participant App as Sua Aplicação participant SDK as AWS SDK (local) participant User as Usuário participant S3 as Amazon S3 App->>SDK: generate_presigned_url(bucket, key, ExpiresIn=3600) Note over SDK: Assina com SigV4
sem chamada de rede SDK-->>App: Presigned URL (string) App-->>User: Envia a URL (e-mail, API, etc.) User->>S3: HTTP GET com assinatura na query string S3->>S3: Valida assinatura e expiração S3-->>User: 200 OK + conteúdo do objeto
  1. Aplicação chama o SDK com bucket, chave e expiração — o SDK assina localmente usando SigV4.
  2. Presigned URL é retornada para a aplicação e repassada ao usuário (e-mail, API response, etc.).
  3. Usuário faz um HTTP GET direto ao S3 — sem passar pela sua aplicação.
  4. S3 valida a assinatura e o tempo de expiração; se válidos, retorna o objeto.
Pense na Presigned URL como um voucher de entrada numerado: quem o possui entra, mas ele expira no horário impresso — independente de quem o emitiu ainda estar na empresa.

Pré-requisitos: Permissões IAM Necessárias

A identidade que gera a URL precisa ter permissão s3:GetObject no objeto alvo. Sem isso, a URL é gerada localmente pelo SDK (a assinatura é calculada offline), mas falha com 403 Forbidden quando o usuário tenta acessá-la.

Política mínima para geração e uso da Presigned URL:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::nome-do-seu-bucket/*"
    }
  ]
}

Se o bucket tiver uma política que inclua um Deny explícito ou se o Block Public Access estiver configurado de forma restritiva, isso não afeta Presigned URLs — elas operam via autenticação SigV4, não via acesso público. Porém, um Deny explícito na bucket policy sobrescreve qualquer Allow na política IAM.

Gerando a Presigned URL para Download com Expiração de 1 Hora

Opção 1: Python com Boto3 (Recomendado)

O método generate_presigned_url do cliente S3 assina a operação get_object localmente. Nenhuma chamada de rede é feita durante a geração — a URL é calculada e retornada imediatamente.

import boto3
from botocore.exceptions import ClientError

def gerar_presigned_url(bucket_name: str, object_key: str, expiration: int = 3600) -> str:
    """
    Gera uma Presigned URL para download de um objeto S3 privado.

    :param bucket_name: Nome do bucket S3
    :param object_key:  Chave (caminho) do objeto no bucket
    :param expiration:  Tempo de expiração em segundos (padrão: 3600 = 1 hora)
    :return:            Presigned URL como string
    """
    s3_client = boto3.client('s3', region_name='us-east-1')

    try:
        url = s3_client.generate_presigned_url(
            ClientMethod='get_object',
            Params={
                'Bucket': bucket_name,
                'Key': object_key
            },
            ExpiresIn=expiration
        )
    except ClientError as e:
        raise RuntimeError(f'Erro ao gerar Presigned URL: {e}') from e

    return url


# Exemplo de uso
if __name__ == '__main__':
    url = gerar_presigned_url(
        bucket_name='meu-bucket-privado',
        object_key='relatorios/janeiro-2025.pdf'
    )
    print(f'URL válida por 1 hora:\n{url}')

Opção 2: JavaScript/TypeScript com AWS SDK v3

No SDK v3, a geração de Presigned URLs usa o pacote separado @aws-sdk/s3-request-presigner em conjunto com o comando GetObjectCommand.

🔽 Clique para expandir — Código JavaScript (AWS SDK v3)
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';

const s3Client = new S3Client({ region: 'us-east-1' });

async function gerarPresignedUrl(
  bucketName: string,
  objectKey: string,
  expirationSeconds: number = 3600
): Promise<string> {
  const command = new GetObjectCommand({
    Bucket: bucketName,
    Key: objectKey,
  });

  const url = await getSignedUrl(s3Client, command, {
    expiresIn: expirationSeconds,
  });

  return url;
}

// Exemplo de uso
gerarPresignedUrl('meu-bucket-privado', 'relatorios/janeiro-2025.pdf')
  .then((url) => console.log('Presigned URL:', url))
  .catch((err) => console.error('Erro:', err));

Opção 3: AWS CLI

Para testes rápidos ou scripts de operação, a CLI resolve sem escrever código. O parâmetro --expires-in aceita segundos.

aws s3 presign s3://meu-bucket-privado/relatorios/janeiro-2025.pdf \
  --expires-in 3600 \
  --region us-east-1

A saída é a URL completa pronta para uso. Útil para validar se as permissões estão corretas antes de implementar no código.

Diagnóstico: Erros Comuns com Presigned URL no S3

O erro mais frequente em produção não é na geração — é um 403 silencioso quando o usuário tenta usar a URL. A armadilha clássica: o SDK gera a URL sem reclamar (a assinatura é local), mas a identidade não tem s3:GetObject no objeto. Você só descobre quando o usuário reporta que o download falhou.

graph TD A["Usuário recebe 403"] --> B{"Identidade tem
s3:GetObject?"}; B -- Não --> C["Adicionar permissão
s3:GetObject na política IAM"]; B -- Sim --> D{"Bucket policy tem
Deny explícito?"}; D -- Sim --> E["Remover ou ajustar
o Deny na bucket policy"]; D -- Não --> F{"Credenciais temporárias
expiraram?"}; F -- Sim --> G["Reduzir ExpiresIn ou
renovar credenciais da role"]; F -- Não --> H{"Região do cliente
está correta?"}; H -- Não --> I["Instanciar cliente S3
com region_name correto"]; H -- Sim --> J["Verificar logs do S3
com CloudTrail"];

Verifique cada camada na ordem do diagrama. Um Deny explícito na bucket policy bloqueia mesmo com a política IAM correta — esse é o ponto que mais gera confusão.

Verificando permissões da identidade que gera a URL

# Identifica qual identidade está sendo usada pelo SDK/CLI
aws sts get-caller-identity
# Simula se a identidade tem s3:GetObject no objeto alvo
aws iam simulate-principal-policy \
  --policy-source-arn arn:aws:iam::123456789012:role/MinhaRole \
  --action-names s3:GetObject \
  --resource-arns arn:aws:s3:::meu-bucket-privado/relatorios/janeiro-2025.pdf \
  --region us-east-1

Verificando a bucket policy

aws s3api get-bucket-policy \
  --bucket meu-bucket-privado \
  --region us-east-1

Problema de região: URL gerada na região errada

Se o cliente S3 for instanciado sem especificar a região, o SDK pode usar us-east-1 como padrão mesmo que o bucket esteja em outra região. O resultado é um erro de redirecionamento ou AuthorizationHeaderMalformed. Sempre instancie o cliente com a região correta do bucket.

Credenciais temporárias e expiração da role

Quando a URL é gerada por uma role assumida (ex: Lambda, EC2 com instance profile), as credenciais temporárias têm sua própria expiração. Se as credenciais da role expirarem antes da Presigned URL, o S3 retorna 403 mesmo que a URL ainda esteja dentro do prazo configurado. Defina ExpiresIn sempre menor ou igual ao tempo restante das credenciais temporárias.

Considerações de Segurança para Presigned URLs em Produção

  • Não logue a URL completa — ela carrega a assinatura e funciona para qualquer pessoa que a possua durante o período de validade.
  • Use o menor tempo de expiração possível para o seu caso de uso. Uma hora é razoável para downloads manuais; para fluxos automatizados, 5-15 minutos costuma ser suficiente.
  • Não é possível revogar uma Presigned URL individualmente — para invalidação imediata, a única opção é remover a permissão s3:GetObject da identidade que a gerou, o que afeta todas as URLs geradas por ela.
  • HTTPS é obrigatório — o SDK gera URLs com https:// por padrão. Não force downgrade para HTTP.

Criando Presigned URL para Upload (PUT)

O mesmo mecanismo funciona para uploads. Troque get_object por put_object no Boto3, ou use PutObjectCommand no SDK v3. O usuário pode fazer um HTTP PUT diretamente para a URL sem passar pela sua aplicação.

import boto3

s3_client = boto3.client('s3', region_name='us-east-1')

url_upload = s3_client.generate_presigned_url(
    ClientMethod='put_object',
    Params={
        'Bucket': 'meu-bucket-privado',
        'Key': 'uploads/arquivo-do-usuario.pdf',
        'ContentType': 'application/pdf'
    },
    ExpiresIn=3600
)

print(f'URL para upload direto ao S3:\n{url_upload}')

A identidade que gera a URL de upload precisa ter s3:PutObject no recurso alvo. O usuário que faz o upload não precisa de nenhuma credencial AWS.

Próximos Passos e Documentação

Presigned URLs cobrem bem o caso de acesso temporário a objetos individuais. Para cenários mais complexos — como acesso a múltiplos objetos ou distribuição com cache global — considere combinar com o CloudFront usando CloudFront Signed URLs, que operam em uma camada diferente e têm semântica de revogação distinta.

Glossário

TermoDefinição
Presigned URLURL com assinatura SigV4 embutida que autoriza uma operação específica no S3 por tempo limitado
SigV4Algoritmo de assinatura criptográfica usado pela AWS para autenticar requisições HTTP
GetObjectOperação S3 que retorna o conteúdo de um objeto; é a operação assinada para URLs de download
ExpiresInParâmetro que define o tempo de validade da URL em segundos a partir do momento da geração
Block Public AccessConfiguração de bucket/conta que bloqueia acesso público anônimo; não afeta Presigned URLs autenticadas

Related Posts

Comentários

Postagens mais visitadas deste blog

Variáveis de Ambiente no Lambda: Configuração, Acesso e Criptografia com KMS

Monitoramento de Memória RAM no EC2: Por que o CloudWatch Agent é Obrigatório

S3 Access Denied: Por que 'Bloquear Acesso Público' impede seu objeto mesmo após torná-lo público