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:
- Coletar dados de amostra
- Gravar o Rego.
- Testar o Rego.
- Configurar um esqueleto de modelo de restrição.
- Colocar o Rego em linha.
- 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 sejaresourcemanager
. - 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 modelometadata.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
ouhigh
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 globexcludedAddresses
: (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.