Mais segurança no Atlantis

Entenda Conftest como requisito mínimo

TL;DR

O foco desse pequeno artigo é enfatizar os pontos de execução de comandos arbitrários dos quais o Atlantis está sujeito pelo Terraform. Ou seja, RCE. Não temos intenção de abordar as mais variadas ferramentas de análise SAST ou algo do tipo. Mas, aproveitar o recurso presente nas imagens Atlantis: o Conftest.

Intro

O Atlantis possui suporte a Conftest por padrão.

O Conftest suporta o arquivo de mudanças (plan) gerado pelo Terraform. Assim como também suporta os códigos HCL do Terraform, mas o padrão tem sido o arquivo de mudanças.

Esse arquivo de mudanças pode ser gerado como demonstrado abaixo:

💡
As variáveis de ambiente $PLANFILE e $SHOWFILE apenas definem os caminhos de arquivo. Você deve definir antes.
terraform init -upgrade
terraform plan -input=false -refresh -out $PLANFILE
terraform show -json $PLANFILE > $SHOWFILE

Feito! O primeiro subsídio está disponível. O arquivo $SHOWFILE será bastante útil.

Conftest

O comando Conftest segue no formato abaixo:

conftest test $SHOWFILE --policy policy/ --all-namespaces

Assim, você pode fazer testes localmente se precisar. Se houver dúvidas, você pode consultar por conftest test --help ou a própria documentação oficial.

No Atlantis, segue no modelo similar:

policy_check:
  steps:
    # ...
    - policy_check:
        extra_args: ["--policy /home/atlantis/policy", "--all-namespaces"]

Confira mais sobre na documentação oficial do Atlantis.

Policies

Mas, qual policy usar?

O Conftest utiliza OPA com policies escritas em REGO (pronuncia-se ray-go).

Atualmente, existem bibliotecas que empoderam a confecção de policies. A síntaxe pode ser bastante distinta. Então, dependendo do grau de profundidade, alguns recursos auxiliares e/ou complementares podem contribuir no seu desenvolvimento.

Um outro ponto muito importante sobre segurança é: o terraform plan não está imune a algumas ações mesmo com a presença das policies. No Atlantis, as policies acontecem posteriormente ao plano de mudanças. Então, pode ser necessário controlar o plano de mudança ou personalizar a step do plano de mudança.

Blocklist de providers

Útil para especificar providers Terraform não permitidos.

package main

blocked_providers = {"registry.terraform.io/hashicorp/external"}

caught_providers[provider] = all {
    some provider
    blocked_providers[provider]
    all := [module |
        module := input.configuration.provider_config[_]
        module.full_name == provider
    ]
}

deny[msg] {
    num_resources := caught_providers[provider]
    num_resources > 0
    msg := sprintf("Provider %s detected in Terraform plan file.", [provider])
}

Allowlist de providers

Pode ser uma alternativa melhor. Assim, apenas providers Terraform autorizados passam. Evitando o risco de providers desconhecidos.

package main

allowed_providers = {"registry.terraform.io/hashicorp/aws"}

deny[msg] {
    filtered := [f | f = input.configuration.provider_config[_].full_name; not allowed_providers[f]]
    count(filtered) > 0
    msg := sprintf("Got %d unauthorized provider(s) like %v.", [count(filtered), filtered[0]])
}

Entre outras infinidades de policies

Acima, são exemplos reais. Você também pode incluir policies para null_resources.

É importante entender a diferença entre policies que conduzem adoção de boas práticas e aquelas que tratam questões pontuais.

Você pode utilizar algumas bibliotecas/frameworks de policies prontas. A Regula é um exemplo. Consulte a documentação sobre Regula e Conftest para saber mais. Também há opção para integrar com GitHub Actions diretamente.

Policy para HCL

Como visto anteriormente, pode ser muito útil verificar o manifesto HCL do Terraform. Aqui vai um exemplo simples:

package main

deny[msg] {
  proto := input.data.external[name]
  count(proto) > 0
  msg = sprintf("External '%v' detected in HCL.", [name])
}

Detecção de HCL para fontes de módulos antes do plano? É possível:

package main

allowed_sources = {
    "git@github.com:business/business.git//modules/"
}

