Criar restrições do Terraform

Antes de começar

Framework de restrição

gcloud beta terraform vet usa políticas do Constraint Framework, que consistem em restrições e modelos de restrição. A diferença entre os dois é a seguinte:

  • Um modelo de restrição é como uma declaração de função. Ele define uma regra e, opcionalmente, usa variáveis como entradas.
  • Uma restrição é um arquivo que faz referência a um modelo de restrição e define os valores que serão usados com ele.

Criar um modelo de restrição

1. Coletar dados de amostra

Para escrever um modelo de restrição, é necessário ter dados de amostra para operar. As restrições baseadas em Terraform operam em dados de alteração de recursos, que são provenientes da chave resource_changes do JSON plano do Terraform.

Por exemplo, o JSON pode ser assim:

// tfplan.json
{
  "format_version": "0.2",
  "terraform_version": "1.0.10",
  "resource_changes": [
    {
      "address": "google_compute_address.internal_with_subnet_and_address",
      "mode": "managed",
      "type": "google_compute_address",
      "name": "internal_with_subnet_and_address",
      "provider_name": "registry.terraform.io/hashicorp/google",
      "change": {
        "actions": [
          "create"
        ],
        "before": null,
        "after": {
          "address": "10.0.42.42",
          "address_type": "INTERNAL",
          "description": null,
          "name": "my-internal-address",
          "network": null,
          "prefix_length": null,
          "region": "us-central1",
          "timeouts": null
        },
        "after_unknown": {
          "creation_timestamp": true,
          "id": true,
          "network_tier": true,
          "project": true,
          "purpose": true,
          "self_link": true,
          "subnetwork": true,
          "users": true
        },
        "before_sensitive": false,
        "after_sensitive": {
          "users": []
        }
      }
    }
  ],
  // other data
}

2. Gravar o Rego

Após os dados de amostra, escreva a lógica do modelo de restrição em Rego. O Rego precisa ter uma regra violations. A alteração de recurso que está sendo analisada está disponível como input.review. Os parâmetros de restrição estão disponíveis como input.parameters. Por exemplo, para exigir que os recursos google_compute_address tenham um address_type permitido, escreva:

# validator/tf_compute_address_address_type_allowlist_constraint_v1.rego
package templates.gcp.TFComputeAddressAddressTypeAllowlistConstraintV1

violation[{
  "msg": message,
  "details": metadata,
}] {
  resource := input.review
  resource.type == "google_compute_address"

  allowed_address_types := input.parameters.allowed_address_types
  count({resource.after.address_type} & allowed_address_types) >= 1
  message := sprintf(
    "Compute address %s has a disallowed address_type: %s",
    [resource.address, resource.after.address_type]
  )
  metadata := {"resource": resource.name}
}

Nomear seu modelo de restrição

O exemplo anterior usa o nome TFComputeAddressAddressTypeAllowlistConstraintV1. Esse é um identificador exclusivo para cada modelo de restrição. Recomendamos seguir estas diretrizes de nomenclatura:

  • Formato geral: TF{resource}{feature}Constraint{version} Use o CamelCase. Em outras palavras, coloque cada palavra nova em letra maiúscula.
  • Para restrições de recursos únicos, siga as convenções do provedor Terraform para a nomenclatura de produtos. Por exemplo, para google_tags_tag, o nome do produto é tags, mesmo que o nome da API seja resourcemanager.
  • Se um modelo se aplicar a mais de um tipo de recurso, omita a parte do recurso e inclua apenas o recurso (por exemplo: "TFAddressTypeAllowlistConstraintV1").
  • O número da versão não segue o formulário semver. é apenas um número. Isso torna cada versão de um modelo único.

Recomendamos o uso de um nome para seu arquivo Rego que corresponda ao nome do modelo de restrição, mas usando snake_case. Em outras palavras, converta o nome em letras minúsculas com _. No exemplo anterior, o nome de arquivo recomendado é tf_compute_address_address_type_allowlist_constraint_v1.rego

3. Testar o Rego

É possível testar o Rego manualmente com o Rego Playground. Use dados não confidenciais.

Recomendamos a criação de testes automatizados. Coloque os dados de amostra coletados em validator/test/fixtures/<constraint filename>/resource_changes/data.json e referencie-os no arquivo de teste desta forma:

# validator/tf_compute_address_address_type_allowlist_constraint_v1_test.rego
package templates.gcp.TFComputeAddressAddressTypeAllowlistConstraintV1

import data.test.fixtures.tf_compute_address_address_type_allowlist_constraint_v1_test.resource_changes as resource_changes

