MongoDB Atlas com Terraform: usuários do banco de dados e Vault
SM
Samuel Molling8 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.
Excelent — permissão negada, como esperado.
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.