Estudar IAM na teoria é tranquilo. Você lê sobre usuários, grupos, políticas, princípio do menor privilégio, e tudo parece fazer sentido. O problema é que só faz sentido de verdade quando você cria o bucket, escreve o JSON, loga com o usuário errado e leva um Access Denied na cara.

Esse post documenta um exercício simples de IAM: criar um bucket S3, dois grupos com permissões diferentes e verificar no console se as restrições funcionam como esperado. Sem infra complexa, sem Terraform, sem automação, só o console da AWS e o básico funcionando direito.


Criando o bucket S3

Ponto de partida: o recurso que vamos proteger.

No console da AWS, pesquiso por S3 e clico em Create bucket. Coloco o nome labs-jeferson-2026, verifico se a opção Block all public access está marcada (ela já vem marcada por padrão, mas vale confirmar) e seleciono a região us-east-1. Não é coincidência: é historicamente a mais barata e a que concentra a maioria dos recursos da AWS, o que simplifica alguns exercícios.

Clico em Create bucket e pronto. Já aproveitei para fazer o upload de um arquivo de teste no bucket pelo usuário root.

Bucket labs-jeferson-2026 criado no console S3

O ARN do bucket fica assim: arn:aws:s3:::labs-jeferson-2026. Guarda esse valor, ele vai entrar direto nas políticas que vou criar a seguir.


Criando os usuários

Com o bucket no ar, vou para o IAM. Dentro de IAM > Users > Create user, crio dois usuários para o teste:

  • jeferson-s3fullaccess
  • jeferson-s3readonly

Para ambos, marco a opção Provide user access to the AWS Management Console, defino uma senha simples e desmarco Users must create a new password at next sign-in. Não tem sentido adicionar atrito num exercício de lab. As políticas de acesso vão ser definidas nos grupos, então por ora os usuários ficam criados sem permissão nenhuma.

Em contexto corporativo real, os grupos já existiriam antes de qualquer usuário novo. Criar o usuário seria só associá-lo ao grupo certo e ajustar o que for específico pra ele. A ordem aqui é invertida por conveniência didática.


Escrevendo as políticas

Antes de criar os grupos, preciso das políticas que eles vão usar. Vou em IAM > Policies > Create policy.

A AWS já tem políticas prontas para o S3 (como AmazonS3FullAccess e AmazonS3ReadOnlyAccess), mas elas são genéricas demais: permitem acesso a qualquer bucket da conta. Quero algo mais cirúrgico, restrito ao bucket que criei.

Política de leitura

Essa política permite listar todos os buckets da conta (necessário para o console carregar a lista), mas restringe GetObject e ListBucket somente ao labs-jeferson-2026:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:ListAllMyBuckets"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::labs-jeferson-2026",
        "arn:aws:s3:::labs-jeferson-2026/*"
      ]
    }
  ]
}

O Resource: "*" no primeiro bloco é necessário porque ListAllMyBuckets opera no nível da conta, não de um bucket específico. Sem ele, o console do S3 não carrega direito.

Política de acesso total

Mesma estrutura, mas o segundo statement usa s3:* no lugar das ações específicas. O escopo continua restrito ao bucket:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:ListAllMyBuckets"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "s3:*"
      ],
      "Resource": [
        "arn:aws:s3:::labs-jeferson-2026",
        "arn:aws:s3:::labs-jeferson-2026/*"
      ]
    }
  ]
}

O asterisco em Action dentro de um Resource específico é o ponto importante aqui. O usuário pode fazer tudo no S3, mas só dentro daquele bucket. Sair do labs-jeferson-2026 e tentar agir em outro recurso: acesso negado.

Outra coisa que vale registrar: as políticas são acumulativas. Um usuário em dois grupos herda as permissões de ambos. Um usuário com política direta e política de grupo também acumula. O IAM vai avaliar o conjunto todo e autorizar ou negar com base no resultado final.


Criando os grupos e associando tudo

Com as políticas criadas, vou em IAM > User groups > Create group.

Crio dois grupos: um para readonly e outro para fullaccess. Durante a criação, já associo o usuário correspondente e seleciono a política customizada que acabei de criar. O filtro Customer managed na tela de políticas ajuda bastante aqui, porque filtra só o que você criou e elimina o ruído das centenas de políticas gerenciadas pela AWS.

Criando grupos de usuários no IAM

Dois grupos criados, dois usuários atribuídos, políticas vinculadas. Hora de verificar se funciona.


Testando as permissões

Agora eu faço o login com cada usuário para verificar o comportamento.

Usuário readonly: consegue ver o arquivo listado, consegue baixá-lo, mas ao tentar deletar recebe Access Denied direto.

Usuário fullaccess: acesso completo. Upload, download, delete, tudo funcionando sem restrição dentro do bucket.

Exatamente o que as políticas definiram. Nada mais, nada menos.


O IAM parece intimidador no começo por causa do volume de opções: ações, recursos, condições, efeitos, políticas inline, políticas gerenciadas, permission boundaries. Mas quando você para pra encarar o JSON, percebe que é estrutura condicional. Um conjunto de regras que diz: nesse contexto, com esse recurso, essa ação é permitida ou negada.

A personalização é absurda. E isso é exatamente o problema de usar as políticas prontas da AWS sem adaptação: elas foram feitas para cobrir o caso geral, não o seu caso.