Crea vincoli Terraform

Prima di iniziare

Framework di vincoli

gcloud beta terraform vet utilizza i criteri del Constraint Framework, costituiti da vincoli e modelli di vincolo. La differenza tra i due è la seguente:

  • Un modello di vincolo è simile a una dichiarazione di funzione: definisce una regola in Rego e, facoltativamente, accetta parametri di input.
  • Un vincolo è un file che fa riferimento a un modello di vincolo e definisce i parametri di input da passare e le risorse coperte dal criterio.

In questo modo puoi evitare ripetizioni. Puoi scrivere un modello di vincolo con un criterio generico, quindi scrivere un numero qualsiasi di vincoli che forniscono parametri di input diversi o regole di corrispondenza delle risorse diverse.

Creare un modello di vincolo

Per creare un modello di vincolo:

  1. Raccogli i dati di esempio.
  2. Scrivi Rego.
  3. Testa il tuo Rego.
  4. Configura uno scheletro del modello di vincolo.
  5. Inserisci la regola in linea.
  6. Configura un vincolo.

Raccogliere dati di esempio

Per scrivere un modello di vincolo, devi disporre di dati di esempio su cui operare. Le limitazioni basate su Terraform operano sui dati relativi alle modifiche delle risorse, che provengono dalla chiave resource_changes del JSON del piano Terraform.

Ad esempio, il codice JSON potrebbe avere il seguente aspetto:

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

Rego di scrittura

Dopo aver ottenuto i dati di esempio, puoi scrivere la logica per il modello di vincolo in Rego. Il tuo Rego deve avere una regola violations. La modifica della risorsa in fase di revisione è disponibile come input.review. I parametri di vincolo sono disponibili come input.parameters. Ad esempio, per richiedere che le risorse google_compute_address abbiano un address_type consentito, scrivi:

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

Assegna un nome al modello di vincolo

L'esempio precedente utilizza il nome TFComputeAddressAddressTypeAllowlistConstraintV1. Si tratta di un identificatore univoco per ogni modello di vincolo. Ti consigliamo di seguire queste linee guida per la denominazione:

  • Formato generale: TF{resource}{feature}Constraint{version}. Utilizza il CamelCase. In altre parole, scrivi ogni nuova parola con la lettera maiuscola.
  • Per i vincoli relativi a una singola risorsa, segui le convenzioni del provider Terraform per la denominazione dei prodotti. Ad esempio, per google_tags_tag il nome del prodotto è tags anche se il nome dell'API è resourcemanager.
  • Se un modello si applica a più di un tipo di risorsa, ometti la parte della risorsa e includi solo la funzionalità (ad es. "TFAddressTypeAllowlistConstraintV1").
  • Il numero di versione non segue la forma semver; è solo un singolo numero. In questo modo, ogni versione di un modello diventa un modello unico.

Ti consigliamo di utilizzare un nome per il file Rego che corrisponda al nome del modello di vincolo, ma utilizzando la notazione snake_case. In altre parole, converti il nome in lettere minuscole e separa le parole con _. Per l'esempio precedente, il nome file consigliato è tf_compute_address_address_type_allowlist_constraint_v1.rego

Testa il tuo Rego

Puoi testare Rego manualmente con Rego Playground. Assicurati di utilizzare dati non sensibili.

Ti consigliamo di scrivere test automatici. Inserisci i dati di esempio raccolti in validator/test/fixtures/<constraint filename>/resource_changes/data.json e fai riferimento a questi dati nel file di test come segue:

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

Inserisci il tuo Rego e il tuo test nella cartella validator della raccolta di criteri.

Configurare uno scheletro del modello di vincolo

Dopo aver creato una regola Rego funzionante e testata, devi impacchettarla come modello di vincolo. Constraint Framework utilizza le definizioni delle risorse personalizzate Kubernetes come contenitore per il criterio Rego.

Il modello di vincolo definisce anche i parametri consentiti come input dei vincoli, utilizzando lo schema OpenAPI V3.

Utilizza lo stesso nome per lo scheletro che hai utilizzato per Rego. In particolare:

  • Utilizza lo stesso nome file del tuo Rego. Esempio: tf_compute_address_address_type_allowlist_constraint_v1.yaml
  • spec.crd.spec.names.kind deve contenere il nome del modello
  • metadata.name deve contenere il nome del modello, ma in minuscolo

Inserisci lo scheletro del modello di vincolo in policies/templates.

Per l'esempio riportato sopra:

# 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

Inserisci il tuo Rego in linea

A questo punto, seguendo l'esempio precedente, il layout della directory sarà simile al seguente:

| 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 hai clonato il repository della libreria di criteri fornita da Google, puoi eseguire make build per aggiornare automaticamente i modelli di vincoli in policies/templates con il Rego definito in validator.

Configurare un vincolo

I vincoli contengono tre informazioni di cui gcloud beta terraform vet ha bisogno per applicare e segnalare correttamente le violazioni:

  • severity: low, medium o high
  • match: parametri per determinare se una limitazione si applica a una determinata risorsa. Sono supportati i seguenti parametri di corrispondenza:
    • addresses: un elenco di indirizzi delle risorse da includere utilizzando la corrispondenza di tipo glob
    • excludedAddresses: (Facoltativo) un elenco di indirizzi delle risorse da escludere utilizzando la corrispondenza di tipo glob.
  • parameters: valori per i parametri di input del modello di vincolo.

Assicurati che kind contenga il nome del modello di vincolo. Ti consigliamo di impostare metadata.name su uno slug descrittivo.

Ad esempio, per consentire solo tipi di indirizzi INTERNAL utilizzando il modello di vincolo dell'esempio precedente, scrivi:

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

Esempi di corrispondenza:

Corrispondenza indirizzi Descrizione
`module.**` Tutte le risorse di qualsiasi modulo
`module.my_module.**` Tutto nel modulo "my_module"
`**.google_compute_global_forwarding_rule.*` Tutte le risorse google_compute_global_forwarding_rule in qualsiasi modulo
`module.my_module.google_compute_global_forwarding_rule.*` Tutte le risorse google_compute_global_forwarding_rule in "my_module"

Se l'indirizzo di una risorsa corrisponde ai valori in addresses e excludedAddresses, viene esclusa.

Limitazioni

I dati del piano Terraform forniscono la migliore rappresentazione disponibile dello stato effettivo dopo l'applicazione. Tuttavia, in molti casi, lo stato dopo l'applicazione potrebbe non essere noto perché viene calcolato lato server.

La creazione di percorsi di ascendenza CAI fa parte della procedura di convalida dei criteri. Utilizza il progetto predefinito fornito per aggirare gli ID progetto sconosciuti. Nel caso in cui non venga fornito un progetto predefinito, il percorso dell'ascendenza predefinito è organizations/unknown.

Puoi non consentire l'ascendenza sconosciuta aggiungendo il seguente vincolo:

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: {}

Risorse supportate

Puoi creare vincoli di modifica delle risorse per qualsiasi risorsa Terraform da qualsiasi provider Terraform.