# Mais segurança no Atlantis

# 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:

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">As variáveis de ambiente <code>$PLANFILE</code> e <code>$SHOWFILE</code> apenas definem os caminhos de arquivo. Você deve definir antes.</div>
</div>

```bash
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:

```bash
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:

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

Confira mais sobre na [documentação oficial do Atlantis](https://www.runatlantis.io/docs/policy-checking.html).

## 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.

```rego
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.

```rego
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](https://regula.dev/integrations/conftest.html) 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:

```rego
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:

```rego
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?

```bash
conftest test *.tf --policy policy/ --all-namespaces
```

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">O Conftest pode combinar arquivos HCL e <code>$SHOWFILE</code>.</div>
</div>

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

```bash
$ 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`.

```bash
conftest test .terraform.lock.hcl -p /home/atlantis/policy
```

```rego
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.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1711587358294/4e1f0db9-3f12-4e16-8048-4c9577115275.png align="center")

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](https://developer.hashicorp.com/terraform/language/resources/terraform-data), 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](https://play.openpolicyagent.org/) para confeccionar algumas *policies* REGO.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1711580610683/11613cce-d598-4dd2-88ce-8c13c5810769.png align="center")

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](https://github.com/outsideris/citizen), [Terrareg](https://github.com/MatthewJohn/terrareg) etc.

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

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

![](https://media2.giphy.com/media/v1.Y2lkPTc5MGI3NjExNTVnczFvbnZhYmIyd3g0Y2pscDYzeDAwazM2eWkzdnJncDllc213MCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/sCBGZzD1DssidYHGOe/giphy.gif align="center")

# Referências

* Atlantis - [https://www.runatlantis.io/](https://www.runatlantis.io/)
    
* Conftest - [https://www.conftest.dev/](https://www.conftest.dev/)
    
* OPA - [https://www.openpolicyagent.org/docs/latest/terraform/](https://www.openpolicyagent.org/docs/latest/terraform/)
    
* go-getter - [https://pkg.go.dev/github.com/hashicorp/go-getter](https://pkg.go.dev/github.com/hashicorp/go-getter)
    
* The Rego Playground - [https://play.openpolicyagent.org/](https://play.openpolicyagent.org/)
    
* Terraform Plan RCE - [https://alex.kaskaso.li/post/terraform-plan-rce](https://alex.kaskaso.li/post/terraform-plan-rce)
    
* Regula - [https://regula.dev/](https://regula.dev/)
    
* HackTricks Cloud - [https://cloud.hacktricks.xyz/pentesting-ci-cd/terraform-security#terraform-apply](https://cloud.hacktricks.xyz/pentesting-ci-cd/terraform-security#terraform-apply)
    
* Atlantis Hardening and Review Fatigue - [https://doordash.engineering/2023/12/05/atlantis-hardening-and-review-fatigue/](https://doordash.engineering/2023/12/05/atlantis-hardening-and-review-fatigue/)
    
* Use Conftest to Audit IaC - [https://www.fairwinds.com/blog/how-to-kubernetes-use-conftest-to-audit-infrastructure-as-code](https://www.fairwinds.com/blog/how-to-kubernetes-use-conftest-to-audit-infrastructure-as-code)
