Criação e configuração de um servidor Vault utilizando Terraform
O problema
Trabalhar com tecnologia nos dias de hoje normalmente quer dizer que você vai utilizar algum produto de software como serviço, que pode ser desde um aplicativo de mensagem (ex: Slack, RocketChat), um sistema que auxilia na organização do trabalho no dia a dia (ex: Trello, Jira) ou até mesmo um sistema que te permita subir e controlar toda sua infraestrutura em nuvem (ex: AWS, Azure).
O problema é que nem sempre é viável ter uma conta por pessoa na empresa, às vezes porque o produto em questão não dá suporte a multiplas contas, às vezes por uma questão de custo, já que muitos softwares cobram por usuário ou simplesmente por uma questão de praticidade, já que é mais fácil gerenciar uma conta só ao invés de vários acessos.
Por conta disso é normal pessoas trabalhando num mesmo projeto terem acesso a uma mesma conta, e o compartilhamento de senhas normalmente é feito por mensagens ou planilhas. Isso gera uma falha de segurança dentro do projeto porque aplicativos de mensagem e planilhas não foram feitos para enviar informações sensíveis como senhas, mas são utilizados dessa forma pela falta de uma alternativa melhor que seja tão prática quanto.
A solução
O Vault é um sistema criado pela Hashicorp com o intuito de tornar seguro esse compartilhamento de informações sensíveis. Ele permite não só o armazenamento de senhas, como também o controle de quem pode ver o que, geração de senhas e credenciais que expiram de forma automática, bem como a possibilidade de utilizar a apliação via interface Web, aplicação de linha de comando, ou requisições HTTP via API.
Só que agora nós caímos em outro problema: criar e configurar um servidor Vault com todas as políticas de acesso rapidamente se torna uma tarefa complicada, com muito passos, e até mesmo confusa, já que a Hashicorp permite que isso seja feito de diversas formas.
O objetivo desse artigo é mostrar como essa tarefa pode ser feita utilizando Terraform.
TLDR: Caso você não esteja interessado em ler o artigo e quer apenas saber como fica a solução final o código resultante está neste repositório.
Porém, se você apenas aplicar o código do repositório não vai funcionar. A ordem de execução de alguns passos é importante então use o repositório como referência e leia o artigo para entender como atingir o mesmo resultado.
Como funciona e por que Terraform?
O Terraform é uma ferramenta criada também pela Hashicorp, e é uma das principais quando se fala de Infraestrutura como Código, ou IaC ( Infrastructure as Code), em termos de orquestração e gerenciamento da infraestrutura.
IaC é uma das práticas do modelo DevOps onde o gerenciamento da infraestrutura é tratado como desenvolvimento de software. Isso permite a aplicação de boas práticas de desenvolvimento em cima não só do código, mas também da infraestrutura onde o código irá ser executado. Essas boas práticas envolvem por exemplo: controle de versão (git), teste e revisão de código, integração e entrega contínua, DRY (Don’t Repeat Yourself).
O Terraform usa a configuração escrita em código para planejar a infraestrutura e fornece um feedback, informando quais recursos serão adicionados, alterados ou excluídos. Se você estiver satisfeito com seu plano, poderá aplicar a infraestrutura, e o Terraform se encarregará de chamar as APIs certas para você.
Após aplicar a configuração escrita, o Terraform armazena o estado da infraestrutura em um arquivo no formato JSON que é usado de referência nos planos futuros.
Por padrão esse arquivo é armazenado localmente e chamado de terraform.tfstate
, porém é recomendado que esse arquivo seja armazenado de forma segura e remota.
Esse artigo utilizará um Bucket da S3 para armazenar o estado do Terraform.
Pré-requisitos
Para seguir os passos descritos nesse artigo você precisa ter:
- A CLI do Terraform instalada.
- A CLI da AWS instalada.
- Uma conta na AWS.
- Suas credenciais da AWS. Você pode criar as credenciais nessa página.
Você também precisa configurar a CLI da AWS com as suas credenciais. Você pode fazer isso através do seguinte comando:
Servidor
Estrutura inicial
Comece o projeto criando uma pasta que irá conter a configuração do Terraform.
Crie o arquivo onde vamos começar a definir nossa configuração, chamado de main.tf
.
Abra o arquivo no seu editor de texto favorito e cole a seguinte configuração:
O bloco terraform
contém as configurações básicas do próprio Terraform. O bloco required_providers
indica quais módulos devem ser baixados do Terraform Registry, aqui no caso estamos usando o módulo da AWS.
O bloco backend
é a configuração que indica onde e como vamos salvar o arquivo de estado do Terraform. Note que nesse caso estamos declarando que o estado deve ser salvo em um Bucket da S3, porém o nome do Bucket está vazio na configuração.
Utilize a CLI da AWS para criar um Bucket privado, trocando <nome-do-bucket>
por um nome válido:
Ative o versionamento e bloqueie todo o acesso público ao Bucket:
Coloque o nome do bucket no bloco de configuração backend
:
Quando você cria uma nova configuração do Terraform você precisa inicializar o diretório, isso irá fazer o download dos módulos necessários, neste caso o módulo da AWS. Faça isso com o seguinte comando:
Armazenamento
Agora vamos criar um módulo para armazenar a configuração do servidor. Um módulo consiste em um conjunto de recursos que são utilizados juntos.
Comece criando uma pasta e um arquivo main.tf
para armazenar a configuração do módulo:
Vamos começar criando um Bucket que será usado pelo Vault como forma de armazenamento:
Note que o nome do Bucket não está explicitamente definido, ao invés disso está esperando o valor de uma variável. Porém a variável não está definida.
Crie um arquivo dentro do módulo para definir as variáveis.
Declare a variável do nome do bucket:
Chave para criptografia
O Vault possui uma funcionalidade de criptografia adicional, mas recomendada. Crie uma chave do AWS KMS que será usada pelo Vault.
Orquestração de containers com ECS
Task Definition
Para executar o Vault vamos utilizar o serviço ECS, o orquestrador de containers da AWS. Para isso vamos criar uma Task Definition, que é o artefato da ECS que irá conter as configurações sobre como executar o container do Vault.
Note que a configuração acima não está completa, é preciso ainda preencher os valores das variáveis de ambiente.
Variáveis de ambiente
Vamos utilizar o Parameter Store
da AWS para armazenar as variáveis:
A configuração do Vault presente na variável VAULT_LOCAL_CONFIG
é a seguinte:
A configuração de backend
e seal
é feita através das variáveis AWS_S3_BUCKET
e VAULT_AWSKMS_SEAL_KEY_ID
.
Além disso, seus valores não são passados de forma arbitrária, mas sim de forma dinâmica através dos recursos criados anteriormente, referenciados com aws_s3_bucket.vault.bucket
e aws_kms_key.vault_key.id
.
Volte na configuração da Task Definition e atualize o valor das variáveis através dos recursos do tipo aws_ssm_parameter
criados.
Permissionamento
Além das variáveis de ambiente, o container do Vault também precisa ter acesso a outros recursos da AWS.
Além de acesso ao Bucket da S3 e à chave do KMS, o container também precisa de acesso à SSM para recuperar o valor das variáveis de ambiente, bem como ao CloudWatch para escrever os logs da aplicação.
A configuração da Task Definition possui dois atributos: task_role_arn
e execution_role_arn
, mas para fins de simplicidade vamos colocar todos os acessos em uma só role.
Primeiro, crie uma policy que dê acesso à chave KMS:
Agora crie uma role para a Task Definition que contenha os acessos mencionados:
Volte à configuração da Task Definition e passe o ARN da role para os atributos necessários:
Cluster
Crie o Cluster da ECS. Um Cluster é um agrupamento lógico de serviços dentro da ECS, sendo um serviço o próximo recurso que iremos configurar. É também no Cluster que iremos atrelar uma instância da EC2.
Serviço
O último recurso a ser criado na ECS é o serviço, que é um conjunto de configurações sobre como executar a Task Definition dentro do Cluster.
Instância EC2 via solicitação Spot
Agora precisamos criar as instâncias da EC2 onde se executará os serviços do Cluster. Para isso vamos utilizar instâncias Spot, um modelo de leilão de instâncias ociosas, com a intenção de reduzir o custo da infraestrutura.
Modelo de execução
Vamos começar criando um modelo de execução que especifica as configurações da instância:
Note que ainda há algumas configurações que precisamos completar.
AMI
Primeiro, vamos buscar na AWS a versão mais recente da AMI feita especificamente para executar Clusters da ECS:
Tipos de instância
Crie uma variável no arquivo modules/server/variables.tf
para receber os tipos de instância como um parâmetro do módulo. A requisição Spot vai tentar localizar uma instância ociosa dentre esses tipos, quanto maior o número de instâncias mais fácil achar uma disponível, mas tipicamente com 3 tipos já se consegue uma alta disponibilidade.
Chave SSH / Keypair
Agora vamos criar um par de chaves que serão utilizadas na comunicação SSH com a instância:
User data
Para associar a instância ao Cluster é preciso adicionar um script ao atributo user_data
. Scripts passados para esse parâmetro são executados quando a instância inicia.
Permissionamento da instância
A instância da EC2 precisa também de uma role que contenha a policy AmazonEC2ContainerServiceforEC2Role
, que contém as permissões que uma instância necessita para ser utilizada por um Cluster da ECS.
É preciso criar juntamente da role um Instance Profile para associar a role à configuração da instância.
Security Group
E para finalizar as configurações da instância, vamos criar o Security Group que vai atuar como firewall virtual do servidor:
A configuração acima vai permitir trafego na porta 8200
, onde estará rodando a aplicação do Vault, e na porta 22
, que será utilizada para a conexão SSH.
Finalização da configuração do modelo
Volte na configuração do modelo de execução e preencha os valores vazios, fazendo referência aos respectivos recursos que acabamos de criar:
Solicitação Spot
Agora vamos criar a configuração para a solicitação Spot passando o modelo de execução:
O bloco dynamic "overrides"
itera sobre a variável de instance_types
e declara um bloco do tipo overrides
para cada item da lista.
Permissionamento da solicitação
A solicitação Spot precisa de permissões para solicitar, executar, encerrar e marcar recursos.
Quando a solicitação é feita pelo console é criado automaticamente uma role chamada aws-ec2-spot-fleet-tagging-role
com as permissões necessárias, crie essa role utilizando a CLI:
Agora é necessário adicionar o ARN da role na configuração, que é um dado que segue o seguinte formato: arn:aws:iam::<id-da-conta>:role/aws-ec2-spot-fleet-tagging-role
.
Vamos buscar o id da conta na AWS:
O bloco locals
define uma variável local que extrai o id da conta da fonte de dados aws_caller_identity
.
Atribua o ARN da role ao atributo iam_fleet_role
:
Importando o módulo do servidor
Volte ao arquivo main.tf
e importe o módulo do servidor, lembrando de passar uma variável para o nome do Bucket:
Planejando e aplicando a configuração
Rode o comando de plan
para ter noção do que será criado pelo Terraform:
Use o comando apply
para criar a infraestrutura:
Pronto, sua infraestrutura foi criada e o seu Vault já está acessível 🚀 mas nosso trabalho ainda não acabou.
E agora?
Com o Vault já disponível é preciso criar os usuários e configurar as todas as políticas, cada uma com seu nível de acesso, que vão ser utilizadas para dar as devidas permissões aos usuários.
Porém, essa é uma tarefa trabalhosa e que não cabe nesse artigo, já que estamos falando sobre a infraestrutura necessária para operar um servidor Vault na AWS e não sobre a aplicação em si.
A essa altura do campeonato você deve estar achando que eu vou deixar o trabalho pela metade, mas não precisa de preocupar!
Pensando em resolver de fato o problema inicial eu escrevi uma Parte 2 onde nós vamos configurar toda a parte de acessos do Vault, e obviamente utilizando Terraform.
Obrigado por ler até aqui, deixa seu feedback e eu espero você na parte 2 😉