deny[msg] {
    module := input.module[name].source
    finding := [trusted | trusted := allowed_sources[_]; not startswith(module, trusted)]
    count(finding) > 0
    msg = sprintf("Unauthorized module.%s source.", [name])
}

O conceito de allowlist aplica-se perfeitamente aqui.

O plano de mudança está sujeito a execução de comandos arbitrários. Portanto, é interessante a ideia de executar a verificação antes do plano de mudança.

E como funcionaria o comando para Conftest nessa estratégia?

conftest test *.tf --policy policy/ --all-namespaces
💡
O Conftest pode combinar arquivos HCL e $SHOWFILE.

Portanto, antes de efetivar o plano de mudança no Terraform, execute o teste.

$ conftest test $SHOWFILE *.tf --policy /home/atlantis/policy -n main --output table

+---------+--------------+-----------+--------------------------------+
| RESULT  |     FILE     | NAMESPACE |            MESSAGE             |
+---------+--------------+-----------+--------------------------------+
| success | outputs.tf   | main      | SUCCESS                        |
| success | outputs.tf   | main      | SUCCESS                        |
| success | tests.tf     | main      | SUCCESS                        |
| failure | tests.tf     | main      | External 'example' detected in |
|         |              |           | HCL.                           |
| success | variables.tf | main      | SUCCESS                        |
| success | variables.tf | main      | SUCCESS                        |
| success | showfile     | main      | SUCCESS                        |
| failure | showfile     | main      | Resource 'null_resource'       |
|         |              |           | detected in Terraform plan     |
|         |              |           | file. Denied.                  |
| success | main.tf      | main      | SUCCESS                        |
| success | main.tf      | main      | SUCCESS                        |
+---------+--------------+-----------+--------------------------------+

Também é possível utilizar o arquivo .terraform.lock.hcl gerado durante terrraform init para verificar providers antes do terraform plan.

conftest test .terraform.lock.hcl -p /home/atlantis/policy
package main

allowed_providers = {
    "registry.terraform.io/hashicorp/aws",
    "registry.terraform.io/hashicorp/random",
    "registry.terraform.io/hashicorp/null"
}

deny[msg] {
    input.provider[name]
    not allowed_providers[name]
    msg = sprintf("Provider '%s' caught.", [name])
}

Veja que é possível combinar os arquivos com as policies de maneiras variadas.

Dessa forma, o plano de mudança não vai acontecer. Pois, com os exemplos acima, retornará um código de saída 1 quando qualquer provider não autorizado for detectado.

If a workflow step returns a non-zero exit code, the workflow will stop.

Esse tipo de medida estabelece uma melhor prevenção.

Ou seja, se o teste ocorrer bem, o plano será executado como esperado. Caso contrário, o risco será mitigado.

Assim, não tem problema ter testes antes do plan e a policy check que acontecerá depois do plan normalmente. As custom workflows de Atlantis são personalizáveis.

Recurso terraform_data

Em poucas palavras, a HashiCorp inseriu o sucessor de null_resource. Esse recurso tem a missão de auxiliar no ciclo de vida de outros recursos. Ele não depende de outros providers, pois vem no embutido.

Na documentação, há duas formas de explorar isso:

  • Usando o recurso terraform_data ;

  • Usando replace_triggered_by no argumento de lifecycle de outro recurso.

No primeiro caso, fica sujeito ao uso de provisioner como local-exec. O que merece muita atenção. Concorda?

Confeccionando policies

Eu utilizei o The Rego Playground para confeccionar algumas policies REGO.

Na imagem acima, o campo esquerdo é reservado para a policy REGO. No campo Input você cola o conteúdo de $SHOWFILE gerado anteriormente. Lembra-se? E o sucesso da policy pode ser acompanhado nos campos Data e Output sempre que Evaluate for chamado. Acredite, isso pode facilitar a sua vida!

E agora?

É importante fazer um refinamento para chegar em um número maior de itens a serem verificados.

Mas, não seja limitado aos testes em si. É importante abranger a segurança para as outras camadas de infraestrutura.

Uma tendência é o uso de registry Terraform privado para módulos e/ou providers. Há opções como Citizen, Terrareg etc.

Conhece mais dicas para evitar instruções arbitrárias?

Enfim, espero que tenham gostado e mantenham-se sempre vigilantes.

Referências