IAM Identity Center com Terraform
Tags: security, aws, terraform, tutorial
Published: 2024-01-27 02:20:54

Recentemente tive um projeto no trabalho envolvendo o Identity Center da AWS (sucessor do Single Sign-On), quis deixar aqui de uma forma mais simples e prática coisas que fui aprendendo.

---

Para ficar um pouco melhor a visualização do que será feito, elaborei a imagem abaixo: 

![Imagem do fluxo que será feito](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1npevlbc8u5af7b68axh.png)


Falando das etapas:

- Criação de um grupo;
- Adicionar membro ao grupo;
- Criação da Permission Set;
- Adicionar policies na Permission Set;
- Criar o que é chamado Account Assignment;

Algumas explicações básicas: 
- **Permission Set** é a forma que o Identity Center trabalha com permissões, por trás nada mais é que uma role que é atribuída à um grupo/usuário;
- Esse processo que chamamos de **Account Assignment** nada mais é que criar o vínculo da Permission Set com a conta que você quer (por exemplo, conta de produção, desenvolvimento, etc). Caso você não conheça muito sobre a estrutura do Identity Center, de forma resumida, ele é centralizado, ou seja, todas as minhas permissões para todas as minhas contas irão ficar centralizada em uma conta só dentro do Identity Center, por isso precisamos criar esse vínculo para que ele saiba em qual conta essas permissões se aplicam. 

---

## Código
Tentei trazer um exemplo de código que possa ser reutilizado, para ficar um pouco mais elaborado e vocês conseguirem visualizar algo a mais e não ser um exemplo tão simples.

**OBS 01:** Estou considerando que você já tenha conhecimento de AWS e Terraform, logo suas credenciais/região já devem estar configuradas.
**OBS 02:** O código só funciona com a versão do provider AWS >= 5.14. Explico melhor sobre no final.

Nós teremos um arquivo de `locals` chamado `local.tf` que terá:

<code>
data "aws_ssoadmin_instances" "my_sso" {}

output "arns" {
  value = tolist(data.aws_ssoadmin_instances.my_sso.arns)[0]
}

output "identity_store_id" {
  value = tolist(data.aws_ssoadmin_instances.my_sso.identity_store_ids)[0]
}

# sempre vamos acabar precisando utilizar esse id e esse arn em outros lugares
locals {
  identity_store_id  = tolist(data.aws_ssoadmin_instances.my_sso.identity_store_ids)[0]
  identity_store_arn = tolist(data.aws_ssoadmin_instances.my_sso.arns)[0]
}
</code>

Nós teremos um outro arquivo chamado `user-group.tf`, nesse arquivo nós iremos colocar um exemplo de estrutura de usuários que queremos adicionar: 

<code>
variable "user_group" {
  description = "Create users in Identity Center"
  default = {
    "kelly.lima" = {
      email      = "kelly.lima@email.com"
      group      = ["group-a"]
      first_name = "Kelly"
      last_name  = "Lima"
    }
  }
}
</code>

Um arquivo chamado `policies-managed.tf` onde colocaremos policies gerenciadas pela AWS a forma de adicioná-las é diferente das policies personalizadas. Nas gerenciadas nós teremos:

<code>
variable "policies_managed" {
  description = "Policies Managed (AWS)"
  type        = list(string)
  default = [
    "arn:aws:iam::aws:policy/ReadOnlyAccess",
    "arn:aws:iam::aws:policy/AWSBillingReadOnlyAccess"
  ]
}
</code>

Esses valores de `arn` serão padrão. Você pode pegar esses valores dentro do próprio IAM.

Arquivo de policies personalizadas se chamará `policies-custom.tf` e terá: 

<code>
variable "policy_group_a" {
  description = "Policies Custom"
  type        = list(string)
  default = [
    "policies-group-a",
    "policies-default"
  ]
}
</code>

No exemplo acima, podemos ver que no Identity Center as policies customizadas nós devemos passar o nome, pois o resource `aws_ssoadmin_customer_managed_policy_attachment` (que iremos utilizar em breve) ele só aceita nome da policy e não o arn. Nesse caso eu estou considerando que essa policy já foi criada utilizando o resource `aws_iam_policy`.

Teremos um arquivo que chamaremos de `accounts.tf` onde terá: 

