IAM: User, Role e Policy do jeito correto
1. Visão geral
IAM (Identity and Access Management) é o serviço fundamental da AWS, que define “quem pode fazer o quê com o quê”. Muitos times cometem erros desde o início: criar usuários com permissões de admin para tudo, vincular policy *:* para ser rápido, ou confundir user com role. Este guia ajuda você a distinguir claramente os três conceitos: user, role, policy, escrever JSON de policy com sintaxe correta, aplicar o princípio de least privilege, e testar o assumir role usando AWS SDK para Java.
Depois de ler, você estará confiante em projetar um conjunto IAM enxuto, seguro, sem “permissões god-mode” para todos os serviços.
2. Três componentes principais
IAM funciona em torno de três conceitos:
- User: identidade para uma pessoa ou aplicação específica, com credenciais de longa duração (password, access key).
- Role: identidade temporária, sem credenciais fixas, que pode ser “assumida” (assume) por um user, serviço, ou outra conta.
- Policy: texto JSON que descreve permissões (allow/deny) anexado a um user, role, group, ou recurso.
Lembrete: Role não é “um user sem senha”. Role gera credenciais temporárias (com prazo de validade, via STS), enquanto user usa credenciais fixas até que você as rotacione.
3. User: quando usar, quando não usar
User é adequado para:
- Pessoas que precisam fazer login no Console para trabalho manual.
- Casos extremos onde role não pode ser usado (raro, por exemplo, sistemas legacy fora da AWS que precisam de access key fixo).
User não é adequado para:
- Workloads rodando em EC2, Lambda, ECS — esses devem usar role.
- Compartilhar um user com várias pessoas (perde rastreabilidade no CloudTrail).
Criar um user via CLI:
aws iam create-user --user-name order-service-deployer
aws iam create-login-profile \
--user-name order-service-deployer \
--password 'TempPass123!' \
--password-reset-required
O primeiro comando cria o user. O comando create-login-profile concede permissão para fazer login no Console com uma senha temporária, forçando uma alteração na primeira vez — evita deixar a senha padrão por muito tempo.
Erro comum: criar access key para o user e fazer hard-code no código, commitando no git. Se for obrigatório usar access key, ative rotação periódica e use Secrets Manager para armazenar.
4. Role: identidade temporária no lugar certo
4.1. EC2 instance role
Quando uma instância EC2 precisa chamar S3, não coloque access key no .env. Crie uma role e anexe instance profile:
aws iam create-role \
--role-name OrderServiceEc2Role \
--assume-role-policy-document file://trust-policy.json
O arquivo trust-policy.json define quem pode assumir essa role:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "Service": "ec2.amazonaws.com" },
"Action": "sts:AssumeRole"
}
]
}
Principal.Service especifica que o serviço EC2 pode assumir essa role através de instance profile. Isso é chamado trust policy — diferente da permission policy descrita depois.
4.2. Cross-account role
Role também é usada para permitir que outra conta AWS acesse sua conta sem compartilhar credenciais:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::111122223333:root" },
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": { "sts:ExternalId": "order-partner-2024" }
}
}
]
}
Condition com sts:ExternalId protege contra confused deputy attack — a terceira parte precisa saber o ExternalId secreto para assumir a role, evitando que um quarto agente explore o ARN público.
5. Policy: estrutura e como escrever
Uma policy JSON consiste em Version, Statement — cada statement tem Effect, Action, Resource, e opcionalmente Condition.
Exemplo: permitir que PaymentService leia/escreva um bucket S3 específico, sem poder deletar:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowReadWritePaymentBucket",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::payment-receipts-bucket/*"
},
{
"Sid": "DenyDeleteObject",
"Effect": "Deny",
"Action": "s3:DeleteObject",
"Resource": "arn:aws:s3:::payment-receipts-bucket/*"
}
]
}
O primeiro statement Allow as duas ações GetObject e PutObject apenas no caminho payment-receipts-bucket/*. O segundo statement Deny torna explícito a operação de deleção — mesmo que depois alguém anexe uma policy s3:*, o deny explícito sempre vence.
Anexar essa policy à role:
aws iam put-role-policy \
--role-name PaymentServiceRole \
--policy-name PaymentBucketAccess \
--policy-document file://payment-bucket-policy.json
5.1. Policy baseada em identidade vs baseada em recurso
- Identity-based policy: anexada a user/role/group, responde “o que essa identidade pode fazer”.
- Resource-based policy: anexada diretamente a um recurso (bucket policy do S3, queue policy do SQS, key policy do KMS), responde “quem pode acessar esse recurso”.
Exemplo de bucket policy (resource-based) que permite apenas uma role específica:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:role/PaymentServiceRole"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::payment-receipts-bucket/*"
}
]
}
Esses dois tipos de policy podem ser combinados — a permissão final é a interseção de ambas (tanto identity-based quanto resource-based devem permitir, se um não permitir é bloqueado, exceto em cross-account onde ambos devem permitir).
6. Princípio de least privilege
Em vez de escrever "Action": "s3:*" por pressa, liste as ações exatas que você precisa. Use Condition para restringir ainda mais.
Exemplo: permitir leitura de objeto apenas se a requisição vem de uma VPC específica:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::order-exports-bucket/*",
"Condition": {
"StringEquals": {
"aws:SourceVpc": "vpc-0abc1234"
}
}
}
]
}
aws:SourceVpc é uma chave de condição global, permite apenas se a requisição vem do VPC endpoint da VPC especificada — útil quando você não quer que dados saiam da rede interna.
Abordagem prática para alcançar least privilege:
- Comece com uma policy vazia (deny por padrão).
- Execute o workload, veja IAM Access Analyzer ou logs do CloudTrail para ações negadas.
- Adicione exatamente essas ações à policy, sem excesso.
- Repita até que o workload rode normalmente sem bloqueios.
Dica: Use
aws iam generate-service-last-accessed-detailspara ver quais ações um user/role realmente usou nos últimos 90 dias — assim você pode remover ações desnecessárias da policy atual.
7. Lógica de avaliação de policy para lembrar
A AWS avalia permissões nesta ordem — memorize isso para debugar rápido:
- Por padrão tudo é negado (implicit deny).
- Se houver qualquer statement com
Denyexplícito que corresponda → resultado final é deny, nada pode salvar. - Se não houver deny explícito, procure um statement
Allowque corresponda → allow. - Se nenhum allow corresponder → ainda é deny (implicit deny por padrão).
Requisição chega → Tem Deny explícito que corresponde?
├─ Sim → DENY (fim)
└─ Não → Tem Allow que corresponde?
├─ Sim → ALLOW
└─ Não → DENY (implicit)
Entender essa ordem certa evita bugs clássicos: anexar AdministratorAccess a um user mas ainda ser bloqueado em uma ação, porque há um SCP (Service Control Policy) no nível Organization que nega explicitamente a ação.
8. Testando assumir role com AWS SDK para Java
Adicione a dependência SDK v2:
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>sts</artifactId>
<version>2.28.11</version>
</dependency>
Um serviço chama STS para assumir a role e obter credenciais temporárias:
public class RoleAssumer {
private final StsClient stsClient;
public RoleAssumer(StsClient stsClient) {
this.stsClient = stsClient;
}
public Credentials assumePaymentServiceRole() {
AssumeRoleRequest request = AssumeRoleRequest.builder()
.roleArn("arn:aws:iam::123456789012:role/PaymentServiceRole")
.roleSessionName("payment-service-session")
.durationSeconds(900)
.build();
AssumeRoleResponse response = stsClient.assumeRole(request);
return response.credentials();
}
}
roleArn especifica a role a assumir, roleSessionName nomeia a sessão para facilitar rastreamento no CloudTrail, durationSeconds limita o tempo de vida das credenciais temporárias — quanto mais curto, mais seguro.
Teste com JUnit 5 e Mockito para verificar que a lógica chama a role ARN correta, sem fazer chamadas AWS reais:
@Test
void assumePaymentServiceRole_shouldRequestCorrectRoleArn() {
StsClient stsClient = mock(StsClient.class);
Credentials fakeCredentials = Credentials.builder()
.accessKeyId("AKIAFAKE")
.secretAccessKey("secretFake")
.sessionToken("tokenFake")
.build();
when(stsClient.assumeRole(any(AssumeRoleRequest.class)))
.thenReturn(AssumeRoleResponse.builder()
.credentials(fakeCredentials)
.build());
RoleAssumer assumer = new RoleAssumer(stsClient);
Credentials result = assumer.assumePaymentServiceRole();
ArgumentCaptor<AssumeRoleRequest> captor = ArgumentCaptor.forClass(AssumeRoleRequest.class);
verify(stsClient).assumeRole(captor.capture());
assertThat(captor.getValue().roleArn())
.isEqualTo("arn:aws:iam::123456789012:role/PaymentServiceRole");
assertThat(result.accessKeyId()).isEqualTo("AKIAFAKE");
}
Esse teste faz mock do StsClient, captura a requisição real enviada, depois faz assert que o roleArn é o esperado. Assim você detecta cedo se alguém acidentalmente muda o ARN hard-coded no código.
assumePaymentServiceRole_shouldRequestCorrectRoleArn() PASSED
9. Erros comuns para evitar
- Anexar
AdministratorAccessa todo service account porque “vamos otimizar depois”. Na prática ninguém volta a otimizar, o risco fica para sempre. - Usar access key para workloads em EC2/Lambda em vez de role — access key vaza fácil, não se rotaciona automaticamente, sem prazo de expiração.
- Esquecer de revisar trust policy da role:
Principalmuito amplo (ex:"AWS": "*") permite que qualquer um assuma. - Não usar
Conditioncom MFA para ações sensíveis (deletar bucket, modificar policy IAM). Adicione"Bool": {"aws:MultiFactorAuthPresent": "true"}para ações de alto risco. - Misturar muitas responsabilidades em uma policy grande dificultando a revisão. Separe por recurso/serviço, com nomes claros (
PaymentBucketReadWrite, nãoMyPolicy1).
Quando NÃO precisa de IAM complexo: Se está fazendo sandbox pessoal, aprendendo um novo serviço, criar role/policy detalhada desde o início pode desacelerar o aprendizado. Nesse caso, usar um user temporário com policy managed existente (
AmazonS3ReadOnlyAccess) é o suficiente, desde que você o delete quando terminar.
10. Conclusão
User é para pessoas que precisam fazer login manual, role é para workloads e acesso temporário, policy é um conjunto de regras JSON que define allow/deny. Memorizar a ordem de avaliação (deny explícito sempre vence, implicit deny é o padrão) ajuda a debugar rápido quando permissões “estranhas” acontecem. Aplique least privilege começando vazio, adicionando gradualmente baseado em logs reais, e sempre escreva testes para a lógica de assumir role no código — essa é a maneira de manter sua infraestrutura AWS segura sem dor de cabeça de manutenção depois.