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 em Rego e, opcionalmente, aceita parâmetros de entrada.
  • Uma restrição é um arquivo que faz referência a um modelo de restrição e define os parâmetros de entrada a serem transmitidos a ele e aos recursos cobertos pela política.

Isso evita repetições. É possível gravar um modelo de restrição com uma política genérica e, em seguida, gravar qualquer número de restrições que forneçam diferentes parâmetros de entrada ou regras de correspondência de recursos distintas.

Criar um modelo de restrição

Para criar um modelo de restrição, siga estas etapas:

  1. Coletar dados de amostra
  2. Gravar o Rego.
  3. Testar o Rego.
  4. Configurar um esqueleto de modelo de restrição.
  5. Colocar o Rego em linha.
  6. Configurar uma restrição.

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
}

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

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.

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

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

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.

A criação de caminhos de ancestralidade do CAI faz parte do processo de validação das políticas. Ele usa o projeto padrão fornecido para contornar IDs de projetos desconhecidos. Se um projeto padrão não for fornecido, o caminho de ancestralidade padrão será organizations/unknown.

É possível proibir a ancestralidade desconhecida adicionando a seguinte restrição:

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: GCPAlwaysViolatesConstraintV1
metadata:
  name: disallow_unknown_ancestry
  annotations:
    description: |
      Unknown ancestry is not allowed; use --project=<project> to set a
      default ancestry
spec:
  severity: high
  match:
    ancestries:
    - "organizations/unknown"
  parameters: {}

Recursos suportados

É possível criar restrições de alteração de recursos para qualquer recurso do Terraform de qualquer provedor do Terraform.