MongoDB Atlas com Terraform: usuários do banco de dados e Vault
SM
Samuel Molling9 min read • Published Apr 15, 2024 • Updated Apr 15, 2024
APLICATIVO COMPLETO
Avalie esse Tutorial
Neste tutorial, mostrarei como criar um usuário para o MongoDB database no MongoDB Atlas usando o Terraform e como armazenar essa credencial com segurança no HashiCorp Vault. Vimos no artigo anterior, MongoDB Atlas With Terraform - Cluster and Backup Políticas, como criar um cluster com políticas de backup configuradas. Agora, Go e criaremos nosso primeiro usuário. Se você não leu os artigos anteriores, sugerimos que você procure para entender como começar.
Este artigo é para qualquer pessoa que pretende usar ou já usa infraestrutura como código (IaC) na plataforma MongoDB Atlas ou deseja saber mais sobre ela.
Tudo o que fazemos aqui está contido na documentação do provedor/recurso:
Neste ponto, criaremos nosso primeiro usuário usando Terraform no MongoDB Atlas e armazenaremos o URI para se conectar ao meu cluster no HashiCorp Vault. Para quem não conhece, o HashiCorp Vault é uma ferramenta de gerenciamento de segredos que permite armazenar, acessar e gerenciar com segurança credenciais confidenciais, como senhas, chaves de API, certificados e muito mais. Ele foi projetado para ajudar as organizações a proteger seus dados e infraestrutura em ambientes de Tl complexos e distribuídos. Nela, armazenaremos o URI de conexão do usuário que será criado com o cluster que criamos no último artigo.
Antes de começarmos, certifique-se de que todos os pré-requisitos mencionados no artigo anterior estejam configurados corretamente: Instalar o Terraform, criar uma chave de API no MongoDB Atlas e configurar um projeto e um cluster no Atlas. Estas etapas são essenciais para garantir o sucesso da criação do seu usuário do banco de dados.
O primeiro passo é executar o HashiCorp Vault para que possamos testar nosso módulo. É possível executar o Vault no Docker Local. Se você não tiver o Docker instalado, poderá baixá-lo. Depois de baixar o Docker, baixaremos a imagem que queremos executar — neste caso, do Vault. Para fazer isso, executaremos um comando no terminal
docker pull vault:1.13.3
ou fará o download usando o Docker Desktop.
Agora, criaremos um container a partir desta imagem. Clique na imagem e clique em Executar. Depois disso, uma caixa será aberta onde só precisamos mapear a porta do nosso computador para o container. Nesse caso, usarei a porta 8200 , que é a porta padrão do Vault. Clique em Executar.

O contêiner começará a ser executado. Se formos ao nosso navegador e inserirmos a URL
localhost:8200/
, a tela de login do Vault aparecerá.
Para acessar o Vault, usaremos o Token Raiz que é gerado quando criarmos o container.

Agora, vamos fazer login. Após a abertura, criaremos um novo motor do tipo KV apenas para ilustrá-lo um pouco melhor. Clique em Secrets Engines -> Enable new Engine -> Generic KV e clique em Next.

