Crie restrições do Terraform

Antes de começar

Framework de restrições

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

  • Um modelo de restrição é como uma declaração de função; define uma regra em Rego e, opcionalmente, usa parâmetros de entrada.
  • Uma restrição é um ficheiro que faz referência a um modelo de restrição e define os parâmetros de entrada a transmitir ao mesmo, bem como os recursos abrangidos pela política.

Isto permite-lhe evitar a repetição. Pode escrever um modelo de restrição com uma política genérica e, em seguida, escrever qualquer número de restrições que forneçam diferentes parâmetros de entrada ou regras de correspondência de recursos diferentes.

Crie um modelo de restrição

Para criar um modelo de restrição, siga estes passos:

  1. Recolha dados de amostra.
  2. Escreva Rego.
  3. Teste o Rego.
  4. Configure um esqueleto de modelo de restrição.
  5. Incorporar o Rego.
  6. Configure uma restrição.

Recolha dados de amostra

Para escrever um modelo de restrição, tem de ter dados de exemplo para operar. As restrições baseadas no Terraform operam em dados de alteração de recursos, que provêm da chave resource_changes do JSON do plano do Terraform.

Por exemplo, o seu JSON pode ter o seguinte aspeto:

// 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
}

Write Rego

Depois de ter dados de exemplo, pode escrever a lógica para o modelo de restrição em Rego. O seu Rego tem de ter uma regra violations. A alteração de recurso que está a ser revista 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.change.after.address_type} & allowed_address_types) >= 1
  message := sprintf(
    "Compute address %s has a disallowed address_type: %s",
    [resource.address, resource.change.after.address_type]
  )
  metadata := {"resource": resource.name}
}

Atribua um nome ao modelo de restrição

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

  • Formato geral: TF{resource}{feature}Constraint{version}. Use CamelCase. (Por outras palavras, use maiúsculas em cada nova palavra.)
  • Para restrições de recursos únicos, siga as convenções de nomenclatura de produtos do fornecedor do Terraform. Por exemplo, para google_tags_tag, o nome do produto é tags, apesar de o nome da API ser resourcemanager.
  • Se um modelo se aplicar a mais do que um tipo de recurso, omita a parte do recurso e inclua apenas a funcionalidade (exemplo: "TFAddressTypeAllowlistConstraintV1").
  • O número da versão não segue o formato semver. É apenas um único número. Isto torna efetivamente cada versão de um modelo num modelo único.

Recomendamos que use um nome para o ficheiro Rego que corresponda ao nome do modelo de restrição, mas que use snake_case. Por outras palavras, converta o nome em letras minúsculas e separe as palavras com _. Para o exemplo anterior, o nome do ficheiro recomendado é tf-compute-address-address-type-allowlist-constraint-v1.rego

Teste o seu Rego

Pode testar o Rego manualmente com o Rego Playground. Certifique-se de que usa dados não confidenciais.

Recomendamos que escreva testes automatizados. Coloque os dados de exemplo recolhidos em validator/test/fixtures/<constraint filename>/resource_changes/data.json e faça referência aos mesmos no ficheiro de teste da seguinte 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 na biblioteca de políticas.

Configure um esqueleto de modelo de restrição

Depois de ter uma regra Rego funcional e testada, tem de a agrupar como um modelo de restrição. A framework de restrições usa definições de recursos personalizados do Kubernetes como o contentor para a política Rego.

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

Use o mesmo nome para o esqueleto que usou para o Rego. Concretamente:

  • Use o mesmo nome de ficheiro que usou para o Rego. Exemplo: tf-compute-address-address-type-allowlist-constraint-v1.yaml
  • spec.crd.spec.names.kind tem de conter o nome do modelo
  • metadata.name tem de conter o nome do modelo, mas em letras minúsculas

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

Por exemplo:

# 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

Incorporar o seu Rego

Neste ponto, seguindo o exemplo anterior, a disposição do diretório tem o seguinte aspeto:

| 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 clonou o repositório da biblioteca de políticas fornecido pela Google, pode executar make build para atualizar automaticamente os modelos de restrições em policies/templates com o Rego definido em validator.

Configure uma restrição

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

  • severity: low, medium ou high
  • match: parâmetros para determinar se uma restrição se aplica a um recurso específico. Os seguintes parâmetros de correspondência são suportados:
    • addresses: uma lista de endereços de recursos a incluir através da correspondência no estilo glob
    • excludedAddresses: (Opcional) Uma lista de endereços de recursos a excluir usando a correspondência no estilo glob.
  • parameters: valores dos parâmetros de entrada do modelo de restrição.

Certifique-se de que kind contém o nome do modelo de restrição. Recomendamos que defina metadata.name como um slug descritivo.

Por exemplo, para permitir apenas tipos de endereços INTERNAL usando o modelo de restrição do 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:

Localizador de moradas 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 aos valores em addresses e excludedAddresses, é 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, uma vez que é calculado no lado do servidor.

A criação de caminhos de ascendência da CAI faz parte do processo de validação de políticas. Usa o projeto predefinido fornecido para contornar IDs de projetos desconhecidos. No caso em que não é fornecido um projeto predefinido, o caminho de hierarquia é predefinido como organizations/unknown.

Pode não permitir a ascendência 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

Pode criar restrições de alteração de recursos para qualquer recurso do Terraform a partir de qualquer fornecedor do Terraform.