<code>
variable "accounts_aws" {
  description = "Accounts AWS"
  default = {
    "prod" = {
      number = "123456789345"
    },
    "dev" = {
      number = "458456789345"
    }
  }
}
</code>

Este é um exemplo de como poderia ficar várias contas, mas nós iremos considerar apenas a conta "prod" para facilitar.

Agora nós teremos o nosso último arquivo onde iremos construir todo esse processo que temos na imagem do começo, ficaria: 

<code>
# Criação do Group A
resource "aws_identitystore_group" "group_a" {
  identity_store_id = local.identity_store_id
  display_name      = "group-a"
  description       = "sso group a"
}

# Adciona os membros baseado no group em var.user_group
resource "aws_identitystore_group_membership" "membership_group_a" {
  for_each          = { for user in local.group_members : user => aws_identitystore_user.user_sso[user].user_id }
  depends_on        = [aws_identitystore_user.user_sso]
  identity_store_id = local.identity_store_id
  group_id          = aws_identitystore_group.group_a.group_id
  member_id         = each.value
}

# Criação da Permission Set
resource "aws_ssoadmin_permission_set" "permission_set_group_a" {
  name         = "PermissionGroupA"
  instance_arn = local.identity_store_arn
  description  = "Permission Set Custom"
}

# Adiciona as policies managed AWS na Permission Set
resource "aws_ssoadmin_managed_policy_attachment" "policy_managed_group_a" {
  instance_arn       = local.identity_store_arn
  count              = length(var.policies_managed)
  managed_policy_arn = var.policies_managed[count.index]
  permission_set_arn = aws_ssoadmin_permission_set.permission_set_group_a.arn
}

# Adiciona as policies customizadas na Permission Set
resource "aws_ssoadmin_customer_managed_policy_attachment" "policy_custom" {
  for_each = {
    for policy_key, policy_value in var.policy_group_a :
    policy_key => var.policy_group_a[policy_key]
  }
  instance_arn       = local.identity_store_arn
  permission_set_arn = aws_ssoadmin_permission_set.permission_set_group_a.arn
  customer_managed_policy_reference {
    name = each.value
    path = "/"
  }
}

# Adiciona a Permission Set com o Group A à conta "prod"
resource "aws_ssoadmin_account_assignment" "prod_env" {
  for_each = {
    for key, key_value in local.permission_set_arns :
    value_key => local.permission_set_arns[value_key]
  }
  instance_arn       = local.identity_store_arn
  permission_set_arn = each.value

  principal_id   = aws_identitystore_group.group_a.group_id
  principal_type = "GROUP"

  target_id   = var.accounts_aws["prod"].number
  target_type = "AWS_ACCOUNT"
}

locals {
# Aqui você pode colocar outros arns de Permission Sets
  permission_set_arns = [aws_ssoadmin_permission_set.permission_set_group_a.arn]

# Deixei esse locals no mesmo arquivo e não no arquivo local.tf apenas para questão didática, ficar mais prático de visualizar
  group_members = [
    for user, details in var.user_group :
    user if contains(details.group, "group-a")
  ]
}
</code>

## Alguns pontos:
Como podem ver é utilizado o `depends_on` no resource `aws_identitystore_group_membership` foi colocado pois o código estava dando um bug na hora do apply, o Terraform reclamava de adicionar um membro que não existia ainda. Há outros problemas similares quando rodamos o destroy envolvendo Permission Sets, em alguns casos ele reclama do estado que está recebendo (espera estado A e recebe B), ao rodar o comando novamente ele consegue finalizar a exclusão do resource, é realmente um bug nos states do Terraform. Tentei utilizar o `depends_on` nesse caso também, mas não obtive sucesso, você pode ver melhor sobre nesses links:
- https://github.com/hashicorp/terraform-provider-aws/issues/26757
- https://github.com/hashicorp/terraform-provider-aws/issues/26807

Sobre o aviso que deixo acima da versão ser >= 5.14 é por conta desses bugs, antes da versão 5.14 eles ocorriam com maior frequência, houve várias [correções](https://github.com/hashicorp/terraform-provider-aws/blob/v5.14.0/CHANGELOG.md#5140-unreleased) referentes a Permission Set.

## Conclusão
Bom, eu tentei ser o mais sucinta possível, espero que tenha ajudado, qualquer coisa só perguntar. Valeu! ✌️