Em Path coloque
kv/my_app
e clique em Enable Engine. Agora temos nosso Vault configurado e funcionando.A próxima etapa é configurar o provedor Terraform. Isso permitirá que o Terraform se comunique com a API MongoDB Atlas and Vault para gerenciar recursos. Adicione o seguinte bloco de código ao seu arquivo providers.tf:
1 provider "mongodbatlas" {} 2 provider "vault" { 3 address = "http://localhost:8200" 4 token = "hvs.brmNeZd31NwEmyky1uYI2wvY" 5 skip_child_token = true 6 }
No artigo anterior, configuramos o provedor Terraform colocando nossas chaves públicas e privadas em variáveis de ambiente. Continuaremos desta forma. Adicionaremos um novo provedor, o Vault. Nele, configuraremos o endereço do Vault, o token de autenticação e o parâmetro skip_child_token para que possamos nos autenticar no Vault.
Observação: não é aconselhável especificar o token de autenticação em um ambiente de produção. Use um dos métodos de autenticação recomendados pela HashiCorp, como app_role. Você pode avaliar as opções nos Docs do Terraform
O arquivo de versão continua tendo a mesma finalidade, conforme mencionado em outros artigos, mas adicionaremos a versão do provedor do Vault como algo novo.
1 terraform { 2 required_version = ">= 0.12" 3 required_providers { 4 mongodbatlas = { 5 source = "mongodb/mongodbatlas" 6 version = "1.14.0" 7 } 8 vault = { 9 source = "hashicorp/vault" 10 version = "4.0.0" 11 } 12 } 13 }
Depois de configurar o arquivo de versão e estabelecer as versões do Terraform e do provedor, a próxima etapa é definir o recurso do usuário no MongoDB Atlas. Isso é feito criando um arquivo .tf arquivo — por exemplo, main.tf — onde criaremos nosso módulo. Como criaremos um módulo que será reutilizável, usaremos variáveis e valores padrão para que outras chamadas possam criar usuários com permissões diferentes, sem precisar escrever um novo módulo.
1 # ------------------------------------------------------------------------------ 2 # RANDOM PASSWORD 3 # ------------------------------------------------------------------------------ 4 resource "random_password" "default" { 5 length = var.password_length 6 special = false 7 } 8 9 # ------------------------------------------------------------------------------ 10 # DATABASE USER 11 # ------------------------------------------------------------------------------ 12 resource "mongodbatlas_database_user" "default" { 13 project_id = data.mongodbatlas_project.default.id 14 username = var.username 15 password = random_password.default.result 16 auth_database_name = var.auth_database_name 17 18 dynamic "roles" { 19 for_each = var.roles 20 content { 21 role_name = try(roles.value["role_name"], null) 22 database_name = try(roles.value["database_name"], null) 23 collection_name = try(roles.value["collection_name"], null) 24 } 25 } 26 27 dynamic "scopes" { 28 for_each = var.scope 29 content { 30 name = scopes.value["name"] 31 type = scopes.value["type"] 32 } 33 } 34 35 36 dynamic "labels" { 37 for_each = local.tags 38 content { 39 key = labels.key 40 value = labels.value 41 } 42 } 43 } 44 45 resource "vault_kv_secret_v2" "default" { 46 mount = var.vault_mount 47 name = var.secret_name 48 data_json = jsonencode(local.secret) 49 }
No início do arquivo, temos o recurso random_password que é usado para gerar uma senha aleatória para o nosso usuário. No recurso mongodbatlas_database_user, especificaremos nossos detalhes de usuário. Estamos colocando alguns valores como variáveis, como feito em outros artigos, como nome e auth_database_name com um valor padrão de admin. Abaixo, criamos três blocos dinâmicos: funções, escopos e rótulos. Para roles, é uma lista de mapas que podem conter o nome da role (read, readWrite ou algum outro), o database_name e o collection_name. Esses valores podem ser opcionais se você criar um usuário com permissão atlasAdmin, como neste caso, não. É necessário especificar um banco de dados ou uma collection ou, se você quiser, especificar apenas o banco de dados e não uma collection específica. Faremos um exemplo. Para o bloco de escopos, o tipo é DATA_LAKE ou CLUSTER. No nosso caso, especificaremos um cluster, que é o nome do nosso cluster criado, o cluster de demonstração. E os rótulos servem como tags para o nosso usuário.
Finalmente, definimos o recurso vault_kv_secret_v2 que criará um segredo em nosso Vault. Ele recebe o monte onde será criado e o nome do segredo. O data_json é o valor do segredo; estamos criando-o no arquivo locals.tf que avaliaremos abaixo. É um valor JSON — é por isso que o estamos codificando.
No arquivo variable.tf, criamos variáveis com valores padrão:
1 variable "project_name" { 2 description = "The name of the Atlas project" 3 type = string 4 } 5 6 variable "cluster_name" { 7 description = "The name of the Atlas cluster" 8 type = string 9 } 10 11 variable "password_length" { 12 description = "The length of the password" 13 type = number 14 default = 20 15 } 16 17 variable "username" { 18 description = "The username of the database user" 19 type = string 20 } 21 22 variable "auth_database_name" { 23 description = "The name of the database in which the user is created" 24 type = string 25 default = "admin" 26 } 27 28 variable "roles" { 29 description = <<HEREDOC 30 Required - One or more user roles blocks. 31 HEREDOC 32 type = list(map(string)) 33 } 34 35 variable "scope" { 36 description = "The scopes to assign to the user" 37 type = list(object({ 38 name = string 39 type = string 40 })) 41 default = [] 42 } 43 44 variable "labels" { 45 type = map(any) 46 default = null 47 description = "A JSON containing additional labels" 48 } 49 50 variable "uri_options" { 51 type = string 52 default = "retryWrites=true&w=majority&readPreference=secondaryPreferred" 53 description = "A string containing additional URI configs" 54 } 55 56 variable "vault_mount" { 57 description = "The mount point for the Vault secret" 58 type = string 59 } 60 61 variable "secret_name" { 62 description = "The name of the Vault secret" 63 type = string 64 } 65 66 variable "application" { 67 description = <<HEREDOC 68 Optional - Key-value pairs that tag and categorize the cluster for billing and organizational purposes. 69 HEREDOC 70 type = string 71 } 72 73 variable "environment" { 74 description = <<HEREDOC 75 Optional - Key-value pairs that tag and categorize the cluster for billing and organizational purposes. 76 HEREDOC 77 type = string 78 }
Configuramos um arquivo chamado locals.tf com os valores do nosso Vault e as marcações que foram criadas, como o último artigo. O interessante aqui é que estamos definindo como a connection string do nosso usuário será montada e salva no Vault. Também não foi possível salvar o nome de usuário e a senha, mas eu pessoalmente preferi salvar o URI. Dessa forma, posso especificar algumas boas práticas, como definir tags de conexão, como readPreference, sem depender do desenvolvedor para colocá-las no aplicativo. No código abaixo, existem alguns tratados de texto para que o URI esteja correto. No final, crio uma variável chamada segredo que possui uma chave de URI e recebe o valor do URI criado.
1 locals { 2 private_connection_srv = data.mongodbatlas_advanced_cluster.default.connection_strings.0.standard_srv 3 cluster_uri = trimprefix(local.private_connection_srv, "mongodb+srv://") 4 private_connection_string = "mongodb+srv://${mongodbatlas_database_user.default.username}:${random_password.default.result}@${local.cluster_uri}/${var.auth_database_name}?${var.uri_options}" 5 6 secret = { "URI" = local.private_connection_string } 7 8 tags = { 9 name = var.application 10 environment = var.environment 11 } 12 }
Neste artigo, adotamos o uso de fontes de dados no Terraform para estabelecer uma conexão dinâmica com os recursos existentes, como nosso projeto MongoDB Atlas e nosso cluster. Especificamente, no arquivo data.tf, definimos uma fonte de dados, mongodbotlas_project e mongodcatlas_advanced_cluster, para acessar informações sobre um projeto e cluster existentes com base em seu nome:
1 data "mongodbatlas_project" "default" { 2 name = var.project_name 3 } 4 5 6 data "mongodbatlas_advanced_cluster" "default" { 7 project_id = data.mongodbatlas_project.default.id 8 name = var.cluster_name 9 }
Finalmente, definimos nosso arquivo de variáveis, terraform.tfvars:
1 project_name = "project-test" 2 username = "usr_myapp" 3 application = "teste-cluster" 4 environment = "dev" 5 cluster_name = "cluster-demo" 6 7 roles = [{ 8 "role_name" = "readWrite", 9 "database_name" = "db1", 10 "collection_name" = "collection1" 11 }, { 12 "role_name" : "read", 13 "database_name" : "db2" 14 }] 15 16 scope = [{ 17 name = "cluster-demo", 18 type = "CLUSTER" 19 }] 20 21 secret_name = "MY_MONGODB_SECRET" 22 vault_mount = "kv/my_app"
Estes valores definidos em terraform.tfvars são utilizados pelo Terraform para preencher variáveis correspondentes em sua configuração. Nele, estamos especificando o escopo do usuário, os valores do Vault e as funções do usuário. O usuário terá permissão de leitura e gravação no banco de dados1 na collection1 e permissão de leitura no banco de dados2 em todas as collections do cluster de demonstração.
A estrutura do arquivo é a seguinte:
- main.tf: Neste arquivo, definiremos o recurso principal, o mongodbatlas_database_user e o vault_kv_secret_v2, junto com uma geração aleatória de senhas. Aqui, você configurou o cluster e as rotinas de backup.
- provider.tf: É neste arquivo que definimos o provedor que estamos usando, no nosso caso, mongodbotlas e Vault.
- terraform.tfvars: Este arquivo contém as variáveis que serão usadas em nosso módulo — por exemplo, o nome do usuário e as informações do Vault, entre outras.
- variable.tf: Aqui, definimos as variáveis mencionadas no arquivo terraform.tfvars, especificando o tipo e, opcionalmente, um valor padrão.
- version.tf: Este arquivo é usado para especificar a versão do Terraform e os fornecedores que estamos usando.
- data.tf: Aqui, especificamos uma fonte de dados que nos trará informações sobre nosso projeto e o cluster criado. Faremos a pesquisa do Atlas por seu nome e para nosso módulo, ele nos fornecerá o ID do projeto e as informações do cluster, como sua connection string.
- locals.tf: Especificamos tags de exemplo a serem usadas em nosso usuário e tratamento para criar o URI no Vault.
Agora é a hora de se inscrever. =D
Executamos um Terraform init no terminal na pasta onde os arquivos estão localizados para que ele baixe os provedores, módulos, etc...
Observação: lembre-se de exportar as variáveis de ambiente com a chave pública e privada.
1 export MONGODB_ATLAS_PUBLIC_KEY="your_public_key" 2 export MONGODB_ATLAS_PRIVATE_KEY=your_private_key"
Agora, executamos init e depois planejamos, como nos artigos anteriores.
Avaliamos que nosso plano é exatamente o que esperamos e executamos a aplicação para criá-lo.
Ao executar o comando
terraform apply
, você será solicitado para aprovação com yes
ou no
. Digite yes
.Agora, vejamos no Atlas se o usuário foi criado com sucesso...


Vamos também dar uma olhada no Vault para ver se nosso segredo foi criado.

Foi criado com sucesso! Agora, vamos testar se o URI está funcionando perfeitamente.
Este é o formato do URI que é gerado:
mongosh "mongodb+srv://usr_myapp:<password>@<clusterEndpoint>/admin?retryWrites=true&majority&readPreference=secondaryPreferred"

Conectamos e faremos uma inserção para avaliar se as permissões são adequadas — inicialmente, em db1 em collection1.

Sucesso! Agora, no db3, verifique se ele não terá permissão em outro banco de dados.

Chegamos ao final desta série de artigos sobre MongoDB. espero que tenham sido úteis para você e para você!
Para saber mais sobre o MongoDB e várias ferramentas, Convido você a visitar o Centro do Desenvolvedor para ler os outros artigos.
Principais comentários nos fóruns
Ainda não há comentários sobre este artigo.