Crea restricciones de Terraform

Antes de comenzar

Constraint Framework

gcloud beta terraform vet usa políticas de Constraint Framework, que constan de restricciones y plantillas de restricciones. La diferencia entre ambas es la siguiente:

  • Una plantilla de restricciones es como una declaración de función. Define una regla en Rego y, de forma opcional, toma parámetros de entrada.
  • Una restricción es un archivo que hace referencia a una plantilla de restricciones y define los parámetros de entrada para pasarla a ella y los recursos que cubre la política.

Esto te permite evitar la repetición. Puedes escribir una plantilla de restricciones con una política genérica y, luego, escribir cualquier cantidad de restricciones que proporcionen diferentes parámetros de entrada o reglas de coincidencia de recursos.

Crea una plantilla de restricción

Para crear una plantilla de restricciones, sigue estos pasos:

  1. Recopila datos de muestra.
  2. Escribe Rego.
  3. Prueba tu Rego.
  4. Configura un esqueleto de plantilla de restricciones.
  5. Intercala tu Rego.
  6. Configura una restricción.

Recopila datos de muestra

Para escribir una plantilla de restricciones, debes tener datos de muestra con los cuales operar. Las restricciones basadas en Terraform operan en datos de cambios de recursos, que provienen de la clave resource_changes del JSON de plan de Terraform.

Por ejemplo, tu JSON podría verse así:

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

Escribe Rego

Una vez que tengas los datos de muestra, puedes escribir la lógica para la plantilla de restricciones en Rego. Tu Rego debe tener una regla violations. El cambio de recurso que se revisa está disponible como input.review. Los parámetros de restricción están disponibles como input.parameters. Por ejemplo, para requerir que los recursos google_compute_address tengan un address_type permitido, escribe lo siguiente:

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

Asigna un nombre a la plantilla de restricción

En el ejemplo anterior, se usa el nombre TFComputeAddressAddressTypeAllowlistConstraintV1. Este es un identificador único para cada plantilla de restricciones. Recomendamos seguir estos lineamientos para asignar nombres:

  • Formato general: TF{resource}{feature}Constraint{version}. Usa CamelCase. (En otras palabras, usa mayúsculas cada palabra nueva).
  • Para las restricciones de recurso único, sigue las convenciones del proveedor de Terraform para la asignación de nombres de productos. Por ejemplo, para google_tags_tag, el nombre del producto es tags, aunque el nombre de la API sea resourcemanager.
  • Si una plantilla se aplica a más de un tipo de recurso, omite la parte de recurso y solo incluye la función (ejemplo: "TFAddressTypeAllowlistConstraintV1").
  • El número de versión no sigue el formulario semver; es solo un número. Esto hace que efectivamente cada versión de una plantilla sea única.

Recomendamos usar un nombre para el archivo Rego que coincida con el nombre de la plantilla de restricción, pero con snake_case. En otras palabras, convierte el nombre en palabras en minúscula separadas con _. Para el ejemplo anterior, el nombre de archivo recomendado es tf_compute_address_address_type_allowlist_constraint_v1.rego

Prueba tu Rego

Puedes probar tu Rego de forma manual con Rego Playground. Asegúrate de usar datos no sensibles.

Te recomendamos escribir pruebas automatizadas. Coloca los datos de muestra recopilados en validator/test/fixtures/<constraint filename>/resource_changes/data.json y haz referencia a ellos en el archivo de prueba de la siguiente manera:

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

Coloca tu Rego y tu prueba en la carpeta validator en tu biblioteca de políticas.

Configura un esqueleto de plantilla de restricciones

Una vez que tengas una regla de Rego que funcione y esté probada, debes empaquetarla como una plantilla de restricción. Constraint Framework usa las definiciones de recursos personalizados de Kubernetes como el contenedor para el Rego de la política.

La plantilla de restricciones también define qué parámetros están permitidos como entradas de restricciones, mediante el esquema OpenAPI V3.

Usa para el esqueleto el mismo nombre que usaste para el Rego. En particular, considera lo siguiente:

  • Usa el mismo nombre de archivo que tu Rego. Ejemplo: tf_compute_address_address_type_allowlist_constraint_v1.yaml
  • spec.crd.spec.names.kind debe contener el nombre de la plantilla.
  • metadata.name debe contener el nombre de la plantilla, pero en minúsculas.

Coloca el esqueleto de la plantilla de restricción en policies/templates.

Para el ejemplo anterior:

# 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

Intercala tu Rego

En este punto, según el ejemplo anterior, el diseño de tu directorio se ve de la siguiente manera:

| 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

Si clonaste el repositorio de bibliotecas de políticas proporcionado por Google, puedes ejecutar make build para actualizar de manera automática tus plantillas de restricciones en policies/templates con el Rego definido en validator.

Configura una restricción

Las restricciones contienen tres datos que gcloud beta terraform vet necesita para aplicar y denunciar los incumplimientos de forma correcta:

  • severity: low, medium o high
  • match: parámetros para determinar si una restricción se aplica a un recurso en particular. Se admiten los siguientes parámetros de coincidencia:
    • addresses: una lista de direcciones de recursos para incluir mediante coincidencias de estilo glob.
    • excludedAddresses: (Opcional) una lista de direcciones de recursos para excluir mediante coincidencias de estilo glob.
  • parameters: valores para los parámetros de entrada de la plantilla de restricción.

Asegúrate de que kind contenga el nombre de la plantilla de restricción. Recomendamos configurar metadata.name como una slug descriptiva.

Por ejemplo, para permitir solo tipos de direcciones INTERNAL con la plantilla de restricción del ejemplo anterior, escribe lo siguiente:

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

Ejemplos de coincidencias:

Comparador de direcciones Descripción
“module.**” Todos los recursos en cualquier módulo
“module.my_module.**” Todo en el módulo “my_module”
“**.google_compute_global_forwarding_rule.*” Todos los recursos google_compute_global_forwarding_rule en cualquier módulo
“module.my_module.google_compute_global_forwarding_rule.*” Todos los recursos google_compute_global_forwarding_rule en “my_module”

Si una dirección de recurso coincide con los valores de addresses y excludedAddresses, se excluye.

Limitaciones

Los datos del plan de Terraform ofrecen la mejor representación disponible del estado real después de la aplicación. Sin embargo, en muchos casos, el estado después de la aplicación puede no conocerse porque se calcula en el lado del servidor.

Compilar rutas de principales de CAI es parte del proceso cuando se validan políticas. Usa el proyecto predeterminado que se proporciona para evitar los ID de proyectos desconocidos. En el caso de que no se proporcione un proyecto predeterminado, la ruta principal se establece de forma predeterminada en organizations/unknown.

Para inhabilitar una entidad principal desconocida, agrega la siguiente restricción:

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 admitidos

Puedes crear restricciones de cambio de recursos para cualquier recurso de Terraform desde cualquier proveedor de Terraform.