Todos os artigos

// Knowledge.log — 技術記事

Quando a permissions boundary IAM corta o s3:* da identity

Identity Allow s3:* não libera PutObject se a permissions boundary só autoriza GetObject no prefixo. Interseção no User Guide, JSON local, sem conta.

O PR mexe na identity policy da role app-reader e coloca Effect: Allow, Action: s3:*, Resource: arn:aws:s3:::amzn-s3-demo-bucket/*. O revisor vê o asterisco, conclui que a aplicação pode fazer qualquer coisa nos objetos do bucket e aprova. Depois do deploy, o PutObject continua falhando. Não existe Deny na role nem na bucket policy. O que existe é uma permissions boundary configurada na role, que fica em outro stack e que ninguém abriu, e ela só autoriza s3:GetObject no prefixo app/.

O diff é generoso. O teto continua onde estava.

A ideia aqui é sair com dois JSON locais, uma identity policy larga e uma boundary estreita, junto com a regra da documentação IAM que explica por que só uma das três requisições passa. A fonte é o IAM User Guide e o S3 User Guide, consultados em 22/10/2026. Nada rodou contra uma conta AWS nem no Policy Simulator.

A regra, uma vez só

A página de permissions boundaries define o mecanismo assim:

> AWS supports permissions boundaries for IAM entities (users or roles). A permissions boundary is an advanced feature for using a managed policy to set the maximum permissions that an identity-based policy can grant to an IAM entity. An entity's permissions boundary allows it to perform only the actions that are allowed by both its identity-based policies and its permissions boundaries.

Dessa definição saem três consequências práticas:

  1. Só users ou roles. A identity policy pode estar anexada a um grupo, mas a boundary é configurada no usuário ou na role. Grupo não tem boundary.
  2. A boundary não concede nada. Ela define o máximo que as identity policies podem conceder. A página de políticas e permissões diz isso com todas as letras: a boundary "defines the maximum permissions that the identity-based policies can grant to an entity, but does not grant permissions".
  3. O resultado é interseção. A página de lógica de avaliação diz que, ao avaliar identity policies e boundary, "the resulting permissions are the intersection of the two categories", e acrescenta que "an explicit deny in either of these policies overrides the allow".

Falta um detalhe que muda a forma como você lê o erro. Na ordem de avaliação, depois da varredura de Deny explícito, a AWS exige o Allow da identity policy e então verifica a boundary:

> If the policy that is used to set the permissions boundary does not allow the requested action, then the request is implicitly denied.

É implicit deny, um teto. Ninguém escreveu Effect: Deny em lugar nenhum. A ação simplesmente não está dentro do que a boundary permite.

Os dois JSON e a tabela

Os placeholders seguem o estilo da documentação AWS: a role arn:aws:iam::111122223333:role/app-reader e o bucket amzn-s3-demo-bucket. Os dois documentos usam "Version": "2012-10-17", a versão atual da linguagem de políticas. Nenhum deles tem Principal: a identity policy não pode ter esse elemento, e a boundary é uma managed policy com o mesmo formato, usada como teto.

Identity policy da role:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "IdentityAllowS3StarOnDemoBucketObjects",
      "Effect": "Allow",
      "Action": "s3:*",
      "Resource": "arn:aws:s3:::amzn-s3-demo-bucket/*"
    }
  ]
}

Permissions boundary da mesma role:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "BoundaryAllowGetObjectAppPrefixOnly",
      "Effect": "Allow",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::amzn-s3-demo-bucket/app/*"
    }
  ]
}

Para tornar a regra visível, um script Python local avalia três requisições contra os dois documentos. Ele implementa só o subconjunto documentado: Allow efetivo se a identity e a boundary permitem, e Deny explícito em qualquer uma das duas vence. Isto não é o motor do IAM. O script não modela SCP, RCP, resource policy, session policy, NotAction nem policy variables. A tabela ilustra a interseção que o User Guide descreve e não substitui uma avaliação na conta.

RequisiçãoIdentityBoundaryEfetivoPor quê
s3:GetObject em .../app/file.txtAllowAllowAllowos dois documentos casam
s3:PutObject em .../app/file.txtAllowimplicit DenyDenya identity permite, a boundary não
s3:GetObject em .../other/file.txtAllowimplicit DenyDenyfora do prefixo da boundary

A coluna da identity mostra Allow nas três linhas, porque s3:* em bucket/* casa com os três ARNs de objeto. Se a revisão para nessa coluna, a resposta sai errada duas vezes em três.

A segunda linha é a do PR do começo: PutObject "liberado" no JSON da role e implicit deny no teto. A terceira pega quem acha que a boundary só restringe ações. O Resource dela também é um teto, e other/ fica fora dele.

Não é o Deny da bucket policy (nem SCP)

O texto sobre Deny na resource policy tratava de outro mecanismo. Lá, identity policy e bucket policy na mesma conta se combinam por união, e um Effect: Deny explícito na bucket policy derruba o Allow da identity. Aqui não há Deny em nenhum dos dois documentos. O acesso falha porque a combinação é por interseção, e a boundary nunca disse Allow para PutObject. Se você abrir a bucket policy procurando o culpado, não vai achar nada lá. Também é diferente do IfExists na política IAM, que era uma chave de contexto ausente dentro de uma única identity policy.

Os outros dois tetos que aparecem nessa conversa:

MecanismoOnde ficaConcede?Como combina com a identity
Permissions boundarymanaged policy no user ou na rolenãointerseção
SCPorganização, OU ou contanãointerseção; com boundary, as três precisam permitir
Session policypassada ao assumir a role ou ao pedir credenciais federadasnãointerseção de identity, session policy e boundary

A página de boundaries resume o caso combinado: quando SCP, boundary e identity policy afetam o mesmo request, "the request is allowed only if all three policy types allow it". A SCP é o teto da organização, anexado em outro nível e mantido por outro time. A session policy só vale para aquela sessão temporária. A boundary acompanha a role.

Template mínimo: role de app Java que só lê app/

Para uma aplicação que deve apenas ler objetos sob app/ e listar esse prefixo, a boundary mínima fica assim:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowGetObjectOnAppPrefix",
      "Effect": "Allow",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::amzn-s3-demo-bucket/app/*"
    },
    {
      "Sid": "AllowListBucketAppPrefix",
      "Effect": "Allow",
      "Action": "s3:ListBucket",
      "Resource": "arn:aws:s3:::amzn-s3-demo-bucket",
      "Condition": {
        "StringLike": {
          "s3:prefix": "app/*"
        }
      }
    }
  ]
}

Os dois Resource são diferentes por um motivo. Nos exemplos de identity policy do S3, as ações de objeto (GetObject, PutObject, DeleteObject) usam o ARN do objeto, arn:aws:s3:::bucket/prefixo/*. Já s3:ListBucket usa o ARN do bucket, sem /*, e restringe a listagem com "Condition": { "StringLike": { "s3:prefix": ["${aws:username}/*"] } }. O template troca a variável por um prefixo fixo, app/*. Trocar os ARNs entre as duas ações é um erro clássico. Repare também que a boundary do fixture anterior, que só tem GetObject, não permite ListBucket.

Três cuidados para não estragar o template:

  • A boundary não concede. A role ainda precisa de uma identity policy com Allow para essas mesmas ações. O caminho mais simples é repetir os dois statements na identity policy. O s3:* em bucket/* do fixture nem cobriria ListBucket, porque o ARN do bucket não casa com bucket/*.
  • Configure como permissions boundary da role, não como mais uma identity policy anexada, e nunca em grupo, que não aceita boundary.
  • Sem Principal. O documento tem o formato de uma identity policy.

Limites do que foi mostrado

Os resultados da tabela vêm da documentação aplicada a dois JSON por um script local. Não houve conta AWS, CloudTrail nem Policy Simulator.

A interseção também não explica todos os caminhos de acesso. Pela página de policies, a boundary não limita o que uma resource-based policy concede, e o User Guide traz exceções: uma resource policy na mesma conta que concede acesso ao ARN de um IAM user não é limitada pelo implicit deny da boundary. Já uma concessão ao ARN de uma role é limitada. Por isso não vale a frase "a boundary sempre vence a bucket policy". O que sempre vale é o Deny explícito. Se a boundary tiver um statement com Effect: Deny que case com o request, ele derruba o acesso como em qualquer outra política avaliada, e os exemplos oficiais de boundary usam esse tipo de statement como guarda-corpo.

O que fazer na próxima revisão

Antes de tratar o s3:* da identity como acesso de produção, abra a permissions boundary configurada no user ou na role. Abra o documento de verdade, não só a policy que o PR altera. Só existe Allow efetivo onde os dois documentos dão Allow e nenhum deles nega. Se a boundary mora em outro stack, ela continua fazendo parte do diff, mesmo sem aparecer nele.

Para uma aplicação que só deve ler app/, a DevDojo coloca esse teto na role com o template acima e mantém uma identity policy com o mesmo Allow. Quando um PutObject falhar com a identity "liberando tudo", a primeira pergunta é "a boundary permite essa ação nesse ARN?", não "quem colocou Deny no bucket?". A DevDojo só abandonaria essa leitura se a boundary tiver Deny explícito, se uma SCP ou session policy entrar no request, ou se o acesso vier de uma resource policy. Nesses casos, a tabela de duas colunas não basta e a avaliação precisa acontecer na conta.

awsiams3

// Continue.training — 次のステップ

Conhecimento só conta quando vira prática.

Volte ao artigo, execute os exemplos e compartilhe o que aprendeu.

Explorar mais artigos