With the runner CA fix (#11) the iac workflow now runs far enough to apply, which exposed two provider problems: cloudflare drift — `cloudflare/cloudflare` floated on `~> 5` with no committed lock file, so CI pulled v5.21.1 where `cloudflare_account_token.policies[].resources` is a JSON string, not a map ("Incorrect attribute value type"). Fix: - pin to `~> 5.21` and commit a multi-platform `.terraform.lock.hcl` (linux_arm64 for the runner + darwin_arm64 for local); - `jsonencode(...)` the module's policy resources; - bind the cloudflare_token module to `cloudflare/cloudflare` explicitly (it was defaulting to `hashicorp/cloudflare`, pulling a redundant provider); - stop `.gitignore` from hiding the lock file (the old `.terraform.*` rule did). gitea provider TLS — it runs inside the dflook/terraform-apply container, which doesn't trust the homelab CA (only the ubuntu-latest-ca runner does), so it failed `x509: certificate signed by unknown authority` reaching gitea.arcodange.lab. Fix: feed it the homelab CA via the provider's `cacert_file` (TF_VAR_gitea_cacert_file -> the homelab.pem the workflow already materializes). Validated locally with `tofu validate` + provider-schema inspection (no prod calls). Complements #11. Out of scope (need a live run / operator): the OVH consumer-key scope, and the R2 bucket "not found" on refresh (a state reconcile). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
82 lines
3.4 KiB
HCL
82 lines
3.4 KiB
HCL
# Récupère toutes les permissions Cloudflare disponibles
|
|
data "cloudflare_account_api_token_permission_groups_list" "all" {
|
|
account_id = var.account_id
|
|
}
|
|
|
|
# Sélectionne uniquement les permissions demandées
|
|
locals {
|
|
# Simplifie le scope Cloudflare (ex: "account" depuis "com.cloudflare.api.account")
|
|
permission_map = {
|
|
for p in data.cloudflare_account_api_token_permission_groups_list.all.result :
|
|
"${split(".", p.scopes[0])[length(split(".", p.scopes[0])) - 1]}:${p.name}" => p.id
|
|
}
|
|
permission_map_from_id = zipmap(values(local.permission_map), keys(local.permission_map))
|
|
|
|
# Résout les permissions (si présentes) pour chaque catégorie
|
|
selected_account_permissions = var.permissions.account != null ? compact([
|
|
for name in var.permissions.account : lookup(local.permission_map, name, null)
|
|
]) : []
|
|
|
|
selected_bucket_permissions = var.bucket != null && try(var.permissions.bucket, null) != null ? compact([
|
|
for name in var.permissions.bucket : lookup(local.permission_map, name, null)
|
|
]) : []
|
|
|
|
# Validation des permissions introuvables
|
|
missing_permissions = concat(
|
|
[for name in coalesce(var.permissions.account, []) : name if lookup(local.permission_map, name, null) == null],
|
|
[for name in coalesce(var.permissions.bucket, []) : name if lookup(local.permission_map, name, null) == null]
|
|
)
|
|
|
|
# Ressources cibles
|
|
account_resource = {
|
|
"com.cloudflare.api.account.${var.account_id}" = "*"
|
|
}
|
|
|
|
bucket_resource = var.bucket != null ? {
|
|
"com.cloudflare.edge.r2.bucket.${var.account_id}_${var.bucket.jurisdiction}_${var.bucket.name}" = "*"
|
|
} : {}
|
|
|
|
# Policies construites dynamiquement
|
|
policies = [for policy in [
|
|
length(local.selected_account_permissions) > 0 ? {
|
|
effect = "allow"
|
|
permission_groups = [for id in local.selected_account_permissions : { id = id }]
|
|
resources = jsonencode(local.account_resource) # cloudflare provider >=5.20 types policies[].resources as a JSON string
|
|
} : null,
|
|
|
|
length(local.selected_bucket_permissions) > 0 ? {
|
|
effect = "allow"
|
|
permission_groups = [for id in local.selected_bucket_permissions : { id = id }]
|
|
resources = jsonencode(local.bucket_resource) # cloudflare provider >=5.20 types policies[].resources as a JSON string
|
|
} : null
|
|
] : policy if policy != null]
|
|
|
|
error_message = length(local.missing_permissions) > 0 ? format("Permissions introuvables : %s", join(", ", local.missing_permissions)) : ""
|
|
}
|
|
|
|
# Création du token
|
|
resource "cloudflare_account_token" "token" {
|
|
account_id = var.account_id
|
|
name = var.token_name
|
|
|
|
policies = local.policies
|
|
|
|
expires_on = null
|
|
|
|
lifecycle {
|
|
ignore_changes = [expires_on, policies] # ignore permission id change as unstable
|
|
replace_triggered_by = [null_resource.cloudflare_account_token_replace] # replace permission name change d
|
|
precondition {
|
|
condition = length(local.missing_permissions) == 0
|
|
error_message = local.error_message
|
|
}
|
|
}
|
|
}
|
|
|
|
resource "null_resource" "cloudflare_account_token_replace" { # replace token when permission names change
|
|
triggers = {
|
|
"account_permissions" = sha256(join("", sort([for p_id in local.selected_account_permissions : lookup(local.permission_map_from_id, p_id)])))
|
|
"bucket_permissions" = sha256(join("", sort([for p_id in local.selected_bucket_permissions : lookup(local.permission_map_from_id, p_id)])))
|
|
}
|
|
}
|