Um revisor abre a identity policy e vê aws:SourceIp com 203.0.113.0/24 dentro de uma Condition. Conclui que o s3:GetObject só roda a partir daquela rede e aprova. Às vezes a leitura está certa. Às vezes o nome do operador termina em IfExists e a mesma condição passa a dizer outra coisa.
Existe um mito comum sobre esse caso: "se a chave não vier na requisição, a AWS ignora a condição e o Allow passa". Não é isso que está documentado. Com operador normal, chave ausente é mismatch, a condição dá falso e aquele Allow não concede nada. O que faz o Allow passar sem a chave é o sufixo ...IfExists.
A ideia é chegar ao fim do texto sabendo olhar três políticas e dizer qual delas falha fechada e qual deixa o GetObject passar quando a requisição não traz nem aws:SourceIp nem aws:SourceVpc.
O material vem do IAM User Guide, recuperado em 29/09/2026, e de três arquivos JSON locais; nada foi executado contra uma conta AWS real, no Policy Simulator ou em deploy.
O que o User Guide diz sobre chave ausente
A página de operadores de condição tem um aviso "Important" que resolve a maior parte da confusão:
> If the key that you specify in a policy condition is not present in the request context, the values do not match and the condition is false.
O mesmo aviso acrescenta dois detalhes. Com operador negado, tipo StringNotLike, a chave ausente deixa a condição verdadeira. E essa lógica vale para todos os operadores, exceto ...IfExists e Null. A página do elemento Condition diz a mesma coisa de forma mais curta: "A context key that is not present in the request is considered a mismatch." Em nenhum lugar aparece "condição ignorada".
O IfExists inverte o caso da ausência. Nas palavras da página de operadores:
> If the condition key is present in the context of the request, process the key as specified in the policy. If the key is not present, evaluate the condition element as true.
O Null serve para outra coisa: ele testa se a chave existe. "true" quer dizer que a chave não existe. "false" quer dizer que ela existe e não é nula.
Falta lembrar como a avaliação funciona. Toda requisição começa com implicit deny, precisa de um Allow explícito, e um Deny explícito vence qualquer Allow. Um Allow com a condição falsa simplesmente não se aplica. Isso é diferente de um Deny explícito, e essa diferença volta mais adiante.
| Operador | Chave presente e casa | Chave presente e não casa | Chave ausente |
|---|---|---|---|
IpAddress em aws:SourceIp | match | no match | falso: o statement não concede |
StringEquals | match | no match | falso (mismatch) |
...IfExists | avalia como o operador base | avalia como o operador base | verdadeiro |
Null com "false" | verdadeiro | verdadeiro | falso |
Política 1: IpAddress em aws:SourceIp
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "AllowGetObjectIfPublicSourceIp",
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::amzn-s3-demo-bucket/*",
"Condition": {
"IpAddress": { "aws:SourceIp": "203.0.113.0/24" }
}
}]
}
O operador é IpAddress, que é o que a documentação usa para comparar faixa CIDR com aws:SourceIp. A política usa a linguagem 2012-10-17, a versão atual, e as três do texto seguem nela.
Se a requisição chega com aws:SourceIp dentro da faixa, o Allow concede. Se chega de fora, não concede. Se a chave não vem, o valor não casa, a condição dá falso e este statement não concede nada.
A chave pode faltar num caso bem concreto, e a página de chaves globais descreve:
> The aws:SourceIp key is always present in the request context, except when the requester uses a VPC endpoint to make the request. In this case, the condition returns false and the request is implicitly denied by this statement.
Ou seja, o VPC endpoint tira o SourceIp do contexto sem avisar ninguém, e a política continua parecendo um cadeado. A mesma página diz ainda que aws:SourceIp só serve para faixas de IP público. Então pôr um CIDR privado aqui não restringe nada do jeito que o autor imaginou.
Política 2: o par IfExists que a própria AWS mostra
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "AllowGetObjectIfExistsIpOrVpc",
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::amzn-s3-demo-bucket/*",
"Condition": {
"IpAddressIfExists": { "aws:SourceIp": ["203.0.113.0/24"] },
"StringEqualsIfExists": { "aws:SourceVpc": ["vpc-1234567890abcdef0"] }
}
}]
}
Essa combinação vem do exemplo "IP range or VPC" da página de chaves globais, e a própria página explica o que acontece:
> If either or both keys are not included in the request context, the condition still returns true. The values are only checked if the specified key is included in the request context.
Juntando as duas frases de disponibilidade, dá para ver o que o desenho pretende. Uma requisição pela internet traz SourceIp, que é comparado com a faixa. Ela não traz SourceVpc, que só existe quando se usa VPC endpoint, então essa metade dá verdadeiro. Uma requisição por VPC endpoint é o contrário: não traz SourceIp, que dá verdadeiro, e traz SourceVpc, que é comparado. Cada caminho é conferido pela chave que ele de fato envia.
O Allow que passa é o terceiro caso: nenhuma das duas chaves chega. Aí os dois elementos dão verdadeiro, a condição inteira dá verdadeiro e o GetObject é concedido sem que nenhuma rede tenha sido verificada. É exatamente o comportamento documentado do IfExists, e ninguém reparou no sufixo porque ele fica no nome do operador, não no valor que o revisor estava lendo.
Tem uma variação que merece atenção. Se só a primeira linha existisse, com IpAddressIfExists sozinho, qualquer requisição feita por qualquer VPC endpoint chegaria sem SourceIp e passaria. Seria justamente o caminho que a política 1 fechava.
Política 3: fechando de propósito com Null
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "AllowGetObjectOnlyWhenSourceIpPresentAndMatches",
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::amzn-s3-demo-bucket/*",
"Condition": {
"IpAddress": { "aws:SourceIp": "203.0.113.0/24" },
"Null": { "aws:SourceIp": "false" }
}
}]
}
Esse JSON não foi copiado de nenhum exemplo da AWS. Ele junta dois operadores documentados. A página do Null usa outra chave como exemplo, não SourceIp. Os dois operadores estão no mesmo bloco Condition, então os dois precisam dar verdadeiro.
Pela tabela, a política 1 já falhava fechada quando a chave faltava. O Null com "false" não muda esse resultado hoje. Ele deixa escrita, e aplicada pelo avaliador, a exigência de que a chave exista. Se alguém acrescentar IfExists ao IpAddress num PR futuro, o Null continua negando a requisição sem SourceIp. Vale como trava contra um sufixo colado sem pensar.
Quando IfExists é só ruído
Algumas chaves estão sempre no contexto. A página de chaves globais diz que aws:CurrentTime, aws:EpochTime e aws:ViaAWSService são "always included" e que aws:PrincipalAccount vem em todas as requisições, inclusive as anônimas. Com essas chaves, o IfExists nunca chega a agir, porque o caso "chave ausente" nunca ocorre. O efeito prático é deixar o próximo revisor pensando por que o sufixo está ali. aws:SourceIp não entra nessa lista: a mesma frase que diz "always present" já traz a exceção do VPC endpoint.
Armadilhas que valem uma leitura
StringEqualscom CIDR. A documentação usaIpAddresspara faixa CIDR.StringEqualsé operador de string e se aplica, por exemplo, aaws:SourceVpcouaws:SourceVpce. Não conte com ele para interpretar máscara de rede.- Outro Allow sem Condition. Uma condição falsa desativa só o statement dela. Se a mesma identity policy tiver outro Allow com
s3:GetObjectno mesmo recurso e sem condição, a ação continua concedida. Revisar a condição sem olhar os outros statements é revisar metade da política. IfExistsem Deny com operador negado. O caso é o oposto do Allow. A página de operadores diz que, com"Effect": "Deny"e algo comoStringNotEqualsIfExists, "the request is still denied even if the condition key is not present". O fail-open deste texto é problema do Allow.- Chamadas entre serviços. Quando um serviço da AWS chama outro em nome do principal, o serviço de destino vê o IP do serviço que chamou, e parte do contexto de rede é removida. VPC e VPCE de origem também não são preservados. A documentação manda não usar
aws:SourceIpnesses casos e recorrer aaws:ViaAWSServiceouaws:CalledVia.
Quando a DevDojo usaria cada forma
Se a intenção é "só a partir desta rede", a DevDojo usaria IpAddress em aws:SourceIp, ou StringEquals em aws:SourceVpc/aws:SourceVpce, sem IfExists. Quando a presença da chave é um requisito que precisa ficar visível, acrescentaria Null com "false", como na política 3.
IfExists em SourceIp ou SourceVpc entra quando alguns clientes legítimos às vezes não mandam a chave e o time decide conscientemente deixar esse caminho aberto. Essa decisão devia estar escrita no PR, e não aparecer só como um sufixo no nome do operador.
A recomendação perde força em duas situações. A primeira é quando a pergunta é o que uma conta real concede agora, com SCPs, permissions boundaries e outras políticas na mesma avaliação. A segunda é quando a política em jogo é uma resource policy, que não foi avaliada aqui. Para as duas, a leitura daqui não substitui uma avaliação na conta.
Também não dá para terceirizar essa checagem para um linter. O Parliament e o CloudFormation Guard analisam JSON local, mas não avaliam request context nem simulam chave ausente. A diferença entre as políticas 1 e 2 fica fora do alcance deles.
Próximo passo
Salve os três JSON e percorra a tabela da seção de operadores do User Guide em três situações: chave presente e dentro da faixa, chave presente e fora da faixa, e chave ausente. Em cada uma, anote se o statement concede. A política 2 deve ser a única que concede quando a chave está ausente. Com credenciais, o Policy Simulator pode confirmar essa leitura.