test_violation_with_disallowed_address_type {
  parameters := {
    "allowed_address_types": "EXTERNAL"
  }
  violations := violation with input.review as resource_changes[_]
    with input.parameters as parameters
  count(violations) == 1
}

Coloque o Rego e o teste na pasta validator da biblioteca de políticas.

4. Configurar um esqueleto de modelo de restrição

Depois de criar uma regra do Rego em funcionamento e testada, é necessário empacotá-la como um modelo de restrição. O Constraint Framework usa as definições de recursos personalizados do Kubernetes como o contêiner do Rego da política.

O modelo de restrição também define quais parâmetros são permitidos como entradas de restrições, usando o esquema OpenAPI V3.

Use o mesmo nome do esqueleto que você usou para o Rego. Especificamente:

  • Use o mesmo nome de arquivo que você tem no Rego. Exemplo: tf_compute_address_address_type_allowlist_constraint_v1.yaml
  • spec.crd.spec.names.kind precisa conter o nome do modelo
  • metadata.name precisa conter o nome do modelo, mas com letras minúsculas

Coloque o esqueleto do modelo de restrição em policies/templates.

Para o exemplo acima:

# policies/templates/tf_compute_address_address_type_allowlist_constraint_v1.yaml
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: tfcomputeaddressaddresstypeallowlistconstraintv1
spec:
  crd:
    spec:
      names:
        kind: TFComputeAddressAddressTypeAllowlistConstraintV1
      validation:
        openAPIV3Schema:
          properties:
            allowed_address_types:
              description: "A list of address_types allowed, for example: ['INTERNAL']"
              type: array
              items:
                type: string
  targets:
    - target: validation.resourcechange.terraform.cloud.google.com
      rego: |
            #INLINE("validator/tf_compute_address_address_type_allowlist_constraint_v1.rego")
            #ENDINLINE

5. Colocar o Rego em linha

Neste momento, seguindo o exemplo anterior, seu layout de diretório é semelhante a este:

| policy-library/
|- validator/
||- tf_compute_address_address_type_allowlist_constraint_v1.rego
||- tf_compute_address_address_type_allowlist_constraint_v1_test.rego
|- policies
||- templates
|||- tf_compute_address_address_type_allowlist_constraint_v1.yaml

Se você clonou o repositório de biblioteca de políticas fornecido pelo Google, pode executar make build para atualizar automaticamente seus modelos de restrição em policies/templates com o Rego definido em validator

6. Configurar uma restrição

As restrições contêm três informações que o gcloud beta terraform vet precisa para aplicar e denunciar violações corretamente:

  • severity: low, medium ou high
  • match: parâmetros para determinar se uma restrição se aplica a um determinado recurso. Os seguintes parâmetros de correspondência são compatíveis:
    • addresses: uma lista de endereços de recursos a serem incluídos usando a correspondência no estilo glob
    • excludedAddresses: (opcional) uma lista de endereços de recursos a serem excluídos usando a correspondência de estilo glob.
  • parameters: valores para os parâmetros de entrada do modelo de restrição.

Verifique se kind contém o nome do modelo de restrição. Recomendamos definir metadata.name como um slug descritivo.

Por exemplo, para permitir apenas os tipos de endereços INTERNAL usando o modelo de restrição de exemplo anterior, escreva:

# policies/constraints/tf_compute_address_internal_only.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: TFComputeAddressAddressTypeAllowlistConstraintV1
metadata:
  name: tf_compute_address_internal_only
spec:
  severity: high
  match:
    addresses:
    - "**"
  parameters:
    allowed_address_types:
    - "INTERNAL"

Exemplos de correspondência:

Correspondência de endereços Descrição
`module.**` Todos os recursos em qualquer módulo
`module.my_module.**` Tudo no módulo `my_module`
`**.google_compute_global_forwarding_rule.*` Todos os recursos google_compute_global_forwarding_rule em qualquer módulo
`module.my_module.google_compute_global_forwarding_rule.*` Todos os recursos google_compute_global_forwarding_rule em `my_module`

Se um endereço de recurso corresponder a valores em addresses e excludedAddresses, ele será excluído.

Limitações

Os dados do plano do Terraform oferecem a melhor representação disponível do estado real após a aplicação. No entanto, em muitos casos, o estado após a aplicação pode não ser conhecido porque é calculado no lado do servidor.

Recursos compatíveis

É possível criar restrições de alteração de recursos para qualquer recurso dos provedores google e google-beta do Terraform.